Sind erstklassige Funktionen ein Ersatz für das Strategiemuster?

15

Das Entwurfsmuster Strategie wird häufig als Ersatz für erstklassige Funktionen in Sprachen angesehen, denen diese fehlen.

Angenommen, Sie möchten Funktionen in ein Objekt übertragen. In Java müssten Sie dem Objekt ein anderes Objekt übergeben, das das gewünschte Verhalten kapselt. In einer Sprache wie Ruby übergeben Sie die Funktionalität einfach selbst in Form einer anonymen Funktion.

Allerdings habe ich darüber nachgedacht und festgestellt, dass Strategy möglicherweise mehr bietet als eine reine anonyme Funktion.

Dies liegt daran, dass ein Objekt einen Status enthalten kann, der unabhängig von dem Zeitraum ist, in dem die Methode ausgeführt wird. Eine anonyme Funktion kann jedoch nur den Status halten, der nicht mehr vorhanden ist, sobald die Ausführung der Funktion abgeschlossen ist.

Hat das Strategiemuster in einer objektorientierten Sprache, die erstklassige Funktionen unterstützt, einen Vorteil gegenüber der Verwendung von Funktionen?

Aviv Cohn
quelle
10
"Eine anonyme Funktion kann jedoch nur den Status halten, der nicht mehr vorhanden ist, sobald die Ausführung der Funktion abgeschlossen ist.": Dies ist nicht der Fall: Ein Abschluss kann den Status halten, der für verschiedene Aufrufe gültig ist.
Giorgio
"Eine anonyme Funktion kann jedoch nur den Status halten, der nicht mehr vorhanden ist, sobald die Ausführung der Funktion abgeschlossen ist." : Nicht zu vergessen globale Variablen und zumindest statische Variablen.
gbjbaanb
5
Siehe auch
Euphoric

Antworten:

13

Wenn die Sprache Verweise auf Funktionen unterstützt ( Java seit Version 8 ), sind diese häufig eine gute Alternative für Strategien, da sie in der Regel dasselbe mit weniger Syntax ausdrücken. Es gibt jedoch einige Fälle, in denen ein reales Objekt nützlich sein kann.

Eine Strategie-Schnittstelle kann mehrere Methoden haben. Nehmen wir RouteFindingStragegyals Beispiel eine Schnittstelle , die verschiedene Routenfindungsalgorithmen kapselt. Es könnte Methoden wie deklarieren

  • Route findShortestRoute(Node start, Node destination)
  • boolean doesRouteExist(Node start, Node destination)
  • Route[] findAllPossibleRoutes(Node start, Node destination)
  • Route findShortestRouteToClosestDestination(Node start, Node[] destinations)
  • Route findTravelingSalesmanRoute(Node[] stations)

was dann alles von der Strategie umgesetzt würde. Einige Routenfindungsalgorithmen ermöglichen möglicherweise interne Optimierungen für einige dieser Anwendungsfälle und andere nicht, sodass der Implementierer entscheiden kann, wie jede dieser Methoden implementiert werden soll.

Ein anderer Fall ist, wenn die Strategie einen inneren Zustand hat. Sicher, in einigen Sprachen können Verschlüsse einen inneren Zustand haben, aber wenn dieser innere Zustand sehr komplex wird, wird es oft eleganter, den Verschluss zu einer vollwertigen Klasse zu befördern.

Philipp
quelle
2
"Wenn dieser innere Zustand sehr komplex wird, ist es oft nützlich, die Schließung einer vollwertigen Klasse zu fördern": Warum? Wenn der innere Zustand komplex wird, können Sie ihn auch in ein Objekt / einen Datensatz einfügen, das / der im Verschluss gespeichert ist.
Giorgio
1
@Giorgio Aber dann müssten Sie zwei Syntax-Entitäten verwalten - den Abschluss und die Klasse, die ihren internen Status verwaltet. Der Code könnte also vereinfacht werden, indem der Abschluss in diese Klasse verschoben wird. Dies ist möglicherweise besser oder nicht, was vom genauen Anwendungsfall, der Programmiersprache und den persönlichen Vorlieben abhängt.
Philipp
Es scheint mir eine vernünftige Sichtweise.
Giorgio
5

Es ist nicht wahr, dass eine anonyme Funktion nur den Status halten kann, der nicht mehr vorhanden ist, wenn die Ausführung der Funktion abgeschlossen ist.

Nehmen Sie das folgende Beispiel in Common Lisp:

