Kupplung. Empfohlene Vorgehensweise


11

Nach diesem Thread habe ich angefangen

Das Singleton-Muster

Ich habe darüber nachgedacht, wie gekoppelt meine Klassen sind und wie ich am besten eine lose Kopplung erreichen kann. Bitte denken Sie daran, dass ich ein neuer Programmierer bin (4 Monate nach meinem ersten Job) und dies ist wirklich die erste Überlegung, die ich dazu gemacht habe, und ich bin sehr daran interessiert, das Konzept zu verstehen.

Was genau macht eine lose Kopplung gegenüber einer schweren Kopplung aus? In meinem aktuellen (und ersten) Projekt arbeite ich an einem ac # winforms-Projekt, bei dem der GUI-Abschnitt Objekte erstellt und deren Ereignisse abonniert. Wenn sie ausgelöst werden, erstellt die GUI ein anderes Objekt (in diesem Beispiel eine Datagrid-Ansicht (eine Klasse) das ich erstellt habe, das eine Standard-Datagrid-Ansicht umschließt und zusätzliche Funktionen hinzufügt) und sie an die GUI anfügt. Ist diese schlechte Kopplung oder gut?

Ich möchte wirklich keine schlechten Gewohnheiten annehmen und schlecht programmieren, daher würde ich mich über Ihre Antworten freuen.

Antworten:


11

Um Ihren Code hier lose zu koppeln, sind einige einfache Dinge zu beachten:

Teil 1:

Technisch bekannt als "Trennung von Bedenken". Jede Klasse hat eine bestimmte Rolle. Sie sollte sich mit Geschäftslogik oder Anwendungslogik befassen. Versuchen Sie, sich von Klassen fernzuhalten, die beide Verantwortlichkeiten verbinden. Das heißt, eine Klasse, die (allgemein) Daten verwaltet, ist Anwendungslogik, während eine Klasse, die Daten verwendet, Geschäftslogik ist.

Persönlich bezeichne ich dies (in meiner eigenen kleinen Welt) als create it or use it. Eine Klasse sollte ein Objekt erstellen oder ein Objekt verwenden, das niemals beides tun sollte.

Teil 2:

So implementieren Sie die Trennung von Bedenken.
Als Ausgangspunkt gibt es zwei einfache Techniken:

Hinweis: Entwurfsmuster sind nicht absolut.
Sie sollen an die jeweilige Situation angepasst werden, haben jedoch ein zugrunde liegendes Thema, das allen Anwendungen ähnlich ist. Schauen Sie sich also die folgenden Beispiele nicht an und sagen Sie, dass ich dies genau befolgen muss. Dies sind nur Beispiele (und dabei etwas erfunden).

Abhängigkeitsinjektion :

Hier übergeben Sie ein Objekt, das eine Klasse verwendet. Das Objekt, das Sie basierend auf einer Schnittstelle übergeben, damit Ihre Klasse weiß, was damit zu tun ist, muss jedoch nicht die tatsächliche Implementierung kennen.

class Tokenizer
{
    public:
        Tokenizer(std::istream& s)
            : stream(s)
        {}
        std::string nextToken() { std::string token; stream >> token;return token;}
    private:
        std::istream& stream;
};

Hier injizieren wir den Stream in einen Tokenizer. Der Tokenizer weiß nicht, um welchen Typ es sich bei dem Stream handelt, solange er die Schnittstelle von std :: istream implementiert.

Service Locator-Muster :

Das Service Locator-Muster ist eine geringfügige Variation der Abhängigkeitsinjektion. Anstatt ein Objekt anzugeben, das es verwenden kann, übergeben Sie ihm ein Objekt, das weiß, wie das zu verwendende Objekt gefunden (erstellt) wird.

class Application
{
     public:
         Application(Persister& p)
             : persistor(p)
         {}

         void save()
         {
             std::auto_ptr<SaveDialog> saveDialog = persistor.getSaveDialog();
             saveDialog.DoSaveAction();
         }

         void load()
         {
             std::auto_ptr<LoadDialog> loadDialog = persistor.getLoadDialog();
             loadDialog.DoLoadAction();
         }
    private:
        Persister& persistor;
};

Hier übergeben wir dem Anwendungsobjekt ein Persistorobjekt. Wenn Sie eine Aktion zum Speichern / Laden ausführen, wird mithilfe des Persistors ein Objekt erstellt, das tatsächlich weiß, wie die Aktion ausgeführt wird. Hinweis: Auch hier ist der Persistor eine Schnittstelle, und Sie können je nach Situation unterschiedliche Implementierungen bereitstellen.

Dies ist nützlich, wenn potentiallyjedes Mal, wenn Sie eine Aktion instanziieren, ein eindeutiges Objekt erforderlich ist.

Persönlich finde ich dies besonders nützlich beim Schreiben von Unit-Tests.

