Является ли это запахом кода, если объект много знает своего владельца?

9

В нашем приложении Delphi 2007 мы используем множество следующих конструкций

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

FindOwnerClass перемещает иерархию Owner текущего компонента вверх, чтобы найти определенный класс (в примере 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 много знает о своем владельце, и мне было интересно, если это какой-то запах кода или Anti-pattern.

Что вы думаете об этом? Это запах кода? Если так, что лучше?

Bascy
источник
1
Если бы он должен был знать так много о другом классе, то был бы предоставлен более простой способ сделать это.
Лорен Печтел

Ответы:

13

Этот вид выглядит как шаблон локатора сервиса, который был впервые описан Мартином Фаулером (который был идентифицирован как общий анти-паттерн).

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

Проблема с использованием Service Locator заключается не в том, что вы берете зависимость от конкретной реализации Service Locator (хотя это тоже может быть проблемой), а в том, что это добросовестный антишаблон. Это даст пользователям вашего API ужасный опыт для разработчиков и сделает вашу жизнь в качестве разработчика технического обслуживания хуже, потому что вам нужно будет использовать значительное количество умственных способностей, чтобы понять последствия каждого внесенного вами изменения.

При использовании Constructor Injection компилятор может предложить как потребителям, так и производителям такую ​​помощь, но ни одна из этих справок не доступна для API, которые используют Service Locator.

Также наиболее заметно, что это также нарушает закон Деметры

Закон Деметры (LoD) или Принцип Наименьшего Знания - это руководство по разработке программного обеспечения, особенно объектно-ориентированных программ. В общем виде LoD является частным случаем слабой связи.

Закон Деметры для функций требует, чтобы метод M объекта O мог вызывать только методы следующих типов объектов:

  1. О себе
  2. Параметры М
  3. любые объекты, созданные / созданные в M
  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

Одна из трудностей, связанных с тем, что дочерние объекты слишком много знают о родительском объекте, заключается в том, что в итоге вы реализуете шаблоны, которые могут (и чаще всего это делают) становиться слишком тесно связанными, что создает головные боли, связанные с зависимостями, и часто впоследствии становится очень трудно безопасно модифицировать и поддерживать позже.

В зависимости от того, насколько глубоко связаны ваши два класса, это звучит немного похоже на описание Фаулером запахов кода зависти к функциям или несоответствующей интимности.

По-видимому, необходимо загрузить или прочитать класс с данными, и в этом случае вы можете использовать несколько альтернативных шаблонов, чтобы разорвать зависимость между ребенком и его цепочкой родителей, и кажется, что вам нужно делегировать задачу доступа ваш класс данных, а не делать класс доступа к данным ответственным за все сам.

S.Robins
источник