Co to jest mixin i dlaczego są przydatne?

953

W „ Programming Python ” Mark Lutz wspomina o „mixinach”. Pochodzę z środowisk C / C ++ / C # i nie słyszałem wcześniej tego terminu. Co to jest mixin?

Czytając między wierszami tego przykładu (do którego podłączyłem, ponieważ jest dość długi), przypuszczam, że jest to przypadek użycia wielokrotnego dziedziczenia w celu rozszerzenia klasy w przeciwieństwie do „właściwej” podklasy. Czy to jest poprawne?

Dlaczego miałbym to robić, zamiast umieszczać nową funkcjonalność w podklasie? Jeśli o to chodzi, dlaczego podejście mieszania / wielokrotnego dziedziczenia byłoby lepsze niż stosowanie kompozycji?

Co odróżnia mixin od wielokrotnego dziedziczenia? Czy to tylko kwestia semantyki?

TarkaDaal
źródło

Odpowiedzi:

709

Mixin jest szczególnym rodzajem wielokrotnego dziedziczenia. Istnieją dwie główne sytuacje, w których używane są miksy:

  1. Chcesz zapewnić wiele opcjonalnych funkcji dla klasy.
  2. Chcesz użyć jednej konkretnej funkcji w wielu różnych klasach.

Na przykład numer jeden rozważ system zapytań i odpowiedzi werkzeug . Mogę zrobić zwykły stary obiekt żądania, mówiąc:

from werkzeug import BaseRequest

class Request(BaseRequest):
    pass

Gdybym chciał dodać obsługę akceptowania nagłówków, zrobiłbym to

from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
    pass

Gdybym chciał utworzyć obiekt żądania obsługujący akceptowanie nagłówków, znaczników, uwierzytelniania i obsługi klienta użytkownika, mógłbym to zrobić:

from werkzeug import BaseRequest, AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin

class Request(AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin, BaseRequest):
    pass

Różnica jest subtelna, ale w powyższych przykładach klasy mixin nie zostały stworzone samodzielnie. W bardziej tradycyjnym wielokrotnym dziedziczeniu AuthenticationMixin(na przykład) prawdopodobnie byłby bardziej podobny Authenticator. Oznacza to, że klasa prawdopodobnie zostałaby zaprojektowana jako samodzielna.

Jason Baker
źródło
123
Trzecia sytuacja: chcesz zapewnić wiele (nie opcjonalnych) funkcji dla klasy, ale chcesz mieć funkcje w osobnych klasach (i osobnych modułach), więc każdy moduł dotyczy jednej cechy (zachowania). do ponownego użycia, ale do podziału na przedziały.
bootchk
60
Prawdopodobnie nie jest to problem w tym przykładzie, ale generalnie chcesz umieścić główną klasę podstawową jako ostatni element w nawiasie, aby utworzyć łańcuch dziedziczenia: Request ==> Mixin ==> ... ==> BaseRequest. Zobacz tutaj: ianlewis.org/en/mixins-and-python
hillel
10
@hillel dobry punkt, ale pamiętaj, że Python będzie wywoływał metody nadklas od lewej do prawej (na przykład, gdy trzeba przesłonić konstruktor).
Eliseu Monar dos Santos
9
Brzmi to bardzo podobnie do wzoru dekoracyjnego.
D-Jones,
4
4th sytuacja: jest już istniejącej rodziny Parentklasy i Child1, Child2, ChildNpodklasy wewnątrz 3rd biblioteki partii, a chcesz dostosowaną zachowanie dla całej rodziny. Idealnie chciałbyś dodać takie zachowanie Parenti mam nadzieję, że zewnętrzny programista biblioteki przyjmie Twoje żądanie ściągnięcia. W przeciwnym razie będziesz musiał zaimplementować własne class NewBehaviorMixin, a następnie zdefiniować pełny zestaw klas opakowań, takich jak class NewParent(NewBehaviorMixin, Parent): passi class NewChildN(NewBehaviorMixin, ChildN): pass, itp. (PS: Czy znasz lepszy sposób?)
RayLuo
240

Po pierwsze, należy pamiętać, że mixiny istnieją tylko w językach wielokrotnego dziedziczenia. Nie możesz zrobić miksu w Javie lub C #.

Zasadniczo mixin to samodzielny typ podstawowy, który zapewnia ograniczoną funkcjonalność i rezonans polimorficzny dla klasy potomnej. Jeśli myślisz w języku C #, pomyśl o interfejsie, którego nie musisz implementować, ponieważ jest już zaimplementowany; po prostu dziedziczysz po nim i korzystasz z jego funkcjonalności.

