2011-08-14 28 views
8

El patrón de diseño de visitante es una forma de separar un algoritmo de una estructura de objeto en la que opera. Esa es la definición oficial de eso. Estoy intentando descubrir cómo esto no rompe la encapsulación. Si digo, por ejemplo, tengo diferentes tipos de clases para diferentes tipos de cuentas bancarias [Ahorro/Fijo/Actual] implementando clase abstracta Cuenta, ¿debo poner el método de interés de cálculo como un método abstracto en la clase de Cuenta abstracta o envío la cuenta? escriba a la implementación del Visitante y calcúlelo allí?Patrón de visitante y encapsulado

Método 1: ¿Debería la implementación del visitante ser responsable de calcular el interés para los diferentes tipos de cuenta?

public interface IInterestVisitor 
{ 
    void GetInterest(Savings AccountType); 
    void GetInterest(Fixed AccountType); 
    void GetInterest(Current AccountType); 
} 

Método 2: O deberían ejecutores de la clase Cuenta hacerlo?

public abstract class Account 
{ 
    public abstract void AcceptCalculateInterestVisitor(IInterestVisitor iv); 
    public abstract int CalculateInterestAmount(); 
} 

Si utilizo el método 1, que es la implementación de visitantes que implementa IInterestVisitor que el anterior, entonces el trabajo de cálculo de interés será delegada a la clase de visitantes. Con este enfoque, si agrego otro tipo de cuenta, tendré que modificar la implementación del visitante cada vez que aparezca una nueva cuenta.

Sin embargo, si dejo el bit de cálculo de intereses a las implementaciones de clases de Cuenta abstractas como en el método 2, entonces en mi opinión [corrija si estoy equivocado aquí] No estoy rompiendo el encapsulamiento. Además hay menos código para modificar como todo lo que hago es añadir una nueva clase y tienen el visitante implementar una interfaz como la de abajo

public interface IInterestVisitor 
{ 
    void GetInterest(Account AccountType); 
} 


public class InterestVisitor : IInterestVisitor 
{ 
    public void GetInterest(Account AccountType) 
    { 
     int i = AccountType.CalculateInterestAmount(); 
     Console.WriteLine(i); 
    } 
} 

Como se puede ver, no hay modificación necesaria para la clase interés de los visitantes utilizando el método 2. ¿El método 1 rompe la encapsulación? ¿Se puede llamar al método 2 como patrón de visitante?

Gracias por leer ...

Respuesta

5

Visitante le permite definir una nueva operación sin cambiar las clases de los elementos en los que opera.

un código, siempre que no veo que tiene que cambiar un objeto con el fin de satisfacer sus necesidades, lo que hace Visitor básicamente, es el cambio de estado objeto utilizando proporcionado Properties/Methods/Fields del propio objeto.

Así, por mí su código puede encajar patrón del visitante, si eso era realmente la pregunta, también causan patrones son directrices y no reglas rígidas.

Yo personalmente elegiría la segunda vía, ya que es mucho más clara y orientada a OOP.

Atentamente.

+0

Gracias. Comprendí a partir de la definición de visitantes que el patrón del visitante se usa para cambiar el estado del objeto utilizando las Propiedades/Métodos/Campos del objeto en cuestión. Me está costando averiguar por qué no se realizarán tales cambios, que la clase original los proporcione; sin tener que agregar un visitante para hacerlo. Me parece que un visitante fue agregado como un pensamiento posterior para hacer más en esa clase de una manera que no rompa el código existente que ya está trabajando en esa clase. ¿Qué piensas? :) – user20358

+1

El visitante básicamente trabaja en un objeto mediante el uso de artefactos proporcionados por el objeto o su agregación. Si necesita equilibrar 0 y deshabilitar la tarjeta de crédito, puede definir en el objeto las funciones BalanceToZero() y DisableCreditCard() y en el visitante puede llamarlas en secuencia. Enfoque NO VISITANTE BÁSICAMENTE, se declararía una tercera función en el objeto Función ZeroBalanceAndDisableCard() donde se invocan ambas funciones. Por cierto, el concepto de patrones de diseño a menudo es "borroso", por lo que generalmente necesito "refactorizarlos" según mis necesidades. – Tigran

