Ich bin ziemlich neu in den SOLID- Designprinzipien. Ich verstehe ihre Gründe und Vorteile, aber ich kann sie nicht auf ein kleineres Projekt anwenden, das ich als praktische Übung zur Anwendung der SOLID-Prinzipien umgestalten möchte. Ich weiß, dass es nicht notwendig ist, eine perfekt funktionierende Anwendung zu ändern, aber ich möchte sie trotzdem überarbeiten, damit ich Designerfahrung für zukünftige Projekte sammeln kann.
Die Anwendung hat die folgende Aufgabe (eigentlich viel mehr als das, aber lassen Sie es uns einfach halten): Sie muss eine XML-Datei lesen, die Definitionen für Datenbanktabellen / Spalten / Ansichten usw. enthält, und eine SQL-Datei erstellen, die zum Erstellen verwendet werden kann ein ORACLE-Datenbankschema.
(Hinweis: Bitte diskutieren Sie nicht, warum ich es brauche oder warum ich kein XSLT verwende. Es gibt Gründe, aber sie sind nicht thematisch.)
Zunächst habe ich mich nur mit Tabellen und Einschränkungen befasst. Wenn Sie Spalten ignorieren, können Sie dies folgendermaßen angeben:
Eine Einschränkung ist Teil einer Tabelle (oder genauer Teil einer CREATE TABLE-Anweisung), und eine Einschränkung kann auch auf eine andere Tabelle verweisen.
Zuerst erkläre ich, wie die Anwendung im Moment aussieht (ohne SOLID):
Derzeit verfügt die Anwendung über eine "Table" -Klasse, die eine Liste von Zeigern auf Einschränkungen der Tabelle sowie eine Liste von Zeigern auf Einschränkungen enthält, die auf diese Tabelle verweisen. Immer wenn eine Verbindung hergestellt wird, wird auch die Rückwärtsverbindung hergestellt. Die Tabelle verfügt über eine createStatement () -Methode, die wiederum die createStatement () -Funktion jeder Einschränkung aufruft. Diese Methode verwendet selbst die Verbindungen zur Eigentümertabelle und zur referenzierten Tabelle, um deren Namen abzurufen.
Dies gilt natürlich überhaupt nicht für SOLID. Beispielsweise gibt es zirkuläre Abhängigkeiten, die den Code in Bezug auf erforderliche Methoden zum Hinzufügen / Entfernen und einige Destruktoren für große Objekte aufgebläht haben.
Es gibt also ein paar Fragen:
- Sollte ich die zirkulären Abhängigkeiten mithilfe der Abhängigkeitsinjektion auflösen? In diesem Fall sollte die Einschränkung die Tabelle owner (und optional die Tabelle, auf die verwiesen wird) in ihrem Konstruktor erhalten. Aber wie könnte ich dann die Liste der Einschränkungen für eine einzelne Tabelle durchgehen?
- Wenn in der Table-Klasse sowohl der Status von sich selbst (z. B. Tabellenname, Tabellenkommentar usw.) als auch die Links zu Einschränkungen gespeichert sind, handelt es sich dabei um eine oder zwei "Verantwortlichkeiten", die sich auf das Prinzip der Einzelverantwortung beziehen?
- Falls 2. richtig ist, sollte ich nur eine neue Klasse in der logischen Business-Schicht erstellen, die die Links verwaltet? Wenn ja, wäre 1. offensichtlich nicht mehr relevant.
- Sollten die "createStatement" -Methoden Teil der Table / Constraint-Klassen sein oder sollte ich sie auch verschieben? Wenn ja, wohin? Eine Manager-Klasse pro Datenspeicherklasse (dh Tabelle, Einschränkung, ...)? Oder lieber eine Managerklasse pro Link erstellen (ähnlich 3.)?
Immer wenn ich versuche, eine dieser Fragen zu beantworten, renne ich irgendwo im Kreis.
Das Problem wird natürlich viel komplexer, wenn Sie Spalten, Indizes usw. einbeziehen. Wenn Sie mir jedoch mit der einfachen Sache "Tabelle / Einschränkung" helfen, kann ich den Rest möglicherweise selbst herausfinden.
quelle
Antworten:
Sie können von einem anderen Standpunkt aus beginnen, um hier das "Prinzip der einheitlichen Verantwortung" anzuwenden. Was Sie uns gezeigt haben, ist (mehr oder weniger) nur das Datenmodell Ihrer Anwendung. SRP bedeutet hier: Stellen Sie sicher, dass Ihr Datenmodell nur für die Aufbewahrung von Daten verantwortlich ist - nicht weniger, nicht mehr.
Also , wenn Sie Ihre XML - Datei zu lesen , gehen, ein Datenmodell daraus erstellen und schreiben SQL, was sollte man nicht tun , ist alles in Ihre implementieren
Table
Klasse , die XML oder SQL spezifisch ist. Sie möchten, dass Ihr Datenfluss wie folgt aussieht:Der einzige Ort, an dem XML-spezifischer Code platziert werden sollte, ist beispielsweise eine Klasse mit dem Namen
Read_XML
. Der einzige Ort für SQL-spezifischen Code sollte eine Klasse wie seinWrite_SQL
. Natürlich werden Sie diese beiden Aufgaben möglicherweise in weitere Unteraufgaben aufteilen (und Ihre Klassen in mehrere Manager-Klassen aufteilen), aber Ihr "Datenmodell" sollte von dieser Ebene keine Verantwortung übernehmen. Fügen Sie dahercreateStatement
keiner Ihrer Datenmodellklassen ein hinzu, da dies Ihrem Datenmodell die Verantwortung für SQL überträgt.Ich sehe kein Problem, wenn Sie beschreiben, dass eine Tabelle dafür verantwortlich ist, alle ihre Teile aufzunehmen (Name, Spalten, Kommentare, Einschränkungen ...), das ist die Idee hinter einem Datenmodell. Sie haben jedoch beschrieben, dass "Tabelle" auch für die Speicherverwaltung einiger seiner Teile verantwortlich ist. Dies ist ein C ++ - spezifisches Problem, mit dem Sie in Sprachen wie Java oder C # nicht so leicht konfrontiert werden. Die C ++ - Methode, diese Verantwortung loszuwerden, besteht darin, intelligente Zeiger zu verwenden und den Besitz an eine andere Ebene zu delegieren (z. B. die Boost-Bibliothek oder Ihre eigene "intelligente" Zeigerebene). Aber Vorsicht, Ihre zyklischen Abhängigkeiten können einige Smart Pointer-Implementierungen "irritieren".
Noch etwas zu SOLID: Hier ist ein schöner Artikel
http://cre8ivethought.com/blog/2011/08/23/software-development-is-not-a-jenga-game
SOLID anhand eines kleinen Beispiels erklären. Lassen Sie uns versuchen, das auf Ihren Fall anzuwenden:
Sie benötigen nicht nur Klassen
Read_XML
undWrite_SQL
, sondern auch eine dritte Klasse, die die Interaktion dieser beiden Klassen verwaltet. Nennen wir es aConversionManager
.DI Prinzip Anwendung könnte bedeuten , hier: ConversionManager sollte nicht Instanzen erstellen
Read_XML
undWrite_SQL
von selbst aus . Stattdessen können diese Objekte über den Konstruktor injiziert werden. Und der Konstruktor sollte eine solche Signatur habenConversionManager(IDataModelReader reader, IDataModelWriter writer)
wo
IDataModelReader
ist eine Schnittstelle, von derRead_XML
erbt, undIDataModelWriter
das gleiche fürWrite_SQL
. Dadurch könnenConversionManager
Erweiterungen geöffnet werden (Sie können ganz einfach verschiedene Leser oder Autoren angeben), ohne dies ändern zu müssen. Wir haben also ein Beispiel für das Open / Closed-Prinzip. Überlegen Sie, was Sie ändern müssen, wenn Sie einen anderen Datenbankanbieter unterstützen möchten - idealerweise müssen Sie nichts in Ihrem Datenmodell ändern, sondern stellen stattdessen einen anderen SQL-Writer bereit.quelle
Nun, Sie sollten in diesem Fall das S von SOLID anwenden.
Eine Tabelle enthält alle darin definierten Einschränkungen. Eine Einschränkung enthält alle Tabellen, auf die sie verweist. Einfaches und einfaches Modell.
Was Sie dabei beachten, ist die Fähigkeit, inverse Lookups durchzuführen, dh herauszufinden, auf welche Einschränkungen in einer Tabelle verwiesen wird.
Also, was Sie eigentlich wollen, ist ein Indexdienst. Das ist eine völlig andere Aufgabe und sollte daher von einem anderen Objekt ausgeführt werden.
Um es auf eine sehr vereinfachte Version herunterzubrechen:
Für die Implementierung des Index gibt es drei Möglichkeiten:
getContraintsReferencing
Methode könnte wirklich nur das GanzeDatabase
fürTable
Instanzen crawlen und derenConstraint
s crawlen , um das Ergebnis zu erhalten. Abhängig davon, wie teuer dies ist und wie oft Sie es benötigen, kann es eine Option sein.Table
undConstraint
Instanzen, wenn sie sich ändern. Eine etwas einfachere Lösung wäre,Index
einen "Snapshot-Index" des Ganzen erstellen zu lassenDatabase
, mit dem Sie arbeiten und den Sie dann verwerfen würden. Dies ist natürlich nur möglich, wenn Ihre Anwendung zwischen "Modellierungszeit" und "Abfragezeit" stark unterscheidet. Wenn es eher wahrscheinlich ist, dass beide gleichzeitig ausgeführt werden, ist dies nicht durchführbar.quelle
Die Heilung für zirkuläre Abhängigkeiten ist das Gelübde, dass Sie sie niemals erschaffen werden. Ich finde, dass Codierungstest zuerst eine starke Abschreckung darstellt.
Auf jeden Fall können zirkuläre Abhängigkeiten immer durch Einführung einer abstrakten Basisklasse aufgehoben werden. Dies ist typisch für Graphendarstellungen. Hier sind die Tabellen Knoten und die Fremdschlüsseleinschränkungen Kanten. Erstellen Sie daher eine abstrakte Table-Klasse, eine abstrakte Constraint-Klasse und möglicherweise eine abstrakte Column-Klasse. Dann können alle Implementierungen von den abstrakten Klassen abhängen. Dies ist möglicherweise nicht die bestmögliche Darstellung, stellt jedoch eine Verbesserung gegenüber miteinander gekoppelten Klassen dar.
Wie Sie jedoch vermuten, erfordert die beste Lösung für dieses Problem möglicherweise keine Nachverfolgung der Objektbeziehungen. Wenn Sie nur XML in SQL übersetzen möchten, benötigen Sie keine speicherinterne Darstellung des Abhängigkeitsgraphen. Das Constraint-Diagramm wäre schön, wenn Sie Diagrammalgorithmen ausführen möchten, aber Sie haben das nicht erwähnt, daher gehe ich davon aus, dass dies keine Voraussetzung ist. Sie benötigen lediglich eine Liste mit Tabellen und Einschränkungen sowie einen Besucher für jeden SQL-Dialekt, den Sie unterstützen möchten. Generieren Sie die Tabellen und generieren Sie dann die Einschränkungen außerhalb der Tabellen. Bis sich die Anforderungen geändert haben, hätte ich kein Problem damit, den SQL-Generator an das XML-DOM zu koppeln. Sparen Sie morgen für morgen.
quelle