Mixiny mają zazwyczaj wąski zakres i nie mają być przedłużane.

[edytuj - dlaczego:]

Przypuszczam, że powinienem odpowiedzieć na pytanie dlaczego, skoro pytasz. Dużą zaletą jest to, że nie musisz robić tego sam od nowa. W języku C # największym miejscem, w którym mieszanka mogłaby skorzystać, może być wzór usuwania . Ilekroć implementujesz IDisposable, prawie zawsze chcesz postępować według tego samego wzorca, ale kończysz się pisaniem i ponownym pisaniem tego samego podstawowego kodu z niewielkimi zmianami. Gdyby istniała rozszerzalna mieszanka usuwania, możesz zaoszczędzić sobie dużo dodatkowego pisania.

[edytuj 2 - aby odpowiedzieć na inne pytania]

Co odróżnia mixin od wielokrotnego dziedziczenia? Czy to tylko kwestia semantyki?

Tak. Różnica między mixinem a standardowym wielokrotnym dziedziczeniem jest tylko kwestią semantyki; klasa, która ma wielokrotne dziedziczenie, może wykorzystywać mixin jako część tego wielokrotnego dziedziczenia.

Celem mixin jest stworzenie typu, który można „wmieszać” do dowolnego innego typu poprzez dziedziczenie bez wpływu na typ dziedziczenia, a jednocześnie oferować pewne korzystne funkcje dla tego typu.

Ponownie pomyśl o interfejsie, który jest już zaimplementowany.

Ja osobiście nie używam mixinów, ponieważ rozwijam się głównie w języku, który ich nie obsługuje, więc mam naprawdę trudny czas, aby wymyślić przyzwoity przykład, który dostarczy tylko „ahah!” chwila dla ciebie. Ale spróbuję jeszcze raz. Będę używał przykładu, który został wymyślony - większość języków już udostępnia tę funkcję w taki czy inny sposób - ale mam nadzieję, że wyjaśni to, w jaki sposób należy tworzyć i wykorzystywać miksy. Tutaj idzie:

Załóżmy, że masz typ, który chcesz móc serializować do iz XML. Chcesz, aby typ zapewniał metodę „ToXML”, która zwraca ciąg zawierający fragment XML z wartościami danych typu, oraz „FromXML”, który pozwala typowi zrekonstruować wartości danych z fragmentu XML w ciągu. Ponownie, jest to wymyślony przykład, więc być może używasz strumienia plików lub klasy XML Writer z biblioteki wykonawczej twojego języka ... cokolwiek. Chodzi o to, że chcesz serializować swój obiekt do formatu XML i odzyskać nowy obiekt z formatu XML.

Innym ważnym punktem w tym przykładzie jest to, że chcesz to zrobić w sposób ogólny. Nie chcesz implementować metod „ToXML” i „FromXML” dla każdego typu, który chcesz serializować, potrzebujesz ogólnych sposobów zapewnienia, że ​​Twój typ to zrobi i po prostu zadziała. Chcesz ponownie użyć kodu.

Jeśli Twój język to obsługuje, możesz utworzyć XmlSerializable, aby wykonać swoją pracę za Ciebie. Ten typ implementuje metody ToXML i FromXML. Korzystając z jakiegoś mechanizmu, który nie jest ważny w tym przykładzie, byłby w stanie zebrać wszystkie niezbędne dane z dowolnego typu, z którym jest zmieszany, w celu zbudowania fragmentu XML zwróconego przez ToXML i byłby równie zdolny do przywracania tych danych, gdy FromXML jest nazywa.

I to wszystko. Aby z niego skorzystać, musisz mieć dowolny typ, który musi być serializowany do XML dziedziczonego z XmlSerializable. Ilekroć trzeba było serializować lub deserializować tego typu, wystarczy wywołać ToXML lub FromXML. W rzeczywistości, ponieważ XmlSerializable jest pełnoprawnym typem i polimorficznym, możliwe jest zbudowanie serializatora dokumentów, który nie wie nic o twoim oryginalnym typie, akceptując tylko, powiedzmy, tablicę typów XmlSerializable.

Teraz wyobraź sobie, że wykorzystujesz ten scenariusz do innych celów, takich jak tworzenie miksu, który zapewnia, że ​​każda klasa, która miksuje go w dzienniku, rejestruje każde wywołanie metody, lub miks, który zapewnia transakcyjność względem typu, który go miksuje. Lista może być długa.

Jeśli po prostu myślisz o mixinie jako małym typie podstawowym zaprojektowanym w celu dodania niewielkiej ilości funkcjonalności do typu bez wpływu na ten typ, to jesteś złoty.

Ufnie. :)

