Czy funkcje pierwszej klasy zastępują wzorzec strategii?

15

Wzornictwo strategia jest często traktowane jako substytut funkcji pierwszej klasy w językach, które ich nie posiadają.

Powiedzmy na przykład, że chcesz przekazać funkcjonalność do obiektu. W Javie musisz przekazać obiektowi inny obiekt, który zawiera pożądane zachowanie. W języku takim jak Ruby wystarczy przekazać samą funkcjonalność w postaci funkcji anonimowej.

Jednak myślałem o tym i zdecydowałem, że może Strategia oferuje coś więcej niż zwykła anonimowa funkcja.

Wynika to z faktu, że obiekt może utrzymać stan, który istnieje niezależnie od okresu, w którym działa jego metoda. Jednak sama anonimowa funkcja może utrzymać stan, który przestaje istnieć w momencie, gdy funkcja zakończy wykonywanie.

Czy w języku obiektowym, który obsługuje funkcje najwyższej klasy, wzorzec strategii ma jakąkolwiek przewagę nad używaniem funkcji?

Aviv Cohn
źródło
10
„Jednak anonimowa funkcja sama w sobie może utrzymywać stan, który przestaje istnieć w momencie, gdy funkcja kończy wykonywanie.”: To nieprawda: zamknięcie może utrzymywać stan występujący w różnych wywołaniach.
Giorgio
„Jednak sama anonimowa funkcja może utrzymać stan, który przestaje istnieć w momencie, gdy funkcja zakończy wykonywanie.” : nie zapominając o zmiennych globalnych, a przynajmniej zmiennych statycznych.
gbjbaanb

Odpowiedzi:

13

Gdy język obsługuje odwołania do funkcji ( Java robi to od wersji 8 ), są one często dobrą alternatywą dla strategii, ponieważ zazwyczaj wyrażają to samo przy mniejszej składni. Istnieją jednak przypadki, w których prawdziwy obiekt może być przydatny.

Interfejs strategii może mieć wiele metod. Weźmy RouteFindingStragegyjako przykład interfejs , który zawiera różne algorytmy wyszukiwania trasy. Może deklarować metody takie jak

  • 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)

które następnie zostałyby wdrożone przez strategię. Niektóre algorytmy wyszukiwania trasy mogą pozwalać na wewnętrzne optymalizacje dla niektórych z tych przypadków użycia, a niektóre mogą nie, więc implementator może zdecydować, jak zaimplementować każdą z tych metod.

Innym przypadkiem jest sytuacja, w której strategia ma stan wewnętrzny. Jasne, w niektórych językach zamknięcia mogą mieć stan wewnętrzny, ale kiedy stan wewnętrzny staje się bardzo złożony, często staje się bardziej elegancki, aby promować zamknięcie pełnej klasy.

Philipp
źródło
2
„kiedy ten stan wewnętrzny staje się bardzo skomplikowany, często przydaje się promowanie zamknięcia pełnoprawnej klasy”: Dlaczego? Jeśli stan wewnętrzny staje się skomplikowany, można go również umieścić w obiekcie / rekordzie przechowywanym wewnątrz zamknięcia.
Giorgio
1
@Giorgio Ale wtedy mielibyśmy dwie jednostki składniowe do utrzymania - zamknięcie i klasę zarządzającą jej stanem wewnętrznym. Tak więc kod można uprościć, przenosząc zamknięcie do tej klasy. Może być lepiej lub nie, co zależy od dokładnego przypadku użycia, języka programowania i osobistych preferencji.
Philipp
Wydaje mi się to rozsądnym poglądem.
Giorgio
5

Nie jest prawdą, że funkcja anonimowa może utrzymać stan, który przestaje istnieć, gdy funkcja zakończy wykonywanie.

Weź następujący przykład z Common Lisp:

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

Ta funkcja pobiera listę ciągów znaków i dodaje licznik do każdego elementu listy. Na przykład wywoływanie

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

daje

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

Funkcja number-stringswewnętrznie korzysta z funkcji anonimowej ze zmienną, counterktóra przechowuje stan (bieżąca wartość licznika), która jest ponownie używana za każdym razem, gdy funkcja jest wywoływana.

