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
um fly
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 :
public class LoginFailure : System.ApplicationException {}
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.
Duckbehavior.quackBehavior
e outros campos 'no seu código?