Esta pregunta se refiere al uso del patrón Decorator para agregar poca funcionalidad a los objetos de clases grandes.
Siguiendo el patrón clásico de Decorator, considere la siguiente estructura de clase:
Por ejemplo, imagina que esto sucede dentro de un juego. Las instancias de ConcreteCharacterDecorator
están destinadas a agregar poca funcionalidad a la ConcreteCharacter
que están 'envolviendo'.
Por ejemplo, methodA()
devuelve un int
valor que representa el daño que el personaje inflige a los enemigos. El ConcreteCharacterDecorator
simplemente agrega a este valor. Por lo tanto, solo necesita agregar código methodA()
. La funcionalidad de methodB()
permanece igual.
ConcreteCharacterDecorator
se verá así:
class ConcreteCharacterDecorator extends AbstractCharacterDecorator{
ConcreteCharacter character;
public ConcreteCharacterDecorator(ConcreteCharacter character){
this.character = character;
}
public int methodA(){
return 10 + character.methodA();
}
public int methodB(){
character.methodB(); // simply delegate to the wrapped object.
}
}
Esto no es problema con clases pequeñas que contienen dos métodos.
¿Pero qué AbstractCharacter
pasa si se definen 15 métodos? ConcreteCharacterDecorator
tendría que implementarlos todos, a pesar de que solo está destinado a agregar poca funcionalidad.
Terminaré con una clase que contiene un método que agrega un poco de funcionalidad y otros 14 métodos que simplemente delegan en el objeto interno.
Se vería así:
class ConcreteCharacterDecorator extends AbstractCharacterDecorator{
ConcreteCharacter character;
public ConcreteCharacterDecorator(ConcreteCharacter character){
this.character = character;
}
public int methodA(){
return 10 + character.methodA();
}
public int methodB(){
character.methodB(); // simply delegate to the wrapped object.
}
public int methodC(){
character.methodC(); // simply delegate to the wrapped object.
}
public int methodD(){
character.methodD(); // simply delegate to the wrapped object.
}
public int methodE(){
character.methodE(); // simply delegate to the wrapped object.
}
public int methodF(){
character.methodF(); // simply delegate to the wrapped object.
}
public int methodG(){
character.methodG(); // simply delegate to the wrapped object.
}
public int methodH(){
character.methodH(); // simply delegate to the wrapped object.
}
public int methodI(){
character.methodI(); // simply delegate to the wrapped object.
}
public int methodJ(){
character.methodJ(); // simply delegate to the wrapped object.
}
public int methodK(){
character.methodK(); // simply delegate to the wrapped object.
}
public int methodL(){
character.methodL(); // simply delegate to the wrapped object.
}
public int methodM(){
character.methodM(); // simply delegate to the wrapped object.
}
public int methodN(){
character.methodN(); // simply delegate to the wrapped object.
}
public int methodO(){
character.methodO(); // simply delegate to the wrapped object.
}
}
Obviamente, muy feo.
Probablemente no soy el primero en encontrar este problema con el Decorador. ¿Cómo puedo evitar esto?
fuente
AbstractCharacterDelegator
'regulares' (ya que todos los métodos 'regulares' en Java son virtuales de forma predeterminada, lo que significa que pueden anularse heredando subclases)? ¿Y su implementación simplemente delega en el objeto interno, y si quiero cambiar eso, simplemente anularlos?AbstractCharacter
en uninterface
. El resto sigue siendo el mismo,ConcreteCharacter
eAbstractCharacterDecorator
implementa la interfaz en lugar de subclasificar. Todo debería funcionar. Aunque probablemente no necesiten extender nada más, sus decoradores no están sujetos a subclasesAbstractCharacterDecorator
ya que siempre podrían hacer la delegación a la antigua usanza si alguna vez necesitaran extender una clase diferente por alguna razón.Si AbstractCharacter tiene 15 métodos, probablemente deba dividirse en clases más pequeñas. Aparte de eso, no hay mucho más que pueda hacer sin el lenguaje que proporciona algún tipo de soporte de sintaxis especial para la delegación. Simplemente deje un comentario donde comience toda la delegación para que el lector sepa que no necesita mirar más abajo. En cuanto a escribir todos los métodos de reenvío, su IDE puede ayudarlo allí.
Aparte de eso, es mi opinión que Decorator funciona mejor con interfaces. Por lo general, desea limitar su abstracción a un número finito (posiblemente 0) de subclases o utilizar una interfaz. La herencia ilimitada tiene todos los inconvenientes de las interfaces (se le podría pasar una implementación incorrecta) pero no obtiene total flexibilidad (solo puede subclasificar una cosa). Peor aún, lo abre al problema de la clase base frágil y es no es tan flexible como la composición (crea SubclassA y SubclassB, pero luego no puede heredar de ambos para crear SubclassAB con características de ambos). Así que me veo obligado a entrar en un árbol de herencia solo para apoyar la decoración como un error.
fuente