В объектно-ориентированных языках программирования примесь (или примесь ) [1] [2] [3] [4] — это класс , который содержит методы для использования другими классами, но не обязательно должен быть родительским классом для этих других классов. То, как эти другие классы получают доступ к методам примеси, зависит от языка. Миксины иногда называют «включенными», а не «унаследованными».
Миксины поощряют повторное использование кода и могут использоваться, чтобы избежать неоднозначности наследования, которую может вызвать множественное наследование [5] (« проблема ромба »), или для обхода отсутствия поддержки множественного наследования в языке. Миксин также можно рассматривать как интерфейс с реализованными методами . Этот шаблон является примером реализации принципа инверсии зависимостей .
Миксины впервые появились в объектно-ориентированной системе Flavors компании Symbolics (разработанной Говардом Кэнноном), которая представляла собой подход к объектной ориентации, используемый в Lisp Machine Lisp . Название было вдохновлено кафе-мороженым «Стивс» в Сомервилле, штат Массачусетс: [1] Владелец магазина мороженого предлагал базовый вкус мороженого (ванильное, шоколадное и т. д.) и смешивал его с дополнительными ингредиентами (орехи, печенье, помадка и т. д.) и назвал этот продукт « примесью », что в то время было его собственным товарным знаком. [2]
Миксины — это языковая концепция, которая позволяет программисту вставлять некоторый код в класс . Миксин-программирование — это стиль разработки программного обеспечения , при котором функциональные единицы создаются в классе, а затем смешиваются с другими классами. [6]
Класс миксина действует как родительский класс, содержащий желаемую функциональность. Подкласс может затем наследовать или просто повторно использовать эту функциональность, но не в качестве средства специализации. Обычно примесь экспортирует желаемую функциональность в дочерний класс , не создавая жестких единых отношений «является». В этом заключается важное различие между концепциями миксинов и наследования , заключающееся в том, что дочерний класс по-прежнему может наследовать все функции родительского класса, но не обязательно применять семантику о том, что дочерний элемент «является своего рода» родительским.
В Simula классы определяются в блоке, в котором атрибуты, методы и инициализация класса определяются вместе; таким образом, все методы, которые можно вызвать в классе, определены вместе, и определение класса завершено.
Во Flavors миксин — это класс, от которого другой класс может наследовать определения слотов и методы. Миксин обычно не имеет прямых экземпляров. Поскольку аромат может наследовать более чем от одного другого аромата, он может наследовать от одного или нескольких примесей. Обратите внимание, что оригинальные Flavors не использовали универсальные функции.
В New Flavors (преемнике Flavors) и CLOS методы организованы в « универсальные функции ». Эти общие функции представляют собой функции, которые определяются в нескольких случаях (методах) с помощью комбинаций диспетчеризации классов и методов.
CLOS и Flavors позволяют методам примеси добавлять поведение к существующим методам: :before
а также :after
демонам, вопперам и оберткам во Flavors. В CLOS добавлены :around
методы и возможность вызывать скрытые методы через CALL-NEXT-METHOD
. Так, например, миксин-блокировка потока может добавить блокировку существующих методов класса потока. В Flavors можно было бы написать обертку или громадину, а в CLOS можно было бы использовать :around
метод. И CLOS, и Flavors допускают повторное использование с помощью комбинаций методов. :before
и :after
методы :around
являются особенностью стандартной комбинации методов. Предусмотрены другие комбинации методов.
Примером является +
комбинация методов, в которой результирующие значения каждого из применимых методов универсальной функции арифметически складываются для вычисления возвращаемого значения. Это используется, например, с примесью border-mixin для графических объектов. Графический объект может иметь общую функцию ширины. Border-mixin добавляет рамку вокруг объекта и имеет метод вычисления ее ширины. Новый класс bordered-button
(который одновременно является графическим объектом и использует примесь border
) будет вычислять свою ширину, вызывая все применимые методы ширины — через +
комбинацию методов. Все возвращаемые значения складываются и создают общую ширину объекта.
В статье OOPSLA 90 [10] Гилад Брача и Уильям Кук по-новому интерпретируют различные механизмы наследования, обнаруженные в Smalltalk, Beta и CLOS, как особые формы наследования миксинов.
Помимо Flavors и CLOS (часть Common Lisp ), некоторые языки, использующие примеси:
Некоторые языки не поддерживают примеси на уровне языка, но могут легко имитировать их, копируя методы из одного объекта в другой во время выполнения, тем самым «заимствуя» методы примеси. Это также возможно со статически типизированными языками, но для этого требуется создание нового объекта с расширенным набором методов.
Другие языки, не поддерживающие примеси, могут поддерживать их окольным путем через другие языковые конструкции. Например, Visual Basic .NET и C# поддерживают добавление методов расширения к интерфейсам. Это означает, что любой класс, реализующий интерфейс с определенными методами расширения, будет иметь методы расширения, доступные как псевдочлены.
Common Lisp предоставляет примеси в CLOS (объектная система Common Lisp), аналогичные Flavors.
object-width
— это универсальная функция с одним аргументом, использующая +
комбинацию методов. Эта комбинация определяет, что будут вызваны все применимые методы для универсальной функции и будут добавлены результаты.
( defgeneric object-width ( object ) ( :method-combination + ))
button
— это класс с одним слотом для текста кнопки.
( кнопка defclass () (( текст :initform "нажми на меня" )))
Для объектов класса button существует метод, который вычисляет ширину на основе длины текста кнопки. +
— квалификатор метода для одноименной комбинации методов.
( defmethod object-width + (( кнопка объекта )) ( * 10 ( длина ( объект значения слота 'текст ))))
Класс 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
.
? ( ширина объекта ( кнопка make-instance ' )) 80
Мы также можем вычислить ширину файла bordered-button
. Вызов object-width
вычисляет 84. Результатом является сумма результатов двух применимых методов: метода object-width
класса button
и метода object-width
класса border-mixin
.
? ( ширина объекта ( make-instance 'bordered-button )) 84
В Python пример концепции миксина можно найти в SocketServer
модуле [17] , который имеет как UDPServer
класс, так и TCPServer
класс. Они действуют как серверы для серверов сокетов UDP и TCP соответственно. Кроме того, есть два класса миксинов: ForkingMixIn
и ThreadingMixIn
. Обычно все новые соединения обрабатываются в рамках одного и того же процесса. Расширяя TCPServer
следующим образом ThreadingMixIn
:
класс ThreadingTCPServer ( ThreadingMixIn , TCPServer ): пройти
этот ThreadingMixIn
класс добавляет функциональность TCP-серверу, так что каждое новое соединение создает новый поток . Используя тот же метод, ThreadingUDPServer
можно создать файл без необходимости дублировать код в ThreadingMixIn
. Альтернативно, использование ForkingMixIn
приведет к разветвлению процесса для каждого нового соединения. Очевидно, что возможность создания нового потока или разветвления процесса не очень полезна в качестве отдельного класса.
В этом примере использования примеси предоставляют альтернативную базовую функциональность, не затрагивая функциональность сервера сокетов.
Большая часть мира Ruby основана на миксинах через Modules
. Концепция миксинов реализована в Ruby с помощью ключевого слова, include
которому мы передаем имя модуля в качестве параметра .
Пример:
class Student включает Comparable # Класс Student наследует модуль Comparable, используя ключевое слово 'include' attr_accessor :name , :score def инициализировать ( имя , оценка ) @name = имя @score = конец оценки # Включение модуля Comparable требует, чтобы реализующий класс определил оператор сравнения <=> # Вот оператор сравнения. Мы сравниваем два примера учащихся на основе их оценок. def <=> ( другое ) @score <=> другое . конец счета # Вот что хорошо: я получаю бесплатный доступ к <, <=, >,>= и другим методам Comparable Interface. конецs1 = Студент . новый ( «Питер» , 100 ) s2 = Студент . новый ( «Джейсон» , 90 ) s1 > s2 #true s1 <= s2 #false
Объектно -литеральный подход и extend
подход
Технически возможно добавить поведение к объекту, привязав функции к ключам в объекте. Однако отсутствие разделения между состоянием и поведением имеет недостатки:
Функция расширения используется для смешивания поведения: [19]
'используйте строгий' ;const Halfling = функция ( fName , lName ) { this . Имя = имя_фамилии ; этот . Фамилия = Имя Имя ; }; const mixin = { fullName () { верните это . имя + '' + это . фамилия ; }, переименуйте ( первый , последний ) { this . ПервоеИмя = первый ; этот . Фамилия = последний ; вернуть это ; } }; // Функция расширения const Extend = ( obj , mixin ) => { Object . ключи ( миксин ). forEach ( ключ => объект [ ключ ] = миксин [ ключ ]); вернуть объект ; }; const sam = новый полурослик ( «Сэм» , «Лоури» ); const frodo = новый халфлинг ( «Фрида» , «Бэггс» ); // Подмешиваем другие методы расширения ( Halfling.prototype , mixin ) ; консоль . журнал ( Сэм . FullName ()); // Консоль Сэма Лоури . журнал ( frodo.fullName ( )) ; // Фрида Бэггс Сэм . переименовать ( 'Samwise' , 'Gamgee' ); Фродо . переименовать ( «Фродо» , «Бэггинс» ); консоль . журнал ( Сэм . FullName ()); // Консоль Samwise Gamgee . журнал ( frodo.fullName ( )) ; // Фродо Бэггинс
Миксин с использованием Object.assign()
'используйте строгий' ;// Создание объекта const obj1 = { name : 'Марк Аврелий' , город : 'Рим' , родился : '121-04-26' }; // Mixin 1 const mix1 = { toString () { return ` $ { this . name } родился в ${ this . город } в ${ this . родился } ` ; }, возраст () { const год = новая дата (). получитьПолныйГод (); constborn = новая дата ( this.born ) . _ _ получитьПолныйГод (); возврат года рождения ; _ } }; // Mixin 2 const mix2 = { toString () { return ` ${ this . имя } - ${ это . город } - ${ это . родился } ` ; } }; // Добавляем методы из миксинов в объект с помощью Object.assign() Object . назначить ( obj1 , mix1 , mix2 ); консоль . журнал ( obj1 . toString ()); // Марк Аврелий — Рим — консоль 121-04-26 . 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 ; // ... общий код. } ; } ()); // Приложение — явное делегирование: // применение [first] и [last] перечислимого поведения к [prototype] массива. EnumerableFirstLast . вызов ( Array.prototype ) ; _// Теперь вы можете сделать: const a = [ 1 , 2 , 3 ]; а . первый (); // 1 а . последний (); // 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] поддерживает композицию объектов на основе признаков и миксинов [25] [26] через функциональные объекты, которые реализуют дополнительное поведение, а затем делегируются через call
или apply
к объектам, которые нуждаются в такой новой функциональности.
Scala имеет богатую систему типов, и Traits являются ее частью, которая помогает реализовать поведение примесей. Как следует из названия, Черты обычно используются для обозначения отдельной особенности или аспекта, который обычно ортогонален ответственности конкретного типа или, по крайней мере, определенного экземпляра. [27] Например, умение петь моделируется как такой ортогональный признак: его можно применить к Птицам, Человекам и т. д.
типаж Singer { def Sing { println ( " пение ... " ) } // дополнительные методы } класс Bird расширяет Singer
Здесь Bird смешал все методы признака в свое собственное определение, как если бы класс Bird определил метод Sing() самостоятельно.
Как extends
также используется для наследования от суперкласса, в случае признака extends
используется, если суперкласс не унаследован, и только для примеси в первом признаке. Все следующие признаки смешиваются с использованием ключевого слова with
.
класс Person класс Actor расширяет Person с помощью Singer класс Actor расширяет Singer с помощью Performer
Scala позволяет смешивать черты (создавать анонимный тип ) при создании нового экземпляра класса. В случае экземпляра класса Person не все экземпляры могут петь. Эта функция используется тогда:
class Person { def Tell { println ( "Human" ) } //больше методов } val SingingPerson = новый человек с Singer SingingPerson . петь
Rust широко использует миксины через трейты . Трейты, как и в Scala, позволяют пользователям реализовывать поведение для определенного типа. Они также используются для дженериков и динамической диспетчеризации , которые позволяют взаимозаменяемо использовать типы с одинаковыми характеристиками статически или динамически во время выполнения соответственно. [28]
// Позволяет типам "говорить" типаж Speak { fn talk (); // Rust позволяет разработчикам определять реализации по умолчанию для функций, определенных в типажах fn Greeting () { println! ( "Привет!" ) } } структура Собака ;impl Speak for Dog { fn talk () { println! ( "Гав гав" ); } } построить робота ;impl Speak for Robot { fn talk () { println! ( «Бип-бип-буп-буп» ); } // Здесь мы переопределяем определение Speak::greet для робота fn Greeting () { println! ( « Робот говорит привет!» ) } }
Миксин можно реализовать в Swift, используя языковую функцию, называемую реализацией по умолчанию в расширении протокола.
протокол ErrorDisplayable { ошибка функции ( сообщение : строка )}расширение ErrorDisplayable { ошибка функции ( сообщение : строка ) { // Делаем то, что нужно, чтобы показать ошибку //... распечатать ( сообщение ) }}структура NetworkManager : ErrorDisplayable { функция onError () { ошибка ( «Пожалуйста, проверьте подключение к Интернету». ) }}
Основные особенности языка Factor:… Объектная система с наследованием, универсальными функциями, диспетчеризацией предикатов и
миксинами.