Como devo adicionar funcionalidade a um objeto que já existe?

25

Eu tenho uma interface que possui uma certa quantidade de funcionalidades bem definidas. Digamos:

interface BakeryInterface {
  public function createCookies();
  public function createIceCream();
}

Isso funciona bem para a maioria das implementações da interface, mas em alguns casos, preciso adicionar algumas novas funcionalidades (como talvez incorporadas a um novo método createBrownies()). A abordagem óbvia / ingênua de fazer isso seria estender a interface:

interface BrownieBakeryInterface extends BakeryInterface {
  public function createBrownies();
}

Mas tem uma desvantagem bastante grande: não posso adicionar a nova funcionalidade sem modificar a API existente (como alterar a classe para usar a nova interface).

Eu estava pensando em usar um adaptador para adicionar a funcionalidade após a instanciação:

class BrownieAdapter {
  private brownieBakery;

  public function construct(BakeryInterface bakery) {
    this->brownieBakery = bakery;
  }

  public function createBrownies() {
    /* ... */
  }
}

O que me renderia algo como:

bakery = new Bakery();
bakery = new BrownieBakery(bakery);
bakery->createBrownies();

Parece uma boa solução para o problema, mas estou me perguntando se estou despertando os deuses antigos fazendo isso. O adaptador é o caminho a seguir? Existe um padrão melhor a seguir? Ou devo apenas estar mordendo a bala e estendendo a interface original?


fonte
O Delphi possui classes auxiliares, é como adicionar métodos às classes existentes sem realmente modificá-las. Por exemplo, o Delphi possui uma classe TBitmap definida em sua unidade gráfica, você pode criar uma classe auxiliar que adiciona, digamos, uma função Flip ao TBitmap. Enquanto a classe auxiliar estiver no escopo, você poderá chamar MyBitmap.Flip;
Bill

Respostas:

14

Qualquer um dos padrões do corpo do identificador pode se encaixar na descrição, dependendo dos requisitos exatos, idioma e nível de abstração necessário.

A abordagem purista seria o padrão Decorator , que faz exatamente o que você procura, adiciona dinamicamente responsabilidades aos objetos. Se você está realmente construindo padarias, é definitivamente um exagero e deve apenas usar o Adapter.

yannis
fonte
Era exatamente isso que eu precisava: percebi que o uso de um adaptador atrapalhava a injeção de dependência, mas o uso de um decorador evita isso.
5

Investigue o conceito de reutilização horizontal , onde você pode encontrar coisas como Traits , a Programação Orientada a Aspectos ainda experimental, mas já à prova de produção, e os Mixins às vezes odiados .

Uma maneira direta de adicionar métodos a uma classe também depende da linguagem de programação. O Ruby permite a correção de macacos enquanto a herança baseada em protótipo do Javascript , onde as classes realmente não existem, você cria um objeto e apenas o copia e continua adicionando, por exemplo:

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, você também pode emular a reutilização horizontal, ou "modificação" e "adição" do tempo de execução do comportamento da classe / objeto com reflexão .

dukeofgaming
fonte
4

Se houver um requisito de que a bakeryinstância deve alterar seu comportamento dinamicamente (dependendo das ações do usuário, etc.), você deve optar pelo padrão Decorator .

Se bakerynão alterar seu comportamento dinamicamente, mas você não puder modificar a Bakery class(API externa, etc.), deverá procurar o padrão do Adaptador .

Se bakerynão alterar seu comportamento dinamicamente e você puder modificar Bakery class, você deverá estender a interface existente (como você propôs inicialmente) ou introduzir uma nova interface BrownieInterface e deixar Bakeryimplementar duas BakeryInterfacee BrownieInterface.
Caso contrário, você adicionará complexidade desnecessária ao seu código (usando o padrão Decorator) sem nenhum motivo!

Dime
fonte