Wie kann ich eine „Freund“-Abhängigkeitserklärung richtig entfernen/umgestalten?

Lesezeit: 7 Minuten

Wie kann ich eine „Freund Abhangigkeitserklarung richtig entfernenumgestalten
πάντα ῥεῖ

Der Hintergrund dieser Frage basiert auf einem praktischen Beispiel, in dem ich eine „Freund“-Abhängigkeit von einem Klassenpaar entfernen wollte, das verwendet wird, um den Lese-/Schreibzugriff auf eine gemeinsam genutzte Ressource zu verwalten.

Hier ist eine Abstraktion des ursprünglichen strukturellen Designs für dieses Szenario:

Ursprüngliches Design mit Freund

Rot markiert gibt es diese hässliche „Freund“-Abhängigkeit, die ich aus dem Design entfernen möchte.

Kurz gesagt, warum habe ich dieses Ding dort:

  1. ClassAProvider teilt einen Verweis auf a ClassA über eine Reihe gleichzeitiger Zugriffe Client Instanzen
  2. Client Instanzen zugreifen sollen ClassA allein durch die ClassAAccessor Hilfsklasse, die die Interna verwaltet
  3. ClassA verbirgt alle Methoden, die verwendet werden sollen ClassAAccessor als geschützt.
  4. Damit ClassA kann dafür sorgen Client muss a verwenden ClassAAccessor Beispiel

Dieses Muster ist vor allem dann nützlich, wenn es darum geht, Instanzen von zu verlassen ClassA in einem definierten Zustand, wenn a Client Operation stürzt ab (z. B. wegen einer nicht abgefangenen Ausnahme). Denk an
ClassA Bereitstellen von (intern sichtbaren) gepaarten Operationen wie lock()/unlock() oder open()/close().

Die (Zustands-)Umkehroperationen sollten auf jeden Fall aufgerufen werden, insbesondere wenn ein Client aufgrund einer Ausnahme abstürzt.
Dies kann sicher durch die gehandhabt werden ClassAAcessordas Lebenszyklusverhalten von , kann die Destruktorimplementierung dies sicherstellen. Das folgende Sequenzdiagramm veranschaulicht das beabsichtigte Verhalten:

Das gewünschte Verhalten des Gesamtkonstrukts

zusätzlich Client Instanzen können eine Feinsteuerung des Zugriffs erreichen ClassA ganz einfach, indem Sie einfach C++-Scope-Blöcke verwenden:

// ...
{ 
    ClassAAccessor acc(provider.getClassA());
    acc.lock();
    // do something exception prone ...
} // safely unlock() ClassA
// ...

Soweit alles in Ordnung, aber die «Freund»-Abhängigkeit dazwischen ClassA und ClassAAccessor sollte aus mehreren guten Gründen entfernt werden

  1. In der UML 2.2 Superstructure, Abschnitt C.2 unter Changes from previous UML heißt es: The following table lists predefined standard elements for UML 1.x that are now obsolete. ... «friend» ...
  2. Die meisten Kodierungsregeln und -richtlinien, die ich gesehen habe, verbieten oder raten dringend von der Verwendung von friend ab, um die enge Abhängigkeit von den exportierenden Klassen zu den Freunden zu vermeiden. Dieses Ding bringt einige ernsthafte Wartungsprobleme mit sich.

Wie mein Fragetitel sagt

