Betrachten Sie eine Schnittstelle:
interface IWaveGenerator
{
SoundWave GenerateWave(double frequency, double lengthInSeconds);
}
Diese Schnittstelle wird von einer Reihe von Klassen implementiert, die Wellen unterschiedlicher Form erzeugen (z. B. SineWaveGenerator
und SquareWaveGenerator
).
Ich möchte eine Klasse implementieren, die SoundWave
basierend auf Musikdaten und nicht auf Rohtondaten generiert . Es würde den Namen einer Note und eine Länge in Beats (nicht Sekunden) erhalten und die IWaveGenerator
Funktionalität intern verwenden , um eine SoundWave
entsprechende Note zu erstellen .
Die Frage ist, sollte das eine NoteGenerator
enthalten IWaveGenerator
oder sollte es von einer IWaveGenerator
Implementierung erben ?
Ich neige aus zwei Gründen zur Komposition:
1- Es erlaubt mir , jeden zu injizieren , IWaveGenerator
um die NoteGenerator
dynamisch. Auch ich brauche nur eine NoteGenerator
Klasse, statt SineNoteGenerator
, SquareNoteGenerator
usw.
2- Es ist nicht erforderlich NoteGenerator
, die durch definierte untergeordnete Schnittstelle freizulegen IWaveGenerator
.
Allerdings poste ich diese Frage, um andere Meinungen dazu zu hören, vielleicht Punkte, an die ich nicht gedacht habe.
Übrigens: Ich würde sagen, es NoteGenerator
ist konzeptionell ein, IWaveGenerator
weil es SoundWave
s erzeugt .
Ob NoteGenerator "konzeptionell" ein IWaveGenerator ist oder nicht, spielt keine Rolle.
Sie sollten nur dann von einer Schnittstelle erben, wenn Sie genau diese Schnittstelle gemäß dem Liskov-Substitutionsprinzip implementieren möchten, dh mit der richtigen Semantik sowie der richtigen Syntax.
Es hört sich so an, als ob Ihr NoteGenerator syntaktisch dieselbe Schnittstelle hat, aber seine Semantik (in diesem Fall die Bedeutung der verwendeten Parameter) ist sehr unterschiedlich, sodass die Verwendung der Vererbung in diesem Fall sehr irreführend und möglicherweise fehleranfällig wäre. Sie bevorzugen hier die Komposition.
quelle
NoteGenerator
implementieren,GenerateWave
aber die Parameter anders interpretieren, ja, ich stimme zu, das wäre eine schreckliche Idee. Ich meinte, NoteGenerator ist eine Art Spezialisierung eines Wellengenerators: Er kann Eingangsdaten mit höherem Pegel anstelle von Rohdaten (z. B. einen Notennamen anstelle einer Frequenz) aufnehmen. DhsineWaveGenerator.generate(440) == noteGenerator.generate("a4")
. Da kommt also die Frage, Zusammensetzung oder Vererbung.Klingt so, als wäre
NoteGenerator
es keinWaveGenerator
, sollte daher die Schnittstelle nicht implementieren.Zusammensetzung ist die richtige Wahl.
quelle
NoteGenerator
ist konzeptionell ein,IWaveGenerator
weil esSoundWave
s erzeugt .GenerateWave
, dann ist es keinIWaveGenerator
. Aber es klingt wie es verwendet einen IWaveGenerator (vielleicht mehr?), Also Zusammensetzung.GenerateWave
Funktion halten muss, wie sie in der Frage geschrieben steht. Aber aus dem obigen Kommentar denke ich, dass das OP nicht wirklich im Sinn hatte.Sie haben ein solides Argument für die Komposition. Möglicherweise müssen Sie auch die Vererbung hinzufügen. Sie können dies anhand des aufrufenden Codes feststellen. Wenn Sie einen
NoteGenerator
in vorhandenen Aufrufcode verwenden möchten, der einen erwartetIWaveGenerator
, müssen Sie die Schnittstelle implementieren. Sie suchen nach einem Bedürfnis nach Substituierbarkeit. Ob es konzeptionell ein Wellengenerator ist, ist nebensächlich.quelle
IHasWaveGenerator
, und die relevante Methode auf dieser Schnittstelle ist die,GetWaveGenerator
die eine Instanz von zurückgibtIWaveGenerator
. Natürlich kann die Benennung geändert werden. (Ich versuche nur, mehr Details zuEs ist in Ordnung
NoteGenerator
, die Schnittstelle zu implementieren undNoteGenerator
eine interne Implementierung zu haben, die (nach Zusammensetzung) auf eine andere verweistIWaveGenerator
.Im Allgemeinen führt die Komposition zu einem besser wartbaren (dh lesbaren) Code, da Sie keine Komplexität von Überschreibungen haben, über die Sie nachdenken müssen. Ihre Beobachtung über die Klassenmatrix, die Sie bei der Verwendung der Vererbung haben würden, ist ebenfalls zutreffend und kann wahrscheinlich als Codegeruch angesehen werden, der auf die Komposition hinweist.
Vererbung wird besser verwendet, wenn Sie eine Implementierung haben, die Sie spezialisieren oder anpassen möchten, was hier nicht der Fall zu sein scheint: Sie müssen nur die Schnittstelle verwenden.
quelle
NoteGenerator
Implementierung ist nicht in Ordnung ,IWaveGenerator
da Noten Beats erfordern. nicht Sekunden-.NoteGenerator
ist konzeptionell ein,IWaveGenerator
weil esSoundWave
s erzeugt ", und er erwog eine Vererbung, so dass ich mentalen Spielraum für die Möglichkeit nahm, dass es eine Implementierung der Schnittstelle geben könnte, obwohl es eine andere gibt bessere Schnittstelle oder Signatur für die Klasse.