Чи слід віддавати перевагу складу чи спадку у цьому сценарії?


11

Розглянемо інтерфейс:

interface IWaveGenerator
{
    SoundWave GenerateWave(double frequency, double lengthInSeconds);
}

Цей інтерфейс реалізований низкою класів, які генерують хвилі різної форми (наприклад, SineWaveGeneratorта SquareWaveGenerator).

Я хочу реалізувати клас, який формує на SoundWaveоснові музичних даних, а не необроблених звукових даних. Він отримав би назву ноти та довжину у перерахунку на удари (не секунди), а внутрішньо використовувати IWaveGeneratorфункціонал для створення SoundWaveвідповідно.

Питання полягає в тому, чи повинен NoteGeneratorмістити IWaveGeneratorабо повинен успадковувати його IWaveGeneratorімплементацію?

Я схиляюся до складу через дві причини:

1- Це дозволяє мені вводити будь- IWaveGeneratorякі NoteGeneratorдинамічно. Крім того , мені потрібен тільки один NoteGeneratorклас, замість SineNoteGenerator, SquareNoteGeneratorі т.д.

2- Не потрібно NoteGeneratorвиставляти інтерфейс нижнього рівня, визначений IWaveGenerator.

Однак я розміщую це питання, щоб почути інші думки щодо цього, можливо, моменти, про які я не замислювався.

BTW: Я б сказав, що NoteGenerator це концептуально, IWaveGeneratorтому що він породжує SoundWaves.

Відповіді:


14

Це дозволяє мені динамічно вводити будь-який IWaveGenerator до NoteGenerator. Також мені потрібен лише один клас NoteGenerator, замість SineNoteGenerator , SquareNoteGenerator тощо.

Це чітка ознака, що тут було б краще використовувати композицію, а не успадковувати від SineGeneratorабо SquareGeneratorабо (гірше) обох. Крім того, буде сенс успадкувати NoteGenerator безпосередньо від a, IWaveGeneratorякщо трохи змінити останнє.

Реальна проблема тут в тому, що, ймовірно , сенс мати NoteGeneratorз методом , як

SoundWave GenerateWave(string noteName, double noOfBeats, IWaveGenerator waveGenerator);

але не методом

SoundWave GenerateWave(double frequency, double lengthInSeconds);

тому що цей інтерфейс занадто специфічний. Ви хочете IWaveGenerator, щоб s були об'єктами, які генерують SoundWaves, але в даний час ваш інтерфейс виражає IWaveGenerators - це об'єкти, які генерують SoundWaves виключно частотою та довжиною . Тож краще розробити такий інтерфейс таким чином

interface IWaveGenerator
{
    SoundWave GenerateWave();
}

і передайте такі параметри, як frequencyабо lengthInSeconds, або зовсім інший набір параметрів через конструктори a SineWaveGenerator, a SquareGeneratorабо будь-який інший генератор, який ви маєте на увазі. Це дозволить створити інший тип IWaveGenerators з абсолютно іншими параметрами конструкції. Можливо, ви хочете додати генератор прямокутних хвиль, якому потрібні параметри частоти та двох довжин, або щось подібне, можливо, ви хочете додати наступний генератор трикутників хвиль, також із щонайменше трьома параметрами. Або ж NoteGenerator, з параметрами конструктора noteName, noOfBeatsі waveGenerator.

Тож загальним рішенням тут є від'єднання вхідних параметрів від вихідної функції та зробити лише функцію виводу частиною інтерфейсу.


Цікаво, не думали про це. Але мені цікаво: чи часто це (встановлення «параметрів поліморфної функції» в конструкторі) часто працює в реальності? Тому що тоді код дійсно повинен був би знати, з яким типом він має справу, тим самим руйнуючи Поліморфізм. Чи можете ви навести приклад, де це би працювало?
Авів Кон

2
@AvivCohn: "код дійсно повинен знати, з яким типом він має справу" - ні, це неправильне уявлення. Тільки частина коду , який будує тип конкретної генератора (Mybe завод), і що повинен знати завжди , який тип він має справу.
Док Браун

... і якщо вам потрібно зробити процес побудови ваших об'єктів поліморфним, ви можете використовувати шаблон "абстрактного заводу" ( en.wikipedia.org/wiki/Ab абстракт_factory_pattern )
Doc Brown

Це рішення, яке я обрав би. Невеликі, непорушні заняття - це правильний шлях.
Стівен

9

Незалежно від того, «NoteGenerator» є «концептуально» IWaveGenerator, значення не має.

Вам слід успадковувати інтерфейс лише у тому випадку, якщо ви плануєте реалізувати цей точний інтерфейс відповідно до принципу заміни Ліскова, тобто з правильною семантикою та правильним синтаксисом.