Randolpho
źródło
25
Hej, podoba ci się to zdanie „rezonans polimorficzny”? Sam to wymyśliłem. Myślę. Może gdzieś słyszałem to w fizyce ...
Randolpho,
50
Nie zgadzam się lekko w pierwszym zdaniu. Ruby jest językiem pojedynczego dziedziczenia, a mixiny są sposobem dodawania metod do danej klasy bez dziedziczenia z innej klasy.
Keltia
23
@Keltia: Myślę, że miksy są - z definicji - wielokrotnym dziedzictwem. W przypadku Ruby są to monkeypatch (lub coś innego), a nie właściwy mixin. Mieszkańcy Ruby mogą nazwać to mixinem, ale to coś innego.
S.Lott,
10
W rzeczywistości prawdziwy mixin nie może korzystać z wielokrotnego dziedziczenia. Mixin zawiera metody, atrybuty itp. Z jednej klasy w drugiej bez dziedziczenia. Wydaje się, że korzyści płynące z ponownego użycia kodu wydają się być polimorficzne, ale pomijają problemy przy określaniu pochodzenia (diament śmierci itp.). Języki wspierające miksy pozwalają również na częściowe włączenie klasy miksy (sprawy zaczynają brzmieć trochę jak aspekty teraz).
Trevor
8
Dla przypomnienia, Java obsługuje teraz mixiny z domyślnymi metodami.
shmosel,
169

Ta odpowiedź ma na celu wyjaśnienie mixin przykładami , które:

  • samodzielny : krótki, bez potrzeby znajomości bibliotek, aby zrozumieć przykład.

  • w Pythonie , a nie w innych językach.

    Zrozumiałe jest, że istniały przykłady z innych języków, takich jak Ruby, ponieważ termin jest znacznie bardziej powszechny w tych językach, ale jest to wątek w języku Python .

Rozważy również kontrowersyjne pytanie:

Czy konieczne jest wielokrotne dziedziczenie, czy nie charakterystyka miksu?

Definicje

Nie widziałem jeszcze cytatu z „autorytatywnego” źródła, w którym wyraźnie mówi się, co to jest mixin w Pythonie.

Widziałem 2 możliwe definicje mixinu (jeśli należy je uważać za różne od innych podobnych pojęć, takich jak abstrakcyjne klasy podstawowe), a ludzie nie do końca zgadzają się co do tego, która z nich jest poprawna.

Konsensus może być różny w różnych językach.

Definicja 1: brak wielokrotnego dziedziczenia

Mixin jest taką klasą, że niektóre metody tej klasy wykorzystują metodę, która nie jest zdefiniowana w klasie.

Dlatego klasa nie jest przeznaczona do tworzenia instancji, ale raczej służy jako klasa podstawowa. W przeciwnym razie instancja miałaby metody, których nie można wywołać bez zgłaszania wyjątku.

Ograniczeniem dodawanym przez niektóre źródła jest to, że klasa może nie zawierać danych, tylko metody, ale nie rozumiem, dlaczego jest to konieczne. W praktyce jednak wiele przydatnych miksów nie ma żadnych danych, a klasy podstawowe bez danych są prostsze w użyciu.

Klasycznym przykładem jest implementacja wszystkich operatorów porównania tylko <=i ==:

class ComparableMixin(object):
    """This class has methods which use `<=` and `==`,
    but this class does NOT implement those methods."""
    def __ne__(self, other):
        return not (self == other)
    def __lt__(self, other):
        return self <= other and (self != other)
    def __gt__(self, other):
        return not self <= other
    def __ge__(self, other):
        return self == other or self > other

class Integer(ComparableMixin):
    def __init__(self, i):
        self.i = i
    def __le__(self, other):
        return self.i <= other.i
    def __eq__(self, other):
        return self.i == other.i

assert Integer(0) <  Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) >  Integer(0)
assert Integer(1) >= Integer(1)

# It is possible to instantiate a mixin:
o = ComparableMixin()
# but one of its methods raise an exception:
#o != o 

Ten konkretny przykład można było osiągnąć za pomocą functools.total_ordering()dekoratora, ale tutaj gra miała na celu wynalezienie koła:

import functools

@functools.total_ordering
class Integer(object):
    def __init__(self, i):
        self.i = i
    def __le__(self, other):
        return self.i <= other.i
    def __eq__(self, other):
        return self.i == other.i

assert Integer(0) < Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) > Integer(0)
assert Integer(1) >= Integer(1)

Definicja 2: wielokrotne dziedziczenie

Mixin to wzorzec projektowy, w którym jakaś metoda klasy bazowej wykorzystuje metodę, której nie definiuje, i ta metoda ma być zaimplementowana przez inną klasę bazową , a nie przez pochodną jak w definicji 1.

