Projektowanie w „mieszanych” językach: projektowanie obiektowe czy programowanie funkcjonalne?

11

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ę?

Lorenzo Dematté
źródło
Co z aktorami?
kiritsuku
Zapomnij o klasach i innych bezużytecznych rzeczach OOPish. Lepiej pomyśl zamiast tego o modułach (inspirację znajdziesz w językach SML i OCaml).
SK-logic
@Antoras Gdybyś mógł porównać z podejściem opartym na Aktorach, byłoby więcej niż mile widziane :)
Lorenzo Dematté
2
@ dema80, OCaml jest doskonale paradygmatem. Moduły w ogóle nie są związane z programowaniem funkcjonalnym. Na przykład istnieje zaawansowany system modułów w absolutnie bezwzględnej Adzie. I cała zdobyta sława OOP powinna faktycznie iść na moduły - całe dobro w OOP to tylko różne formy symulacji funkcjonalności modułów. Możesz całkowicie zapomnieć o wszystkich tych klasach i zamiast tego używać ich składni do wyrażania modułów, myśląc w kategoriach modułów, a nie OOP. Przy okazji, właśnie to Microsoft zrobiło ze swoim mscorlibem - niewiele OOP, tylko moduły i przestrzenie nazw.
SK-logic
1
Myślę, że lepsze pytanie brzmi: „Czy jest jakaś przejrzystość lub organizacja, którą tracisz, robiąc to w sposób FP?”
djechlin

Odpowiedzi:

0

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.

Filip Dupanović
źródło
Myślę, że ta odpowiedź jest nieco myląca. Programowanie funkcjonalne to nie programowanie (tylko z) funkcjami. Programowanie funkcjonalne wykorzystuje również abstrakcje, więc nie ma tutaj dychotomii.
Doval
„kompozycja lub dziedziczenie, w których będziesz miał trochę więcej swobody w ponownym wykorzystywaniu i rozszerzaniu istniejących zachowań”, mówisz w ten sposób, NIE JEST to jedna z podstawowych zalet czystej FP. modułowość, możliwość składania i wielokrotnego użytku zdarzają się naturalnie, kiedy kodujesz funkcjonalnie, to nie jest „sprawa OOP”
sara,
@ FilipDupanović Miałem na myśli ponowne użycie i rozszerzenie istniejącego kodu. czyste funkcje są prawdopodobnie najbardziej kompozycyjnymi rzeczami w całym programowaniu. jesteś napisany tak, jakbyś nie mógł zarządzać złożonością w czystym funkcjonalnym środowisku. zarządzanie złożonością poprzez łączenie prostych części w większe, ale wciąż proste i nieprzezroczyste części jest całym rdzeniem FP, a wielu twierdzi, że jest nawet o wiele lepsza niż OOP. Nie zgadzam się z dychotomią przedstawioną przez „funkcjonalne jednowarstwowe, które nie skalują VS OOP, które nie skaluje żadnego problemu”.
sara,
powiedziałeś, że FP jest gorszy, jeśli chodzi o komponowanie i ponowne wykorzystywanie kodu, a gdy aplikacje stają się bardziej złożone, OOP będzie mniej więcej zawsze „lepsze”. Twierdzę, że jest to po prostu fałszywe i mylące, więc pomyślałem, że uzasadnia to głosowanie negatywne. Czy to naprawdę „purystyczna gorliwość”? Nie będę tutaj jednak omawiał tego dalej, ponieważ komentarze nie są przeznaczone do dłuższych dyskusji takich jak te.
sara,
5

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.

DeadMG
źródło
Zgadzam się i mam takie same odczucia.
Lorenzo Dematté
Zgadzam się i mam takie samo odczucie. Ale ja w pewnym sensie „oszukiwałem” moim kodem funkcjonalnym: w moim przypadku jest wszystko, czego potrzebuję, ale co, jeśli muszę dodać i usunąć „obserwatorów”? Zredagowałem swoje pytanie
Lorenzo Dematté
5

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 IItemObserverjest 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.

Doval
źródło
+1 za (IMO) dobrą odpowiedź. Dziękuję również za link do artykułu: przeczytałem go jakiś czas temu, a potem go zgubiłem. Teraz znów to znalazłem.
Giorgio
-1

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.

DPD
źródło
1
„Możesz z wielką starannością napisać cały program zawierający obiekty, które nie mają stanu, naśladując FP.”, Oczywiście i poprzez dyscyplinę możesz zrobić to samo w językach imperatywnych. :) Ogólnie rzecz biorąc, nie wydaje mi się, aby wzornictwo projektowe było czymś, co nadrabia ograniczenia, ale rozważmy przypadek wzoru gościa.
Lorenzo Dematté
Poza tym nie jestem zainteresowany kodem: chciałbym jednak skonfrontować się z innymi programistami na temat tego podejścia (według mojego zrozumienia, po to jest programmers.se, w przeciwnym razie opublikowałbym moje Q na SO)
Lorenzo Dematté
Za każdym razem, gdy zauważasz powtarzający się wzorzec, jest to tylko wskazanie poważnego ograniczenia twojego języka i ram modelowania. W idealnym świecie nie będzie żadnych wzorów.
SK-logic
@ SK-logic: Niestety świat nie jest idealny, a świat rzeczywisty ma wzorce. Dlatego języki OO mają dziedziczenie, aby implementować powtarzalny kod bez konieczności ponownego ich przepisywania. Prawdziwy obiekt świata ma stan, dlatego istnieje wzorzec stanu
DPD
1
@ SK-logic: Wiem, że niektóre języki są programowalne, a niektóre zezwalają na wymuszanie efektów również z systemu typów. Chodzi mi o to, że podobnie jak „OOP”, „wzór” jest niestety niezrozumianym terminem. Wzór jest czymś, co pojawia się ponownie w podobnych, ale różnych formach , co nie pozwala na jednolite rozwiązanie . Czasami możesz sprawić, że te quasirepetitions znikną - dzięki wielu metodom zbędny staje się gość / podwójna wysyłka. Nie oznacza to, że „wszystkie wzorce są oznakami niedoboru”, ponieważ oznacza to, że prawdziwy świat jest wadliwy. Wzory powstały w architekturze , a nie w oprogramowaniu.
Frank Shearar