Considere uma interface:
interface IWaveGenerator
{
SoundWave GenerateWave(double frequency, double lengthInSeconds);
}
Essa interface é implementada por várias classes que geram ondas de diferentes formas (por exemplo, SineWaveGenerator
e SquareWaveGenerator
).
Quero implementar uma classe que gere uma SoundWave
base em dados musicais, não em dados brutos de som. Ele receberia o nome de uma nota e um comprimento em termos de batidas (não segundos) e usaria internamente a IWaveGenerator
funcionalidade para criar uma SoundWave
conformidade.
A questão é: deve NoteGenerator
conter IWaveGenerator
ou deve herdar de uma IWaveGenerator
implementação?
Estou me inclinando para a composição por dois motivos:
1- Ele me permite injetar qualquer IWaveGenerator
à NoteGenerator
dinamicamente. Além disso, eu só preciso de uma NoteGenerator
classe, em vez de SineNoteGenerator
, SquareNoteGenerator
, etc.
2- Não há necessidade de NoteGenerator
expor a interface de nível inferior definida por IWaveGenerator
.
No entanto, estou postando esta pergunta para ouvir outras opiniões sobre isso, talvez pontos em que não pensei.
BTW: Eu diria que NoteGenerator
é conceitualmente um IWaveGenerator
porque gera SoundWave
s.
Se o NoteGenerator é ou não "conceitualmente" um IWaveGenerator não importa.
Você só deve herdar de uma interface se planeja implementar essa interface exata de acordo com o Princípio de Substituição de Liskov, ou seja, com a semântica correta e com a sintaxe correta.
Parece que seu NoteGenerator pode ter sintaticamente a mesma interface, mas sua semântica (neste caso, o significado dos parâmetros necessários) será muito diferente, portanto, usar herança neste caso seria altamente enganoso e potencialmente propenso a erros. Você está certo em preferir composição aqui.
fonte
NoteGenerator
iria implementar,GenerateWave
mas interpretar os parâmetros de maneira diferente, sim, eu concordo que seria uma péssima idéia. Eu quis dizer que o NoteGenerator é uma espécie de especialização de um gerador de ondas: ele é capaz de captar dados de entrada de 'nível superior' em vez de apenas dados de som não processados (por exemplo, um nome de nota em vez de uma frequência). Ou sejasineWaveGenerator.generate(440) == noteGenerator.generate("a4")
. Então chega a pergunta, composição ou herança.Parece que
NoteGenerator
não é umWaveGenerator
, portanto, não deve implementar a interface.Composição é a escolha correta.
fonte
NoteGenerator
é conceitualmente umIWaveGenerator
porque geraSoundWave
s.GenerateWave
, não éIWaveGenerator
. Mas parece que ele usa um IWaveGenerator (talvez mais?), Daí a composição.GenerateWave
função conforme está escrito na pergunta. Mas pelo comentário acima, acho que não é isso que o OP realmente tinha em mente.Você tem um argumento sólido para composição. Você pode ter um caso para adicionar também herança. A maneira de saber é observando o código de chamada. Se você quiser usar um
NoteGenerator
no código de chamada existente que espera umIWaveGenerator
, precisará implementar a interface. Você está procurando uma necessidade de substituibilidade. Se conceitualmente "é um gerador de ondas" está fora de questão.fonte
IHasWaveGenerator
, por exemplo , e o método relevante nessa interface seria oGetWaveGenerator
que retorna uma instância deIWaveGenerator
. É claro que a nomeação pode ser alterada. (Eu só estou tentando carne para fora mais detalhes - deixe-me saber se meus dados estão errados.)É bom
NoteGenerator
implementar a interface e tambémNoteGenerator
ter uma implementação interna que faça referência (por composição) a outraIWaveGenerator
.Geralmente, a composição resulta em um código mais sustentável (ou seja, legível), porque você não tem complexidades de substituições para raciocinar. Sua observação sobre a matriz de classes que você teria ao usar herança também é relevante e provavelmente pode ser pensada como um cheiro de código apontando para a composição.
A herança é melhor usada quando você tem uma implementação que deseja especializar ou personalizar, o que não parece ser o caso aqui: você só precisa usar a interface.
fonte
NoteGenerator
implementarIWaveGenerator
porque porque as notas exigem batidas. não segundos-.NoteGenerator
é conceitualmente umIWaveGenerator
porque geraSoundWave
s", e, como ele estava considerando a herança, tomei latitude mental pela possibilidade de que houvesse alguma implementação da interface, mesmo que exista outra melhor interface ou assinatura para a classe.