Termin klasa mixin odnosi się do klas podstawowych, które mają być stosowane w tym wzorze projektowym (TODO tych, które stosują tę metodę, lub tych, które ją wdrażają?)

Nie jest łatwo zdecydować, czy dana klasa jest miksem, czy nie: metoda może być po prostu zaimplementowana w klasie pochodnej, w którym to przypadku wracamy do definicji 1. Musisz wziąć pod uwagę intencje autora.

Ten wzór jest interesujący, ponieważ możliwe jest ponowne połączenie funkcjonalności z różnymi wyborami klas podstawowych:

class HasMethod1(object):
    def method(self):
        return 1

class HasMethod2(object):
    def method(self):
        return 2

class UsesMethod10(object):
    def usesMethod(self):
        return self.method() + 10

class UsesMethod20(object):
    def usesMethod(self):
        return self.method() + 20

class C1_10(HasMethod1, UsesMethod10): pass
class C1_20(HasMethod1, UsesMethod20): pass
class C2_10(HasMethod2, UsesMethod10): pass
class C2_20(HasMethod2, UsesMethod20): pass

assert C1_10().usesMethod() == 11
assert C1_20().usesMethod() == 21
assert C2_10().usesMethod() == 12
assert C2_20().usesMethod() == 22

# Nothing prevents implementing the method
# on the base class like in Definition 1:

class C3_10(UsesMethod10):
    def method(self):
        return 3

assert C3_10().usesMethod() == 13

Autorytatywne wystąpienia Pythona

W oficjalnej dokumentacji dla kolekcji.abc w dokumentacji wyraźnie użyto terminu Metody mieszania .

Stwierdza, że ​​jeśli klasa:

  • narzędzia __next__
  • dziedziczy po jednej klasie Iterator

wtedy klasa otrzymuje __iter__ metodę miksowania za darmo.

Dlatego przynajmniej w tym punkcie dokumentacji mixin nie wymaga wielokrotnego dziedziczenia i jest spójny z definicją 1.

Dokumentacja może oczywiście być sprzeczna w różnych punktach, a inne ważne biblioteki Pythona mogą używać innej definicji w swojej dokumentacji.

Ta strona używa również terminu Set mixin, który wyraźnie sugeruje, że klasy lubią Seti Iteratormogą być nazywane klasami Mixin.

W innych językach

  • Ruby: Najwyraźniej nie wymaga wielokrotnego dziedziczenia dla mixin, jak wspomniano w głównych książkach, takich jak Programowanie Ruby i Język programowania Ruby

  • C ++: Metoda, która nie jest zaimplementowana, jest metodą czysto wirtualną.

    Definicja 1 pokrywa się z definicją klasy abstrakcyjnej (klasy, która ma czysto wirtualną metodę). Nie można utworzyć tej klasy.

    Definicja 2 jest możliwa w przypadku dziedziczenia wirtualnego: wielokrotne dziedziczenie z dwóch klas pochodnych

Ciro Santilli
źródło
37

Myślę o nich jako o zdyscyplinowanym sposobie korzystania z wielokrotnego dziedziczenia - ponieważ ostatecznie mixin jest po prostu kolejną klasą python, która (może) przestrzega konwencji o klasach zwanych mixinami.

Rozumiem konwencje, które rządzą czymś, co nazwałbyś Mixinem, że Mixin:

  • dodaje metody, ale nie zmienne instancji (stałe klasy są OK)
  • dziedziczy tylko object(w Pythonie)

W ten sposób ogranicza potencjalną złożoność wielokrotnego dziedziczenia i ułatwia śledzenie przepływu programu, ograniczając miejsce, w którym należy szukać (w porównaniu do pełnego wielokrotnego dziedziczenia). Są podobne do modułów ruby .

Jeśli chcę dodać zmienne instancji (z większą elastycznością, niż pozwala na to pojedyncze dziedziczenie), to zwykle wybieram kompozycję.

Powiedziawszy to, widziałem klasy o nazwie XYZMixin, które mają zmienne instancji.

Hamish Downer
źródło
30

Mixiny to koncepcja programowania, w której klasa zapewnia funkcjonalności, ale nie jest przeznaczona do tworzenia instancji. Głównym celem Mixins jest zapewnienie niezależnych funkcjonalności i najlepiej byłoby, gdyby same mixiny nie miały dziedziczenia z innymi mixinami, a także unikały stanu. W językach takich jak Ruby istnieje bezpośrednie wsparcie dla języków, ale dla Pythona nie. Można jednak użyć dziedziczenia wielu klas, aby wykonać funkcje dostarczone w języku Python.