Wie kann ich eine Friend-Deklaration richtig entfernen/umgestalten (vorzugsweise beginnend mit dem UML-Design für meine Klassen)?

  • völlig off-topic, aber welches uml-tool hast du verwendet?

    – mittel

    15. Dezember 2014 um 20:09 Uhr

  • Ich habe diese Fragen und Antworten eingerichtet, die durch diese Frage motiviert sind: C++ OOP Gewähren Sie nur Zugriff auf bestimmte Klassen. Das ist die Essenz, wie man eine Freundschaftsbeziehung umgestaltet, die ich einmal in einem Artikel geschrieben habe und jetzt hier als Fragen und Antworten zur Verfügung stelle.

    – πάντα ῥεῖ

    15. Dezember 2014 um 20:10 Uhr

  • @midor Unternehmensarchitekt ursprünglich. Die hier geposteten Diagrammbilder sind Screenshots aus einem PDF, das ich tatsächlich habe.

    – πάντα ῥεῖ

    15. Dezember 2014 um 20:10 Uhr


  • @πάντα ῥεῖ Versuchen Sie nicht, Ihren Code auf die neuesten UML-Änderungen zu zwingen. UML ist ein gutes Werkzeug, aber ursprünglich war es “zu sehr an Java gebunden” und schließlich flexibler für andere PL(s). Einige Funktionen von UML, ob neu oder veraltet, sind sehr konzeptionell, um auf Quellcode angewendet zu werden. “Friend” (“Package” in Java) ist ein nützliches Feature, sollte vielleicht in UML “retagged” werden, aber es ist nicht falsch, es zu verwenden.

    – umlkat

    16. Dezember 2014 um 23:25 Uhr


  • @umlcat “Versuchen Sie nicht, Ihren Code auf die neuesten UML-Änderungen zu zwingen.” Das mache ich eigentlich nicht. Meine Bedenken beziehen sich hauptsächlich auf das C++-Design. Die Freund Beziehung wurde in C++-Designs (aus den genannten Gründen) lange bevor UML sie für obsolet erklärte, abgeraten. Mein Punkt bei der Verwendung von UML ist nur, darauf hinzuweisen, welche Änderungen in einer bestimmten Reihenfolge (oder einem bestimmten Schema) von einem strukturellen POV aus vorgenommen werden müssen.

    – πάντα ῥεῖ

    17. Dezember 2014 um 0:13 Uhr


Wie kann ich eine „Freund Abhangigkeitserklarung richtig entfernenumgestalten
πάντα ῥεῖ

Lassen Sie uns zuerst einige Einschränkungen für das Refactoring einrichten:

  1. Die öffentlich sichtbare Schnittstelle des ClassAAccessor sollte sich in keiner Weise ändern
  2. Die internen Operationen von ClassA sollten für die Öffentlichkeit nicht sichtbar/zugänglich sein
  3. Die Gesamtleistung und der Platzbedarf des ursprünglichen Designs sollten nicht beeinträchtigt werden

Schritt 1: Führen Sie eine abstrakte Schnittstelle ein

Für eine erste Einstellung habe ich das Klischee «Freund» ausgeklammert und durch eine Klasse (Schnittstelle) ersetzt.
InternalInterface und die entsprechenden Relationen.

1st-Shot-Refaktorisierung

Was die «Freund»-Abhängigkeit ausmachte, wurde in eine einfache Abhängigkeitsbeziehung (blau) und eine «Anruf»-Abhängigkeit (grün) gegenüber dem Neuen aufgeteilt InternalInterface Element.


Schritt 2: Verschieben Sie die Operationen, die die «call»-Abhängigkeit bilden, auf die Schnittstelle

Der nächste Schritt besteht darin, die «Call»-Abhängigkeit zu reifen. Dazu ändere ich das Diagramm wie folgt:

Ausgereiftes Design

  • Aus der «call»-Abhängigkeit wurde eine gerichtete Assoziation
    ClassAAccessor zum InternalInterface (d. h ClassAAccessor enthält eine private Variable internalInterfaceRef).
  • Die betreffenden Betriebe wurden verlegt ClassA zu InternalInterface.
  • InternalInterface wird mit einem geschützten Konstruktor erweitert, der nur bei der Vererbung nützlich ist.
  • ClassA‘s «Verallgemeinerung» Assoziation zu InternalInterface ist gekennzeichnet als protectedalso ist es öffentlich unsichtbar gemacht.

Schritt 3: In der Umsetzung alles zusammenkleben

Im letzten Schritt müssen wir einen Weg modellieren, wie ClassAAccessor einen Hinweis bekommen kann InternalInterface. Da die Verallgemeinerung nicht öffentlich sichtbar ist, ClassAAcessor kann es nicht von initialisieren ClassA im Konstruktor übergebene Referenz nicht mehr. Aber ClassA kann Zugreifen InternalInterfaceund übergeben Sie eine Referenz mit einer zusätzlichen Methode setInternalInterfaceRef() eingeführt in ClassAAcessor:

Alles zusammenkleben


Hier ist die C++-Implementierung:

