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 potentially
jedes 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.