+0

Aunque el visitante puede cambiar el estado del objeto, ya que tiene el objeto, una recomendación general es no cambiar el estado del objeto. La funcionalidad como mostrar informe en pantalla, imprimir el recibo, leer el informe por voz es un buen ejemplo. –

5

no veo la necesidad de un visitante aquí en absoluto - su enfoque ilustra perfectamente que pueda solucionar este problema simplemente con el polimorfismo usando un método CalculateInterestAmount las subclases pueden poner en práctica.

En mi opinión, realmente necesita un caso muy convincente para considerar el uso del patrón Visitor: la mayoría de las veces, otras soluciones son más sencillas y se ajustan de forma más natural.

Una vez dicho esto, su versión 2 solo está usando polimorfismo; no hay beneficio de usar un visitante de esta manera. La versión 1 ilustra mejor el enfoque de "despacho doble" de un visitante, así es como generalmente trabaja un visitante.

+0

Gracias. Solo estaba tratando de ir con un ejemplo simple para fines ilustrativos :) Asumiría que si este fuera un escenario del mundo real, entonces sería el método 2 seguido de una acción universal en todos los tipos de cuenta en el método GetInterest de la implementación de InterestVisitor . ¿Correcto? – user20358

+1

@ user20358: El Método 2 realmente no aprovecha que el Visitante pueda ejecutar * código * diferente según el tipo real de la clase que se está visitando; solo usa polimorfismo, por lo tanto, no hay ninguna ventaja al usar un Visitante en el primer lugar. El Método 1 es más apropiado en general para un visitante, aunque no tiene sentido en su ejemplo simple. – BrokenGlass

1

El visitante ayuda en situaciones donde la lógica puede depender tanto del tipo de Visado como del tipo de Visitante: es una forma de estructurar el envío doble.

En su escenario tiene un requisito para calcular el interés y esta es una operación razonable de una cuenta y depende muy claramente de la naturaleza de la cuenta. Por lo tanto, como señala, Visitor no agrega ningún valor, y parece inapropiado.

Ahora tomemos un conjunto de cálculos un poco más complejo. Imagine que tiene una variedad de cuentas de ahorro, algunas son cuentas de ahorro regulares, otras están condicionadas a comportamientos bursátiles, otras tienen elementos de seguro de vida.Para tales cuentas, tenemos métodos para obtener datos tales como tasas de interés, tasas de bonificación, cronogramas de pagos regulares, fechas de vencimiento, etc., cada uno diferente para diferentes tipos de cuentas.

También tenemos que realizar ciertos cálculos: ¿cuál es el valor previsto de la cuenta en una fecha determinada? ¿Cuál es el valor de la cuenta como préstamo de garantía ahora? ¿Cuál es el valor de la cuenta si está cerrada hoy? Dichos cálculos deberán hacerse de forma diferente (tal vez utilizando los captadores de datos brutos) para cada tipo de cuenta.

Ahora vemos algún valor en un patrón de visitante. A medida que diseñamos un nuevo cálculo interesante, escribimos un nuevo Visitante, no necesitamos lanzar nuevas versiones de las clases de cuenta.

+0

Gracias :) ... ¿Alguna sugerencia sobre cómo descubrir en qué situaciones algo un visitante podría ser una buena opción? – user20358

+1

La clave es "envío doble". En mi ejemplo, el algoritmo depende del tipo de cuenta y el tipo de cálculo. Además, está claro que ambos tipos de cálculos y tipos de cuentas pueden aumentar de forma independiente. – djna

1

@Tigran señaló que Visitor es útil cuando no puede cambiar las clases que se visitan.

Dado que está utilizando C#, es probablemente más modular y eficaz utilizar Extension Methods. Hacen lo mismo, agregando funcionalidad a un tipo después del hecho, pero ya no es necesario que lleve a cabo todas las implementaciones posibles en un solo lugar.

+0

Sí, pero siguen siendo métodos estáticos y no tiene ningún tipo de polimorfismo, por lo que hace que la solución sea menos flexible. Yo diría que los métodos de extensión son buenos para algún tipo de operaciones de utilidad. – sll

+1