class ClassAAccessor {
public:
    ClassAAccessor(ClassA& classA);
    void setInternalInterfaceRef(InternalInterface & newValue) {
        internalInterfaceRef = &newValue;
    }
private:  
    InternalInterface* internalInterfaceRef;
};

Diese heißt eigentlich, wenn die ebenfalls neu eingeführte Methode ClassA::attachAccessor()
Methode heißt:

class ClassA : protected InternalInterface {
public:
    // ...
    attachAccessor(ClassAAccessor & accessor);
    // ...
};

ClassA::attachAccessor(ClassAAccessor & accessor) {
    accessor.setInternalInterfaceRef(*this); // The internal interface can be handed
                                             // out here only, since it's inherited 
                                             // in the protected scope.
}

Somit kann der Konstruktor von ClassAAccessor folgendermaßen umgeschrieben werden:

ClassAAccessor::ClassAAccessor(ClassA& classA)
: internalInterfaceRef(0) {
    classA.attachAccessor(*this);
}

Schließlich können Sie die Implementierungen noch weiter entkoppeln, indem Sie eine andere einführen InternalClientInterface so was:

Geben Sie hier die Bildbeschreibung ein


Es ist zumindest notwendig zu erwähnen, dass dieser Ansatz gegenüber der Verwendung einige Nachteile hat friend Erklärungen:

  1. Es macht den Code noch komplizierter
  2. friend muss keine abstrakten Schnittstellen einführen (die den Footprint beeinflussen können, daher ist Bedingung 3. nicht vollständig erfüllt)
  3. Die protected generalization relationsip wird von der UML-Darstellung nicht gut unterstützt (ich musste diese Einschränkung verwenden)

Abhängigkeiten sagen nichts über den Zugriff auf Attribute oder Operationen aus. Abhängigkeit wird verwendet, um die Definitionsabhängigkeit zwischen Modellelementen darzustellen ! Entfernen Sie alle Abhängigkeiten aus Ihrem Modell und lernen Sie, wie Sie Sichtbarkeit verwenden. Wenn Ihre Freundschaftsbeziehung den Zugriff auf eine Funktion (Attribut oder Operation) von einem bestimmten Typ (Klasse) darstellt, können Sie die Sichtbarkeit des Attributs oder der Operation auf Paket festlegen. Paketsichtbarkeit bedeutet, dass der Attributwert von Instanzen aus zugänglich ist, deren Klassen im selben Paket definiert sind.

Definieren Sie ClassAProvider und Client im selben Paket und setzen Sie die Sichtbarkeit des classA-Attributs auf den Paket-Sichtbarkeitstyp. Die Clientinstanz kann den Attributwert classA lesen, aber Instanzen anderer Typen, die nicht im selben Paket definiert sind, können dies nicht.

  • So etwas gibt es nicht Sichtbarkeit des Pakets (mittels Namespaces) verfügbar für c++ (im Vergleich zu c# oder anderen Sprachen). Ihr Vorschlag ist willkommen, hilft aber nicht für meinen konkreten Fall, der bewusst an C++-Lösungen gebunden ist.

    – πάντα ῥεῖ

    16. Dezember 2014 um 20:38 Uhr

  • Ok, aber die Sichtbarkeit des Pakettyps in UML entspricht der Friend-Deklaration in C++

    – Wladimir

    17. Dezember 2014 um 5:43 Uhr

  • Brunnen, friend kann eine korrekte Implementierung der Paketsichtbarkeit sein, und der aktuelle Sprachstandard unterstützt dies besser in Bezug auf die Unterstützung von freistehenden Inline-Definitionen friend Funktionen aus dem Namensraum. Für den Unterricht führt das jedoch ein Wartungsalptraum Ich habe in meiner Frage erwähnt (siehe Grund 2.). Wenn ich mir sicher bin, was ich tue, und mir ziemlich sicher sein kann, dass das Wartungsproblem unwahrscheinlich ist, ignoriere ich auch gut, dass „Freund“-Beziehungen in UML veraltet sind. Ich wollte nur einen Weg zeigen, wie es auf angemessene, äquivalente Weise (durch Zugriffsrichtlinien) umgestaltet werden kann.

    – πάντα ῥεῖ

    17. Dezember 2014 um 17:44 Uhr

924640cookie-checkWie kann ich eine „Freund“-Abhängigkeitserklärung richtig entfernen/umgestalten?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy