Jakie są zalety OOP opartego na prototypach w porównaniu z OOP opartym na klasie?


47

Kiedy po raz pierwszy zacząłem programować Javascript po tym, jak przede wszystkim miałem do czynienia z OOP w kontekście języków opartych na klasach, byłem zdezorientowany, dlaczego OOP oparte na prototypach byłoby kiedykolwiek preferowane od OOP opartego na klasach.

  1. Jakie są strukturalne zalety używania OOP opartego na prototypach, jeśli takie istnieją? (np. czy spodziewalibyśmy się, że w niektórych aplikacjach będzie on szybszy lub mniej intensywny?)
  2. Jakie są zalety z perspektywy programisty? (np. Czy łatwiej jest kodować niektóre aplikacje lub rozszerzać kod innej osoby za pomocą prototypowania?)

Proszę nie patrzeć na to pytanie w szczególności na pytanie o Javascript (który miał wiele błędów przez lata, które są całkowicie niezwiązane z prototypowaniem). Zamiast tego spójrz na to w kontekście teoretycznych zalet prototypowania w porównaniu z klasami.

Dziękuję Ci.


Odpowiedzi:


46

Miałem dość duże doświadczenie w obu tych podejściach, pisząc grę RPG w Javie. Oryginalnie napisałem całą grę przy użyciu OOP opartego na klasach, ale w końcu zdałem sobie sprawę, że to niewłaściwe podejście (stawało się niemożliwe do utrzymania w miarę rozszerzania się hierarchii klas). Dlatego przekonwertowałem całą bazę kodu na kod oparty na prototypach. Rezultat był znacznie lepszy i łatwiejszy do zarządzania.

Kod źródłowy tutaj, jeśli jesteś zainteresowany ( Tyrant - Java Roguelike )

Oto główne zalety:

  • Tworzenie nowych „klas” jest banalne - wystarczy skopiować prototyp i zmienić kilka właściwości oraz voila ... nową klasę. Użyłem tego, aby zdefiniować nowy typ mikstury, na przykład w 3-6 wierszach Java. Znacznie lepiej niż plik nowej klasy i mnóstwo płyt grzewczych!
  • Możliwe jest budowanie i utrzymywanie bardzo dużej liczby „klas” przy stosunkowo niewielkim kodzie - na przykład Tyrant miał około 3000 różnych prototypów z łączną liczbą około 42 000 linii. To niesamowite dla Javy!
  • Wielokrotne dziedziczenie jest łatwe - wystarczy skopiować podzbiór właściwości z jednego prototypu i wkleić je nad właściwościami w innym prototypie. Na przykład w RPG możesz chcieć, aby „stalowy golem” miał niektóre właściwości „stalowego obiektu” i niektóre właściwości „golema” oraz niektóre właściwości „nieinteligentnego potwora”. Łatwe dzięki prototypom, spróbuj zrobić to z dziedziczeniem dziedzicznym ......
  • Za pomocą modyfikatorów właściwości można robić sprytne rzeczy - umieszczając sprytną logikę w ogólnej metodzie „czytaj właściwość”, można implementować różne modyfikatory. Na przykład łatwo było zdefiniować magiczny pierścień, który dodawał +2 siły każdemu, kto go nosił. Logika tego leżała w obiekcie pierścienia, a nie w metodzie „siły odczytu”, więc uniknąłeś konieczności umieszczania wielu testów warunkowych gdzie indziej w bazie kodu (np. „Czy postać nosi pierścień wzrostu siły?”)
  • Instancje mogą stać się szablonami dla innych instancji - np. Jeśli chcesz „sklonować” obiekt, jest to łatwe, wystarczy użyć istniejącego obiektu jako prototypu nowego obiektu. Nie trzeba pisać wielu złożonych logik klonowania dla różnych klas.
  • Bardzo łatwo jest zmienić zachowanie w czasie wykonywania - tzn. Możesz zmienić właściwości i „przekształcić” obiekt w sposób dowolny w czasie wykonywania. Pozwala na fajne efekty w grze, a jeśli połączysz to z „językiem skryptowym”, prawie wszystko jest możliwe w czasie wykonywania.
  • Jest bardziej odpowiedni dla „funkcjonalnego” stylu programowania - zwykle piszesz wiele funkcji, które odpowiednio analizują obiekty, a nie logikę w metodach przypisanych do konkretnych klas. Osobiście wolę ten styl FP.

Oto główne wady:

  • Tracisz pewność pisania statycznego - ponieważ skutecznie tworzysz dynamiczny system obiektowy. Zwykle oznacza to, że musisz napisać więcej testów, aby upewnić się, że zachowanie jest prawidłowe i że obiekty są właściwego „rodzaju”
  • Istnieje pewien narzut związany z wydajnością - ponieważ odczyty właściwości obiektów są zwykle zmuszane do przejścia przez jedno lub więcej przeglądów map, płacisz niewielki koszt pod względem wydajności. W moim przypadku nie był to problem, ale w niektórych przypadkach może to stanowić problem (np. 3D FPS z pytaniem o wiele obiektów w każdej klatce)
  • Refaktoryzacje nie działają w ten sam sposób - w systemie opartym na prototypach zasadniczo „budujesz” swoją dziedziczność za pomocą kodu. IDE / narzędzia refaktoryzujące naprawdę nie mogą ci pomóc, ponieważ nie mogą zaspokoić twojego podejścia. Nigdy nie uważałem tego za problem, ale może się wymknąć spod kontroli, jeśli nie będziesz ostrożny. Prawdopodobnie chcesz, aby testy sprawdzały, czy twoja hierarchia dziedziczenia jest konstruowana poprawnie!
  • To trochę obce - ludzie przyzwyczajeni do konwencjonalnego stylu OOP mogą łatwo się pomylić. „Co masz na myśli mówiąc, że istnieje tylko jedna klasa o nazwie„ Rzecz ”?!?” - „Jak przedłużyć tę ostatnią klasę Thing!?!” - „Łamiesz zasady OOP !!!” - „Błędem jest mieć wszystkie te funkcje statyczne, które działają na dowolny obiekt!?!?”

