Mixin - Mixin

W zorientowanych obiektowo językach programowania , mixin (lub mix-in ) to klasa zawierająca metody do wykorzystania przez inne klasy bez konieczności bycia klasą nadrzędną dla tych innych klas. Sposób, w jaki te inne klasy uzyskują dostęp do metod miksera, zależy od języka. Składniki są czasami opisywane jako „uwzględnione”, a nie „dziedziczone”.

Miksery zachęcają do ponownego użycia kodu i mogą być używane w celu uniknięcia niejednoznaczności dziedziczenia, które może powodować wielokrotne dziedziczenie („ problem z diamentami ”), lub w celu obejścia braku obsługi wielokrotnego dziedziczenia w języku. Mixin może być również postrzegany jako interfejs z zaimplementowanymi metodami . Ten wzorzec jest przykładem egzekwowania zasady inwersji zależności .

Historia

Mixiny po raz pierwszy pojawiły się w zorientowanym obiektowo systemie Flavours firmy Symbolics (opracowanym przez Howarda Cannona), który był podejściem do orientacji obiektowej używanym w Lisp Machine Lisp . Nazwa została zainspirowana lodziarnią Steve'a w Somerville w stanie Massachusetts: właściciel lodziarni zaoferował podstawowy smak lodów (waniliowy, czekoladowy itp.) I zmieszany z kombinacją dodatkowych elementów (orzechy, ciastka, krówki itp.) i nazwał przedmiot „ mix-in ”, co było jego własnym znakiem towarowym w tamtym czasie.

Definicja

Miksery to koncepcja języka, która umożliwia programiście wstrzyknięcie kodu do klasy . Programowanie Mixin to styl tworzenia oprogramowania , w którym jednostki funkcjonalności są tworzone w klasie, a następnie mieszane z innymi klasami.

Klasa mieszana działa jako klasa nadrzędna, zawierająca żądaną funkcjonalność. Podklasa może wtedy dziedziczyć lub po prostu ponowne wykorzystanie tej funkcji, ale nie jako środek specjalizacji. Zazwyczaj mixin wyeksportuje żądaną funkcjonalność do klasy podrzędnej , bez tworzenia sztywnej, pojedynczej relacji „jest”. Tutaj leży istotna różnica między koncepcjami mieszania i dziedziczenia , ponieważ klasa potomna może nadal dziedziczyć wszystkie cechy klasy nadrzędnej, ale semantyka mówiąca o tym, że dziecko „jest rodzajem” rodzica, nie musi być koniecznie stosowana.

Zalety

  1. Zapewnia mechanizm wielokrotnego dziedziczenia , umożliwiając jednej klasie używanie wspólnych funkcji wielu klas, ale bez złożonej semantyki wielokrotnego dziedziczenia.
  2. Możliwość ponownego wykorzystania kodu : miksery są przydatne, gdy programista chce udostępnić funkcje między różnymi klasami. Zamiast powtarzać ten sam kod w kółko, wspólną funkcjonalność można po prostu zgrupować w mixin, a następnie włączyć do każdej klasy, która tego wymaga.
  3. Domieszki umożliwiają dziedziczenie i używanie tylko pożądanych funkcji z klasy nadrzędnej, niekoniecznie wszystkich funkcji z klasy nadrzędnej.

Wdrożenia

W Simuli klasy są zdefiniowane w bloku, w którym atrybuty, metody i inicjalizacja klas są zdefiniowane razem; w ten sposób wszystkie metody, które można wywołać w klasie, są zdefiniowane razem, a definicja klasy jest kompletna.

W Flavours , mixin to klasa, z której inna klasa może dziedziczyć definicje slotów i metody. Mixin zwykle nie ma bezpośrednich instancji. Ponieważ Smak może dziedziczyć z więcej niż jednego innego Smaku, może dziedziczyć z jednego lub więcej miksów. Zwróć uwagę, że oryginalne Flavours nie korzystały z funkcji ogólnych.

W New Flavours (następcy Flavours) i CLOS metody są zorganizowane w „ funkcje generyczne ”. Te funkcje ogólne to funkcje, które są definiowane w wielu przypadkach (metodach) przez wywołanie klasy i kombinacje metod.

CLOS i Flavors pozwalają metodom mieszania dodać zachowanie do istniejących metod: :before oraz :after demonów, whopperów i opakowań w Flavours. CLOS dodał :around metody i możliwość wywoływania metod w tle za pośrednictwem CALL-NEXT-METHOD . Na przykład stream-lock-mixin może dodać blokowanie wokół istniejących metod klasy strumienia. W Flavours można by napisać wrapper lub whopper, aw CLOS użyć :around metody. Zarówno CLOS, jak i Flavors pozwalają na obliczone ponowne użycie poprzez kombinacje metod. :before , :after a :around metody są cechą standardowej kombinacji metod. Dostępne są inne kombinacje metod.