Hinweis zu Mustern:

Designmuster sind ein großes Thema für sich. Dies ist keineswegs eine exklusive Liste von Mustern, die Sie zur Unterstützung der losen Kopplung verwenden können. Dies ist nur ein gemeinsamer Ausgangspunkt.

Mit der Erfahrung werden Sie feststellen, dass Sie diese Muster bereits verwenden, nur dass Sie ihre formalen Namen nicht verwendet haben. Indem wir ihre Namen standardisieren (und alle dazu bringen, sie zu lernen), stellen wir fest, dass es einfach und schneller ist, Ideen zu kommunizieren.


3
@ Darren Young: Danke, dass du es akzeptiert hast. Aber Ihre Frage ist erst drei Stunden alt. Ich würde in ungefähr einem Tag wiederkommen und überprüfen, ob andere keine besseren Antworten geliefert haben.
Martin York

Vielen Dank für die hervorragende Antwort. In Bezug auf die Trennung von Bedenken ... Ich habe Klassen, die einige Daten für ihre Verwendung benötigen und dann etwas mit diesen Daten tun. So habe ich in der Vergangenheit Klassen entworfen. Wäre es daher besser, eine Klasse zu erstellen, die die Daten erhält und an die richtige Form anpasst, und dann eine andere Klasse, um die Daten tatsächlich zu verwenden?
Darren Young

@Darren Young: Wie bei allen Programmen ist die Linie grau und verschwommen, nicht gut definiert. Es ist schwierig, eine genaue Antwort zu geben, ohne Ihren Code zu lesen. Aber wenn ich darüber managing the dataspreche, beziehe ich mich auf die Variablen (nicht auf die tatsächlichen Daten). Dinge wie Zeiger müssen also so verwaltet werden, dass sie nicht auslaufen. Es können jedoch Daten eingefügt oder die Art und Weise, wie die Daten abgerufen werden, abstrahiert werden (sodass Ihre Klasse mit verschiedenen Datenabrufmethoden wiederverwendet werden kann). Es tut mir leid, ich kann nicht genauer sein.
Martin York

1
@Darren Young: Wie von @StuperUser in seiner Antwort vermerkt. Gehen Sie nicht über Bord (AKA minutae of loose coupling(liebe dieses Wort minutae)). Das Geheimnis der Programmierung besteht darin, zu lernen, wann die Techniken anzuwenden sind. Überbeanspruchung kann zu einem Codegewirr führen.
Martin York

@ Martin - danke für den Rat. Ich denke, dort habe ich derzeit Probleme ..... Ich mache mir ständig Sorgen um die Architektur meines Codes und versuche auch, die spezifische Sprache zu lernen, die ich mit C # verwende. Ich denke, es wird mit Erfahrung und meinem Wunsch kommen, dieses Zeug zu lernen. Ich freue mich über Ihre Kommentare.
Darren Young

6

Ich bin ein ASP.NET-Entwickler, weiß also nicht viel über die WinForms-Kopplung, weiß aber ein wenig über N-Tier-Webanwendungen Bescheid, vorausgesetzt, es handelt sich um eine dreistufige Anwendungsarchitektur mit Benutzeroberfläche, Domäne und Datenzugriffsschicht (DAL).

Bei der losen Kopplung geht es um Abstraktionen.

Wie @MKO angibt, besteht eine lose Kopplung, wenn Sie eine Assembly durch eine andere ersetzen können (z. B. ein neues UI-Projekt, das Ihr Domain-Projekt verwendet, eine neue DAL, die in einer Tabelle anstatt in einer Datenbank gespeichert wird). Wenn Ihre Domain und DAL von Projekten weiter unten in der Kette abhängen, ist die Kopplung möglicherweise lockerer.

Ein Aspekt von etwas, das lose gekoppelt ist, ist, ob Sie ein Objekt durch ein anderes ersetzen können, das dieselbe Schnittstelle implementiert. Es hängt nicht vom tatsächlichen Objekt ab, sondern von der abstrakten Beschreibung dessen, was es tut (seiner Schnittstelle).
Lose Kopplung, Schnittstellen und Abhängigkeitsinjektoren (DI) und Inversion of Control (IoC) sind nützlich für den Isolationsaspekt des Entwurfs für Testbarkeit.

Beispiel: Ein Objekt im UI-Projekt ruft ein Repository-Objekt im Domänenprojekt auf.
Sie können ein gefälschtes Objekt erstellen , das dieselbe Schnittstelle wie das Repository implementiert, das der zu testende Code verwendet, und dann ein spezielles Verhalten für Tests schreiben ( Stubs, um zu verhindern, dass Produktionscode, der speichert / löscht / aufgerufen wird, aufgerufen wird, und Mocks , die als Stubs fungieren und den Überblick behalten des Zustands des gefälschten Objekts zu Testzwecken).
Das heißt, der einzige aufgerufene Produktionscode befindet sich jetzt nur noch in Ihrem UI-Objekt. Ihr Test wird nur gegen diese Methode durchgeführt, und alle Testfehler isolieren den Fehler auf diese Methode.

