Ich habe eine Klasse, die zur Verarbeitung von Kundenzahlungen verwendet wird. Mit einer Ausnahme sind alle Methoden dieser Klasse für jeden Kunden gleich, mit Ausnahme einer, die (zum Beispiel) berechnet, wie viel der Benutzer des Kunden schuldet. Dies kann von Kunde zu Kunde sehr unterschiedlich sein und es gibt keine einfache Möglichkeit, die Logik der Berechnungen in so etwas wie einer Eigenschaftendatei zu erfassen, da es eine beliebige Anzahl benutzerdefinierter Faktoren geben kann.
Ich könnte hässlichen Code schreiben, der basierend auf der Kunden-ID wechselt:
switch(customerID) {
case 101:
.. do calculations for customer 101
case 102:
.. do calculations for customer 102
case 103:
.. do calculations for customer 103
etc
}
Dies erfordert jedoch, dass die Klasse jedes Mal neu aufgebaut wird, wenn wir einen neuen Kunden erhalten. Was ist der bessere Weg?
[Bearbeiten] Der "doppelte" Artikel ist völlig anders. Ich frage nicht, wie ich eine switch-Anweisung vermeiden soll, sondern nach dem modernen Design, das für diesen Fall am besten geeignet ist - das ich mit einer switch-Anweisung lösen könnte, wenn ich Dinosaurier-Code schreiben möchte. Die dort bereitgestellten Beispiele sind allgemein gehalten und nicht hilfreich, da sie im Wesentlichen sagen: "Hey, der Schalter funktioniert in einigen Fällen ziemlich gut, in anderen nicht."
[Bearbeiten] Ich habe mich aus folgenden Gründen für die Antwort mit dem höchsten Rang entschieden (erstellen Sie eine separate "Kunden" -Klasse für jeden Kunden, der eine Standardschnittstelle implementiert):
Konsistenz: Ich kann eine Schnittstelle erstellen, die sicherstellt, dass alle Kundenklassen die gleiche Ausgabe erhalten und zurückgeben, auch wenn sie von einem anderen Entwickler erstellt wurden
Wartbarkeit: Der gesamte Code ist in der gleichen Sprache (Java) geschrieben, sodass niemand eine separate Programmiersprache erlernen muss, um das beizubehalten, was eine absolut einfache Funktion sein sollte.
Wiederverwendung: Falls im Code ein ähnliches Problem auftritt, kann ich die Customer-Klasse erneut verwenden, um eine beliebige Anzahl von Methoden zum Implementieren der "benutzerdefinierten" Logik zu speichern.
Vertrautheit: Ich weiß bereits, wie man das macht, damit ich es schnell erledigen und mich anderen, dringenderen Themen widmen kann.
Nachteile:
Für jeden neuen Kunden ist eine Kompilierung der neuen Kundenklasse erforderlich, wodurch die Kompilierung und Bereitstellung von Änderungen möglicherweise etwas komplexer wird.
Jeder neue Kunde muss von einem Entwickler hinzugefügt werden - ein Support-Mitarbeiter kann die Logik nicht einfach einer Eigenschaftendatei hinzufügen. Das ist nicht ideal ... aber dann war ich mir auch nicht sicher, wie ein Support-Mitarbeiter die erforderliche Geschäftslogik aufschreiben kann, insbesondere wenn sie (wie wahrscheinlich) mit vielen Ausnahmen komplex ist.
Es wird nicht gut skalieren, wenn wir viele, viele neue Kunden hinzufügen. Dies ist nicht zu erwarten, aber wenn es passiert, müssen wir viele andere Teile des Codes sowie diesen einen überdenken.
Für die von Ihnen Interessierten können Sie Java Reflection verwenden, um eine Klasse nach Namen aufzurufen:
Payment payment = getPaymentFromSomewhere();
try {
String nameOfCustomClass = propertiesFile.get("customClassName");
Class<?> cpp = Class.forName(nameOfCustomClass);
CustomPaymentProcess pp = (CustomPaymentProcess) cpp.newInstance();
payment = pp.processPayment(payment);
} catch (Exception e) {
//handle the various exceptions
}
doSomethingElseWithThePayment(payment);
Antworten:
Mir fallen zwei Möglichkeiten ein.
Option 1: Machen Sie Ihre Klasse zu einer abstrakten Klasse, wobei die Methode, die zwischen Kunden variiert, eine abstrakte Methode ist. Dann legen Sie für jeden Kunden eine Unterklasse an.
Option 2: Erstellen Sie eine
Customer
Klasse oder eineICustomer
Schnittstelle, die die gesamte kundenabhängige Logik enthält. Anstatt Ihre Zahlungsverarbeitungsklasse eine Kunden-ID akzeptieren zu lassen, lassen Sie sie einCustomer
oder einICustomer
Objekt akzeptieren . Immer wenn es etwas kundenabhängiges tun muss, ruft es die entsprechende Methode auf.quelle
Möglicherweise möchten Sie die benutzerdefinierten Berechnungen als "Plug-Ins" für Ihre Anwendung schreiben. Anschließend teilen Sie Ihrem Programm anhand einer Konfigurationsdatei mit, welches Berechnungs-Plugin für welchen Kunden verwendet werden soll. Auf diese Weise muss Ihre Hauptanwendung nicht für jeden neuen Kunden neu kompiliert werden. Sie muss lediglich eine Konfigurationsdatei lesen (oder erneut lesen) und neue Plug-Ins laden.
quelle
Ich würde mit einem Regelsatz gehen, um die Berechnungen zu beschreiben. Dies kann in einem beliebigen Persistenzspeicher gespeichert und dynamisch geändert werden.
Beachten Sie alternativ Folgendes:
Wo
customerOpsIndex
der richtige Operationsindex berechnet wurde (Sie wissen, welcher Kunde welche Behandlung benötigt).quelle
So etwas wie das Folgende:
Beachten Sie, dass Sie immer noch die switch-Anweisung im Repo haben. Es gibt jedoch kein wirkliches Umgehen, irgendwann müssen Sie der erforderlichen Logik eine Kunden-ID zuordnen. Sie können clever werden und es in eine Konfigurationsdatei verschieben, das Mapping in ein Wörterbuch stellen oder dynamisch in die Logikbaugruppen laden, aber es läuft im Wesentlichen darauf hinaus, zu wechseln.
quelle
Klingt so, als hätten Sie eine 1: 1-Zuordnung von Kunden zu benutzerdefiniertem Code. Da Sie eine kompilierte Sprache verwenden, müssen Sie Ihr System jedes Mal neu erstellen, wenn Sie einen neuen Kunden gewinnen
Probieren Sie eine eingebettete Skriptsprache aus.
Wenn sich Ihr System beispielsweise in Java befindet, können Sie JRuby einbetten und dann für jeden Kunden einen entsprechenden Ruby-Codeausschnitt speichern. Idealerweise irgendwo unter Versionskontrolle, entweder im selben oder in einem separaten Git-Repo. Und werten Sie dieses Snippet dann im Kontext Ihrer Java-Anwendung aus. JRuby kann jede Java-Funktion aufrufen und auf jedes Java-Objekt zugreifen.
Dies ist ein sehr verbreitetes Muster. Beispielsweise sind viele Computerspiele in C ++ geschrieben, verwenden jedoch eingebettete Lua-Skripte, um das Kundenverhalten jedes Gegners im Spiel zu definieren.
Wenn Sie dagegen eine 1: 1-Zuordnung von Kunden zu benutzerdefiniertem Code haben, verwenden Sie einfach das bereits vorgeschlagene "Strategie" -Muster.
Wenn die Zuordnung nicht auf der Benutzer-ID make basiert, fügen Sie
match
jedem Strategieobjekt eine Funktion hinzu und treffen Sie eine geordnete Auswahl der zu verwendenden Strategie.Hier ist ein Pseudocode
quelle
Ich werde gegen den Strom schwimmen.
Ich würde versuchen, meine eigene Ausdruckssprache mit ANTLR zu implementieren .
Bisher basieren alle Antworten stark auf der Anpassung des Codes. Die Implementierung konkreter Klassen für jeden Kunden scheint mir zu einem späteren Zeitpunkt nicht gut skalierbar zu sein. Die Wartung wird teuer und schmerzhaft sein.
Mit Antlr besteht die Idee darin, Ihre eigene Sprache zu definieren. Dass Sie Benutzern (oder Entwicklern) erlauben können, Geschäftsregeln in einer solchen Sprache zu schreiben.
Nehmen Sie Ihren Kommentar als Beispiel:
Mit Ihrer EL sollte es Ihnen möglich sein, Sätze wie:
Dann...
Du könntest. Es ist eine Zeichenfolge, die Sie als Eigenschaft oder Attribut speichern können.
Ich werde nicht lügen. Es ist ziemlich kompliziert und schwer. Noch schwieriger ist es, wenn auch die Geschäftsregeln kompliziert sind.
Hier einige SO-Fragen, die Sie interessieren könnten:
Hinweis: ANTLR generiert auch Code für Python und Javascript. Das kann helfen, Proofs of Concept zu schreiben, ohne zu viel Aufwand.
Wenn Sie feststellen, dass Antlr zu schwierig ist, können Sie es mit Bibliotheken wie Expr4J, JEval, Parsii versuchen. Diese arbeiten mit einer höheren Abstraktionsebene.
quelle
Sie können den Algorithmus zumindest externalisieren, damit sich die Kundenklasse nicht ändern muss, wenn ein neuer Kunde hinzugefügt wird, indem Sie ein Entwurfsmuster mit der Bezeichnung "Strategiemuster" verwenden (es befindet sich in der Vierergruppe).
Aus dem Snippet, das Sie gegeben haben, ist es fraglich, ob das Strategiemuster weniger oder mehr wartbar ist, aber es würde zumindest das Wissen der Kundenklasse darüber beseitigen, was getan werden muss (und würde Ihren Switch-Fall beseitigen).
Ein StrategyFactory-Objekt erstellt einen StrategyIntf-Zeiger (oder eine Referenz) basierend auf der CustomerID. Die Factory kann eine Standardimplementierung für nicht spezielle Kunden zurückgeben.
Die Kundenklasse muss nur die Fabrik nach der richtigen Strategie fragen und sie dann anrufen.
Dies ist ein sehr kurzes Pseudo-C ++, um Ihnen zu zeigen, was ich meine.
Der Nachteil dieser Lösung besteht darin, dass Sie für jeden neuen Kunden, der eine spezielle Logik benötigt, eine neue Implementierung der CalculationsStrategyIntf erstellen und die Factory aktualisieren müssen, um sie für den / die entsprechenden Kunden zurückzugeben. Dies erfordert auch das Kompilieren. Aber Sie würden zumindest den ständig wachsenden Spaghetti-Code in der Kundenklasse vermeiden.
quelle
Erstellen Sie eine Schnittstelle mit einer einzelnen Methode und verwenden Sie Lamdas in jeder Implementierungsklasse. Oder Sie können Sie anonyme Klasse, um die Methoden für verschiedene Clients zu implementieren
quelle