similitud similaridad proximidad ley experiencia destino definicion curva continuidad común comun buena java design-patterns visitor open-closed-principle

java - similaridad - ley de proximidad definicion



¿Cómo el patrón de visitante no viola el principio de cierre abierto? (2)

De Wikipedia:

La idea era que una vez completada, la implementación de una clase solo podía modificarse para corregir errores; Las características nuevas o modificadas requieren que se cree una clase diferente. Esa clase podría reutilizar la codificación de la clase original a través de la herencia

Por lo que entiendo, el patrón de visitante es una poderosa técnica para atravesar objetos similares pero diferentes que implementan la misma interfaz mediante el uso de doble envío. En uno de mis ejemplos de Java, creé un conjunto compuesto de objetos que forman una estructura de árbol, y cada implementación específica de esos objetos implementa la interfaz visitable. La interfaz del visitante tiene un método para cada uno de los objetos visitables, y el visitante concreto implementa qué hacer para cada uno de esos casos.

Lo que estoy tratando de entender es el hecho de que si tuviera que agregar una nueva implementación a la estructura compuesta que también implementa visitable, entonces necesito volver a abrir la interfaz de visitantes y agregar ese caso a la misma, lo que también me obliga Modificar cada implementación del visitante.

Si bien esto está bien, ya que tendría que hacer esto de todos modos (¿qué ventajas tiene agregar a sus visitables si el visitante no puede entenderlas?) Pero a nivel académico, ¿no estaría esto violando el principio de cierre abierto? ¿No es esa una de las razones principales de los patrones de diseño? Tratar de mostrar una razón decente para cambiar a este patrón en lugar de mantener una declaración de cambio para finalizar todas las declaraciones de cambio, pero todos argumentan que el código será el mismo de todos modos, con un método para cada caso en lugar de un bloque de interruptor, simplemente roto y más difícil de leer.


Un patrón es aplicable a ciertos casos. Del libro de GoF (pág. 333):

Usa el patrón de visitante cuando

  • [...]

  • las clases que definen la estructura del objeto rara vez cambian, pero a menudo desea definir nuevas operaciones sobre la estructura. Cambiar las clases de estructura de objetos requiere redefinir la interfaz para todos los visitantes, lo que es potencialmente costoso. Si las clases de estructura de objetos cambian a menudo, entonces probablemente sea mejor definir las operaciones en esas clases.

Si cambia con frecuencia las clases de los objetos que conforman la estructura, la jerarquía de clases de visitantes puede ser difícil de mantener. En tal caso, puede ser más fácil definir las operaciones en las clases que conforman la estructura.


John Vlissides, uno de los GoF, escribió un excelente capítulo sobre el tema en su libro Patterns Hatching . Discute la preocupación de que extender la jerarquía es incompatible con mantener al visitante intacto. Su solución es un híbrido entre un visitante y un enum basado en la enum (o basado en el tipo), donde al visitante se le proporciona un método visitOther llamado por todas las clases fuera de la jerarquía "base" que el visitante entiende fuera de la caja. Este método le proporciona una forma de escape para tratar las clases agregadas a la jerarquía después de que el visitante haya sido finalizado.

abstract class Visitable { void accept(Visitor v); } class VisitableSubclassA extends Visitable { void accept(Visitor v) { v.visitA(this); } } class VisitableSubclassB extends Visitable { void accept(Visitor v) { v.visitB(this); } } interface Visitor { // The "boilerplate" visitor void visitB(VisitableSubclassA a); void visitB(VisitableSubclassB b); // The "escape clause" for all other types void visitOther(Visitable other); }

Cuando agrega esta modificación, su visitante ya no viola el principio de apertura-cierre , ya que está abierto a la extensión sin la necesidad de modificar su código fuente.

Probé este método híbrido en varios proyectos y funcionó razonablemente bien. Mi jerarquía de clases principal se define en una biblioteca compilada por separado que no es necesario cambiar. Cuando agrego nuevas implementaciones de Visitable , Visitable mis implementaciones de Visitor para esperar estas nuevas clases en sus métodos de visitOther . Dado que tanto los visitantes como las clases ampliadas están ubicadas en la misma biblioteca, este enfoque funciona muy bien.

PD: Hay otro artículo llamado Visitante Revisado que discute precisamente esa pregunta. El autor concluye que se puede volver a un doble despacho basado en enum , porque el Patrón de visitante original no presenta una mejora significativa sobre el despacho basado en enum . No estoy de acuerdo con el autor, porque en los casos en que la mayor parte de su jerarquía de herencia es sólida, y se espera que los usuarios proporcionen algunas implementaciones aquí y allá, un enfoque híbrido proporciona beneficios significativos en la legibilidad; no tiene sentido tirar todo debido a un par de clases que podemos encajar en la jerarquía con relativa facilidad.