インターフェースを考えてみましょう:
interface IWaveGenerator
{
SoundWave GenerateWave(double frequency, double lengthInSeconds);
}
このインターフェイスは、さまざまな形状の波(たとえば、SineWaveGenerator
およびSquareWaveGenerator
)を生成するいくつかのクラスによって実装されます。
生のサウンドデータではなく、音楽データに基づいてSoundWave
を生成するクラスを実装したいと思います。それはノートの名前とビート(秒ではなく)の長さを受け取り、内部でIWaveGenerator
機能を使用してSoundWave
を作成します。
問題は、NoteGenerator
にIWaveGenerator
を含める必要があるか、それともIWaveGenerator
実装から継承する必要があるかです。
私は2つの理由から作曲に傾いています:
1- IWaveGenerator
にNoteGenerator
を動的に注入することができます。また、NoteGenerator
、SineNoteGenerator
などではなく、SquareNoteGenerator
クラスが1つだけ必要です。
2- NoteGenerator
で定義された下位レベルのインターフェースを公開するためにIWaveGenerator
を使用する必要はありません。
しかし、私はこの質問を投稿して、これに関する他の意見を聞いています。おそらく私が考えていなかった点です。
ところで:NoteGenerator
isはIWaveGenerator
sを生成するので、概念的にはSoundWave
と言います。
IWaveGeneratorをNoteGeneratorに動的に挿入できます。また、SineNoteGenerator、SquareNoteGeneratorなどの代わりに、NoteGeneratorクラスが1つだけ必要です。
これは明確な兆候であり、ここではコンポジションを使用する方が適切であり、SineGenerator
またはSquareGenerator
または(さらに悪い)両方から継承しないでください。それでも、後者を少し変更する場合は、IWaveGenerator
からNoteGeneratorを直接継承するのが理にかなっています。
本当の問題ここにあるのは、次のようなメソッドでNoteGenerator
を指定することはおそらく意味がある
SoundWave GenerateWave(string noteName, double noOfBeats, IWaveGenerator waveGenerator);
メソッドではなく
SoundWave GenerateWave(double frequency, double lengthInSeconds);
このインターフェースは具体的すぎるためです。 IWaveGenerator
sをSoundWave
sを生成するオブジェクトにする必要がありますが、現在のインターフェイスはIWaveGenerator
sを表現してSoundWave
sを生成するオブジェクトです周波数と長さのみから。このようにこのようなインターフェースをより良く設計する
interface IWaveGenerator
{
SoundWave GenerateWave();
}
frequency
やlengthInSeconds
などのパラメーター、またはSineWaveGenerator
、SquareGenerator
、またはその他のジェネレーターのコンストラクターを介して完全に異なるパラメーターのセットを渡す思っている。これにより、完全に異なる構築パラメーターを使用して、他の種類のIWaveGenerator
sを作成できます。周波数と2つの長さパラメータを必要とする矩形波ジェネレータなどを追加したい場合もあれば、次に3つ以上のパラメータを持つ三角波ジェネレータを追加したい場合もあります。または、コンストラクタパラメータNoteGenerator
、noteName
、およびnoOfBeats
を使用したwaveGenerator
。
したがって、ここでの一般的な解決策は、入力パラメーターを出力関数から切り離し、出力関数のみをインターフェースの一部にすることです。
NoteGeneratorが「概念的に」IWaveGeneratorであるかどうかは問題ではありません。
Liskov Substitution Principleに従って正確なインターフェースを実装することを計画している場合、つまり正しい構文と正しいセマンティクスでインターフェースを継承する必要があります。
NoteGeneratorは構文的に同じインターフェースを持っているように思えますが、そのセマンティクス(この場合、取得するパラメーターの意味)は非常に異なるため、この場合に継承を使用すると誤解を招きやすく、エラーが発生しやすくなります。ここで作文を好むのは当然だ。
2- NoteGeneratorがIWaveGeneratorによって定義された下位レベルのインターフェイスを公開する必要はありません。
NoteGenerator
のようなサウンドはWaveGenerator
ではないため、インターフェースを実装しないでください。
構成は正しい選択です。
あなたは作曲のためのしっかりしたケースを持っています。 also継承を追加するケースがあるかもしれません。伝える方法は、呼び出しコードを調べることです。 NoteGenerator
を期待する既存の呼び出しコードでIWaveGenerator
を使用できるようにする場合は、インターフェースを実装する必要があります。代替性の必要性を探しています。それが概念的に「is-a」波発生器であるかどうかは重要ではありません。
NoteGenerator
がインターフェイスを実装し、NoteGenerator
が別のIWaveGenerator
を(構成によって)参照する内部実装を持つことは問題ありません。
一般的に、構成することでコードの保守性が向上します(つまり、コードが読みやすくなります)。理由を説明するためのオーバーライドの複雑さはありません。継承を使用するときに持つクラスのマトリックスについてのあなたの観察も適切であり、おそらく合成を指すコード臭いと考えることができます。
継承は、特殊化またはカスタマイズしたい実装がある場合によりよく使用されますが、ここではそうではないように見えます。インターフェースを使用する必要があるだけです。