Чи не погані ієрархії глибокого складу?


19

Вибачте, якщо "Ієрархія композиції" - це не річ, але я поясню, що я маю на увазі під питанням.

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

Скажімо, нам потрібна збірка звітів із деталізацією результатів експерименту:

class Model {
    // ... interface

    Array<Result> m_results;
}

Кожен результат має певні властивості. Сюди входить час експерименту, а також деякі метадані з кожного етапу експерименту:

enum Stage {
    Pre = 1,
    Post
};

class Result {
    // ... interface

    Epoch m_epoch;
    Map<Stage, ExperimentModules> m_modules; 
}

ОК здорово. Тепер кожен експериментальний модуль містить рядок, що описує результат експерименту, а також колекцію посилань на експериментальні вибіркові набори:

class ExperimentalModules {
    // ... interface

    String m_reportText;
    Array<Sample> m_entities;
}

І тоді кожен зразок має ... ну, ви отримуєте малюнок.

Проблема полягає в тому, що якщо я моделюю об’єкти з мого домену додатків, це здається дуже природним підходом, але в кінці дня, Resultце просто німий контейнер даних! Створювати для цього велику групу класів не варто.

Якщо припустити, що наведені вище структури даних та класи правильно моделюють взаємозв'язки в області додатків, чи є кращий спосіб моделювати такий "результат", не вдаючись до глибокої ієрархії композиції? Чи є якийсь зовнішній контекст, який допоможе вам визначити, чи є такий дизайн хорошим чи ні?


Не розумію зовнішньої частини контексту.
Тулен Кордова

@ TulainsCórdova, що я намагаюся тут запитати, "чи існують ситуації, для яких глибокий склад є хорошим рішенням"?
AwesomeSauce

Я додав відповідь.
Тулен Кордова

5
Так, саме. Проблеми, які люди намагаються виправити, замінивши спадщину складом, не магічно згасають лише тому, що ви змінили одну стратегію агрегації функцій на іншу. Вони обидва рішення для управління складністю, і ця складність все ще існує і з нею потрібно якось впоратися.
Мейсон Уілер

3
Я не бачу нічого поганого в моделях глибоких даних. Але я також не бачу, як ви могли моделювати це за допомогою спадкування, оскільки це відношення 1: N на кожному шарі.
usr

Відповіді:


17

Це те, про що йдеться в Законі Деметра / принцип найменших знань . Користувач Modelне повинен обов'язково знати про ExperimentalModulesінтерфейс, щоб виконувати свою роботу.

Загальна проблема полягає в тому, що коли клас успадковує від іншого типу або має публічне поле з певним типом або коли метод приймає тип як параметр або повертає тип, інтерфейси всіх цих типів стають частиною ефективного інтерфейсу мого класу. Постає питання: ці типи, від яких я залежу: використовую ці цілі точки мого класу? Або це просто деталі реалізації, які я випадково викрив? Оскільки, якщо вони є детальними відомостями про реалізацію, я лише відкрив їх і дозволив користувачам мого класу залежати від них - мої клієнти тепер щільно пов'язані з моїми деталями реалізації.

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

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

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

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


Ви згадали про головну стурбованість у мене; ідея, що мені довелося досягти далеко в реалізації, щоб зробити щось корисне на більш високих рівнях абстракції. Дуже корисна дискусія, дякую.
AwesomeSauce

7

Чи не погані ієрархії глибокого складу?

Звичайно. Насправді це правило повинно говорити "тримайте всі ієрархії максимально рівними".

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

Ваш приклад не вважає мене "глибокою ієрархією". Форма успадковується від прямокутника, який успадковує від набору точок, який успадковує від координат x і y, і я навіть ще не отримав повної голови пари.


4

Перефразовуючи Ейнштейна:

тримайте всі ієрархії максимально рівними, але не плоскішими

Тобто, якщо проблема, яку ви моделюєте, така, у вас не повинно виникати проблем щодо її моделювання.

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


1

Так, ви можете моделювати його як реляційні таблиці

Result {
    Id
    ModelId
    Epoch
}

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


0

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.