Na koniec kilka uwag dotyczących implementacji:

  • Użyłem Java HashMap dla właściwości i wskaźnika „macierzystego” dla prototypu. Działa to dobrze, ale ma następujące wady: a) odczyty właściwości czasami musiały prześledzić długi łańcuch nadrzędny, pogarszając wydajność b) jeśli zmutowałeś prototyp nadrzędny, zmiana wpłynie na wszystkie dzieci, które nie zastąpiły zmieniającej się właściwości. Może to powodować subtelne błędy, jeśli nie będziesz ostrożny!
  • Gdybym to robił ponownie, użyłbym niezmiennej trwałej mapy właściwości (coś w rodzaju trwałych map Clojure ) lub mojej własnej trwałej implementacji mapy mieszającej Java . Wtedy zyskasz korzyść z taniego kopiowania / zmian w połączeniu z niezmiennym zachowaniem i nie będziesz musiał trwale łączyć obiektów z ich rodzicami.
  • Możesz się dobrze bawić, osadzając funkcje / metody we właściwościach obiektu. Hack, którego użyłem w Javie do tego (anonimowe podtypy klasy „Script”) nie był zbyt elegancki - jeśli to zrobię ponownie, prawdopodobnie użyłbym odpowiedniego, łatwego do osadzenia języka dla skryptów (Clojure lub Groovy)


(+1) To dobra analiza. Altought, bardziej oparty na modelu Java, na przykład Delphi, C #, VB.Net ma wyraźne właściwości.
umlcat

3
@umlcat - Wydaje mi się, że model Java jest prawie taki sam jak model Delphi / C # (oprócz ładnego cukru syntaktycznego do uzyskiwania dostępu do właściwości) - nadal musisz statycznie zadeklarować właściwości, które chcesz w definicji klasy. Prototypowym modelem jest to, że ta definicja nie jest statyczna i nie trzeba wcześniej składać żadnych deklaracji ...
mikera

Brakowało dużego. Możesz zmienić prototyp, który skutecznie zmienia właściwości w każdej instancji, nawet po ich utworzeniu bez dotykania konstruktora prototypu.
Erik Reppen,

Jeśli chodzi o łatwiejsze wielokrotne dziedziczenie, porównałeś go z Javą, która nie obsługuje wielokrotnego dziedziczenia, ale czy jest łatwiej w porównaniu z językami, które go obsługują, np. C ++?
Piovezan

2

Główną zaletą opartego na prototypach OOP jest to, że obiekty i „klasy” mogą być rozszerzane w czasie wykonywania.

W klasycznym OOP jest kilka dobrych funkcji, niestety zależy to od języka programowania.

Object Pascal (Delphi), VB.Net & C # ma bardzo bezpośredni sposób używania właściwości (nie mylić z polami) i metod dostępu do właściwości, podczas gdy Java i C ++, właściwości są dostępne metodami. PHP ma mieszankę obu, zwanych „magicznymi metodami”.

Istnieje kilka klas pisania dynamicznego, chociaż języki OO klasy głównej mają pisanie statyczne. Myślę, że pisanie statyczne za pomocą Class OO jest bardzo przydatne, ponieważ umożliwia funkcję o nazwie Object Introspection, która umożliwia tworzenie IDE i tworzenie stron internetowych, wizualnie i szybko.


0

Muszę się zgodzić z @umlcat. Rozszerzenie klasy jest główną zaletą. Załóżmy na przykład, że chcesz dodać więcej funkcji do klasy ciągów w długim okresie czasu. W C ++ można to zrobić poprzez ciągłe dziedziczenie poprzednich generacji klas łańcuchów. Problem z tym podejściem polega na tym, że każda generacja zasadniczo staje się innym rodzajem własnego, co może prowadzić do masowego przepisywania istniejących baz kodu. Dzięki prototypowemu dziedziczeniu po prostu „dołączasz” nową metodę do oryginalnej klasy bazowej ... bez masowego tworzenia dziedziczonych klas i relacji dziedziczenia wszędzie. Chciałbym, aby C ++ zaproponował podobny mechanizm rozszerzenia w swoim nowym standardzie. Ale ich komitet wydaje się być prowadzony przez ludzi, którzy chcą dodać chwytliwe i popularne funkcje.


1
Możesz przeczytać Monolity „Unstrung” , std::stringma już zbyt wiele elementów, które powinny być niezależnymi algorytmami lub przynajmniej nieprzyjaznymi osobami niebędącymi członkami. Poza tym można dodawać nowe funkcje składowe bez zmiany układu w pamięci, jeśli można zmodyfikować oryginalną klasę.
Deduplicator
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.