Przykładem jest + kombinacja metod, w której wynikowe wartości każdej z odpowiednich metod funkcji ogólnej są dodawane arytmetycznie w celu obliczenia zwracanej wartości. Jest to używane na przykład w przypadku border-mixin dla obiektów graficznych. Obiekt graficzny może mieć ogólną funkcję szerokości. Border-mixin doda obramowanie wokół obiektu i ma metodę obliczającą jego szerokość. Nowa klasa bordered-button (będąca jednocześnie obiektem graficznym i korzystająca z border mieszanki) obliczałaby swoją szerokość, wywołując wszystkie możliwe metody szerokości - za pomocą + kombinacji metod. Wszystkie zwracane wartości są dodawane i tworzą połączoną szerokość obiektu.

W artykule OOPSLA 90 Gilad Bracha i William Cook reinterpretują różne mechanizmy dziedziczenia występujące w Smalltalk, Beta i CLOS jako specjalne formy dziedziczenia mieszanki.

Języki programowania używające mixinów

Oprócz Flavours i CLOS (część Common Lisp ), niektóre języki używające mixinów to:

Niektóre języki nie obsługują miksów na poziomie języka, ale można je łatwo naśladować, kopiując metody z jednego obiektu do drugiego w czasie wykonywania, a tym samym „pożyczając” metody miksera. Jest to również możliwe w przypadku języków statycznych , ale wymaga zbudowania nowego obiektu z rozszerzonym zestawem metod.

Inne języki, które nie obsługują miksów, mogą je obsługiwać w sposób okrężny za pośrednictwem innych konstrukcji językowych. C # i Visual Basic .NET obsługują dodawanie metod rozszerzających do interfejsów, co oznacza, że ​​każda klasa implementująca interfejs ze zdefiniowanymi metodami rozszerzającymi będzie miała metody rozszerzające dostępne jako pseudoelementy.

Przykłady

W Common Lisp

Common Lisp zapewnia miksy w CLOS (Common Lisp Object System) podobne do Flavours.

object-width jest funkcją ogólną z jednym argumentem, która używa + kombinacji metod. Ta kombinacja określa, że ​​zostaną wywołane wszystkie metody mające zastosowanie do funkcji ogólnej, a wyniki zostaną dodane.

(defgeneric object-width (object)
  (:method-combination +))

button to klasa z jednym miejscem na tekst przycisku.

(defclass button ()
  ((text :initform "click me")))

Istnieje metoda dla obiektów klasy przycisk, która oblicza szerokość na podstawie długości tekstu przycisku. + to kwalifikator metody dla kombinacji metod o tej samej nazwie.

