Ja po prostu czytać na ten temat w The uzasadnionych Rubyist (świetna książka, nawiasem mówiąc). Autor lepiej tłumaczy, niż ja bym, więc zacytuję go:
Żadna pojedyncza reguła ani formuła nie zawsze prowadzi do właściwego projektu. Jednak podczas podejmowania decyzji dotyczących klas kontra modułów warto pamiętać o kilku kwestiach:
Moduły nie mają instancji. Wynika z tego, że jednostki lub rzeczy są generalnie najlepiej modelowane w klasach, a cechy lub właściwości jednostek lub rzeczy najlepiej hermetyzować w modułach. Odpowiednio, jak zauważono w sekcji 4.1.1, nazwy klas są zwykle rzeczownikami, podczas gdy nazwy modułów są często przymiotnikami (stos kontra podobny do stosu).
Klasa może mieć tylko jedną superklasę, ale może mieszać dowolną liczbę modułów. Jeśli używasz dziedziczenia, priorytetowo traktuj tworzenie rozsądnych relacji nadklasy / podklasy. Nie używaj jedynej relacji klasy nadklasy, aby nadać klasie coś, co może okazać się tylko jednym z kilku zestawów cech.
Podsumowując te zasady na jednym przykładzie, oto czego nie należy robić:
module Vehicle
...
class SelfPropelling
...
class Truck < SelfPropelling
include Vehicle
...
Raczej powinieneś to zrobić:
module SelfPropelling
...
class Vehicle
include SelfPropelling
...
class Truck < Vehicle
...
Druga wersja znacznie dokładniej modeluje jednostki i właściwości. Ciężarówka wywodzi się z Pojazdu (co ma sens), podczas gdy SelfPropelling to cecha pojazdów (przynajmniej tych wszystkich, na których nam zależy w tym modelu świata) - cecha, która jest przekazywana ciężarówkom z racji tego, że Truck jest potomkiem, lub wyspecjalizowana forma pojazdu.
Truck
JEST AVehicle
- nie maTruck
tego, co by nie byłoVehicle
. Jakkolwiek nazwałbym moduł możeSelfPropelable
(:?) HmmSelfPropeled
brzmi dobrze, ale jest prawie taki sam: D. W każdym razie nie umieściłbym tego w,Vehicle
ale wTruck
- ponieważ są pojazdy, których NIE MASelfPropeled
. Dobrą wskazówką jest również pytanie - czy istnieją inne rzeczy, a NIE pojazdy, które SĄSelfPropeled
? - Cóż, może, ale trudniej byłoby mnie znaleźć. CzyliVehicle
może dziedziczyć po klasie SelfPropelling (jako klasa nie pasowałaby jakoSelfPropeled
- bo to bardziej rola)Myślę, że mixiny to świetny pomysł, ale jest inny problem, o którym nikt nie wspomniał: kolizje przestrzeni nazw. Rozważać:
Który wygrywa? W Rubim okazuje się, że to drugie
module B
, ponieważ umieściłeś go późniejmodule A
. Teraz łatwo jest uniknąć tego problemu: upewnij się, że wszystkomodule A
imodule B
„s stałe i metody są w nieprawdopodobnych nazw. Problem polega na tym, że kompilator w ogóle nie ostrzega o kolizjach.Twierdzę, że to zachowanie nie dotyczy dużych zespołów programistów - nie należy zakładać, że osoba wdrażająca
class C
wie o każdym nazwisku w zakresie. Ruby pozwoli ci nawet zastąpić stałą lub metodę innego typu . Nie jestem pewien, czy kiedykolwiek można to uznać za prawidłowe zachowanie.źródło
C#sayhi
wynikiB::HELLO
nie dlatego, że Ruby miesza stałe, ale dlatego, że ruby rozwiązuje stałe z bliższej do dalszej odległości - więcHELLO
odwołanie w programieB
zawsze rozwiązywałoby doB::HELLO
. Dzieje się tak nawet wtedy, gdy klasa C również zdefiniowała swoją własnąC::HELLO
.Moja opinia: moduły służą do udostępniania zachowań, a klasy do modelowania relacji między obiektami. Technicznie rzecz biorąc, możesz po prostu uczynić wszystko instancją Object i wmieszać dowolne moduły, które chcesz uzyskać, aby uzyskać pożądany zestaw zachowań, ale byłby to kiepski, przypadkowy i raczej nieczytelny projekt.
źródło
Odpowiedź na twoje pytanie jest w dużej mierze kontekstowa. Wychodząc z obserwacji pubba, wybór jest napędzany przede wszystkim przez rozważaną dziedzinę.
I tak, ActiveRecord powinien zostać uwzględniony, a nie rozszerzony o podklasę. Kolejny ORM - datamapper - dokładnie to osiąga!
źródło
Bardzo podoba mi się odpowiedź Andy'ego Gaskella - chciałem tylko dodać, że tak, ActiveRecord nie powinien używać dziedziczenia, ale raczej zawierać moduł dodający zachowanie (głównie trwałość) do modelu / klasy. ActiveRecord po prostu używa złego paradygmatu.
Z tego samego powodu bardzo podoba mi się MongoId zamiast MongoMapper, ponieważ pozostawia on programistom możliwość wykorzystania dziedziczenia jako sposobu na modelowanie czegoś znaczącego w domenie problemowej.
To smutne, że prawie nikt w społeczności Railsów nie używa „dziedziczenia Ruby” w sposób, w jaki powinien być używany - do definiowania hierarchii klas, a nie tylko do dodawania zachowania.
źródło
Najlepszym sposobem, w jaki rozumiem, że mixiny są wirtualne klasy. Miksery to „klasy wirtualne”, które zostały wprowadzone do łańcucha przodków klasy lub modułu.
Kiedy używamy „włączania” i przekazujemy mu moduł, dodaje on moduł do łańcucha nadrzędnego bezpośrednio przed klasą, z której dziedziczimy:
Każdy obiekt w Rubim ma również klasę pojedynczą. Metody dodane do tej klasy pojedynczej mogą być wywoływane bezpośrednio w obiekcie, a więc działają jako metody „klasowe”. Kiedy używamy "rozszerzania" na obiekcie i przekazujemy obiektowi moduł, dodajemy metody modułu do pojedynczej klasy obiektu:
Możemy uzyskać dostęp do klasy singleton za pomocą metody singleton_class:
Ruby udostępnia kilka punktów zaczepienia dla modułów, gdy są one mieszane w klasy / moduły.
included
jest metodą przechwytującą dostarczaną przez Rubiego, która jest wywoływana za każdym razem, gdy włączysz moduł do jakiegoś modułu lub klasy. Podobnie jak w zestawie, jest powiązanyextended
hak do rozszerzenia. Zostanie wywołany, gdy moduł zostanie rozszerzony o inny moduł lub klasę.Tworzy to interesujący wzorzec, którego mogą użyć programiści:
Jak widać, ten pojedynczy moduł dodaje metody instancji, metody „klasy” i działa bezpośrednio na klasę docelową (w tym przypadku wywołanie metody a_class_method ()).
ActiveSupport :: Concern hermetyzuje ten wzorzec. Oto ten sam moduł przepisany do korzystania z ActiveSupport :: Concern:
źródło
W tej chwili myślę o
template
wzorcu projektowym. Po prostu nie byłoby dobrze z modułem.źródło