Ich werde einige Zeichenfolgen-Nutzdaten in der Datenbank speichern. Ich habe zwei globale Konfigurationen:
- Verschlüsselung
- Kompression
Diese können über die Konfiguration so aktiviert oder deaktiviert werden, dass entweder nur einer von ihnen aktiviert ist, beide aktiviert sind oder beide deaktiviert sind.
Meine aktuelle Implementierung ist folgende:
if (encryptionEnable && !compressEnable) {
encrypt(data);
} else if (!encryptionEnable && compressEnable) {
compress(data);
} else if (encryptionEnable && compressEnable) {
encrypt(compress(data));
} else {
data;
}
Ich denke über das Decorator-Muster nach. Ist es die richtige Wahl oder gibt es vielleicht eine bessere Alternative?
design
design-patterns
object-oriented-design
refactoring
Damith Ganegoda
quelle
quelle
if
Aussagen?Antworten:
Beim Entwerfen von Code haben Sie immer zwei Möglichkeiten.
Ich werde mich nicht auf den ersten der beiden konzentrieren, denn es gibt wirklich nichts zu sagen. Wenn Sie es nur zum Laufen bringen möchten, können Sie den Code so lassen, wie er ist.
Aber was würde passieren, wenn Sie es auf umständliche Weise tun und das Problem mit Designmustern tatsächlich so lösen würden, wie Sie es wollten?
Möglicherweise sehen Sie sich den folgenden Prozess an:
Beim Entwerfen von OO-Code müssen die meisten
if
in einem Code enthaltenen s nicht vorhanden sein. Wenn Sie zwei Skalartypen vergleichen möchten, z. B.int
s oderfloat
s, ist es wahrscheinlich, dass Sie einenif
haben. Wenn Sie jedoch die Prozeduren basierend auf der Konfiguration ändern möchten, können Sie den Polymorphismus verwenden , um das zu erreichen, was Sie möchtenif
s) von Ihrer Geschäftslogik zu einem Ort, an dem Objekte instanziiert werden - zu Fabriken .Ab sofort kann Ihr Prozess 4 verschiedene Pfade durchlaufen:
data
ist weder verschlüsselt noch komprimiert (call nothing, returndata
)data
ist komprimiert (callcompress(data)
and return it)data
ist verschlüsselt (anrufenencrypt(data)
und zurücksenden)data
wird komprimiert und verschlüsselt (callencrypt(compress(data))
and return it)Wenn Sie sich nur die 4 Pfade ansehen, finden Sie ein Problem.
Sie haben einen Prozess, der 3 verschiedene Methoden aufruft (theoretisch 4, wenn Sie nichts als eine aufruft), die die Daten manipulieren und sie dann zurückgeben. Die Methoden haben unterschiedliche Namen , unterschiedliche sogenannte öffentliche APIs (die Art und Weise, wie die Methoden ihr Verhalten kommunizieren).
Mithilfe des Adaptermusters können wir die aufgetretene Namenskollision lösen (wir können die öffentliche API vereinen). Einfach gesagt, hilft der Adapter, dass zwei inkompatible Schnittstellen zusammenarbeiten. Außerdem definiert der Adapter eine neue Adapterschnittstelle, die Klassen, die versuchen, ihre API zu vereinen, implementieren.
Ich gehe davon aus, dass Sie im Moment zwei Klassen haben können, die für die Komprimierung und Verschlüsselung verantwortlich sind.
In einer Unternehmenswelt werden mit hoher Wahrscheinlichkeit sogar diese spezifischen Klassen durch Schnittstellen ersetzt, z. B. würde das
class
Schlüsselwort durchinterface
(sollten Sie sich mit Sprachen wie C #, Java und / oder PHP befassen) ersetzt, oder dasclass
Schlüsselwort wird beibehaltenCompress
undEncrypt
Methoden würden als rein virtuell definiert , sollten Sie in C ++ programmieren.Um einen Adapter herzustellen, definieren wir eine gemeinsame Schnittstelle.
Dann müssen wir Implementierungen der Schnittstelle bereitstellen, um sie nützlich zu machen.
Auf diese Weise erhalten Sie 4 Klassen, von denen jede etwas völlig anderes ausführt, von denen jedoch jede dieselbe öffentliche API bereitstellt. Die
Process
Methode.In Ihrer Geschäftslogik, in der Sie sich mit der Entscheidung Keine / Verschlüsselung / Komprimierung / Beides befassen, werden Sie Ihr Objekt so entwerfen, dass es von der zuvor von
DataProcessing
uns entworfenen Schnittstelle abhängt .Der Prozess selbst könnte dann so einfach sein:
Keine weiteren Bedingungen. Die Klasse
DataService
hat keine Ahnung, was wirklich mit den Daten geschehen wird, wenn sie an dasdataProcessing
Mitglied übergeben werden, und kümmert sich nicht wirklich darum, es liegt nicht in ihrer Verantwortung.Im Idealfall sollten Sie die 4 von Ihnen erstellten Adapterklassen durch Komponententests testen lassen, um sicherzustellen, dass sie funktionieren. Sie müssen dann Ihren Test bestehen. Und wenn sie bestehen, können Sie ziemlich sicher sein, dass sie funktionieren, egal wo Sie sie in Ihrem Code aufrufen.
Wenn ich das so mache, habe ich nie mehr
if
s in meinem Code?Nein. Es ist weniger wahrscheinlich, dass Ihre Geschäftslogik Bedingun- gen enthält, aber diese müssen sich noch irgendwo befinden. Der Ort ist Ihre Fabriken.
Und das ist gut so. Sie trennen die Belange der Erstellung und der tatsächlichen Verwendung des Codes. Wenn Sie Ihre Fabriken zuverlässig machen (in Java können Sie sogar so weit gehen, dass Sie das Guice- Framework von Google verwenden), müssen Sie sich in Ihrer Geschäftslogik nicht darum kümmern, die richtige Klasse für die Injektion auszuwählen. Weil Sie wissen, dass Ihre Fabriken funktionieren und liefern, was gefragt wird.
Müssen all diese Klassen, Schnittstellen usw. vorhanden sein?
Dies bringt uns zurück zum Anfang.
Wenn Sie in OOP den Pfad für die Verwendung von Polymorphismus wählen, wirklich Entwurfsmuster verwenden, die Merkmale der Sprache ausnutzen und / oder dem Gedanken folgen möchten, dass alles eine Objektideologie ist, dann ist es das auch. Und selbst dann zeigt dieses Beispiel nicht alle Fabriken, die Sie benötigen. Wenn Sie die Klassen und umgestalten
Compression
undEncryption
stattdessen Schnittstellen erstellen möchten, müssen Sie auch deren Implementierungen einbeziehen.Am Ende stehen Ihnen Hunderte kleiner Klassen und Schnittstellen zur Verfügung, die sich auf ganz bestimmte Dinge konzentrieren. Was nicht unbedingt schlecht ist, aber möglicherweise nicht die beste Lösung für Sie ist, wenn Sie nur zwei Zahlen addieren möchten.
Wenn Sie es schnell erledigen möchten, können Sie sich die Lösung von Ixrec zulegen , die es zumindest geschafft hat, die
else if
und -Blöcke zu beseitigenelse
, die meiner Meinung nach sogar ein bisschen schlechter sind als eine einfache Lösungif
.Update 2: Es gab eine wilde Diskussion über die erste Version meiner Lösung. Diskussion meistens von mir verursacht, wofür ich mich entschuldige.
Ich habe beschlossen, die Antwort so zu bearbeiten, dass dies eine der Möglichkeiten ist, die Lösung zu betrachten, aber nicht die einzige. Ich habe auch den Dekorationsteil entfernt, wo ich stattdessen Fassade meinte, was ich am Ende ganz weggelassen habe, weil ein Adapter eine Fassadenvariante ist.
quelle
Compression
undEncryption
völlig überflüssig. Ich bin mir nicht sicher, ob Sie vorschlagen, dass sie für den Dekorationsprozess irgendwie notwendig sind oder nur implizieren, dass sie extrahierte Konzepte darstellen. Das zweite Problem ist, dass das Bilden einer Klasse wieCompressionEncryptionDecorator
zu der gleichen Art von kombinatorischer Explosion führt wie die Bedingungen des OP. Ich sehe das Dekorationsmuster im vorgeschlagenen Code auch nicht deutlich genug.Das einzige Problem, das ich bei Ihrem aktuellen Code sehe, ist das Risiko einer kombinatorischen Explosion, wenn Sie weitere Einstellungen hinzufügen. Dies kann leicht durch eine Strukturierung des Codes wie folgt verringert werden:
Mir ist kein "Designmuster" oder "Idiom" bekannt, für das dies ein Beispiel sein könnte.
quelle
else
zwischen meinen beiden if-Anweisungen kein Platz ist und warum ich siedata
jedes Mal zuordne . Wenn beide Flags wahr sind, wird compress () ausgeführt, und encrypt () wird für das Ergebnis von compress () wie gewünscht ausgeführt.Ich denke, Ihre Frage ist nicht auf Praktikabilität ausgerichtet. In diesem Fall ist die Antwort von lxrec die richtige, sondern auf Designmuster.
Offensichtlich ist das Befehlsmuster ein Overkill für ein so triviales Problem wie das, das Sie vorschlagen, aber zur Veranschaulichung hier ist es:
Wie Sie sehen, können die Befehle / Transformationen in einer Liste nacheinander ausgeführt werden. Offensichtlich werden beide ausgeführt, oder nur einer davon, abhängig davon, was Sie in die Liste aufgenommen haben, ohne if-Bedingungen.
Offensichtlich werden die Bedingungen in einer Fabrik enden, die die Befehlsliste zusammenstellt.
BEARBEITEN für den Kommentar von @ texacre:
Es gibt viele Möglichkeiten, die if-Bedingungen im Erstellungsbereich der Lösung zu umgehen. Nehmen wir zum Beispiel eine Desktop-GUI-App . Sie können Kontrollkästchen für die Komprimierungs- und Verschlüsselungsoptionen aktivieren. Bei
on clic
diesen Kontrollkästchen instanziieren Sie den entsprechenden Befehl und fügen ihn der Liste hinzu oder entfernen ihn aus der Liste, wenn Sie die Option deaktivieren.quelle
commands.add(new EncryptCommand());
odercommands.add(new CompressCommand());
sind.Ich denke, "Design Patterns" orientieren sich unnötigerweise an "Oo Patterns" und vermeiden ganz und gar einfachere Ideen. Wir sprechen hier von einer (einfachen) Datenpipeline.
Ich würde versuchen, es in Clojure zu tun. Jede andere Sprache, in der die Funktionen erstklassig sind, ist wahrscheinlich ebenfalls in Ordnung. Vielleicht könnte ich später ein C # -Beispiel machen, aber es ist nicht so schön. Meine Lösung wäre die folgenden Schritte mit einigen Erklärungen für Nicht-Clojurians:
1. Stellen Sie eine Reihe von Transformationen dar.
Dies ist eine Karte, dh eine Nachschlagetabelle / ein Wörterbuch / was auch immer, von Schlüsselwörtern zu Funktionen. Ein weiteres Beispiel (Schlüsselwörter zu Strings):
Also, schreiben
(transformations :encrypt)
oder(:encrypt transformations)
würde die Verschlüsselungsfunktion zurückgeben. ((fn [data] ... )
Ist nur eine Lambda-Funktion.)2. Holen Sie sich die Optionen als Folge von Schlüsselwörtern:
3. Filtern Sie alle Transformationen mit den angegebenen Optionen.
Beispiel:
4. Kombiniere Funktionen zu einer:
Beispiel:
5. Und dann zusammen:
Das einzige, was sich ändert, wenn wir eine neue Funktion hinzufügen wollen, sagen wir "debug-print", ist das Folgende:
quelle
funcs-to-run-here (map options funcs)
filtert und wählt so eine Reihe von Funktionen aus, die angewendet werden sollen. Vielleicht sollte ich die Antwort aktualisieren und ein bisschen mehr ins Detail gehen.[Im Wesentlichen ist meine Antwort eine Fortsetzung der Antwort von @Ixrec oben . ]
Eine wichtige Frage: Wird die Anzahl der unterschiedlichen Kombinationen, die Sie abdecken müssen, zunehmen? Sie kennen Ihre Betreff-Domain besser. Dies ist Ihr Urteil.
Kann die Anzahl der Varianten möglicherweise wachsen? Das ist nicht unvorstellbar. Beispielsweise müssen Sie möglicherweise mehr unterschiedliche Verschlüsselungsalgorithmen berücksichtigen.
Wenn Sie damit rechnen, dass die Anzahl der verschiedenen Kombinationen zunimmt, kann Ihnen das Strategiemuster helfen. Es wurde entwickelt, um Algorithmen zu kapseln und eine austauschbare Schnittstelle zum aufrufenden Code bereitzustellen. Wenn Sie die entsprechende Strategie für jede bestimmte Zeichenfolge erstellen (instanziieren), steht Ihnen immer noch ein geringer logischer Aufwand zur Verfügung.
Sie haben oben kommentiert, dass Sie nicht erwarten, dass sich die Anforderungen ändern. Wenn Sie nicht damit rechnen, dass die Anzahl der Varianten zunimmt (oder wenn Sie dieses Refactoring aufschieben können), behalten Sie die Logik bei. Derzeit verfügen Sie über eine kleine und überschaubare Menge an Logik. (Geben Sie in den Kommentaren zu einem möglichen Refactoring eines Strategiemusters möglicherweise einen Hinweis für sich selbst an.)
quelle
Eine Möglichkeit, dies in Scala zu tun, wäre:
Die Verwendung von Dekorationsmustern zur Erreichung der oben genannten Ziele (Trennung der einzelnen Verarbeitungslogiken und deren Verknüpfung) wäre zu ausführlich.
Wenn Sie ein Entwurfsmuster benötigen, um diese Entwurfsziele in einem OO-Programmierparadigma zu erreichen, bietet die funktionale Sprache native Unterstützung durch die Verwendung von Funktionen als erstklassige Bürger (Zeile 1 und 2 im Code) und als funktionale Komposition (Zeile 3).
quelle