Im Menü "Analysieren" in VS (abhängig von Ihrer Version) finden Sie außerdem Tools zum Berechnen von Codemetriken für Ihr Projekt, darunter die Klassenkopplung. Weitere Informationen hierzu finden Sie in der MSDN-Dokumentation.

Lassen Sie sich nicht TOO verzetteln in der minutae der losen Kopplung aber, wenn es keine Chance , dass die Dinge bekommen wiederverwendet werden (zB eine Domäne Projekt mit mehr als einem UI) und die Lebensdauer des Produkts ist klein, dann lose Kopplung wird weniger von Priorität (es wird weiterhin berücksichtigt), aber es liegt weiterhin in der Verantwortung der Architekten / Techniker, die Ihren Code überprüfen.


3

Kopplung bezieht sich auf den Grad des direkten Wissens, das eine Klasse über eine andere hat . Dies ist nicht als Einkapselung oder Nichteinkapselung zu interpretieren. Es bezieht sich nicht auf das Wissen einer Klasse über die Attribute oder die Implementierung einer anderen Klasse, sondern auf das Wissen über diese andere Klasse selbst. Eine starke Kopplung tritt auf, wenn eine abhängige Klasse einen Zeiger direkt auf eine konkrete Klasse enthält, die das erforderliche Verhalten liefert. Die Abhängigkeit kann nicht ersetzt oder ihre "Signatur" geändert werden, ohne dass eine Änderung der abhängigen Klasse erforderlich ist. Eine lose Kopplung tritt auf, wenn die abhängige Klasse nur einen Zeiger auf eine Schnittstelle enthält, die dann von einer oder mehreren konkreten Klassen implementiert werden kann.Die Abhängigkeit der abhängigen Klasse besteht zu einem "Vertrag", der von der Schnittstelle angegeben wird. Eine definierte Liste von Methoden und / oder Eigenschaften, die implementierende Klassen bereitstellen müssen. Jede Klasse, die die Schnittstelle implementiert, kann somit die Abhängigkeit einer abhängigen Klasse erfüllen, ohne die Klasse ändern zu müssen. Dies ermöglicht eine Erweiterbarkeit des Software-Designs. Eine neue Klasse, die eine Schnittstelle implementiert, kann geschrieben werden, um eine aktuelle Abhängigkeit in einigen oder allen Situationen zu ersetzen, ohne dass eine Änderung an der abhängigen Klasse erforderlich ist. Die neuen und alten Klassen können frei ausgetauscht werden. Eine starke Kopplung erlaubt dies nicht. Ref Link.


3

Schauen Sie sich die 5 SOLID- Prinzipien an. Durch die Einhaltung des SRP werden der ISP und der DIP die Kopplung erheblich verringern, wobei der DIP bei weitem der stärkste ist. Es ist das Grundprinzip unter dem bereits erwähnten DI .

Auch GRASP ist einen Blick wert. Es ist eine seltsame Mischung zwischen abstrakten Konzepten (die zunächst schwer umzusetzen sind) und konkreten Mustern (die tatsächlich hilfreich sein könnten), aber Schönheit ist derzeit wahrscheinlich das geringste Ihrer Anliegen.

Und zuletzt finden Sie diesen Abschnitt über IoC als Einstiegspunkt für gängige Techniken sehr nützlich.

Tatsächlich habe ich eine Frage zum Stackoverflow gefunden , in der ich die Anwendung von SOLID auf ein konkretes Problem demonstriere. Könnte eine interessante Lektüre sein.


1

Laut Wikipedia:

Beim Computer- und Systemdesign ist ein lose gekoppeltes System eines, bei dem jede seiner Komponenten nur wenig oder gar keine Kenntnis von den Definitionen anderer separater Komponenten hat oder verwendet.

Das Problem der engen Kupplung macht es schwierig, Änderungen vorzunehmen. (Viele Autoren scheinen zu vermuten, dass dies hauptsächlich zu Problemen bei der Wartung führt, aber meiner Erfahrung nach ist dies auch bei der anfänglichen Entwicklung relevant.) In eng gekoppelten Systemen kann es vorkommen, dass eine Änderung an einem Modul im System zusätzliche Änderungen erfordert in den Modulen, an die es gekoppelt ist. Meistens erfordert dies mehr Änderungen in anderen Modulen und so weiter.

Im Gegensatz dazu sind in einem lose gekoppelten System Änderungen relativ isoliert. Sie sind daher kostengünstiger und können mit größerem Vertrauen hergestellt werden.