Ogólnie rzecz biorąc, zamknięcie można traktować jako obiekt za pomocą tylko jednej metody. Alternatywnie, obiekt jest zbiorem zamknięć, które mają te same zmienne zamknięte. Nie jestem więc pewien, czy istnieją przypadki, w których trzeba użyć obiektu zamiast zamknięcia: argumentowałbym, że oba są sposobami patrzenia na ten sam wzór z różnych perspektyw.

W szczególności wzorzec strategii wymaga obiektu z tylko jedną metodą, więc zamknięcie powinno wykonać zadanie. Ale, jak zauważył Philipp w swojej odpowiedzi, w zależności od okoliczności (stan złożony) i języków programowania można uzyskać bardziej eleganckie rozwiązanie, używając obiektów.

Giorgio
źródło
Czy w języku, który obsługuje funkcje pierwszej klasy jako zamknięcia, czy nadal używałbyś „klasycznej” strategii?
Aviv Cohn
1
Zwykle zgadzam się z Filipem: zależy to od języka i osobistych preferencji. Zawsze wybrałbym podejście, które sprawia, że ​​notacja jest tak prosta, jak to możliwe. Na przykład w Lisp mógłbym zdefiniować swój stan jako listę zmiennych poprzez a, leta następnie zdefiniować moje zamknięcie w nim. Zasadniczo zdefiniowałbym obiekt za pomocą jednej metody w locie. W innym języku (np. Java) może być wygodniejsze (prostsze składniowo) zdefiniowanie odpowiedniego obiektu do utrzymania stanu. Więc decydowałbym od przypadku do przypadku.
Giorgio
1

To, że dwa projekty mogą rozwiązać ten sam problem, nie oznacza, że ​​są one bezpośrednimi substytucjami.

Jeśli potrzebujesz śledzić stan w programie funkcjonalnym, nie mutujesz zmiennej zamkniętej, nawet jeśli język na to pozwala. Możesz wywołać funkcję, która przyjmuje argument jako jeden stan i zwraca nowy stan jako jego wartość zwracaną.

Twoja architektura będzie wyglądać zupełnie inaczej, ale osiągniesz ten sam cel. Nie próbuj narzucać wzorców jednego paradygmatu bezpośrednio na drugi.

Karl Bielefeldt
źródło
„Jeśli chcesz śledzić stan w programie funkcjonalnym, nie mutujesz zmiennej zamkniętej, nawet jeśli język na to pozwala.”: Jestem fanem czysto funkcjonalnego stylu i zgadzam się z twoją radą. Z drugiej strony zamknięcia są nie tylko funkcjonalnym konstruktem, a tym bardziej czysto funkcjonalnym. Idea zamykania zmiennych z kontekstu leksykalnego jest ortogonalna do referencyjnej przezroczystości / niezmienności.
Giorgio
Przepraszam, ale nie mogę śledzić twojej argumentacji. Co ma do czynienia z obsługą stanu w programowaniu czysto funkcjonalnym z omawianym pytaniem?
Philipp
1
Chodzi o to, że jeśli używasz jednej części funkcjonalnego paradygmatu, takiego jak funkcje pierwszej klasy, nie zdziw się, jeśli będziesz musiał wciągnąć inne części paradygmatu, aby działał płynnie.
Karl Bielefeldt,
1

Strategia to koncepcja , przydatna recepta na rozwiązanie konkretnego, powtarzającego się problemu. To nie jest konstrukcja językowa, ani też żadna forma implementacji . Zamknięcia można użyć do wdrożenia Strategii jednego dnia i Obserwatora następnego dnia.

Określenie strategii jest przede wszystkim przydatna w rozmowach z innymi programistów do zwięźle wyrazić swoje zamiary. Nie ma w tym nic magicznego.

idoby
źródło
2
Pytanie w szczególności wymienia wzorzec projektowania strategii, który ma określoną strukturę klas. Inne znaczenie „strategii” jako planu działania mającego na celu osiągnięcie określonego celu jest w tym kontekście niedokładne.
Jestem skłonny zgodzić się z @Snowman. Czy na pewno mówisz o schemacie strategii ?
Rowan Freeman
1
@Snowman, nawet strona, do której prowadzisz link, nie określa dokładnie, w jaki sposób należy wdrożyć ten wzorzec, ale podaje przykłady w określonych językach, ale na diagramie UML nie ma nic, co mówi, że muszę użyć dziedziczenia C ++, interfejsów Java lub bloków Ruby . Więc uprzejmie nie zgadzam się z twoją analizą.
idoby