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
- Zapewnia mechanizm wielokrotnego dziedziczenia , umożliwiając jednej klasie używanie wspólnych funkcji wielu klas, ale bez złożonej semantyki wielokrotnego dziedziczenia.
- 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.
- 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:
- Ada (poprzez rozszerzenie istniejącego otagowanego rekordu o dowolne operacje w typie ogólnym)
- Kobra
- ColdFusion (oparte na klasach przy użyciu dołączeń i na podstawie obiektów poprzez przypisywanie metod z jednego obiektu do drugiego w czasie wykonywania)
- Curl (z Curl RTE)
- D (zwane „szablonowymi mieszankami” ; D zawiera również instrukcję „mixin” , która kompiluje ciągi znaków jako kod).
- Strzałka
- Czynnik
- Groovy
- Delegowanie JavaScript - funkcje jako role (cechy i mieszanki)
- Kotlin
- Mniej
- OCaml
- Perl (poprzez role w rozszerzeniu Moose systemu obiektowego Perl 5)
- „ Cechy ” PHP
- Magik
- MATLAB
- Pyton
- Rakieta ( dokumentacja miksów )
- Raku
- Rubin
- Scala
- XOTcl / TclOO (systemy obiektowe wbudowane w Tcl )
- Sass (język arkuszy stylów)
- Pogawędka
- Vala
- Szybki
- SystemVerilog
- TypeScript ( dokumentacja mixins )
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:
- Przeplata właściwości domeny modelu z domeną implementacji.
- 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ż
- Typ abstrakcyjny
- Wzór dekoratora
- Projektowanie oparte na zasadach
- Cecha , podobna struktura, która nie wymaga liniowej kompozycji
Bibliografia
Linki zewnętrzne
- MixIn w Portland Pattern Repository
- Mieszanki w ActionScript
- Common Lisp Object System: Przegląd przez Richarda P. Gabriela i Linda DeMichiel stanowi dobry wstęp do motywacji do definiowania klas za pomocą funkcji generycznych.