Obejrzałem ten film http://www.youtube.com/watch?v=v_uKI2NOLEM, aby zrozumieć podstawy miksów. Dla początkującego bardzo przydatne jest zrozumienie podstaw mixin i ich działania oraz problemów, jakie możesz napotkać przy ich wdrażaniu.

Wikipedia jest wciąż najlepsza: http://en.wikipedia.org/wiki/Mixin

lakesh
źródło
29

Co odróżnia mixin od wielokrotnego dziedziczenia? Czy to tylko kwestia semantyki?

Mixin jest ograniczoną formą wielokrotnego dziedziczenia. W niektórych językach mechanizm dodawania mixinu do klasy jest nieco inny (pod względem składni) od dziedziczenia.

Szczególnie w kontekście Pythona, mixin jest klasą nadrzędną, która zapewnia funkcjonalność podklasom, ale nie jest przeznaczona do tworzenia instancji.

To, co może sprawić, że powiesz „to tylko wielokrotne dziedziczenie, a nie tak naprawdę mixin”, to fakt, że klasa, która mogłaby zostać pomylona z mixinem, może być faktycznie utworzona i użyta - tak więc jest to różnica semantyczna i bardzo realna.

Przykład wielokrotnego dziedziczenia

Ten przykład z dokumentacji to licznik zamówień:

class OrderedCounter(Counter, OrderedDict):
     'Counter that remembers the order elements are first encountered'

     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))

     def __reduce__(self):
         return self.__class__, (OrderedDict(self),)

To podklas zarówno Counteri OrderedDictod collectionsmodułu.

Zarówno Counteri OrderedDictsą przeznaczone do wystąpienia i stosowane na własną rękę. Jednak poprzez podklasowanie ich obu, możemy mieć licznik, który jest uporządkowany i ponownie wykorzystuje kod w każdym obiekcie.

Jest to skuteczny sposób na ponowne użycie kodu, ale może być również problematyczny. Jeśli okaże się, że w jednym z obiektów jest błąd, poprawienie go bez opieki może spowodować błąd w podklasie.

Przykład mieszanki

Mixiny są zwykle promowane jako sposób na ponowne użycie kodu bez potencjalnych problemów z łączeniem, które mogłyby mieć wielokrotne dziedziczenie kooperatywne, takie jak licznik zleceń. Korzystając z miksów, korzystasz z funkcji, które nie są tak ściśle powiązane z danymi.

W przeciwieństwie do powyższego przykładu, mixin nie jest przeznaczony do samodzielnego stosowania. Zapewnia nową lub inną funkcjonalność.

Na przykład standardowa biblioteka ma kilka mixin w socketserverbibliotece .

Wersje rozwidlające i wątkowe dla każdego typu serwera można tworzyć za pomocą tych klas wtyczek. Na przykład ThreadingUDPServer jest tworzony w następujący sposób:

class ThreadingUDPServer(ThreadingMixIn, UDPServer):
    pass

Klasa wtyczki jest na pierwszym miejscu, ponieważ zastępuje metodę zdefiniowaną w UDPServer. Ustawienie różnych atrybutów zmienia również zachowanie mechanizmu bazowego serwera.

W takim przypadku metody mieszania zastępują metody w UDPServerdefinicji obiektu, aby umożliwić współbieżność.

Metoda wydaje się być przesłonięte process_requesti zapewnia również inną metodę process_request_thread. Oto z kodu źródłowego :

class ThreadingMixIn:
        """Mix-in class to handle each request in a new thread."""

        # Decides how threads will act upon termination of the
        # main process
        daemon_threads = False

        def process_request_thread(self, request, client_address):
            """Same as in BaseServer but as a thread.
            In addition, exception handling is done here.
            """
            try:
                self.finish_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
            finally:
                self.shutdown_request(request)

        def process_request(self, request, client_address):
            """Start a new thread to process the request."""
            t = threading.Thread(target = self.process_request_thread,
                                 args = (request, client_address))
            t.daemon = self.daemon_threads
            t.start()

Wymyślony przykład

To jest mixin przeznaczony głównie do celów demonstracyjnych - większość obiektów ewoluuje poza użyteczność tego repr:

class SimpleInitReprMixin(object):
    """mixin, don't instantiate - useful for classes instantiable
    by keyword arguments to their __init__ method.
    """
    __slots__ = () # allow subclasses to use __slots__ to prevent __dict__
    def __repr__(self):
        kwarg_strings = []
        d = getattr(self, '__dict__', None)
        if d is not None:
            for k, v in d.items():
                kwarg_strings.append('{k}={v}'.format(k=k, v=repr(v)))
        slots = getattr(self, '__slots__', None)
        if slots is not None:
            for k in slots:
                v = getattr(self, k, None)
                kwarg_strings.append('{k}={v}'.format(k=k, v=repr(v)))
        return '{name}({kwargs})'.format(
          name=type(self).__name__,
          kwargs=', '.join(kwarg_strings)
          )

