Ist es ein Codegeruch, wenn ein Objekt viel von seinem Besitzer kennt?

9

In unserer Delphi 2007-Anwendung verwenden wir viele der folgenden Konstrukte

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

Die FindOwnerClass wandert die Eigentümerhierarchie der aktuellen Komponente nach oben, um eine bestimmte Klasse zu finden (im Beispiel TdmBasicData). Das resultierende Objekt wird in der Feldvariablen FdmBasic gespeichert. Wir verwenden dies hauptsächlich, um Datenmodule weiterzugeben.

Beispiel: Beim Generieren eines Berichts werden die resultierenden Daten komprimiert und in einem Blob-Feld einer Tabelle gespeichert, auf die über ein Datenmodul TdmReportBaseData zugegriffen wird. In einem separaten Modul unserer Anwendung gibt es Funktionen zum Anzeigen der Daten aus dem Bericht in einer Seitenform mit ReportBuilder. Der Hauptcode dieses Moduls (TdmRBReport) verwendet eine Klasse TRBTempdatabase, um die komprimierten Blob-Daten in verschiedene Tabellen zu konvertieren, die im Reportbuilder-Laufzeit-Reportdesigner verwendet werden können. TdmRBReport hat Zugriff auf TdmReportBaseData für alle Arten von berichtbezogenen Daten (Berichtstyp, Berichtsberechnungseinstellungen usw.). TRBTempDatabase wird in TdmRBReport erstellt, muss jedoch Zugriff auf TdmReportBasedata haben. Dies geschieht nun mit der obigen Konstruktion:

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

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

Mein Gefühl ist, dass dies bedeutet, dass TRBTempDatabase viel von seinem Besitzer kennt, und ich habe mich gefragt, ob dies eine Art Codegeruch oder Anti-Pattern ist.

Was denkst du darüber? Ist das ein Code-Geruch? Wenn ja, was ist ein besserer Weg?

Bascy
quelle
1
Wenn es so viel über eine andere Klasse wissen sollte, wäre es eine einfachere Möglichkeit gewesen, dies zu tun.
Loren Pechtel

Antworten:

13

Diese Art von sieht aus wie ein Service Locator Pattern, das zuerst von Martin Fowler beschrieben wurde (das als allgemeines Anti-Pattern identifiziert wurde).

Die konstruktionsbasierte Abhängigkeitsinjektion wird einem Service Locator vorgezogen, da sie die Sichtbarkeit der erforderlichen Parameter fördert und das Testen von Einheiten vereinfacht.

Das Problem bei der Verwendung eines Service Locator besteht nicht darin, dass Sie von einer bestimmten Service Locator-Implementierung abhängig sind (obwohl dies ebenfalls ein Problem sein kann), sondern darin, dass es sich um ein echtes Anti-Pattern handelt. Dies wird den Verbrauchern Ihrer API eine schreckliche Entwicklererfahrung bieten und Ihr Leben als Wartungsentwickler verschlechtern, da Sie beträchtliche Mengen an Gehirnleistung einsetzen müssen, um die Auswirkungen jeder von Ihnen vorgenommenen Änderung zu erfassen.

Der Compiler kann sowohl Verbrauchern als auch Herstellern so viel Hilfe bieten, wenn Constructor Injection verwendet wird, aber keine dieser Hilfen ist für APIs verfügbar, die auf Service Locator basieren.

Vor allem verstößt es auch gegen das Gesetz von Demeter

Das Gesetz des Demeters (LoD) oder das Prinzip des geringsten Wissens ist eine Entwurfsrichtlinie für die Entwicklung von Software, insbesondere objektorientierten Programmen. In seiner allgemeinen Form ist der LoD ein spezieller Fall einer losen Kopplung.

Das Demeter-Gesetz für Funktionen verlangt, dass eine Methode M eines Objekts O nur die Methoden der folgenden Arten von Objekten aufrufen darf:

  1. O selbst
  2. Ms Parameter
  3. alle innerhalb von M erstellten / instanziierten Objekte
  4. O's direkte Komponentenobjekte
  5. eine globale Variable, auf die O im Bereich von M zugreifen kann

Insbesondere sollte ein Objekt vermeiden, Methoden eines Mitgliedsobjekts aufzurufen, die von einer anderen Methode zurückgegeben werden. Für viele moderne objektorientierte Sprachen, die einen Punkt als Feldkennung verwenden, kann das Gesetz einfach als "nur einen Punkt verwenden" angegeben werden. Das heißt, der Code abMethod () verstößt gegen das Gesetz, wo a.Method () dies nicht tut. Als einfaches Beispiel, wenn man einen Hund laufen möchte, wäre es töricht, den Beinen des Hundes zu befehlen, direkt zu gehen; stattdessen befiehlt man dem Hund und lässt ihn sich um seine eigenen Beine kümmern.

Der bessere Weg

Tatsächlich ist es besser, den Service Locator-Aufruf innerhalb der Klasse zu entfernen und den richtigen Eigentümer als Parameter innerhalb des Konstruktors zu übergeben. Selbst wenn dies bedeutet, dass Sie eine Serviceklasse haben, die eine Besitzersuche durchführt und diese dann an den Klassenkonstruktor übergibt

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

  FdmReportBaseData := TdmRBReport(ownerClass).dmReportBaseData;
end;{- .Create }
Justin Shield
quelle
3
Tolle Antwort und Requisiten an alle, die sich diese wunderbare Analogie ausgedacht haben: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

Eine der Schwierigkeiten, wenn untergeordnete Objekte zu viel über die Eltern wissen, besteht darin, dass Sie am Ende Muster implementieren, die (und meistens) zu eng gekoppelt werden können, was zu großen Abhängigkeitskopfschmerzen führt und später oft sehr schwierig zu ändern und sicher zu warten ist später.

Abhängig davon, wie eng Ihre beiden Klassen miteinander verbunden sind, klingt es ein bisschen so, als ob Fowlers Beschreibung des Feature Envy oder unangemessene Intimacy-Code-Gerüche offensichtlich sind.

Es scheint notwendig zu sein, eine Klasse mit Daten zu laden oder zu lesen. In diesem Fall können Sie eine Reihe alternativer Muster verwenden, um die Abhängigkeit zwischen dem Kind und seiner Elternkette zu lösen, und es scheint, als müssten Sie die Zugriffsaufgabe delegieren Ihre Datenklasse, anstatt die Datenzugriffsklasse dafür verantwortlich zu machen, dass alles selbst erledigt wird.

S.Robins
quelle