(defun number-strings (ss)
  (let ((counter 0))
    (mapcar #'(lambda (s) (format nil "~a: ~a" (incf counter) s)) ss)))

Diese Funktion erstellt eine Liste von Zeichenfolgen und stellt jedem Element der Liste einen Zähler voran. Also zum Beispiel aufrufen

(number-strings '("a" "b" "c"))

gibt

("1: a" "2: b" "3: c")

Die Funktion verwendet number-stringsintern eine anonyme Funktion mit einer Variablen counter, die den Status (den aktuellen Wert des Zählers) enthält und bei jedem Aufruf der Funktion erneut verwendet wird.

Im Allgemeinen können Sie sich einen Abschluss als ein Objekt mit nur einer Methode vorstellen. Alternativ ist ein Objekt eine Sammlung von Closures, die dieselben Closed-Over-Variablen verwenden. Ich bin mir also nicht sicher, ob es Fälle gibt, in denen Sie ein Objekt anstelle eines Abschlusses verwenden müssen: Ich würde argumentieren, dass beide Arten der Betrachtung desselben Musters aus verschiedenen Perspektiven sind.

Insbesondere erfordert das Strategiemuster ein Objekt mit nur einer Methode, sodass ein Abschluss die Aufgabe erfüllen sollte. Aber, wie Philipp in seiner Antwort bemerkt hat, können Sie je nach den Umständen (komplexer Zustand) und Programmiersprachen eine elegantere Lösung erhalten, indem Sie Objekte verwenden.

Giorgio
quelle
Würden Sie in einer Sprache, die erstklassige Funktionen als Abschluss unterstützt, jemals noch die "klassische" Strategie anwenden?
Aviv Cohn
1
Ich stimme eher mit Philipp überein: es kommt auf die Sprache und die persönlichen Vorlieben an. Ich würde immer den Ansatz wählen, der die Notation so einfach wie möglich macht. Zum Beispiel könnte ich in Lisp meinen Zustand als Liste von Variablen durch a letdefinieren und dann meinen Abschluss darin definieren. Grundsätzlich hätte ich ein Objekt mit einer Methode on the fly definiert. In einer anderen Sprache (z. B. Java) ist es möglicherweise praktischer (syntaktisch einfacher), ein geeignetes Objekt zu definieren, um den Status zu halten. Ich würde mich also von Fall zu Fall entscheiden.
Giorgio
1

Nur weil zwei Designs das gleiche Problem lösen können, bedeutet dies nicht, dass sie einander direkt ersetzen.

Wenn Sie den Status in einem Funktionsprogramm nachverfolgen müssen, mutieren Sie eine geschlossene Variable nicht, selbst wenn die Sprache dies zulässt. Sie arrangieren den Aufruf einer Funktion, die einen Zustand als Argument verwendet und den neuen Zustand als Rückgabewert zurückgibt.

Ihre Architektur wird sehr unterschiedlich aussehen, aber Sie werden das gleiche Ziel erreichen. Versuchen Sie nicht, die Muster eines Paradigmas direkt auf das andere zu übertragen.

Karl Bielefeldt
quelle
"Wenn Sie den Status in einem funktionalen Programm verfolgen müssen, mutieren Sie eine geschlossene Variable nicht, selbst wenn die Sprache dies zulässt.": Ich bin ein Fan von rein funktionalem Stil und stimme Ihrem Rat zu. Verschlüsse sind aber nicht nur ein funktionales Konstrukt, geschweige denn ein rein funktionales. Die Idee, Variablen aus dem lexikalischen Kontext zu schließen, ist orthogonal zur referentiellen Transparenz / Unveränderlichkeit.
Giorgio
Sorry, aber ich kann deiner Argumentation nicht folgen. Was hat das State Handling in der rein funktionalen Programmierung mit der vorliegenden Frage zu tun?
Philipp
1
Der Punkt ist, wenn Sie einen Teil eines Funktionsparadigmas verwenden, wie erstklassige Funktionen, seien Sie nicht überrascht, wenn Sie andere Teile des Paradigmas einbinden müssen, damit es reibungslos funktioniert.
Karl Bielefeldt
1

Strategie ist ein Konzept , ein nützliches Rezept zur Lösung eines bestimmten, wiederkehrenden Problems. Es handelt sich weder um ein Sprachkonstrukt noch um eine Implementierungsform . Ein Abschluss kann verwendet werden, um die Strategie an einem Tag und den Beobachter am nächsten Tag umzusetzen.

Der Begriff Strategie ist vor allem in Gesprächen mit anderen Programmierern nützlich, um Ihre Absichten präzise auszudrücken. Es ist nichts Magisches daran.

Idobie
quelle
2
In der Frage wird speziell das Strategieentwurfsmuster erwähnt , das eine bestimmte Klassenstruktur aufweist. Die andere Bedeutung von "Strategie" als Aktionsplan zur Erreichung eines bestimmten Ziels ist in diesem Zusammenhang ungenau.
Ich bin geneigt, @Snowman zuzustimmen. Sind Sie sicher, dass Sie über das Strategiemuster sprechen ?
Rowan Freeman
1
@Snowman, selbst die Seite, auf die Sie verlinkt haben, gibt nicht genau an, wie dieses Muster implementiert werden soll, sondern enthält Beispiele in bestimmten Sprachen. Das UML-Diagramm enthält jedoch nichts, was besagt, dass ich C ++ - Vererbung, Java-Schnittstellen oder Ruby-Blöcke verwenden muss . Daher bin ich mit Ihrer Analyse nicht einverstanden.
idoby