a użycie byłoby:

class Foo(SimpleInitReprMixin): # add other mixins and/or extend another class here
    __slots__ = 'foo',
    def __init__(self, foo=None):
        self.foo = foo
        super(Foo, self).__init__()

I użycie:

>>> f1 = Foo('bar')
>>> f2 = Foo()
>>> f1
Foo(foo='bar')
>>> f2
Foo(foo=None)
Aaron Hall
źródło
11

Myślę, że było tu kilka dobrych wyjaśnień, ale chciałem przedstawić inną perspektywę.

W Scali możesz tworzyć miksy, jak to tutaj opisano, ale bardzo interesujące jest to, że miksy są w rzeczywistości „połączone” w celu stworzenia nowego rodzaju klasy, z której można dziedziczyć. Zasadniczo nie dziedziczysz po wielu klasach / miksach, ale generujesz nowy rodzaj klasy ze wszystkimi właściwościami miksu do dziedziczenia. Ma to sens, ponieważ Scala jest oparty na JVM, w którym wielokrotne dziedziczenie nie jest obecnie obsługiwane (od Java 8). Nawiasem mówiąc, ten typ klasy mixin jest specjalnym typem zwanym Trait in Scala.

Jest to wskazane w sposobie definiowania klasy: klasa NewClass rozszerza FirstMixin o SecondMixin o ThirdMixin ...

Nie jestem pewien, czy interpreter CPython robi to samo (mixin-skład klasy), ale nie byłbym zaskoczony. Ponadto, wywodząc się ze środowiska C ++, nie nazwałbym ABC ani „interfejsem” równoważnym mixinowi - to podobna koncepcja, ale różni się w użyciu i implementacji.

SilentDirge
źródło
9

Odradzałbym pomyłki w nowym kodzie Python, jeśli można znaleźć inne rozwiązanie (takie jak kompozycja zamiast dziedziczenia lub po prostu metody łatania małp w swoich klasach), które nie są niczym więcej wysiłek.

W klasach w starym stylu można użyć wtyczek jako sposobu na pobranie kilku metod z innej klasy. Ale w świecie w nowym stylu wszystko, nawet wtyczka, dziedziczy po object. Oznacza to, że każde użycie wielokrotnego dziedziczenia w naturalny sposób wprowadza problemy z MRO .

Istnieją sposoby na sprawienie, aby wielodoliczkowe MRO działało w Pythonie, w szczególności funkcja super (), ale oznacza to, że musisz wykonać całą hierarchię klas za pomocą super () i znacznie trudniej jest zrozumieć przepływ kontroli.

Bobin
źródło
3
Od wersji 2.3 Python korzysta z „rozdzielczości metody C3” objaśnionej w kolejności rozwiązywania metod w Python 2.3 lub kolejności rozwiązywania metod .
webwurst
11
Osobiście w większości przypadków przejmowałbym mixiny zamiast łatania małp; łatwiej jest zrozumieć kod i postępować zgodnie z nim.
tdammers
5
Doceniony. Podczas gdy twoja odpowiedź wyraża prawidłową opinię o stylach rozwoju, tak naprawdę nie odnosisz się do rzeczywistego pytania.
Ryan B. Lynch,
8

Być może pomoże kilka przykładów.

Jeśli budujesz klasę i chcesz, aby działała jak słownik, możesz zdefiniować wszystkie __ __niezbędne metody. Ale to trochę boli. Alternatywnie możesz po prostu zdefiniować kilka i odziedziczyć (oprócz każdego innego dziedziczenia) z UserDict.DictMixin(przeniesione do collections.DictMixinw py3k). Spowoduje to automatyczne zdefiniowanie całej reszty interfejsu API słownika.

Drugi przykład: zestaw narzędzi GUI wxPython umożliwia tworzenie kontrolek listy z wieloma kolumnami (np. Wyświetlanie pliku w Eksploratorze Windows). Domyślnie te listy są dość podstawowe. Możesz dodać dodatkowe funkcje, takie jak możliwość sortowania listy według konkretnej kolumny, klikając nagłówek kolumny, dziedzicząc z ListCtrl i dodając odpowiednie miksy.

John Fouhy
źródło
8

Nie jest to przykład w języku Python, ale w języku programowania D termin mixinten odnosi się do konstrukcji używanej w podobny sposób; dodając stos rzeczy do klasy.