(defmethod object-width + ((object button))
   (* 10 (length (slot-value object 'text))))

border-mixin Klasa. Nazewnictwo to tylko konwencja. Nie ma superklas ani automatów.

(defclass border-mixin () ())

Istnieje metoda obliczania szerokości obramowania. Tutaj jest tylko 4.

(defmethod object-width + ((object border-mixin))
  4)

bordered-button jest klasą dziedziczącą po obu border-mixin i button .

(defclass bordered-button (border-mixin button) ())

Możemy teraz obliczyć szerokość przycisku. Wywołanie object-width oblicza 80. Wynik jest wynikiem jednej stosownej metody: metody object-width dla klasy button .

? (object-width (make-instance 'button))
80

Możemy również obliczyć szerokość bordered-button . Wywołanie object-width oblicza 84. Wynik jest sumą wyników dwóch możliwych do zastosowania metod: metody object-width dla klasy button i metody object-width dla klasy border-mixin .

? (object-width (make-instance 'bordered-button))
84

W Pythonie

W Pythonie The SocketServer Moduł posiada zarówno UDPServer klasę i TCPServer klasę. Działają jako serwery odpowiednio dla serwerów gniazd UDP i TCP . Dodatkowo istnieją dwie klasy mieszane: ForkingMixIn i ThreadingMixIn . Zwykle wszystkie nowe połączenia są obsługiwane w ramach tego samego procesu. Rozszerzając TCPServer w ThreadingMixIn następujący sposób:

class ThreadingTCPServer(ThreadingMixIn, TCPServer):
    pass

ThreadingMixIn klasa podnosi funkcjonalność serwera TCP, tak że każde nowe połączenie tworzy nowy wątek . Przy użyciu tej samej metody ThreadingUDPServer można utworzyć plik bez konieczności duplikowania kodu w ThreadingMixIn . Alternatywnie użycie polecenia ForkingMixIn spowoduje rozwidlenie procesu dla każdego nowego połączenia. Oczywiście funkcja tworzenia nowego wątku lub rozgałęziania procesu nie jest zbyt przydatna jako samodzielna klasa.

W tym przykładzie użycia miksery zapewniają alternatywną podstawową funkcjonalność bez wpływu na funkcjonalność serwera gniazd.

W Ruby

Większość świata Rubiego opiera się na miksowaniach za pośrednictwem Modules . Koncepcja mixinów jest zaimplementowana w Rubim przez słowo kluczowe, include do którego przekazujemy nazwę modułu jako parametr .

Przykład:

class Student
  include Comparable # The class Student inherits the Comparable module using the 'include' keyword
  attr_accessor :name, :score

  def initialize(name, score)
    @name = name
    @score = score
  end

  # Including the Comparable module requires the implementing class to define the <=> comparison operator
  # Here's the comparison operator. We compare 2 student instances based on their scores.

  def <=>(other)
    @score <=> other.score
  end

  # Here's the good bit - I get access to <, <=, >,>= and other methods of the Comparable Interface for free.
end

s1 = Student.new("Peter", 100)
s2 = Student.new("Jason", 90)

s1 > s2 #true
s1 <= s2 #false

W JavaScript

Obiektowe dosłownym i extend podejście

Technicznie możliwe jest dodanie zachowania do obiektu poprzez powiązanie funkcji z kluczami w obiekcie. Jednak ten brak rozdziału między stanem a zachowaniem ma wady:

  1. Przeplata właściwości domeny modelu z domeną implementacji.
  2. Brak wspólnego zachowania. Metaobiekty rozwiązują ten problem, oddzielając specyficzne dla domeny właściwości obiektów od ich właściwości specyficznych dla zachowania.

Funkcja rozszerzania służy do mieszania zachowania w:

'use strict';

const Halfling = function (fName, lName) {
  this.firstName = fName;
  this.lastName = lName;
};

const mixin = {
  fullName() {
    return this.firstName + ' ' + this.lastName;
  },
  rename(first, last) {
    this.firstName = first;
    this.lastName = last;
    return this;
  }
};

// An extend function
const extend = (obj, mixin) => {
  Object.keys(mixin).forEach(key => obj[key] = mixin[key]);
  return obj;
};

const sam = new Halfling('Sam', 'Loawry');
const frodo = new Halfling('Freeda', 'Baggs');

// Mixin the other methods
extend(Halfling.prototype, mixin);

console.log(sam.fullName());  // Sam Loawry
console.log(frodo.fullName());  // Freeda Baggs

sam.rename('Samwise', 'Gamgee');
frodo.rename('Frodo', 'Baggins');

console.log(sam.fullName());  // Samwise Gamgee
console.log(frodo.fullName());  // Frodo Baggins

Połącz z użyciem Object. assign ()

'use strict';

// Creating an object
const obj1 = {
  name: 'Marcus Aurelius',
  city: 'Rome',
  born: '121-04-26'
};

// Mixin 1
const mix1 = {
  toString() {
    return `${this.name} was born in ${this.city} in ${this.born}`;
  },
  age() {
    const year = new Date().getFullYear();
    const born = new Date(this.born).getFullYear();
    return year - born;
  }
};
// Mixin 2
const mix2 = {
  toString() {
    return `${this.name} - ${this.city} - ${this.born}`;
  }
};

//  Adding the methods from mixins to the object using Object.assign()
Object.assign(obj1, mix1, mix2);

console.log(obj1.toString());   // Marcus Aurelius - Rome - 121-04-26
console.log(`His age is ${obj1.age()} as of today`);  // His age is 1897 as of today

Podejście Flight-Mixin oparte na czystej funkcji i delegacji

Chociaż pierwsze opisane podejście jest w większości szeroko rozpowszechnione, następne jest bliższe temu, co zasadniczo oferuje rdzeń języka JavaScript - delegowanie .

Dwa wzorce oparte na obiektach funkcji już załatwiają sprawę bez potrzeby implementacji przez stronę trzecią extend .

'use strict';

// Implementation
const EnumerableFirstLast = (function () { // function based module pattern.
  const first = function () {
      return this[0];
    },
    last = function () {
      return this[this.length - 1];
    };
  return function () {      // function based Flight-Mixin mechanics ...
    this.first  = first;  // ... referring to ...
    this.last   = last;   // ... shared code.
  };
}());

// Application - explicit delegation:
// applying [first] and [last] enumerable behavior onto [Array]'s [prototype].
EnumerableFirstLast.call(Array.prototype);

// Now you can do:
const a = [1, 2, 3];
a.first(); // 1
a.last();  // 3

W innych językach

W języku zawartości sieci Web Curl dziedziczenie wielokrotne jest używane, ponieważ klasy bez instancji mogą implementować metody. Wspólne wstawek obejmować wszystkie skinnable ControlUI s dziedziczenie z SkinnableControlUI , obiektów interfejsu użytkownika delegatów, które wymagają rozwijanych menu dziedziczenie z StandardBaseDropdownUI i wyraźnie nazwanych wstawianej takich klas jak FontGraphicMixin , FontVisualMixin i NumericAxisMixin-of klasy. Wersja 7.0 dodała dostęp do biblioteki, dzięki czemu mieszanki nie muszą znajdować się w tym samym pakiecie ani być publiczną abstrakcją. Konstruktory curl to fabryki, które ułatwiają korzystanie z wielokrotnego dziedziczenia bez jawnej deklaracji interfejsów lub mikserów.

Interfejsy i cechy

Java 8 wprowadza nową funkcję w postaci domyślnych metod dla interfejsów. Zasadniczo umożliwia zdefiniowanie metody w interfejsie z aplikacją w scenariuszu, gdy nowa metoda ma zostać dodana do interfejsu po zakończeniu konfiguracji programowania klasy interfejsu. Dodanie nowej funkcji do interfejsu oznacza zaimplementowanie metody w każdej klasie używającej interfejsu. Pomagają w tym metody domyślne, które mogą zostać wprowadzone do interfejsu w dowolnym momencie i mają zaimplementowaną strukturę, która jest następnie używana przez skojarzone z nimi klasy. Stąd domyślne metody dodają możliwość zastosowania koncepcji w pewien sposób.

Interfejsy w połączeniu z programowaniem aspektowym mogą również tworzyć pełnoprawne mieszanki w językach obsługujących takie funkcje, takich jak C # lub Java. Dodatkowo, dzięki zastosowaniu wzorca interfejsu znacznika , programowania ogólnego i metod rozszerzających, C # 3.0 ma możliwość naśladowania miksów. Wraz z C # 3.0 pojawiło się wprowadzenie metod rozszerzających, które mogą być stosowane nie tylko do klas, ale także do interfejsów. Metody rozszerzające zapewniają dodatkowe funkcje w istniejącej klasie bez modyfikowania klasy. Następnie staje się możliwe utworzenie statycznej klasy pomocniczej dla określonej funkcjonalności, która definiuje metody rozszerzające. Ponieważ klasy implementują interfejs (nawet jeśli rzeczywisty interfejs nie zawiera żadnych metod ani właściwości do zaimplementowania), przejmie również wszystkie metody rozszerzające. C # 8.0 dodaje funkcję domyślnych metod interfejsu.

ECMAScript (w większości przypadków zaimplementowany jako JavaScript) nie musi naśladować kompozycji obiektu przez stopniowe kopiowanie pól z jednego obiektu do drugiego. Obsługuje on natywnie cecha i wstawionej kompozycji opartej na przedmiot za pomocą obiektów funkcyjnych, które stosują dodatkowe zachowania i następnie są przekazywane za pośrednictwem call lub apply obiektów, które wymagają takiej nowej funkcjonalności.

W Scala

Scala ma bogaty system typów, a cechy są jego częścią, co pomaga zaimplementować zachowanie mieszania. Jak ujawnia ich nazwa, cechy są zwykle używane do reprezentowania odrębnej cechy lub aspektu, który jest zwykle ortogonalny w stosunku do odpowiedzialności konkretnego typu lub przynajmniej określonej instancji. Na przykład umiejętność śpiewania jest modelowana jako taka ortogonalna cecha: można ją zastosować do ptaków, osób itp.

trait Singer{
  def sing { println(" singing … ") }
  //more methods
}

class Bird extends Singer

Tutaj Bird wymieszał wszystkie metody cechy w swojej własnej definicji, tak jakby klasa Bird miała samodzielnie zdefiniowaną metodę sing ().

Jak extends jest również używany do dziedziczenia z superklasy, w przypadku cechy extends jest używana, jeśli żadna superklasa nie jest dziedziczona i tylko do mieszania w pierwszej cechy. Wszystkie poniższe cechy łączą się w używaniu słowa kluczowego with .

class Person
class Actor extends Person with Singer
class Actor extends Singer with Performer

Scala umożliwia mieszanie cechy (tworzenie typu anonimowego ) podczas tworzenia nowej instancji klasy. W przypadku instancji klasy Person nie wszystkie instancje mogą śpiewać. Ta funkcja jest wtedy używana:

class Person{
  def tell {  println (" Human ") }
  //more methods
}

val singingPerson = new Person with Singer
singingPerson.sing

W Swift

Mixin można osiągnąć w Swift za pomocą funkcji języka o nazwie Domyślna implementacja w rozszerzeniu protokołu.

protocol ErrorDisplayable {
    func error(message:String)
}

extension ErrorDisplayable {
    func error(message:String) {
        // Do what it needs to show an error
        //...
        print(message)
    }
}

struct NetworkManager : ErrorDisplayable {
    func onError() {
        error("Please check your internet Connection.")
    }
}

Zobacz też

Bibliografia

Linki zewnętrzne