W ostatnich latach języki, których lubię używać, stają się coraz bardziej „funkcjonalne”. Teraz używam języków, które są rodzajem „hybrydowym”: C #, F #, Scala. Lubię projektować swoją aplikację przy użyciu klas, które odpowiadają obiektom domeny, i używam funkcji funkcjonalnych, dzięki którym kodowanie jest łatwiejsze, bardziej spójne i bezpieczniejsze (szczególnie podczas pracy na kolekcjach lub podczas przekazywania funkcji).
Jednak dwa światy „zderzają się”, jeśli chodzi o projektowanie wzorów. Konkretnym przykładem, z którym się ostatnio spotkałem, jest wzorzec Observer. Chcę, aby producent powiadomił inny kod („konsumenci / obserwatorzy”, powiedzmy pamięć DB, rejestrator itd.) O utworzeniu lub zmianie elementu.
Początkowo robiłem to „funkcjonalnie” w ten sposób:
producer.foo(item => { updateItemInDb(item); insertLog(item) })
// calls the function passed as argument as an item is processed
Ale teraz zastanawiam się, czy powinienem zastosować bardziej „OO”:
interface IItemObserver {
onNotify(Item)
}
class DBObserver : IItemObserver ...
class LogObserver: IItemObserver ...
producer.addObserver(new DBObserver)
producer.addObserver(new LogObserver)
producer.foo() //calls observer in a loop
Jakie są zalety i wady obu podejść? Kiedyś usłyszałem, jak guru FP powiedział, że wzorce projektowe istnieją tylko z powodu ograniczeń języka i dlatego tak mało jest języków funkcjonalnych. Może to może być tego przykład?
EDYCJA: W moim konkretnym scenariuszu nie jest mi potrzebny, ale ... jak wprowadziłbyś usuwanie i dodawanie „obserwatorów” w sposób funkcjonalny? (Tj. Jak zaimplementowałbyś wszystkie funkcje we wzorcu?) Na przykład przekazując nową funkcję?
źródło
Odpowiedzi:
To dobry przykład dwóch różnych podejść, które niosą ze sobą pojęcie wykonywania zadania poza granicami zainteresowania obiektu wzywającego.
Chociaż w tym przykładzie jest jasne, że powinieneś wybrać podejście funkcjonalne, ogólnie będzie ono naprawdę zależeć od tego, jak złożone jest zachowanie wywoływanego obiektu. Jeśli naprawdę jest to kwestia złożonego zachowania, w którym często stosujesz podobną logikę, a generatorów funkcji nie można użyć do wyraźnego wyrażenia jej, prawdopodobnie prawdopodobnie wybierzesz kompozycję klas lub dziedziczenie, gdzie będziesz mają nieco więcej swobody w ponownym wykorzystywaniu i rozszerzaniu istniejących zachowań na zasadzie ad hoc.
Zauważyłem jednak pewien wzorzec, że programiści zwykle początkowo stosują podejście funkcjonalne i dopiero gdy pojawia się zapotrzebowanie na bardziej szczegółowe zachowanie, decydują się na podejście klasowe. Wiem na przykład, że Django przeszedł od widoków opartych na funkcjach, programów ładujących szablony i programów testujących do opartych na klasach, gdy korzyści i wymagania stały się oczywiste, ale nie wcześniej.
źródło
Wersja funkcjonalna jest znacznie krótsza, łatwiejsza w utrzymaniu, łatwiejsza do odczytania i ogólnie o wiele lepsza pod każdym możliwym względem.
Wiele - choć dalekie od wszystkich - wzorów ma zrekompensować brak funkcji w OOP, takich jak obserwatorzy. Są one znacznie lepiej modelowane funkcjonalnie.
źródło
Twój „guru FP” ma częściowo rację; wiele wzorców OO to hacki do robienia czynności funkcjonalnych. (Jego twierdzenie, że jest to powód, dla którego niewiele języków FP wydaje się co najmniej wątpliwe.) Wzorce Obserwatora i Strategii próbują naśladować funkcje pierwszej klasy. Wzorzec gościa to hack symulujący dopasowanie wzorca. Twoja
IItemObserver
jest tylko funkcją w przebraniu. Udawanie, że różni się od jakiejkolwiek innej funkcji, która bierze przedmiot, niczego ci nie kupuje.Obiekty są tylko jednym rodzajem abstrakcji danych. Ten artykuł pomoże rzucić nieco światła na ten temat. Obiekty mogą być przydatne, ale ważne jest, aby rozpoznać, że nie są odpowiednie do wszystkiego. Nie ma dychotomii; po prostu kwestia wyboru odpowiedniego narzędzia do właściwej pracy, a programowanie funkcjonalne w żaden sposób nie wymaga rezygnacji z obiektów. Poza tym programowanie funkcjonalne to coś więcej niż tylko korzystanie z funkcji. Chodzi również o zminimalizowanie efektów ubocznych i mutacji.
źródło
Naprawdę nie mogę odpowiedzieć na pytanie, ponieważ nie jestem dobry w językach funkcjonalnych; ale myślę, że powinieneś być mniej zainteresowany podejściem, o ile masz to, co masz. Z tego, co rozumiem, o ile nie dodajesz więcej detektorów w przyszłości ani nie zmieniasz detektorów podczas wykonywania, możesz bardzo dobrze pominąć tutaj wzorzec Observer.
Nie zgadzam się, że wzorce projektowe nadrabiają „ograniczenia” w językach OO. Są tam, aby dobrze wykorzystać funkcje OOP. Polimorfizm i dziedziczenie to cechy, a nie ograniczenia. Wzory projektowe wykorzystują te funkcje do promowania elastycznego projektowania. Możesz stworzyć program całkowicie inny niż OO. Możesz z wielką ostrożnością napisać cały program zawierający obiekty, które nie mają stanu, naśladując FP.
źródło