W D (który, nawiasem mówiąc, nie robi MI), robi się to poprzez wstawienie szablonu (pomyśl makrozkładne i bezpieczne makra, a będziesz blisko) do zakresu. Pozwala to na rozwinięcie pojedynczej linii kodu w klasie, strukturze, funkcji, module lub czymkolwiek innym do dowolnej liczby deklaracji.

BCS
źródło
2
Mixin jest ogólnym terminem używanym w D, Ruby itp. Według Wikipedii powstały one w oldschoolowych systemach lisp i zostały po raz pierwszy udokumentowane w 1983 r .: en.wikipedia.org/wiki/…
Lee B
7

OP wspomniał, że nigdy nie słyszał o mixinie w C ++, być może dlatego, że w C ++ są one nazywane Curiously Recurring Pattern Pattern (CRTP). Ponadto @Ciro Santilli wspomniał, że mixin jest implementowany za pomocą abstrakcyjnej klasy bazowej w C ++. Podczas gdy abstrakcyjna klasa bazowa może być użyta do implementacji mixin, jest to przesada, ponieważ funkcjonalność funkcji wirtualnej w czasie wykonywania można uzyskać za pomocą szablonu w czasie kompilacji bez narzutu związanego z wyszukiwaniem tabeli wirtualnej w czasie wykonywania.

Wzór CRTP jest szczegółowo opisany tutaj

Przekształciłem przykład Pythona w odpowiedzi @Ciro Santilli na C ++ przy użyciu klasy szablonów poniżej:

    #include <iostream>
    #include <assert.h>

    template <class T>
    class ComparableMixin {
    public:
        bool operator !=(ComparableMixin &other) {
            return ~(*static_cast<T*>(this) == static_cast<T&>(other));
        }
        bool operator <(ComparableMixin &other) {
            return ((*(this) != other) && (*static_cast<T*>(this) <= static_cast<T&>(other)));
        }
        bool operator >(ComparableMixin &other) {
            return ~(*static_cast<T*>(this) <= static_cast<T&>(other));
        }
        bool operator >=(ComparableMixin &other) {
            return ((*static_cast<T*>(this) == static_cast<T&>(other)) || (*(this) > other));
        }
        protected:
            ComparableMixin() {}
    };

    class Integer: public ComparableMixin<Integer> {
    public:
     Integer(int i) {
         this->i = i;
     }
     int i;
     bool operator <=(Integer &other) {
         return (this->i <= other.i);
     }
     bool operator ==(Integer &other) {
         return (this->i == other.i);
     }
    };

int main() {

    Integer i(0) ;
    Integer j(1) ;
    //ComparableMixin<Integer> c; // this will cause compilation error because constructor is protected.
    assert (i < j );
    assert (i != j);
    assert (j >  i);
    assert (j >= i);

    return 0;
}

EDYCJA: Dodano chroniony konstruktor w ComparableMixin, dzięki czemu można go tylko dziedziczyć i nie tworzyć jego instancji. Zaktualizowano przykład, aby pokazać, jak chroniony konstruktor spowoduje błąd kompilacji, gdy zostanie utworzony obiekt ComparableMixin.

bigdata2
źródło
Mixiny i CRTP nie są dokładnie tym samym w C ++.
ashrasmun
6

Może przykład z rubinu może pomóc:

Możesz dołączyć mixin Comparablei zdefiniować jedną funkcję "<=>(other)", mixin zapewnia wszystkie te funkcje:

<(other)
>(other)
==(other)
<=(other)
>=(other)
between?(other)

Robi to, wywołując <=>(other)i zwracając właściwy wynik.

"instance <=> other"zwraca 0, jeśli oba obiekty są równe, mniej niż 0, jeśli instancejest większy, othera więcej niż 0, jeśli otherjest większy.

Georg Schölly
źródło
Tutaj post zapewniający podobny mixin dla Pythona. Chociaż sugestia jest definiowana __lt__jako podstawa zamiast __cmp__, z których ta ostatnia jest faktycznie przestarzała i odradzana w użyciu. Wydaje mi się, że łatwiej jest używać tego mixinu zamiast dość skomplikowanych dekoratorów (część funools ) - chociaż ten może być w stanie bardziej dynamicznie reagować na to, jakie porównania są zapewnione ...
Tobias Kienzler
6

mixin daje możliwość dodania funkcjonalności w klasie, tzn. możesz wchodzić w interakcje z metodami zdefiniowanymi w module, włączając moduł do pożądanej klasy. Chociaż ruby ​​nie obsługuje wielokrotnego dziedziczenia, ale zapewnia mixin jako alternatywę do osiągnięcia tego.

