Czy to zapach kodu, jeśli obiekt zna dużo swojego właściciela?

9

W naszej aplikacji Delphi 2007 używamy wielu następujących konstrukcji

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

FindOwnerClass podróżuje w górę hierarchii właściciela bieżącego komponentu w celu znalezienia określonej klasy (w przykładzie TdmBasicData). Powstały obiekt jest przechowywany w zmiennej Field FdmBasic. Używamy tego przede wszystkim do przekazywania modułów danych.

Przykład: podczas generowania raportu dane wynikowe są kompresowane i przechowywane w polu Blob tabeli dostępnej przez moduł danych TdmReportBaseData. W oddzielnym module naszej aplikacji jest funkcjonalność umożliwiająca wyświetlanie danych z raportu w formie stronicowanej przy użyciu ReportBuilder. Główny kod tego modułu (TdmRBReport) wykorzystuje klasę TRBTempdatabase do konwersji skompresowanych danych obiektów blob na różne tabele, które można wykorzystać w narzędziu Reportdesigner środowiska wykonawczego Reportbuilder. TdmRBReport ma dostęp do TdmReportBaseData dla wszystkich rodzajów danych związanych z raportem (rodzaj raportu, ustawienia obliczeń raportów itp.). TRBTempDatabase jest zbudowany w TdmRBReport, ale musi mieć dostęp do TdmReportBasedata. Jest to teraz wykonywane przy użyciu powyższej konstrukcji:

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

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

Mam wrażenie, że oznacza to, że TRBTempDatabase zna wielu swoich właścicieli i zastanawiałem się, czy to jest jakiś zapach kodu lub anty-wzór.

Co o tym sądzisz? Czy to zapach kodu? Jeśli tak, jaki jest lepszy sposób?

Bascy
źródło
1
Gdyby miał wiedzieć tyle o innej klasie, łatwiej byłoby to zrobić.
Loren Pechtel

Odpowiedzi:

13

Ten rodzaj wygląda jak wzorzec lokalizatora usług, który został po raz pierwszy opisany przez Martina Fowlera (który został zidentyfikowany jako wspólny anty-wzorzec).

Wstrzykiwanie zależności w oparciu o konstrukcję jest preferowane w stosunku do lokalizatora usług, ponieważ promuje widoczność wymaganych parametrów i promuje prostsze testowanie jednostek.

Problem z używaniem Lokalizatora usług nie polega na tym, że bierzesz zależność od konkretnej implementacji Lokalizatora usług (chociaż może to być również problem), ale że jest to bona-fide anty-wzorzec. Zapewni to użytkownikom interfejsu API straszne wrażenia programistyczne i pogorszy Twoje życie jako programisty konserwacji, ponieważ będziesz musiał użyć znacznej ilości mocy mózgu, aby zrozumieć konsekwencje każdej wprowadzonej zmiany.

Kompilator może zaoferować zarówno konsumentom, jak i producentom tak wiele pomocy, gdy używany jest Constructor Injection, ale żadna z tych pomocy nie jest dostępna dla interfejsów API opartych na Service Locator.

Co najważniejsze, łamie także prawo Demeter

Prawo Demetera (LoD) lub Zasada najmniejszej wiedzy to wytyczne projektowe do tworzenia oprogramowania, w szczególności programów obiektowych. W swojej ogólnej formie LoD jest szczególnym przypadkiem luźnego sprzężenia.

Prawo Demetera dla funkcji wymaga, aby metoda M obiektu O mogła wywoływać tylko metody następujących rodzajów obiektów:

  1. O sama
  2. Parametry M.
  3. wszelkie obiekty utworzone / utworzone w M
  4. Bezpośrednie obiekty składowe O.
  5. zmienna globalna, dostępna dla O, w zakresie M

W szczególności obiekt powinien unikać wywoływania metod obiektu członka zwróconego przez inną metodę. W przypadku wielu współczesnych języków obiektowych, które używają kropki jako identyfikatora pola, prawo można określić po prostu jako „używaj tylko jednej kropki”. Oznacza to, że kod abMethod () łamie prawo, którego nie robi a.Method (). Jako prosty przykład, gdy ktoś chce wyprowadzać psa, szaleństwem byłoby nakazać jego nogom iść bezpośrednio; zamiast tego dowodzi się psem i pozwala dbać o własne nogi.

The Better Way

Skutecznie lepszym sposobem jest usunięcie wywołania lokalizatora usług w klasie i przekazanie poprawnego właściciela jako parametru wewnątrz jego konstruktora. Nawet jeśli oznacza to, że masz klasę usług, która wykonuje wyszukiwanie właściciela, a następnie przekazuje to do konstruktora klasy

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

  FdmReportBaseData := TdmRBReport(ownerClass).dmReportBaseData;
end;{- .Create }
Justin Shield
źródło
3
Świetna odpowiedź i rekwizyty dla każdego, kto wymyślił tę cudowną analogię: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.
Andy Hunt
3

Jedną z trudności w tym, że obiekty potomne wiedzą za dużo o rodzicu, jest to, że w końcu wdrażasz wzorce, które mogą (i najczęściej tak robią) być zbyt ściśle powiązane, co powoduje poważne uzależnienia i często później bardzo trudno jest je bezpiecznie modyfikować i utrzymywać później.

W zależności od tego, jak głęboko są połączone dwie klasy, brzmi to trochę tak, jakby były widoczne opisy Fowlera dotyczące cech zazdrości lub nieprzyjemnych zapachów kodu intymności.

Wydaje się, że trzeba załadować lub odczytać klasę z danymi, w którym to przypadku można użyć szeregu alternatywnych wzorców, aby przełamać zależność między dzieckiem a jego łańcuchem rodziców, i wygląda na to, że trzeba przekazać zadanie dostępu twoją klasę danych, zamiast uczynić klasę akcesorów odpowiedzialną za robienie wszystkiego sama.

S.Robins
źródło