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, SineWaveGenerator
y SquareWaveGenerator
).
Quiero implementar una clase que genere SoundWave
datos 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 IWaveGenerator
funcionalidad para crear un SoundWave
acorde.
La pregunta es, ¿debería NoteGenerator
contener IWaveGenerator
o debería heredar de una IWaveGenerator
implementación?
Me inclino por la composición por dos razones:
1- Me permite inyectar cualquiera IWaveGenerator
a la NoteGenerator
dinámica. Además, sólo necesito una NoteGenerator
clase, en lugar de SineNoteGenerator
, SquareNoteGenerator
, etc.
2- No es necesario NoteGenerator
exponer 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 IWaveGenerator
porque genera SoundWave
s.
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
NoteGenerator
que implementaría,GenerateWave
pero 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
NoteGenerator
no es unWaveGenerator
, por lo tanto, no debe implementar la interfaz.La composición es la elección correcta.
fuente
NoteGenerator
es conceptualmente unIWaveGenerator
porque generaSoundWave
s.GenerateWave
, entonces no es unIWaveGenerator
. Pero parece que usa un IWaveGenerator (¿quizás más?), Por lo tanto, composición.GenerateWave
funció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
NoteGenerator
có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 elGetWaveGenerator
que 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
NoteGenerator
implementar la interfaz y tambiénNoteGenerator
tener 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
NoteGenerator
implementarloIWaveGenerator
porque las notas requieren ritmos. no segundosNoteGenerator
es conceptualmenteIWaveGenerator
porque generaSoundWave
s", 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.