В объектно-ориентированных языках программирования миксин (или mix -in ) [1] [2] [3] [4] — это класс , содержащий методы для использования другими классами, не будучи родительским классом этих других классов. То, как эти другие классы получают доступ к методам миксина, зависит от языка. Миксины иногда описываются как «включенные», а не «унаследованные».
Миксины поощряют повторное использование кода и могут использоваться для избежания неоднозначности наследования, которую может вызвать множественное наследование [5] (« проблема ромба »), или для обхода отсутствия поддержки множественного наследования в языке. Миксин также можно рассматривать как интерфейс с реализованными методами . Этот шаблон является примером реализации принципа инверсии зависимостей .
Миксины впервые появились в объектно-ориентированной системе Flavors компании Symbolics (разработанной Говардом Кэнноном), которая представляла собой подход к объектной ориентации, используемый в Lisp Machine Lisp . Название было навеяно кафе-мороженым Steve's Ice Cream Parlor в Сомервилле, штат Массачусетс: [1] Владелец магазина мороженого предлагал базовый вкус мороженого (ваниль, шоколад и т. д.) и смешивал его с комбинацией дополнительных ингредиентов (орехи, печенье, помадка и т. д.) и называл этот ингредиент « микс-ином », что в то время было его собственным зарегистрированным термином. [2]
Миксины — это языковая концепция, которая позволяет программисту вводить некоторый код в класс . Миксиновое программирование — это стиль разработки программного обеспечения , в котором единицы функциональности создаются в классе, а затем смешиваются с другими классами. [6]
Класс mixin выступает в качестве родительского класса, содержащего желаемую функциональность. Затем подкласс может наследовать или просто повторно использовать эту функциональность, но не как средство специализации. Обычно mixin экспортирует желаемую функциональность в дочерний класс , не создавая жесткого, единственного отношения «является». Здесь лежит важное различие между концепциями mixin и наследования , в том, что дочерний класс все еще может наследовать все функции родительского класса, но семантика о том, что дочерний класс «является видом» родителя, не обязательно должна применяться.
В Simula классы определяются в блоке, в котором атрибуты, методы и инициализация класса определяются вместе; таким образом, все методы, которые могут быть вызваны для класса, определяются вместе, и определение класса является полным.
В Flavors миксин — это класс, из которого другой класс может наследовать определения слотов и методы. Миксин обычно не имеет прямых экземпляров. Поскольку Flavor может наследовать от более чем одного другого Flavor, он может наследовать от одного или нескольких миксинов. Обратите внимание, что оригинальные Flavors не использовали универсальные функции.
В New Flavors (преемнике Flavors) и CLOS методы организованы в « универсальные функции ». Эти универсальные функции — это функции, которые определены в нескольких случаях (методах) с помощью диспетчеризации классов и комбинаций методов.
CLOS и Flavors позволяют методам mixin добавлять поведение к существующим методам: :before
и :after
демонам, вопперам и оберткам в Flavors. CLOS добавил :around
методы и возможность вызывать затененные методы через CALL-NEXT-METHOD
. Так, например, stream-lock-mixin может добавлять блокировку вокруг существующих методов потокового класса. В Flavors можно было бы написать обертку или воппер, а в CLOS можно было бы использовать метод :around
. И CLOS, и Flavors допускают вычисляемое повторное использование через комбинации методов. :before
, :after
а :around
методы являются функцией стандартной комбинации методов. Предоставляются и другие комбинации методов.
Примером является +
комбинация методов, где результирующие значения каждого из применимых методов универсальной функции арифметически складываются для вычисления возвращаемого значения. Это используется, например, с border-mixin для графических объектов. Графический объект может иметь универсальную функцию ширины. border-mixin добавит границу вокруг объекта и имеет метод, вычисляющий его ширину. Новый класс bordered-button
(который является одновременно графическим объектом и использует border
mixin) вычислит его ширину, вызывая все применимые методы ширины — через +
комбинацию методов. Все возвращаемые значения складываются и создают объединенную ширину объекта.
В статье OOPSLA 90 [10] Гилад Браха и Уильям Кук переосмысливают различные механизмы наследования, обнаруженные в Smalltalk, Beta и CLOS, как особые формы наследования миксинов.
Помимо Flavors и CLOS (часть Common Lisp ), вот некоторые языки, которые используют миксины:
Некоторые языки не поддерживают миксины на уровне языка, но могут легко имитировать их, копируя методы из одного объекта в другой во время выполнения, тем самым "заимствуя" методы миксина. Это также возможно в статически типизированных языках, но для этого требуется создание нового объекта с расширенным набором методов.
Другие языки, не поддерживающие миксины, могут поддерживать их обходным путем через другие языковые конструкции. Например, Visual Basic .NET и C# поддерживают добавление методов расширения в интерфейсы, что означает, что любой класс, реализующий интерфейс с определенными методами расширения, будет иметь методы расширения, доступные как псевдочлены.
Common Lisp предоставляет миксины в CLOS (Common Lisp Object System), аналогичные Flavors.
object-width
— это универсальная функция с одним аргументом, которая использует +
комбинацию методов. Эта комбинация определяет, что будут вызваны все применимые методы для универсальной функции, а результаты будут добавлены.
( defgeneric object-width ( object ) ( :method-combination + ))
button
это класс с одним слотом для текста кнопки.
( defclass button () (( text :initform "нажми меня" )))
Для объектов класса button существует метод, который вычисляет ширину на основе длины текста кнопки. +
— это квалификатор метода для комбинации методов с тем же именем.
( defmethod object-width + (( object button )) ( * 10 ( length ( slot-value object 'text ))))
Класс border-mixin
. Наименование — это просто соглашение. Нет суперклассов и слотов.
( defclass border-mixin () ())
Есть метод вычисления ширины границы. Здесь она равна всего 4.
( defmethod object-width + (( object border-mixin )) 4 )
bordered-button
— это класс, наследующий как от , так border-mixin
и от button
.
( defclass bordered-button ( border-mixin button ) ())
Теперь мы можем вычислить ширину кнопки. Вызов object-width
вычисляет 80. Результат — это результат единственного применимого метода: метода object-width
для класса button
.
? ( ширина-объекта ( кнопка создания-экземпляра )) 80
Мы также можем вычислить ширину a bordered-button
. Вызов object-width
computes 84. Результат представляет собой сумму результатов двух применимых методов: метода object-width
для класса button
и метода object-width
для класса border-mixin
.
? ( ширина-объекта ( сделать-экземпляр ' кнопка-окантовка )) 84
В Python пример концепции mixin можно найти в SocketServer
модуле [17] , который имеет как UDPServer
класс, так и TCPServer
класс. Они действуют как серверы для серверов сокетов UDP и TCP соответственно. Кроме того, есть два mixin-класса: ForkingMixIn
и ThreadingMixIn
. Обычно все новые соединения обрабатываются в рамках одного процесса. При расширении TCPServer
с помощью ThreadingMixIn
следующим образом:
класс ThreadingTCPServer ( ThreadingMixIn , TCPServer ): проход
класс ThreadingMixIn
добавляет функциональность к TCP-серверу, так что каждое новое соединение создает новый поток . Используя тот же метод, ThreadingUDPServer
можно создать без необходимости дублировать код в ThreadingMixIn
. В качестве альтернативы использование ForkingMixIn
приведет к разветвлению процесса для каждого нового соединения. Очевидно, что функциональность создания нового потока или разветвления процесса не очень полезна в качестве отдельного класса.
В этом примере использования миксины предоставляют альтернативную базовую функциональность, не влияя на функциональность сокет-сервера.
Большая часть мира Ruby основана на миксинах через Modules
. Концепция миксинов реализована в Ruby ключевым словом include
, которому мы передаем имя модуля в качестве параметра .
Пример:
class Student include Comparable # Класс Student наследует модуль Comparable с помощью ключевого слова 'include' attr_accessor :name , :score def initialize ( имя , счет ) @имя = имя @счет = счет конец # Включение модуля Comparable требует, чтобы реализующий класс определил оператор сравнения <=> # Вот оператор сравнения. Мы сравниваем 2 экземпляра студентов на основе их оценок. def <=> ( другое ) @score <=> другое . оценка конец # А вот и хорошая часть - я получаю доступ к <, <=, >,>= и другим методам Comparable Interface бесплатно. конецs1 = Студент . новый ( "Питер" , 100 ) s2 = Студент . новый ( "Джейсон" , 90 ) s1 > s2 #истина s1 <= s2 #ложь
Объект -буквальный и extend
подход
Технически возможно добавить поведение к объекту, привязав функции к ключам в объекте. Однако это отсутствие разделения между состоянием и поведением имеет недостатки:
Функция расширения используется для смешивания поведения в: [19]
«использовать строго» ;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 ; } } ; // Функция расширения const extend = ( obj , mixin ) => { Object . keys ( mixin ). forEach ( key => obj [ key ] = mixin [ key ]); return obj ; }; const sam = new Halfling ( 'Сэм' , 'Лоури' ); const frodo = new Halfling ( 'Фрида' , 'Бэггс' ); // Примешиваем другие методы extend ( Halfling . prototype , mixin ); console.log ( sam.fullName ( )); // Сэм Лоури console.log ( frodo.fullName ( ) ) ; // Фрида Бэггс sam.rename ( 'Сэмвайс' , ' Гэмджи ' ) ; frodo.rename ( 'Фродо' , 'Бэггинс' ) ; console.log ( sam.fullName ( ) ); // Сэм Гэмджи console.log ( frodo.fullName () ) ; // Фродо Бэггинс
Миксин с использованием Object.assign()
«использовать строго» ;// Создание объекта const obj1 = { name : 'Марк Аврелий' , city : 'Рим' , born : '121-04-26' }; // Миксин 1 const mix1 = { toString ( ) { return ` $ { this.name } родился в ${ this.city } в ${ this.born } ` ; }, age ( ) { const year = new Date (). getFullYear ( ) ; const born = new Date ( this.born ) .getFullYear ( ) ; return year - born ; } } ; // Миксин 2 const mix2 = { toString ( ) { return ` $ { this.name } - $ { this.city } - $ { this.born } ` ; } } ; // Добавляем методы из миксинов к объекту с помощью Object.assign( ) Object.assign ( obj1 , mix1 , mix2 ) ; console.log ( obj1.toString ( ) ); // Марк Аврелий - Рим - 121-04-26 console.log ( `Его возраст $ { obj1.age () } на сегодняшний день` ) ; // Его возраст 1897 на сегодняшний день
Подход Flight-Mixin, основанный на чистой функции и делегировании
Несмотря на то, что описанный первым подход наиболее распространен, следующий подход ближе к тому, что по сути предлагает ядро языка JavaScript — делегирование .
Два шаблона на основе функциональных объектов уже справляются с этой задачей без необходимости сторонней реализации extend
.
«использовать строго» ;// Реализация const EnumerableFirstLast = ( function () { // шаблон модуля на основе функции. const first = function () { return this [ 0 ]; }, last = function () { return this [ this . length - 1 ]; }; return function () { // механика Flight-Mixin на основе функции ... this . first = first ; // ... ссылка на ... this . last = last ; // ... общий код. }; }()); // Применение - явное делегирование: // применение [первого] и [последнего] перечислимого поведения к [прототипу] [Array]. EnumerableFirstLast . call ( Array . prototype );// Теперь вы можете сделать: const a = [ 1 , 2 , 3 ]; a . first (); // 1 a . last (); // 3
В языке веб-контента Curl используется множественное наследование, поскольку классы без экземпляров могут реализовывать методы. Обычные миксины включают все скины ControlUI
, наследуемые от SkinnableControlUI
, объекты делегатов пользовательского интерфейса, которым требуются выпадающие меню, наследуемые от StandardBaseDropdownUI, и такие явно именованные классы миксинов, как FontGraphicMixin
, FontVisualMixin
и NumericAxisMixin-of
class. В версии 7.0 добавлен доступ к библиотеке, так что миксины не должны находиться в одном пакете или быть общедоступными абстрактными. Конструкторы Curl — это фабрики, которые облегчают использование множественного наследования без явного объявления интерфейсов или миксинов. [ необходима цитата ]
Java 8 представляет новую функцию в виде методов по умолчанию для интерфейсов. [20] По сути, это позволяет определить метод в интерфейсе с применением в сценарии, когда новый метод должен быть добавлен к интерфейсу после завершения настройки программирования класса интерфейса. Добавить новую функцию к интерфейсу означает реализовать метод в каждом классе, который использует интерфейс. Методы по умолчанию помогают в этом случае, поскольку они могут быть введены в интерфейс в любое время и имеют реализованную структуру, которая затем используется связанными классами. Следовательно, методы по умолчанию добавляют возможность применять концепцию миксина в Java.
Интерфейсы в сочетании с аспектно-ориентированным программированием также могут создавать полноценные миксины в языках, которые поддерживают такие возможности, таких как C# или Java. Кроме того, благодаря использованию шаблона интерфейса маркера , обобщенного программирования и методов расширения, C# 3.0 имеет возможность имитировать миксины. С Dart 2.7 и C# 3.0 появились методы расширения, которые можно применять не только к классам, но и к интерфейсам. Методы расширения предоставляют дополнительную функциональность существующему классу без изменения класса. Затем становится возможным создать статический вспомогательный класс для определенной функциональности, который определяет методы расширения. Поскольку классы реализуют интерфейс (даже если фактический интерфейс не содержит никаких методов или свойств для реализации), он также подхватит все методы расширения. [3] [4] [21] C# 8.0 добавляет функцию методов интерфейса по умолчанию. [22] [23]
ECMAScript (в большинстве случаев реализованный как JavaScript) не нуждается в имитации композиции объектов путем пошагового копирования полей из одного объекта в другой. Он изначально [24] поддерживает композицию объектов на основе Trait и mixin [25] [26] через функциональные объекты, которые реализуют дополнительное поведение, а затем делегируются через call
или apply
объектам, которым нужна такая новая функциональность.
Scala имеет богатую систему типов, и черты являются ее частью, которая помогает реализовать поведение миксина. Как видно из их названия, черты обычно используются для представления отдельной функции или аспекта, который обычно ортогонален ответственности конкретного типа или, по крайней мере, определенного экземпляра. [27] Например, способность петь моделируется как такая ортогональная функция: ее можно применять к птицам, людям и т. д.
черта Певец { def sing { println ( " пение … " ) } //еще методы } класс Птица расширяет Певицу
Здесь Bird смешал все методы признака в своем собственном определении, как если бы класс Bird определил метод sing() самостоятельно.
As extends
также используется для наследования от суперкласса, в случае, если черта extends
используется, если суперкласс не унаследован, и только для примеси в первой черте. Все последующие черты смешиваются с использованием ключевого слова with
.
класс Person класс Actor расширяет Person с Singer класс Actor расширяет Singer с Performer
Scala позволяет смешивать черты (создавать анонимный тип ) при создании нового экземпляра класса. В случае экземпляра класса Person не все экземпляры могут петь. Эта функция используется тогда:
class Person { def tell { println ( " Human " ) } //еще методы } val singingPerson = новый Person с Singer singingPerson . sing
Rust широко использует миксины через черты . Черты, как и в Scala, позволяют пользователям реализовывать поведения для определенного типа. Они также используются для дженериков и динамической диспетчеризации , позволяя типам, реализующим черту, использоваться взаимозаменяемо статически или динамически во время выполнения. [28]
// Позволяет типам «говорить» черта Speak { fn speak (); // Rust позволяет разработчикам определять реализации по умолчанию для функций, определенных в трейтах fn greet () { println! ( "Привет!" ) } } структура Собака ;impl Speak for Dog { fn speak () { println! ( "Гав-гав" ); } } структура Робот ;impl Speak for Robot { fn speak () { println! ( "Бип-бип-буп-буп" ); } // Здесь мы переопределяем определение Speak::greet для Robot fn greet () { println! ( "Robot says howdy!" ) } }
Миксин можно реализовать в Swift, используя языковую функцию, которая называется «Реализация по умолчанию» в расширении протокола.
протокол ErrorDisplayable { ошибка функции ( сообщение : строка )}расширение ErrorDisplayable { ошибка функции ( сообщение : строка ) { // Сделайте то, что нужно, чтобы показать ошибку //... печать ( сообщение ) }}структура NetworkManager : ErrorDisplayable { функция onError () { ошибка ( «Проверьте подключение к Интернету». ) }}
Основные возможности языка Factor: … Система объектов с наследованием, обобщенными функциями, диспетчеризацией предикатов и
миксинами