oto przykład, który wyjaśnia, w jaki sposób osiąga się wielokrotne dziedziczenie za pomocą mixin.

module A    # you create a module
    def a1  # lets have a method 'a1' in it
    end
    def a2  # Another method 'a2'
    end
end

module B    # let's say we have another module
    def b1  # A method 'b1'
    end
    def b2  #another method b2
    end
end

class Sample    # we create a class 'Sample'
    include A   # including module 'A' in the class 'Sample' (mixin)
    include B   # including module B as well

    def S1      #class 'Sample' contains a method 's1'
    end
end

samp = Sample.new    # creating an instance object 'samp'

# we can access methods from module A and B in our class(power of mixin)

samp.a1     # accessing method 'a1' from module A
samp.a2     # accessing method 'a2' from module A
samp.b1     # accessing method 'b1' from module B
samp.b2     # accessing method 'a2' from module B
samp.s1     # accessing method 's1' inside the class Sample
Akash Soti
źródło
4
Jaka jest różnica między tym a wielokrotnym dziedziczeniem w ogóle?
Ciro Santilli 法轮功 病毒 审查 六四 事件 法轮功
Różnica polega na tym, że nie można tworzyć instancji z modułów, ale jeśli nie ma rozróżnienia między klasami ogólnymi a modułami, mixiny nie są jednoznaczne i trudno jest zrozumieć, gdzie jest klasa ogólna, a gdzie jest mixin
ka8725
Czyli w Ruby mixiny są tylko klasami, których nie można utworzyć instancji, ale muszą być używane do wielokrotnego dziedziczenia?
Trilarion
6

Właśnie użyłem miksu Pythona do zaimplementowania testowania jednostkowego milterów Pythona. Zwykle milter rozmawia z MTA, co utrudnia testy jednostkowe. Mixin testowy zastępuje metody komunikujące się z MTA i zamiast tego tworzy symulowane środowisko oparte na przypadkach testowych.

Więc bierzesz niezmodyfikowaną aplikację milter, taką jak spfmilter i mixin TestBase, taką jak ta:

class TestMilter(TestBase,spfmilter.spfMilter):
  def __init__(self):
    TestBase.__init__(self)
    spfmilter.config = spfmilter.Config()
    spfmilter.config.access_file = 'test/access.db'
    spfmilter.spfMilter.__init__(self)

Następnie użyj TestMilter w przypadkach testowych aplikacji milter:

def testPass(self):
  milter = TestMilter()
  rc = milter.connect('mail.example.com',ip='192.0.2.1')
  self.assertEqual(rc,Milter.CONTINUE)
  rc = milter.feedMsg('test1',sender='[email protected]')
  self.assertEqual(rc,Milter.CONTINUE)
  milter.close()

http://pymilter.cvs.sourceforge.net/viewvc/pymilter/pymilter/Milter/test.py?revision=1.6&view=markup

Stuart Gathman
źródło
4

Myślę, że poprzednie odpowiedzi bardzo dobrze określały, czym są MixIns . Jednak w celu lepszego ich zrozumienia przydatne może być porównanie MixIns z klasami abstrakcyjnymi i interfejsami z perspektywy kodu / implementacji:

1. Klasa abstrakcyjna

  • Klasa, która musi zawierać jedną lub więcej metod abstrakcyjnych

  • Klasa abstrakcyjna może zawierać metody stanowe (zmienne instancji) i metody nieabstrakcyjne

2. Interfejs

  • Interfejs zawiera tylko metody abstrakcyjne (brak metod nieabstrakcyjnych i brak stanu wewnętrznego)

3. MixIns

  • MixIns (podobnie jak interfejsy) nie zawierają stanu wewnętrznego (zmienne instancji)
  • MixIns zawierają jedną lub więcej metod nieabstrakcyjnych ( mogą zawierać metody nieabstrakcyjne, w przeciwieństwie do interfejsów)

Na przykład w Pythonie są to tylko konwencje, ponieważ wszystkie powyższe są zdefiniowane jako classes. Jednak wspólną cechą klas abstrakcyjnych, interfejsów i MixIns jest to, że nie powinny one istnieć same, tzn. Nie powinny być tworzone.

Tomasz Bartkowiak
źródło
3

Przeczytałem, że masz tło ac #. Tak więc dobrym punktem wyjścia może być implementacja mixin dla platformy .NET.

Możesz sprawdzić projekt codeplex na stronie http://remix.codeplex.com/

Obejrzyj link Sympozjum lang.net, aby uzyskać przegląd. Dokumentacja na stronie codeplex wymaga jeszcze więcej.

w odniesieniu do Stefana

Stefan Papp
źródło