Por exemplo:
var duckBehaviors = new Duckbehavior();
duckBehaviors.quackBehavior = new Quack();
duckBehaviors.flyBehavior = new FlyWithWings();
Duck mallardDuck = new Duck(DuckTypes.MallardDuck, duckBehaviors)
Como a classe Duck contém todos os comportamentos (abstrato), a criação de uma nova classe MallardDuck
(que se estende Duck
) não parece ser necessária.
Referência: Head First Design Pattern, Capítulo 1.
design-patterns
dependency-injection
strategy-pattern
Deepak Mishra
fonte
fonte
Duckbehavior.quackBehavior
e outros campos 'no seu código?Respostas:
Claro, mas chamamos isso de composição e delegação . O padrão de estratégia e a injeção de dependência podem parecer estruturalmente semelhantes, mas suas intenções são diferentes.
O Padrão de Estratégia permite a modificação do comportamento em tempo de execução na mesma interface. Eu poderia dizer a um pato-real para voar e vê-lo voar com asas. Troque-o por um pato piloto de jato e observe-o voar com as companhias aéreas da Delta. Fazer isso enquanto o programa está em execução é uma coisa do Padrão de Estratégia.
A Injeção de Dependência é uma técnica para evitar dependências de código rígido, para que elas possam mudar independentemente, sem exigir que os clientes sejam modificados quando mudarem. Os clientes simplesmente expressam suas necessidades sem saber como serão atendidas. Assim, como eles são cumpridos é decidido em outro lugar (geralmente no principal). Você não precisa de dois patos para fazer uso dessa técnica. Apenas algo que usa um pato sem saber ou se importar com qual pato. Algo que não constrói o pato ou procura, mas fica perfeitamente feliz em usar o pato que você lhe der.
Se eu tenho uma classe de pato concreta, ela pode implementar o comportamento da mosca. Eu poderia até mudar comportamentos de voar com asas para voar com Delta com base em uma variável de estado. Essa variável pode ser um booleano, um int ou
FlyBehavior
umfly
método que faça qualquer estilo de execução sem que eu precise testá-la com um if. Agora eu posso mudar os estilos de vôo sem alterar os tipos de patos. Agora os Marreco podem se tornar pilotos. Isso é composição e delegação . O pato é composto de um FlyBehavior e pode delegar solicitações de vôo a ele. Você pode substituir todos os seus comportamentos de pato de uma só vez dessa maneira ou manter algo para cada comportamento ou qualquer combinação entre eles.Isso lhe dá todos os mesmos poderes que a herança possui, exceto um. A herança permite expressar quais métodos do Duck você está substituindo nos subtipos de Duck. A composição e a delegação exigem que o Duck delegue explicitamente os subtipos desde o início. Isso é muito mais flexível, mas envolve mais digitação do teclado e o Duck precisa saber que está acontecendo.
No entanto, muitas pessoas acreditam que a herança deve ser explicitamente projetada desde o início. E que, se não tiver sido, você deve marcar suas aulas como seladas / finais para proibir a herança. Se você adota essa visão, a herança realmente não tem vantagem sobre a composição e a delegação. Porque, de qualquer maneira, é necessário projetar a extensibilidade desde o início ou estar disposto a derrubar as coisas mais tarde.
Derrubar as coisas é realmente uma opção popular. Esteja ciente de que há casos em que é um problema. Se você implantou bibliotecas ou módulos de código de forma independente que não pretende atualizar com a próxima versão, pode acabar lidando com versões de classes que não sabem nada sobre o que você está fazendo agora.
Embora esteja disposto a derrubar as coisas mais tarde, você pode liberar o excesso de projeto; há algo muito poderoso em ser capaz de projetar algo que use um pato sem precisar saber o que o pato realmente fará quando usado. Que não saber é algo poderoso. Ele permite que você pare de pensar em patos por um tempo e pense no restante do seu código.
"Podemos" e "devemos" são perguntas diferentes. Favorecer a composição sobre a herança não diz nunca usar herança. Ainda existem casos em que a herança faz mais sentido. Vou mostrar meu exemplo favorito :
A herança permite criar exceções com nomes descritivos e mais específicos em apenas uma linha.
Tente fazer isso com a composição e você terá uma bagunça. Além disso, não há risco do problema ioiô de herança porque não há dados ou métodos aqui para reutilizar e incentivar o encadeamento de herança. Tudo isso acrescenta um bom nome. Nunca subestime o valor de um bom nome.
fonte
LoginException
não é um bom nome. É o clássico "nome do smurf" e, se eu tirarException
, tudo o que tenho éLogin
que não me diz nada do que deu errado.Exception
encerrar todas as classes de exceção, mas não confunda "preso a ele" com "bom".StackOverflow
,OutOfMemory
,NullReferenceAccess
,LoginFailure
etc. Basicamente, tomar "Exception" fora do nome. E, se necessário, corrija-os para que descreva o que deu errado.Você pode substituir quase qualquer metodologia por qualquer outra metodologia e ainda produzir software funcional. No entanto, alguns são mais adequados para um problema específico do que outros.
Depende de muitas coisas que é preferível. Técnica anterior no aplicativo, experiência na equipe, desenvolvimentos futuros esperados, preferência pessoal e quão difícil será para um iniciante entender o assunto, para citar alguns.
À medida que você se torna mais experiente e se debate com mais frequência com as criações de outras pessoas, provavelmente dará mais ênfase à última contemplação.
A herança ainda é uma ferramenta de modelagem válida e forte, que nem sempre é a mais flexível, mas oferece uma orientação forte a novas pessoas que podem ser gratas pelo mapeamento claro do domínio do problema.
fonte
A herança não é tão importante quanto se pensava. Ainda é importante , e removê-lo seria um erro grave.
Um exemplo extremo é o Objective-C, onde tudo é uma subclasse de NSObject (o compilador, na verdade, não permite que você declare uma classe que não possui uma classe básica, portanto, tudo o que não está incorporado no compilador deve ser uma subclasse de algo embutido no compilador). Há muitas coisas úteis incorporadas ao NSObject que você não precisaria perder.
Outro exemplo interessante é o UIView, a classe "view" básica no desenvolvimento do UIKit para iOS. Esta é uma classe que é, por um lado, um pouco como uma classe abstrata que declara a funcionalidade que as subclasses devem preencher, mas também é útil por si só. Possui subclasses fornecidas pelo UIKit, que são frequentemente usadas como estão. Há composição pelo desenvolvedor que instala subvisões em uma visualização. E muitas vezes existem subclasses definidas pelo desenvolvedor, que costumam usar composição. Não existe uma regra estrita ou única, você usa o que melhor se adapta aos seus requisitos.
fonte
type
, todos os não-primitiva em herda Java a partirObject
, tudo em JavaScript tem um protótipo terminando comObject
, etc.[Arriscarei uma resposta atrevida à pergunta titular.]
Sim ... exceto que o próprio padrão de estratégia usa herança. O padrão de estratégia funciona na herança da interface. Substituir herança por composição + estratégia move a herança para um local diferente. No entanto, muitas vezes vale a pena fazer essa substituição, pois permite separar hierarquias .
fonte
interface
design craptástico consertado com herança. O problema oculto aqui é a implementação inconsistente de transformar comportamentos dependentes. P, S. herança e interface não são mutuamente exclusivas,interface
Não. Se um pato-real exige parâmetros diferentes para instanciar do que qualquer outro tipo de pato, seria um pesadelo mudar. Você também deve ser capaz de instanciar um pato? É mais um pato-real ou pato mandarim ou qualquer outro tipo de patinho que você tenha.
Também posso querer alguma lógica em torno dos tipos, para que uma hierarquia de tipos possa ser melhor.
Agora, se a reutilização de código se tornar problemática para algumas das funcionalidades, poderíamos compor passando as funções pelo construtor.
Novamente, isso realmente depende do seu caso de uso. Se você tem apenas um pato e um pato-real, uma hierarquia de classes é uma solução muito mais simples.
Mas aqui está um exemplo em que você deseja usar o padrão de estratégia: se você tem classes de clientes e deseja passar uma estratégia de cobrança (interface) que pode ser uma estratégia de cobrança de happy hour ou estratégia de cobrança regular, você pode passá-lo no construtor. Dessa forma, você não precisa criar duas classes diferentes de clientes e não precisa de uma hierarquia. Você apenas tem a classe de um cliente.
fonte