Це кодовий запах, якщо об’єкт знає багато свого власника?


9

У нашому додатку Delphi 2007 ми використовуємо багато наступних конструкцій

FdmBasic:=TdmBasicData(FindOwnerClass(AOwner,TdmBasicData));

FindOwnerClass переміщує ієрархію власника поточного компонента вгору, щоб знайти конкретний клас (у прикладі TdmBasicData). Отриманий об'єкт зберігається у змінній Field FdmBasic. Ми використовуємо це в першу чергу для передачі модулів даних.

Приклад: Під час генерації звіту отримані дані стискаються та зберігаються у полі Blob таблиці, до якої можна отримати доступ через модуль даних TdmReportBaseData. В окремому модулі нашої програми є функція показу даних із звіту у форматі, що охоплюється, за допомогою ReportBuilder. Основний код цього модуля (TdmRBReport), використовує клас TRBTempdatabase для перетворення стислих даних блобу в різні таблиці, які можна використовувати в програмі звітування про виконання часу Reportbuilder. TdmRBReport має доступ до TdmReportBaseData для всіх видів даних про звіт (тип звіту, налаштування розрахунків звітів тощо). TRBTempDatabase побудований у TdmRBReport, але повинен мати доступ до TdmReportBasedata. Отже, це робиться за допомогою конструкції вище:

constructor TRBTempDatabase.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);

  FdmReportBaseData := TdmRBReport(FindOwnerClass(Owner, TdmRBReport)).dmReportBaseData;
end;{- .Create }

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

Які ваші думки з цього приводу? Це кодовий запах? Якщо так, що є кращим способом?


1
Якби ми знали, що багато іншого про інший клас, було б запропоновано простіший спосіб зробити це.
Лорен Печтел

Відповіді:


13

Цей вид схожий на шаблон локатора послуг, який вперше описав Мартін Фаулер (який був визначений як поширений антидіапазон).

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

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

Компілятор може запропонувати як споживачам, так і виробникам стільки допомоги, коли використовується інжекція конструктора, але жодна з цієї допомоги не доступна для API, які покладаються на сервіс-локатор.

Крім того, він особливо порушує закон Деметера

Закон Деметера (LoD) або Принцип найменших знань - це орієнтир проектування розробки програмного забезпечення, зокрема об'єктно-орієнтованих програм. У загальному вигляді LoD - це конкретний випадок сипучого зчеплення.

Закон Деметера для функцій вимагає, щоб метод M об'єкта O міг посилатися лише на методи таких типів об'єктів:

  1. O себе
  2. Параметри М
  3. будь-які об’єкти, створені / створені в М
  4. О прямі компоненти об'єктів
  5. глобальна змінна, доступна O, в області M

Зокрема, об’єкт повинен уникати виклику методів об’єкта-члена, повернутих іншим методом. Для багатьох сучасних об'єктно-орієнтованих мов, які використовують крапку як ідентифікатор поля, закон можна констатувати просто як "використовувати лише одну крапку". Тобто, код abMethod () порушує закон, де a.Method () цього не робить. Як простий приклад, коли хочеться вигулювати собаку, нерозумно буде наказати ногам собаки ходити прямо; натомість хтось командує собакою та дозволяє їй піклуватися про власні ноги.

Кращий шлях

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

constructor TRBTempDatabase.Create(aOwner: TComponent, ownerClass: IComponent);
begin
  inherited Create(aOwner);

  FdmReportBaseData := TdmRBReport(ownerClass).dmReportBaseData;
end;{- .Create }

3
Чудова відповідь, і реквізит тому, хто придумав цю чудову аналогію:As a simple example, when one wants to walk a dog, it would be folly to command the dog's legs to walk directly; instead one commands the dog and lets it take care of its own legs.
Енді Хант,

3

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

Залежно від того, наскільки глибоко пов’язані два ваші класи, це трохи схоже на опис Фаулером Feature Envy або Невідповідні запахи коду Intimacy.

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

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