En los lenguajes de programación orientados a objetos , un mixin (o mix-in ) [1] [2] [3] [4] es una clase que contiene métodos para que los usen otras clases sin tener que ser la clase padre de esas otras clases. La forma en que esas otras clases obtienen acceso a los métodos del mixin depende del lenguaje. A veces se describe a los mixins como "incluidos" en lugar de "heredados".
Los mixins fomentan la reutilización de código y se pueden utilizar para evitar la ambigüedad de herencia que puede causar la herencia múltiple [5] (el " problema del diamante "), o para solucionar la falta de soporte para la herencia múltiple en un lenguaje. Un mixin también se puede ver como una interfaz con métodos implementados . Este patrón es un ejemplo de aplicación del principio de inversión de dependencias .
Los mixins aparecieron por primera vez en el sistema Flavors orientado a objetos de Symbolics (desarrollado por Howard Cannon), que era un enfoque de la orientación a objetos utilizado en Lisp Machine Lisp . El nombre se inspiró en la heladería Steve's Ice Cream Parlor en Somerville, Massachusetts: [1] El dueño de la heladería ofrecía un sabor básico de helado (vainilla, chocolate, etc.) y lo mezclaba con una combinación de elementos adicionales (nueces, galletas, dulce de azúcar, etc.) y llamaba al elemento " mix-in ", su propio término registrado en ese momento. [2]
Los mixins son un concepto de lenguaje que permite a un programador inyectar código en una clase . La programación mixin es un estilo de desarrollo de software en el que se crean unidades de funcionalidad en una clase y luego se mezclan con otras clases. [6]
Una clase mixin actúa como la clase padre, que contiene la funcionalidad deseada. Una subclase puede heredar o simplemente reutilizar esta funcionalidad, pero no como un medio de especialización. Normalmente, el mixin exportará la funcionalidad deseada a una clase hija , sin crear una relación rígida y única de "es un". Aquí radica la diferencia importante entre los conceptos de mixins y herencia , en que la clase hija todavía puede heredar todas las características de la clase padre, pero no es necesario aplicar la semántica sobre que la hija "es un tipo de" la clase padre.
En Simula , las clases se definen en un bloque en el que los atributos, métodos y la inicialización de la clase se definen juntos; por lo tanto, todos los métodos que se pueden invocar en una clase se definen juntos y la definición de la clase está completa.
En Flavors , un mixin es una clase de la que otra clase puede heredar definiciones y métodos de slot. El mixin normalmente no tiene instancias directas. Dado que un Flavor puede heredar de más de otro Flavor, puede heredar de uno o más mixins. Tenga en cuenta que los Flavors originales no usaban funciones genéricas.
En New Flavors (un sucesor de Flavors) y CLOS , los métodos se organizan en " funciones genéricas ". Estas funciones genéricas son funciones que se definen en múltiples casos (métodos) mediante el envío de clases y combinaciones de métodos.
CLOS y Flavors permiten que los métodos mixin agreguen comportamiento a los métodos existentes: :before
y :after
daemons, whoppers y wrappers en Flavors. CLOS agregó :around
métodos y la capacidad de llamar a métodos shadowed a través de CALL-NEXT-METHOD
. Entonces, por ejemplo, un stream-lock-mixin puede agregar bloqueos alrededor de los métodos existentes de una clase stream. En Flavors uno escribiría un wrapper o un whopper y en CLOS uno usaría un :around
método. Tanto CLOS como Flavors permiten la reutilización computada a través de combinaciones de métodos. :before
, :after
y :around
los métodos son una característica de la combinación de métodos estándar. Se proporcionan otras combinaciones de métodos.
Un ejemplo es la +
combinación de métodos, donde los valores resultantes de cada uno de los métodos aplicables de una función genérica se suman aritméticamente para calcular el valor de retorno. Esto se utiliza, por ejemplo, con el border-mixin para objetos gráficos. Un objeto gráfico puede tener una función de ancho genérica. El border-mixin agregaría un borde alrededor de un objeto y tiene un método que calcula su ancho. Una nueva clase bordered-button
(que es a la vez un objeto gráfico y utiliza el border
mixin) calcularía su ancho llamando a todos los métodos de ancho aplicables, a través de la +
combinación de métodos. Se suman todos los valores de retorno y se crea el ancho combinado del objeto.
En un artículo de OOPSLA 90, [10] Gilad Bracha y William Cook reinterpretan diferentes mecanismos de herencia encontrados en Smalltalk, Beta y CLOS como formas especiales de una herencia mixin.
Además de Flavors y CLOS (una parte de Common Lisp ), algunos lenguajes que usan mixins son:
Algunos lenguajes no admiten mixins a nivel de lenguaje, pero pueden imitarlos fácilmente copiando métodos de un objeto a otro en tiempo de ejecución, "tomando prestados" así los métodos del mixin. Esto también es posible con lenguajes de tipado estático , pero requiere construir un nuevo objeto con el conjunto extendido de métodos.
Otros lenguajes que no admiten mixins pueden admitirlos de forma indirecta a través de otras construcciones del lenguaje. Por ejemplo, Visual Basic .NET y C# admiten la incorporación de métodos de extensión en interfaces, lo que significa que cualquier clase que implemente una interfaz con métodos de extensión definidos tendrá los métodos de extensión disponibles como pseudomiembros.
Common Lisp proporciona mixins en CLOS (Common Lisp Object System) similares a Flavors.
object-width
es una función genérica con un argumento que utiliza la +
combinación de métodos. Esta combinación determina que se llamarán todos los métodos aplicables para una función genérica y se sumarán los resultados.
( defgeneric object-width ( objeto ) ( :combinación-de-métodos + ))
button
es una clase con un espacio para el texto del botón.
( defclass botón () (( texto :initform "haz clic en mí" )))
Hay un método para los objetos de la clase botón que calcula el ancho en función de la longitud del texto del botón. +
es el calificador del método para la combinación de métodos del mismo nombre.
( defmethod objeto-ancho + (( objeto botón )) ( * 10 ( longitud ( valor-ranura objeto 'texto ))))
Una border-mixin
clase. El nombre es solo una convención. No hay superclases ni espacios.
( defclass borde-mix () ())
Hay un método para calcular el ancho del borde. Aquí es solo 4.
( defmethod objeto-ancho + (( objeto borde-mix )) 4 )
bordered-button
es una clase que hereda tanto de border-mixin
como button
.
( defclass botón con borde ( botón de mezcla de borde ) ())
Ahora podemos calcular el ancho de un botón. La llamada object-width
calcula 80. El resultado es el resultado del único método aplicable: el método object-width
de la clase button
.
? ( objeto-ancho ( crear-instancia 'botón )) 80
También podemos calcular el ancho de un bordered-button
. Al llamar object-width
se calcula 84. El resultado es la suma de los resultados de los dos métodos aplicables: el método object-width
para la clase button
y el método object-width
para la clase border-mixin
.
? ( objeto-ancho ( crear-instancia 'botón-bordeado )) 84
En Python , un ejemplo del concepto de mixin se encuentra en el SocketServer
módulo [17] , que tiene tanto una UDPServer
clase como una TCPServer
clase. Actúan como servidores para servidores de sockets UDP y TCP , respectivamente. Además, hay dos clases de mixin: ForkingMixIn
y ThreadingMixIn
. Normalmente, todas las conexiones nuevas se manejan dentro del mismo proceso. Al extender TCPServer
con lo ThreadingMixIn
siguiente:
clase ThreadingTCPServer ( ThreadingMixIn , TCPServer ): pasar
La ThreadingMixIn
clase agrega funcionalidad al servidor TCP de modo que cada nueva conexión crea un nuevo hilo . Con el mismo método, ThreadingUDPServer
se puede crear un sin tener que duplicar el código en ThreadingMixIn
. Alternativamente, el uso de ForkingMixIn
haría que el proceso se bifurcara para cada nueva conexión. Claramente, la funcionalidad para crear un nuevo hilo o bifurcar un proceso no es demasiado útil como una clase independiente.
En este ejemplo de uso, los mixins proporcionan una funcionalidad subyacente alternativa sin afectar la funcionalidad como servidor de socket.
La mayor parte del mundo Ruby se basa en mixins a través de Modules
. El concepto de mixins se implementa en Ruby mediante la palabra clave include
a la que pasamos el nombre del módulo como parámetro .
Ejemplo:
clase Estudiante incluye Comparable # La clase Estudiante hereda el módulo Comparable usando la palabra clave 'include' attr_accessor :name , :score def initialize ( nombre , puntuación ) @nombre = nombre @puntuación = puntuación fin # La inclusión del módulo Comparable requiere que la clase implementadora defina el operador de comparación <=> # Aquí está el operador de comparación. Comparamos 2 instancias de estudiantes en función de sus puntuaciones. def <=> ( otro ) @score <=> otro . puntuación fin # Aquí viene la parte buena: obtengo acceso a <, <=, >,>= y otros métodos de la Interfaz Comparable de forma gratuita. fins1 = Estudiante . nuevo ( "Peter" , 100 ) s2 = Estudiante . nuevo ( "Jason" , 90 ) s1 > s2 #verdadero s1 <= s2 #falso
El objeto-literal y extend
el enfoque
Técnicamente es posible agregarle comportamiento a un objeto vinculando funciones a claves del objeto. Sin embargo, esta falta de separación entre estado y comportamiento tiene desventajas:
Se utiliza una función de extensión para mezclar el comportamiento en: [19]
'uso estricto' ;const Halfling = function ( fName , lName ) { this .firstName = fName ; this .apellido = lName ; } ; const mixin = { fullName ( ) { devuelve este.firstName + ' ' + este.apellido ; } , rename ( nombre , apellido ) { este.firstName = nombre ; este.apellido = apellido ; devuelve este ; } } ; // Una función de extensión const extend = ( obj , mixin ) => { Object . keys ( mixin ). forEach ( key => obj [ key ] = mixin [ key ]); return obj ; }; const sam = nuevo Halfling ( 'Sam' , 'Loawry' ); const frodo = nuevo Halfling ( 'Freeda' , 'Baggs' ); // Mezclando los otros métodos extend ( Halfling . prototipo , mixin ); console.log ( sam.nombrecompleto ( ) ) ; // Sam Loawry console.log ( frodo.nombrecompleto () ) ; // Freeda Baggs sam . rename ( 'Samwise' , 'Gamgi' ); frodo . rename ( 'Frodo' , 'Bolsón' ); console.log ( sam.fullName ( ) ); // Samwise Gamgee console.log ( frodo.fullName ( ) ) ; // Frodo Baggins
Combinación con el uso de Object.assign()
'uso estricto' ;// Creando un objeto const obj1 = { nombre : 'Marco Aurelio' , ciudad : 'Roma' , nacimiento : '121-04-26' }; // Mixin 1 const mix1 = { toString () { return ` ${ this . name } nació en ${ this . city } en ${ this . born } ` ; }, edad () { const año = new Date (). getFullYear (); const nacido = new Date ( this . born ). getFullYear (); return año - nacido ; } }; // Mixin 2 const mix2 = { toString () { return ` ${ this . name } - ${ this . city } - ${ this . born } ` ; } }; // Agregar los métodos de mixins al objeto usando Object.assign() Object .assign ( obj1 , mix1 , mix2 ); console.log ( obj1.toString ()); // Marco Aurelio - Roma - 121-04-26 console.log ( ` Su edad es ${ obj1.age () } a día de hoy` ) ; //Su edad es 1897 a día de hoy
El enfoque Flight-Mixin basado en funciones puras y delegación
Aunque el primer enfoque descrito es el más extendido, el siguiente se acerca más a lo que ofrece fundamentalmente el núcleo del lenguaje JavaScript: la delegación .
Dos patrones basados en objetos de función ya hacen el trabajo sin necesidad de una implementación de terceros extend
.
'uso estricto' ;// Implementación const EnumerableFirstLast = ( function () { // Patrón de módulo basado en funciones. const first = function () { return this [ 0 ]; }, last = function () { return this [ this . length - 1 ]; }; return function () { // Mecánica basada en funciones de Flight-Mixin ... this . first = first ; // ... haciendo referencia a ... this . last = last ; // ... código compartido. }; }()); // Aplicación - delegación explícita: // aplicando el comportamiento enumerable [primero] y [último] al [prototipo] de [Array]. EnumerableFirstLast . call ( Array . prototipo );// Ahora puedes hacer: const a = [ 1 , 2 , 3 ]; a . first (); // 1 a . last (); // 3
En el lenguaje de contenido web Curl , se utiliza herencia múltiple, ya que las clases sin instancias pueden implementar métodos. Los mixins comunes incluyen todos ControlUI
los s que se pueden personalizar y que heredan de SkinnableControlUI
, objetos delegados de interfaz de usuario que requieren menús desplegables que heredan de StandardBaseDropdownUI y clases de mixins nombradas explícitamente como FontGraphicMixin
, FontVisualMixin
y NumericAxisMixin-of
class. La versión 7.0 agregó acceso a bibliotecas para que los mixins no necesiten estar en el mismo paquete o ser abstractos públicos. Los constructores de Curl son fábricas que facilitan el uso de herencia múltiple sin una declaración explícita de interfaces o mixins. [ cita requerida ]
Java 8 introduce una nueva característica en forma de métodos predeterminados para interfaces. [20] Básicamente, permite definir un método en una interfaz con la aplicación en el escenario en el que se debe agregar un nuevo método a una interfaz después de que se haya realizado la configuración de programación de la clase de interfaz. Agregar una nueva función a la interfaz significa implementar el método en cada clase que usa la interfaz. Los métodos predeterminados ayudan en este caso, ya que se pueden introducir en una interfaz en cualquier momento y tienen una estructura implementada que luego es utilizada por las clases asociadas. Por lo tanto, los métodos predeterminados agregan la capacidad de aplicar el concepto de mixin en Java.
Las interfaces combinadas con la programación orientada a aspectos también pueden producir mixins completos en lenguajes que admiten dichas características, como C# o Java. Además, mediante el uso del patrón de interfaz de marcador , la programación genérica y los métodos de extensión, C# 3.0 tiene la capacidad de imitar mixins. Con Dart 2.7 y C# 3.0 llegó la introducción de métodos de extensión que se pueden aplicar, no solo a clases, sino también a interfaces. Los métodos de extensión proporcionan funcionalidad adicional en una clase existente sin modificar la clase. Luego, se hace posible crear una clase auxiliar estática para una funcionalidad específica que define los métodos de extensión. Debido a que las clases implementan la interfaz (incluso si la interfaz real no contiene ningún método o propiedad para implementar), también recogerá todos los métodos de extensión. [3] [4] [21] C# 8.0 agrega la característica de métodos de interfaz predeterminados. [22] [23]
ECMAScript (en la mayoría de los casos implementado como JavaScript) no necesita imitar la composición de objetos copiando campos paso a paso de un objeto a otro. De forma nativa [24] admite la composición de objetos basada en Trait y mixin [25] [26] a través de objetos de función que implementan un comportamiento adicional y luego se delegan a través de call
o apply
hacia objetos que necesitan esa nueva funcionalidad.
Scala tiene un sistema de tipos rico y los rasgos son una parte de él que ayuda a implementar el comportamiento de mixin. Como su nombre lo revela, los rasgos se utilizan generalmente para representar una característica o aspecto distintivo que normalmente es ortogonal a la responsabilidad de un tipo concreto o al menos de una determinada instancia. [27] Por ejemplo, la capacidad de cantar se modela como una característica ortogonal: podría aplicarse a pájaros, personas, etc.
rasgo Cantante { def sing { println ( " cantando … " ) } //más métodos } La clase Pájaro extiende a Singer
Aquí, Bird ha mezclado todos los métodos del rasgo en su propia definición como si la clase Bird hubiera definido el método sing() por sí sola.
Como extends
también se usa para heredar de una superclase, en el caso de que extends
se use un rasgo si no se hereda ninguna superclase y solo para mezclar en el primer rasgo. Todos los rasgos siguientes se mezclan usando la palabra clave with
.
clase Persona clase Actor extiende Persona con Cantante clase Actor extiende Cantante con Intérprete
Scala permite mezclar un rasgo (crear un tipo anónimo ) al crear una nueva instancia de una clase. En el caso de una instancia de la clase Person, no todas las instancias pueden cantar. Esta característica se utiliza entonces:
clase Persona { def tell { println ( " Humano " ) } //más métodos } val singerPerson = new Persona con Singer singerPerson . sing
Rust hace un uso extensivo de mixins a través de rasgos . Los rasgos, como en Scala, permiten a los usuarios implementar comportamientos para un tipo definido. También se utilizan para genéricos y despacho dinámico , lo que permite que los tipos que implementan un rasgo se utilicen indistintamente de forma estática o dinámica en tiempo de ejecución. [28]
// Permite que los tipos "hablen" el rasgo Speak { fn speak (); // Rust permite a los implementadores definir implementaciones predeterminadas para las funciones definidas en los rasgos fn greeting () { println! ( "Hola!" ) } } estructura Perro ;impl Habla por Perro { fn hablar () { println! ( "Guau guau" ); } } estructura Robot ;impl Hablar para Robot { fn hablar () { println! ( "Bip bip bip bip" ); } // Aquí anulamos la definición de Speak::greet para Robot fn greeting () { println! ( "El robot dice hola!" ) } }
Mixin se puede lograr en Swift mediante el uso de una característica del lenguaje llamada Implementación predeterminada en la extensión del protocolo.
Error de protocolo Desplegable { error de función ( mensaje : cadena )}extensión ErrorDisplayable { func error ( mensaje : Cadena ) { //Haz lo que sea necesario para mostrar un error //... imprimir ( mensaje ) }}Estructura NetworkManager : ErrorDisplayable { función onError () { Error ( "Por favor verifique su conexión a Internet." ) }}
Principales características del lenguaje de Factor: … Sistema de objetos con herencia, funciones genéricas, envío de predicados y
mixins