Considere una interfaz:
interface IWaveGenerator
{
SoundWave GenerateWave(double frequency, double lengthInSeconds);
}
Esta interfaz se implementa mediante una serie de clases que generan ondas de diferentes formas (por ejemplo, SineWaveGeneratory SquareWaveGenerator).
Quiero implementar una clase que genere SoundWavedatos basados en datos musicales, no en sonido sin formato. Recibiría el nombre de una nota y una duración en términos de latidos (no segundos), y utilizaría internamente la IWaveGeneratorfuncionalidad para crear un SoundWaveacorde.
La pregunta es, ¿debería NoteGeneratorcontener IWaveGeneratoro debería heredar de una IWaveGeneratorimplementación?
Me inclino por la composición por dos razones:
1- Me permite inyectar cualquiera IWaveGeneratora la NoteGeneratordinámica. Además, sólo necesito una NoteGeneratorclase, en lugar de SineNoteGenerator, SquareNoteGenerator, etc.
2- No es necesario NoteGeneratorexponer la interfaz de nivel inferior definida por IWaveGenerator.
Sin embargo, estoy publicando esta pregunta para escuchar otras opiniones al respecto, tal vez puntos en los que no he pensado.
Por cierto: diría que NoteGenerator es conceptualmente IWaveGeneratorporque genera SoundWaves.

Si NoteGenerator es o no "conceptualmente" un IWaveGenerator no importa.
Solo debe heredar de una interfaz si planea implementar esa interfaz exacta de acuerdo con el Principio de sustitución de Liskov, es decir, con la semántica correcta y la sintaxis correcta.
Parece que su NoteGenerator podría tener sintácticamente la misma interfaz, pero su semántica (en este caso, el significado de los parámetros que toma) será muy diferente, por lo que el uso de la herencia en este caso sería muy engañoso y potencialmente propenso a errores. Tienes razón al preferir la composición aquí.
fuente
NoteGeneratorque implementaría,GenerateWavepero interpretaría los parámetros de manera diferente, sí, estoy de acuerdo en que sería una idea terrible. Quise decir que NoteGenerator es una especie de especialización de un generador de ondas: es capaz de recibir datos de entrada de 'nivel superior' en lugar de solo datos de sonido sin procesar (por ejemplo, un nombre de nota en lugar de una frecuencia). Es decirsineWaveGenerator.generate(440) == noteGenerator.generate("a4"). Entonces viene la pregunta, composición o herencia.Parece que
NoteGeneratorno es unWaveGenerator, por lo tanto, no debe implementar la interfaz.La composición es la elección correcta.
fuente
NoteGeneratores conceptualmente unIWaveGeneratorporque generaSoundWaves.GenerateWave, entonces no es unIWaveGenerator. Pero parece que usa un IWaveGenerator (¿quizás más?), Por lo tanto, composición.GenerateWavefunción tal como está escrita en la pregunta. Pero por el comentario anterior, supongo que eso no es lo que el OP realmente tenía en mente.Tienes un caso sólido para la composición. Es posible que tenga un caso para agregar también herencia. La forma de saberlo es mirando el código de llamada. Si desea poder utilizar un
NoteGeneratorcódigo de llamada existente que espera unIWaveGenerator, entonces necesita implementar la interfaz. Estás buscando una necesidad de sustituibilidad. Si conceptualmente es "un generador de onda" no viene al caso.fuente
IHasWaveGenerator, por ejemplo , y el método relevante en esa interfaz sería elGetWaveGeneratorque devuelve una instancia deIWaveGenerator. Por supuesto, los nombres se pueden cambiar. (Solo estoy tratando de dar más detalles, avíseme si mis detalles están mal)Está bien
NoteGeneratorimplementar la interfaz y tambiénNoteGeneratortener una implementación interna que haga referencia (por composición) a otraIWaveGenerator.En general, la composición da como resultado un código más fácil de mantener (es decir, legible), porque no tiene complejidades de anulaciones para razonar. Tu observación sobre la matriz de clases que tendrías al usar la herencia también es correcta, y probablemente se pueda considerar como un olor a código que apunta hacia la composición.
La herencia se usa mejor cuando tienes una implementación que deseas especializar o personalizar, lo cual no parece ser el caso aquí: solo necesitas usar la interfaz.
fuente
NoteGeneratorimplementarloIWaveGeneratorporque las notas requieren ritmos. no segundosNoteGeneratores conceptualmenteIWaveGeneratorporque generaSoundWaves", y, al considerar la herencia, tomé la libertad mental por la posibilidad de que pudiera haber alguna implementación de la interfaz, aunque haya otra Mejor interfaz o firma para la clase.