@silev. Ninguno rompe la encapsulación, y una persona que se esfuerza por un patrón de visitante casi siempre lo usa incorrectamente. Por ejemplo, para ser un visitante convencional, 'GetInterest' debería reemplazarse por un método llamado 'Visit' y una clase derivada del visitante podría ser un 'GetInterestVisitor', y otro con 'ClearDailyLogVisitor'. El visitante es preferible en los casos en los que me gustaría adjuntar elementos de trabajo a una cola, sin embargo, no estoy seguro de lo que ese trabajo realmente podría ser, o me gustaría la flexibilidad para cambiar el tipo de trabajo que estoy haciendo en el futuro. –

+1

absolutamente, no puedo sugerir nada con respecto al Visitante en el alcance de esta pregunta en particular. Acabo de decir que, desde mi punto de vista, Extension Methods introducirá más código acoplado y le dará menos flexibilidad para mantenerlo y extenderlo debido a statc EM – sll

2

Aquí hay un ejemplo de un visitante que informa el estado de muchas maneras sin afectar las clases/objetos de la cuenta original. No es bueno reemplazar las capacidades de los Visitantes en funciones virtuales como PrintReport(), SmsReport(), etc. donde tendrá demasiados métodos para implementar. Al usar Visitor, puede ampliar las capacidades de otra forma sin afectar el objeto. Di este ejemplo, ya que ha preguntado/mencionado dónde un visitante encajaría en .. En mi opinión, el visitante no coincidiría con los dos métodos que ha mencionado.

public interface IAccount 
{ 
    //Below are properties and methods which you expose outside 
    // accoring to your class/domain 
    double Interest { get;} 
    //double Balance { get;} 
    //ContactInfo Contact{ get;} 
    void Visit(IAccountVisitor visitor); 
} 

public class AccountBase 
{ 
} 

public class FixedDeposit: AccountBase, IAccount 
{ 
    double Interest { 
      get{ 
       return CalculateInterest(); //don't change object state      
      } 
        ;} 

    protected double CalculateInterest() 
    { 
     return <<your expression to calculate>>; 
    } 

    public void Visit(IAccountVisitor visitor) 
    { 
     visitor.Visit(this); 
    } 
} 

public class XYZBlashBlahDeposit: AccountBase, IAccount 
{ 
    public void Visit(IAccountVisitor visitor) 
    { 
     visitor.Visit(this); 
    } 
} 

... 
... 

public interface IAccountVisitor 
{ 
    //void Report(IAccount account); //This is prefered and safe and 
    void Visit(Account account); 
} 

public class PrintReportVisitor: IAccountVisitor 
{ 
    public void Visit(Account account) 
    { 
     //Get object information 
     //Print to printer 
    } 
} 

public class DisplayReportVisitor: IAccountVisitor 
{ 
    public void Visit(Account account) 
    { 
     //Get object information 
     //Display on monitor 
    } 
} 

public class ReportAudioVisitor: IAccountVisitor 
{ 
    public void Visit(Account account) 
    { 
     //Get object information 
     //Read out the information 
    } 
} 

public class SmsReportVisitor: IAccountVisitor 
{ 
    public void Visit(Account account) 
    { 
     //Get object information 
     //Send SMS to my mobile registered with account 
    } 
} 

public class EmailReportVisitor: IAccountVisitor 
{ 
    public void Visit(Account account) 
    { 
     //Get object information 
     //Send Email to my email registered with account 
    } 
} 

Consulte si tiene la aplicación, cómo usar Visitor.

public class ReportViewer: UserControl 
{ 
    IAccount _Account; 
    public ReportViewer(IAccount account) 
    { 
     this._Account = account; 
     InitializeComponent(); 
    } 
    void btnClick_Print() 
    { 
     _Account.Visit(new PrintReportVisitor()); 
    } 

    void btnClick_ViewReport() 
    { 
     _Account.Visit(new DisplayReportVisitor(this)); 
    } 

    void btnClick_SendSMS() 
    { 
     _Account.Visit(new SMSReportVisitor(this)); 
    } 

    void btnClick_SendEmail() 
    { 
     _Account.Visit(new EmailReportVisitor(this)); 
    } 
} 
Cuestiones relacionadas