¿Cómo debo agregar funcionalidad a un objeto que ya existe?

25

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?


fuente
Delphi tiene clases auxiliares, es como agregar métodos a las clases existentes sin modificarlas realmente. Por ejemplo, Delphi tiene una clase TBitmap definida en su unidad gráfica, puede crear una clase auxiliar que agregue, por ejemplo, una función Flip a TBitmap. Mientras la clase auxiliar esté dentro del alcance, puede llamar a MyBitmap.Flip;
Bill

Respuestas:

14

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.

Yannis
fuente
Esto es exactamente lo que necesitaba: me di cuenta de que usar un adaptador se atornillaría con la inyección de dependencia, pero usar un decorador lo soluciona.
5

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:

var MyClass = {
    do : function(){...}
};

var MyNewClass = new MyClass;
MyClass.undo = function(){...};


var my_new_object = new MyNewClass;
my_new_object.do();
my_new_object.undo();

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 .

dukeofgaming
fuente
4

Si hay un requisito de que la bakeryinstancia debe cambiar su comportamiento dinámicamente (dependiendo de las acciones del usuario, etc.), entonces debe elegir el patrón Decorador .

Si bakeryno cambia su comportamiento dinámicamente pero no puede modificar la Bakery class(API externa, etc.), entonces debe elegir el patrón Adaptador .

Si bakeryno cambia su comportamiento dinámicamente y puede modificarlo, Bakery classentonces debe extender la interfaz existente (como propuso inicialmente) o introducir una nueva interfaz BrownieInterface y dejar Bakeryimplementar dos interfaces BakeryInterfacey BrownieInterface.
De lo contrario, ¡agregará una complejidad innecesaria a su código (usando el patrón Decorador) sin ninguna buena razón!

Moneda de diez centavos
fuente