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?
Respostas:
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.
fonte
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:
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 .
fonte
Se houver um requisito de que a
bakery
instância deve alterar seu comportamento dinamicamente (dependendo das ações do usuário, etc.), você deve optar pelo padrão Decorator .Se
bakery
não alterar seu comportamento dinamicamente, mas você não puder modificar aBakery class
(API externa, etc.), deverá procurar o padrão do Adaptador .Se
bakery
não alterar seu comportamento dinamicamente e você puder modificarBakery class
, você deverá estender a interface existente (como você propôs inicialmente) ou introduzir uma nova interfaceBrownieInterface
e deixarBakery
implementar duasBakeryInterface
eBrownieInterface
.Caso contrário, você adicionará complexidade desnecessária ao seu código (usando o padrão Decorator) sem nenhum motivo!
fonte
Você encontrou o problema da expressão. Este artigo de Stuart Sierra discute a solução de clojures, mas ele também fornece um exemplo em Java e lista as soluções populares no OO clássico.
Há também esta apresentação de Chris Houser que discute praticamente o mesmo fundamento.
fonte