Considérez une interface:
interface IWaveGenerator
{
SoundWave GenerateWave(double frequency, double lengthInSeconds);
}
Cette interface est implémentée par un certain nombre de classes qui génèrent des vagues de formes différentes (par exemple, SineWaveGenerator
et SquareWaveGenerator
).
Je veux implémenter une classe qui génère un SoundWave
basé sur des données musicales, pas des données sonores brutes. Il recevrait le nom d'une note et une longueur en termes de battements (pas de secondes), et utiliserait en interne la IWaveGenerator
fonctionnalité pour créer un en SoundWave
conséquence.
La question est de savoir si le doit NoteGenerator
contenir IWaveGenerator
ou doit-il hériter d'une IWaveGenerator
implémentation?
Je penche vers la composition pour deux raisons:
1- Il me permet d'injecter tout IWaveGenerator
le NoteGenerator
dynamique. En outre, je ne ai besoin d' une NoteGenerator
classe, au lieu de SineNoteGenerator
, SquareNoteGenerator
etc.
2- Il n'est pas nécessaire NoteGenerator
d'exposer l'interface de niveau inférieur définie par IWaveGenerator
.
Cependant, je poste cette question pour entendre d'autres opinions à ce sujet, peut-être des points auxquels je n'ai pas pensé.
BTW: Je dirais que NoteGenerator
c'est conceptuellement un IWaveGenerator
parce qu'il génère l' SoundWave
art.
Que NoteGenerator soit ou non "conceptuellement" un IWaveGenerator n'a pas d'importance.
Vous ne devez hériter d'une interface que si vous prévoyez d'implémenter cette interface exacte selon le principe de substitution Liskov, c'est-à-dire avec la sémantique correcte ainsi que la syntaxe correcte.
Il semble que votre NoteGenerator puisse avoir syntaxiquement la même interface, mais sa sémantique (dans ce cas, la signification des paramètres qu'il prend) sera très différente, donc l'utilisation de l'héritage dans ce cas serait très trompeuse et potentiellement sujette aux erreurs. Vous avez raison de préférer la composition ici.
la source
NoteGenerator
implémenterGenerateWave
mais interpréter les paramètres différemment, oui je suis d'accord que ce serait une idée terrible. Je voulais dire que NoteGenerator est une sorte de spécialisation d'un générateur d'ondes: il est capable d'accepter des données d'entrée de «niveau supérieur» au lieu de simplement des données sonores brutes (par exemple, un nom de note au lieu d'une fréquence). C'est à diresineWaveGenerator.generate(440) == noteGenerator.generate("a4")
. Alors vient la question, la composition ou l'héritage.Il semble que ce
NoteGenerator
ne soit pas unWaveGenerator
, il ne faut donc pas implémenter l'interface.La composition est le bon choix.
la source
NoteGenerator
c'est conceptuellement unIWaveGenerator
parce qu'il génère l'SoundWave
art.GenerateWave
, ce n'est pas unIWaveGenerator
. Mais on dirait qu'il utilise un IWaveGenerator (peut-être plus?), D'où la composition.GenerateWave
fonction telle qu'elle est écrite dans la question. Mais d'après le commentaire ci-dessus, je suppose que ce n'est pas ce que le PO avait vraiment en tête.Vous avez un solide dossier de composition. Vous pouvez avoir un cas pour ajouter également l' héritage. La façon de le dire est de regarder le code appelant. Si vous voulez pouvoir utiliser un
NoteGenerator
dans un code d'appel existant qui attend unIWaveGenerator
, vous devez implémenter l'interface. Vous recherchez un besoin de substituabilité. Qu'il soit conceptuellement «générateur de vagues» n'est pas la question.la source
IHasWaveGenerator
, et la méthode appropriée sur cette interface serait celleGetWaveGenerator
qui renvoie une instance deIWaveGenerator
. Bien sûr, le nom peut être modifié. (J'essaie juste de donner plus de détails - faites-moi savoir si mes détails sont faux.)C'est bien pour
NoteGenerator
implémenter l'interface, et aussi, pourNoteGenerator
avoir une implémentation interne qui référence (par composition) une autreIWaveGenerator
.Généralement, la composition se traduit par un code plus facile à gérer (c'est-à-dire lisible), car vous n'avez pas de complexités de remplacements à raisonner. Votre observation sur la matrice des classes que vous auriez lors de l'utilisation de l'héritage est également pertinente, et peut probablement être considérée comme une odeur de code pointant vers la composition.
L'héritage est mieux utilisé lorsque vous avez une implémentation que vous souhaitez spécialiser ou personnaliser, ce qui ne semble pas être le cas ici: il vous suffit d'utiliser l'interface.
la source
NoteGenerator
implémenterIWaveGenerator
parce que les notes nécessitent des battements. pas secondes-.NoteGenerator
c'est conceptuellement unIWaveGenerator
parce qu'il génère desSoundWave
s", et, il envisageait l'héritage, j'ai donc pris une latitude mentale pour la possibilité qu'il puisse y avoir une certaine implémentation de l'interface, même s'il existe un autre meilleure interface ou signature pour la classe.