Tengo una interfaz que tiene una cierta cantidad de funcionalidad bien definida. Digamos:
interface BakeryInterface {
public function createCookies();
public function createIceCream();
}
Esto funciona bien para la mayoría de las implementaciones de la interfaz, pero en algunos casos, necesito agregar algunas funcionalidades nuevas (como quizás un nuevo método createBrownies()
). El enfoque obvio / ingenuo para hacer esto sería extender la interfaz:
interface BrownieBakeryInterface extends BakeryInterface {
public function createBrownies();
}
Pero tiene una desventaja bastante grande en que no puedo agregar la nueva funcionalidad sin modificar la API existente (como cambiar la clase para usar la nueva interfaz).
Estaba pensando en usar un adaptador para agregar la funcionalidad después de la creación de instancias:
class BrownieAdapter {
private brownieBakery;
public function construct(BakeryInterface bakery) {
this->brownieBakery = bakery;
}
public function createBrownies() {
/* ... */
}
}
Lo que me daría algo como:
bakery = new Bakery();
bakery = new BrownieBakery(bakery);
bakery->createBrownies();
Esto parece una buena solución al problema, pero me pregunto si estoy despertando a los viejos dioses al hacerlo. ¿Es el adaptador el camino a seguir? ¿Hay un mejor patrón a seguir? ¿O realmente debería estar mordiendo la bala y simplemente extendiendo la interfaz original?
Respuestas:
Cualquiera de los patrones de Handle Body podría ajustarse a la descripción, según sus requisitos exactos, el idioma y el nivel de abstracción necesario.
El enfoque purista sería el patrón Decorador , que hace exactamente lo que está buscando, agregar dinámicamente responsabilidades a los objetos. Si realmente está construyendo panaderías, definitivamente es exagerado y debería ir con Adaptador.
fuente
Investigue el concepto de reutilización horizontal , donde puede encontrar cosas como Rasgos , la Programación Orientada a Aspectos todavía experimental pero ya a prueba de producción y los Mixins a veces odiados .
Una forma directa de agregar métodos a una clase también depende del lenguaje de programación. Ruby permite parches de mono mientras que la herencia basada en prototipos de Javascript , donde las clases realmente no existen, se crea un objeto y simplemente se copia y se sigue agregando, por ejemplo:
Finalmente, también puede emular la reutilización horizontal o la "modificación" y "adición" del tiempo de ejecución del comportamiento de clase / objeto con reflexión .
fuente
Si hay un requisito de que la
bakery
instancia debe cambiar su comportamiento dinámicamente (dependiendo de las acciones del usuario, etc.), entonces debe elegir el patrón Decorador .Si
bakery
no cambia su comportamiento dinámicamente pero no puede modificar laBakery class
(API externa, etc.), entonces debe elegir el patrón Adaptador .Si
bakery
no cambia su comportamiento dinámicamente y puede modificarlo,Bakery class
entonces debe extender la interfaz existente (como propuso inicialmente) o introducir una nueva interfazBrownieInterface
y dejarBakery
implementar dos interfacesBakeryInterface
yBrownieInterface
.De lo contrario, ¡agregará una complejidad innecesaria a su código (usando el patrón Decorador) sin ninguna buena razón!
fuente
Has encontrado el problema de expresión. Este artículo de Stuart Sierra analiza la solución de clojures, pero también da un ejemplo en Java y enumera las soluciones populares en OO clásico.
También está esta presentación de Chris Houser que analiza más o menos el mismo terreno.
fuente