Beim Entwerfen von Code haben Sie immer zwei Möglichkeiten.
- erledige es einfach, in diesem Fall wird so ziemlich jede Lösung für dich funktionieren
- Sei pedantisch und entwerfe eine Lösung, die die Macken der Sprache und ihrer Ideologie ausnutzt (OO-Sprachen in diesem Fall - die Verwendung des Polymorphismus als Mittel, um die Entscheidung zu treffen)
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 oder float
s, ist es wahrscheinlich, dass Sie einen if
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öchten if
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, return data
)
data
ist komprimiert (call compress(data)
and return it)
data
ist verschlüsselt (anrufen encrypt(data)
und zurücksenden)
data
wird komprimiert und verschlüsselt (call encrypt(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.
Dies ist keine konkrete Sprache. Es handelt sich um einen generischen Ansatz, bei dem jedes Schlüsselwort für einen beliebigen Typ steht. In einer Sprache wie C # können Sie es durch generics ( <T>
) ersetzen .
Ich gehe davon aus, dass Sie im Moment zwei Klassen haben können, die für die Komprimierung und Verschlüsselung verantwortlich sind.
class Compression
{
Compress(data : any) : any { ... }
}
class Encryption
{
Encrypt(data : any) : any { ... }
}
In einer Unternehmenswelt werden mit hoher Wahrscheinlichkeit sogar diese spezifischen Klassen durch Schnittstellen ersetzt, z. B. würde das class
Schlüsselwort durch interface
(sollten Sie sich mit Sprachen wie C #, Java und / oder PHP befassen) ersetzt, oder das class
Schlüsselwort wird beibehalten Compress
und Encrypt
Methoden würden als rein virtuell definiert , sollten Sie in C ++ programmieren.
Um einen Adapter herzustellen, definieren wir eine gemeinsame Schnittstelle.
interface DataProcessing
{
Process(data : any) : any;
}
Dann müssen wir Implementierungen der Schnittstelle bereitstellen, um sie nützlich zu machen.
// when neither encryption nor compression is enabled
class DoNothingAdapter : DataProcessing
{
public Process(data : any) : any
{
return data;
}
}
// when only compression is enabled
class CompressionAdapter : DataProcessing
{
private compression : Compression;
public Process(data : any) : any
{
return this.compression.Compress(data);
}
}
// when only encryption is enabled
class EncryptionAdapter : DataProcessing
{
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(data);
}
}
// when both, compression and encryption are enabled
class CompressionEncryptionAdapter : DataProcessing
{
private compression : Compression;
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(
this.compression.Compress(data)
);
}
}
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 .
class DataService
{
private dataProcessing : DataProcessing;
public DataService(dataProcessing : DataProcessing)
{
this.dataProcessing = dataProcessing;
}
}
Der Prozess selbst könnte dann so einfach sein:
public ComplicatedProcess(data : any) : any
{
data = this.dataProcessing.Process(data);
// ... perhaps work with the data
return data;
}
Keine weiteren Bedingungen. Die Klasse DataService
hat keine Ahnung, was wirklich mit den Daten geschehen wird, wenn sie an das dataProcessing
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
und Encryption
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 beseitigen else
, die meiner Meinung nach sogar ein bisschen schlechter sind als eine einfache Lösungif
.
Beachten Sie, dass dies meine Art ist, gutes OO-Design zu machen. So habe ich es in den letzten Jahren gemacht und es ist der Ansatz, mit dem ich mich am wohlsten fühle.
Ich persönlich mag die Wenn-Weniger-Programmierung mehr und würde die längere Lösung über die 5 Codezeilen viel mehr schätzen. So bin ich es gewohnt, Code zu entwerfen, und ich kann ihn sehr gut lesen.
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.
if
Aussagen?