In Ihrem speziellen Beispiel bietet das Ereignisbehandlungsmaterial eine gewisse Trennung zwischen der GUI und den zugrunde liegenden Daten. Es hört sich jedoch so an, als gäbe es andere Bereiche der Trennung, die Sie erkunden könnten. Ohne weitere Details Ihrer speziellen Situation ist es schwierig, genau zu sein. Sie können jedoch mit einer dreistufigen Architektur beginnen, die sich voneinander trennt:

  • Logik zum Speichern und Abrufen von Daten
  • Geschäftslogik
  • Logik erforderlich, um die Benutzeroberfläche auszuführen

Zu berücksichtigen ist, dass sich bei kleinen, einmaligen Anwendungen mit einem einzelnen Entwickler die Vorteile der Durchsetzung einer losen Kopplung auf allen Ebenen möglicherweise nicht lohnen. Auf der anderen Seite sind größere, komplexere Anwendungen mit mehreren Entwicklern ein Muss. Anfänglich fallen Kosten für die Einführung der Abstraktionen und die Schulung von Entwicklern an, die mit dem Code hinsichtlich seiner Architektur nicht vertraut sind. Auf lange Sicht bietet die lose Kupplung jedoch Vorteile, die die Kosten bei weitem überwiegen.

Wenn Sie es ernst meinen, lose gekoppelte Systeme zu entwerfen, informieren Sie sich über SOLID-Prinzipien und Entwurfsmuster.

Es ist jedoch wichtig zu erkennen, dass diese Muster und Prinzipien genau das sind - Muster und Prinzipien. Sie sind keine Regeln. Dies bedeutet, dass sie pragmatisch und intelligent angewendet werden müssen

Wenn es um Muster geht, ist es wichtig zu verstehen, dass es keine einzige "korrekte" Implementierung eines der Muster gibt. Sie sind auch keine Cookie-Cutter-Vorlagen zum Entwerfen Ihrer eigenen Implementierung. Sie sind da, um Ihnen zu sagen, welche Form eine gute Lösung haben könnte, und um Ihnen eine gemeinsame Sprache für die Kommunikation von Entwurfsentscheidungen mit anderen Entwicklern bereitzustellen.

Alles Gute.


1

Verwenden Sie Abhängigkeitsinjektion, Strategiemuster und Ereignisse. Im Allgemeinen: Informieren Sie sich über Entwurfsmuster, es geht um lose Kopplung und das Reduzieren von Abhängigkeiten. Ich würde sagen, Ereignisse sind ungefähr so ​​lose miteinander verbunden, wie Sie es erwarten würden, während Abhängigkeitsinjektion und Strategiemuster einige Schnittstellen erfordern.

Ein guter Trick besteht darin, Klassen in verschiedenen Bibliotheken / Assemblys zu platzieren und sie von so wenigen anderen Bibliotheken wie möglich abhängig zu machen, sodass Sie gezwungen sind, den Refactor zu verwenden, um weniger Abhängigkeiten zu verwenden


4
Ist Kondom ein abstrakter IT-Begriff oder verstehe ich das Wortspiel einfach nicht?
Darren Young

3
Es ist ein anderer Name für den Abhängigkeitsinjektionscontainer.
Mchl

2
Auch eine gute Möglichkeit, die Ausbreitung von Viren zu verhindern. Sprechen wir über dasselbe?
Sova

1
Schwere Kopplung wird oft im Backend durchgeführt
Homde

1
Das Setzen einer Überschrift missbraucht kaum die Formatierung
Homde

0

Lassen Sie mich eine alternative Ansicht geben. Ich denke nur daran, dass jede Klasse eine gute API ist. Die Reihenfolge, in der die Methoden aufgerufen werden, ist offensichtlich. Was sie tun, ist offensichtlich. Sie haben die Anzahl der Methoden auf das erforderliche Minimum reduziert. Zum Beispiel,

init, öffnen, schließen

gegen

setTheFoo, setBar, initX, getConnection, close

Das erste ist offensichtlich und sieht aus wie eine schöne API. Der zweite kann Fehler verursachen, wenn er in der falschen Reihenfolge aufgerufen wird.

Ich mache mir keine Sorgen, dass ich Anrufer ändern und neu kompilieren muss. Ich pflege eine Menge Code, einige davon neu und einige 15 Jahre alt. Ich möchte normalerweise Compilerfehler, wenn ich Änderungen vornehme. Manchmal werde ich aus diesem Grund absichtlich eine API brechen. Es gibt mir die Möglichkeit, die Auswirkungen auf jeden Anrufer zu berücksichtigen. Ich bin kein großer Fan von Abhängigkeitsinjektion, weil ich meinen Code ohne Blackboxen visuell verfolgen möchte und der Compiler so viele Fehler wie möglich abfangen soll.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.