Здається, що ваш NoteGenerator може мати синтаксично той самий інтерфейс, але його семантика (у цьому випадку значення значень параметрів, які він приймає) буде дуже різною, тому використання успадкування в цьому випадку було б дуже оманливим і потенційно схильним до помилок. Ви праві віддати перевагу композиції тут.


Насправді я не мав на увазі NoteGeneratorреалізувати, GenerateWaveале інтерпретувати параметри по-різному, так, я згоден, це було б жахливою ідеєю. Я мав на увазі NoteGenerator - це свого роду спеціалізація генератора хвиль: він здатний приймати вхідні дані "вищого рівня" замість просто необроблених звукових даних (наприклад, назва ноти замість частоти). Тобто sineWaveGenerator.generate(440) == noteGenerator.generate("a4"). Тож виникає питання, склад чи спадщина.
Авів Кон

Якщо ви можете створити єдиний інтерфейс, який відповідає як класам генерації хвиль високого, так і низького рівня, то успадкування може бути прийнятним. Але це здається дуже важким і навряд чи матиме реальну користь. Склад, безумовно, здається більш природним вибором.
Іксрек

@Ixrec: насправді не дуже складно мати єдиний інтерфейс для всіх типів генераторів, ОП, ймовірно, повинен робити і те, і інше, використовувати композицію для введення генератора низького рівня та успадковувати з спрощеного інтерфейсу (але не успадковувати NoteGenerator від реалізація генератора низького рівня) Дивіться мою відповідь.
Doc Brown

5

2- Немає потреби в NoteGenerator викривати інтерфейс нижнього рівня, визначений IWaveGenerator.

Звучить, що NoteGeneratorце не є WaveGenerator, тому не слід реалізовувати інтерфейс.

Склад - правильний вибір.


Я б сказав, що NoteGenerator це концептуально, IWaveGeneratorтому що це породжує SoundWaves.
Авів Кон

1
Ну, якщо цього не потрібно виставляти GenerateWave, то це не є IWaveGenerator. Але це здається, що він використовує IWaveGenerator (може, більше?), Отже, композицію.
Ерік Кінг

@EricKing: це правильна відповідь, доки потрібно дотримуватися GenerateWaveфункції, як це написано у питанні. Але з коментаря вище я здогадуюсь, що це не те, що ОП насправді малося на увазі.
Док Браун

3

У вас є солідний корпус для композиції. У вас може бути справа також додати спадщину. Шлях дізнатися - перегляньте код виклику. Якщо ви хочете мати можливість використовувати NoteGeneratorіснуючий код виклику, який очікує IWaveGenerator, тоді вам потрібно реалізувати інтерфейс. Ви шукаєте потребу в замінюваності. Незалежно від того, чи це концептуально "є -", генератор хвиль знаходиться поруч.


У такому випадку, тобто, вибираючи склад, але все ж потрібне це успадкування для того, щоб здійснити заміну, "спадщина" буде названа, наприклад IHasWaveGenerator, і відповідний метод на цьому інтерфейсі був би тим, GetWaveGeneratorщо повертає екземпляр IWaveGenerator. Звичайно, найменування можна змінити. (Я просто намагаюся розкрити більше деталей - повідомте мені, чи мої дані неправильні.)
rwong

2

Це добре для NoteGeneratorреалізації інтерфейсу, а також для того, NoteGeneratorщоб мати внутрішню реалізацію, яка посилається (за складом) на іншу IWaveGenerator.

Як правило, композиція призводить до отримання більш корисного (тобто читабельного) коду, оскільки у вас немає складності переосмислення. Ваші спостереження щодо матриці класів, які ви мали б при використанні успадкування, також є суттєвими, і, ймовірно, можуть вважатися запахом коду, що вказує на композицію.

Спадкування краще використовувати, коли у вас є реалізація, яку ви хочете спеціалізувати або налаштувати, що, мабуть, не так: вам просто потрібно використовувати інтерфейс.


1
Це не добре для NoteGeneratorреалізації, IWaveGeneratorтому що ноти вимагають ударів. не секунди-.
Тулен Кордова

Так, звичайно, якщо немає розумної реалізації інтерфейсу, то клас не повинен його реалізовувати. Однак ОП заявив, що "я б сказав, що NoteGeneratorце концептуально, IWaveGeneratorтому що це породжує SoundWaves", і він розглядав спадщину, тому я взяв ментальну широту на можливість того, що може бути якась реалізація інтерфейсу, хоча є інший кращий інтерфейс або підпис для класу.
Ерік Ейдт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.