¿Qué es un mixin y por qué es útil?

Resuelto TarkaDaal asked hace 15 años • 18 respuestas

En Programación Python , Mark Lutz menciona el término mixin . Tengo experiencia en C/C++/C# y no había escuchado el término antes. ¿Qué es un mixin?

Al leer entre líneas de este ejemplo (al que he vinculado porque es bastante largo), supongo que se trata de un caso de uso de herencia múltiple para extender una clase en lugar de una subclasificación adecuada. ¿Es esto correcto?

¿Por qué querría hacer eso en lugar de poner la nueva funcionalidad en una subclase? De hecho, ¿por qué un enfoque mixin/herencia múltiple sería mejor que usar la composición?

¿Qué separa un mixin de la herencia múltiple? ¿Es sólo una cuestión de semántica?

TarkaDaal avatar Feb 11 '09 01:02 TarkaDaal
Aceptado

Un mixin es un tipo especial de herencia múltiple. Hay dos situaciones principales en las que se utilizan mixins:

  1. Desea proporcionar muchas funciones opcionales para una clase.
  2. Desea utilizar una característica particular en muchas clases diferentes.

Como ejemplo del número uno, considere el sistema de solicitud y respuesta de werkzeug . Puedo crear un antiguo objeto de solicitud diciendo:

from werkzeug import BaseRequest

class Request(BaseRequest):
    pass

Si quiero agregar soporte para aceptar encabezado, lo haría

from werkzeug import BaseRequest, AcceptMixin

class Request(AcceptMixin, BaseRequest):
    pass

Si quisiera crear un objeto de solicitud que admita aceptar encabezados, etiquetas electrónicas, autenticación y soporte de agente de usuario, podría hacer esto:

from werkzeug import BaseRequest, AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin

class Request(AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthenticationMixin, BaseRequest):
    pass

La diferencia es sutil, pero en los ejemplos anteriores, las clases mixtas no fueron creadas para funcionar por sí solas. En una herencia múltiple más tradicional, AuthenticationMixin(por ejemplo) probablemente sería algo más parecido a Authenticator. Es decir, la clase probablemente estaría diseñada para funcionar por sí sola.

Jason Baker avatar Feb 13 '2009 21:02 Jason Baker

Primero, debes tener en cuenta que los mixins sólo existen en lenguajes de herencia múltiple. No puedes hacer un mixin en Java o C#.

Básicamente, un mixin es un tipo base independiente que proporciona funcionalidad limitada y resonancia polimórfica para una clase secundaria. Si está pensando en C#, piense en una interfaz que no tenga que implementar porque ya está implementada; simplemente lo heredas y te beneficias de su funcionalidad.

Los mixins suelen tener un alcance limitado y no están destinados a ampliarse.

[editar - en cuanto a por qué:]

Supongo que debería explicar por qué, ya que preguntaste. El gran beneficio es que no tienes que hacerlo tú mismo una y otra vez. En C#, el lugar más importante donde un mixin podría beneficiarse podría ser el patrón Disposal . Siempre que implementas IDisposable, casi siempre quieres seguir el mismo patrón, pero terminas escribiendo y reescribiendo el mismo código básico con variaciones menores. Si hubiera una combinación de eliminación extensible, podría ahorrarse un montón de escritura adicional.

[editar 2 - para responder a sus otras preguntas]

¿Qué separa un mixin de la herencia múltiple? ¿Es sólo una cuestión de semántica?

Sí. La diferencia entre un mixin y una herencia múltiple estándar es sólo una cuestión de semántica; una clase que tiene herencia múltiple podría utilizar un mixin como parte de esa herencia múltiple.

El objetivo de un mixin es crear un tipo que pueda "mezclarse" con cualquier otro tipo mediante herencia sin afectar el tipo heredado y al mismo tiempo ofrecer alguna funcionalidad beneficiosa para ese tipo.

Nuevamente, piense en una interfaz que ya esté implementada.

Personalmente no uso mixins ya que me desarrollo principalmente en un lenguaje que no los admite, por lo que me está costando mucho encontrar un ejemplo decente que proporcione ese "¡ajá!" momento para ti. Pero lo intentaré de nuevo. Voy a usar un ejemplo que es artificial (la mayoría de los lenguajes ya proporcionan la característica de una forma u otra) pero que, con suerte, explicará cómo se supone que se crean y usan los mixins. Aquí va:

Suponga que tiene un tipo que desea poder serializar hacia y desde XML. Desea que el tipo proporcione un método "ToXML" que devuelva una cadena que contenga un fragmento XML con los valores de datos del tipo y un "FromXML" que permita al tipo reconstruir sus valores de datos a partir de un fragmento XML en una cadena. Nuevamente, este es un ejemplo artificial, por lo que quizás use una secuencia de archivos o una clase de escritura XML de la biblioteca de tiempo de ejecución de su idioma... lo que sea. El punto es que desea serializar su objeto en XML y recuperar un nuevo objeto de XML.

El otro punto importante en este ejemplo es que desea hacer esto de forma genérica. No desea tener que implementar un método "ToXML" y "FromXML" para cada tipo que desee serializar; desea algún medio genérico para garantizar que su tipo lo haga y simplemente funcione. Quieres reutilizar el código.

Si su idioma lo admite, puede crear el mixin XmlSerializable para que haga el trabajo por usted. Este tipo implementaría los métodos ToXML y FromXML. Utilizando algún mecanismo que no es importante para el ejemplo, sería capaz de recopilar todos los datos necesarios de cualquier tipo con el que se mezcle para construir el fragmento XML devuelto por ToXML y sería igualmente capaz de restaurar esos datos cuando FromXML sea llamado.

Y eso es. Para usarlo, cualquier tipo que deba serializarse en XML heredará de XmlSerializable. Siempre que necesitara serializar o deserializar ese tipo, simplemente llamaría a ToXML o FromXML. De hecho, dado que XmlSerializable es un tipo completo y polimórfico, es posible construir un serializador de documentos que no sepa nada sobre su tipo original, aceptando sólo, digamos, una serie de tipos XmlSerializable.

Ahora imagine usar este escenario para otras cosas, como crear un mixin que garantice que cada clase que lo mezcla registre cada llamada a método, o un mixin que proporcione transaccionalidad al tipo que lo mezcla. La lista puede seguir y seguir.

Si simplemente piensas en un mixin como un tipo base pequeño diseñado para agregar una pequeña cantidad de funcionalidad a un tipo sin afectar ese tipo, entonces estás de oro.

Con un poco de suerte. :)

Randolpho avatar Feb 10 '2009 19:02 Randolpho

Esta respuesta tiene como objetivo explicar los mixins con ejemplos que son:

  • autónomo : breve, sin necesidad de conocer ninguna biblioteca para comprender el ejemplo.

  • en Python , no en otros idiomas.

    Es comprensible que hubiera ejemplos de otros lenguajes como Ruby ya que el término es mucho más común en esos lenguajes, pero este es un hilo de Python .

También considerará la cuestión controvertida:

¿Es necesaria o no la herencia múltiple para caracterizar un mixin?

Definiciones

Todavía tengo que ver una cita de una fuente "autorizada" que diga claramente qué es un mixin en Python.

He visto 2 definiciones posibles de un mixin (si deben considerarse diferentes de otros conceptos similares, como clases base abstractas), y la gente no está del todo de acuerdo sobre cuál es la correcta.

El consenso puede variar entre diferentes idiomas.

Definición 1: sin herencia múltiple

Un mixin es una clase tal que algún método de la clase utiliza un método que no está definido en la clase.

Por lo tanto, la clase no está destinada a ser instanciada, sino que sirve como clase base. De lo contrario, la instancia tendría métodos que no se pueden llamar sin generar una excepción.

Una restricción que agregan algunas fuentes es que la clase no puede contener datos, solo métodos, pero no veo por qué esto es necesario. Sin embargo, en la práctica, muchos mixins útiles no tienen ningún dato y las clases base sin datos son más sencillas de usar.

Un ejemplo clásico es la implementación de todos los operadores de comparación desde only <=y ==:

class ComparableMixin(object):
    """This class has methods which use `<=` and `==`,
    but this class does NOT implement those methods."""
    def __ne__(self, other):
        return not (self == other)
    def __lt__(self, other):
        return self <= other and (self != other)
    def __gt__(self, other):
        return not self <= other
    def __ge__(self, other):
        return self == other or self > other

class Integer(ComparableMixin):
    def __init__(self, i):
        self.i = i
    def __le__(self, other):
        return self.i <= other.i
    def __eq__(self, other):
        return self.i == other.i

assert Integer(0) <  Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) >  Integer(0)
assert Integer(1) >= Integer(1)

# It is possible to instantiate a mixin:
o = ComparableMixin()
# but one of its methods raise an exception:
#o != o 

Este ejemplo en particular podría haberse logrado mediante el functools.total_ordering()decorador, pero el juego aquí era reinventar la rueda:

import functools

@functools.total_ordering
class Integer(object):
    def __init__(self, i):
        self.i = i
    def __le__(self, other):
        return self.i <= other.i
    def __eq__(self, other):
        return self.i == other.i

assert Integer(0) < Integer(1)
assert Integer(0) != Integer(1)
assert Integer(1) > Integer(0)
assert Integer(1) >= Integer(1)

Definición 2: herencia múltiple

Un mixin es un patrón de diseño en el que algún método de una clase base utiliza un método que no define, y ese método debe ser implementado por otra clase base , no por la derivada como en la Definición 1.

El término clase mixin se refiere a las clases base que están destinadas a usarse en ese patrón de diseño (¿TODO las que usan el método o las que lo implementan?)

No es fácil decidir si una clase determinada es un mixin o no: el método podría simplemente implementarse en la clase derivada, en cuyo caso volvemos a la Definición 1. Hay que considerar las intenciones del autor.

Este patrón es interesante porque es posible recombinar funcionalidades con diferentes opciones de clases base:

class HasMethod1(object):
    def method(self):
        return 1

class HasMethod2(object):
    def method(self):
        return 2

class UsesMethod10(object):
    def usesMethod(self):
        return self.method() + 10

class UsesMethod20(object):
    def usesMethod(self):
        return self.method() + 20

class C1_10(HasMethod1, UsesMethod10): pass
class C1_20(HasMethod1, UsesMethod20): pass
class C2_10(HasMethod2, UsesMethod10): pass
class C2_20(HasMethod2, UsesMethod20): pass

assert C1_10().usesMethod() == 11
assert C1_20().usesMethod() == 21
assert C2_10().usesMethod() == 12
assert C2_20().usesMethod() == 22

# Nothing prevents implementing the method
# on the base class like in Definition 1:

class C3_10(UsesMethod10):
    def method(self):
        return 3

assert C3_10().usesMethod() == 13

Ocurrencias autorizadas de Python

En la documentación oficial de collections.abc, la documentación utiliza explícitamente el término Mixin Methods .

Afirma que si una clase:

  • implementos__next__
  • hereda de una sola claseIterator

entonces la clase obtiene un __iter__ método mixin gratis.

Por lo tanto, al menos en este punto de la documentación, mixin no requiere herencia múltiple y es coherente con la Definición 1.

Por supuesto, la documentación podría ser contradictoria en diferentes puntos y otras bibliotecas importantes de Python podrían estar usando la otra definición en su documentación.

Esta página también utiliza el término Set mixin, que sugiere claramente que a las clases les gusta Sety Iteratorpueden llamarse clases Mixin.

En otros idiomas

  • Ruby: Claramente no requiere herencia múltiple para mixin, como se menciona en los principales libros de referencia como Programming Ruby y The Ruby programming Language.

  • C++: un virtualmétodo establecido =0es un método virtual puro.

    La definición 1 coincide con la definición de una clase abstracta (una clase que tiene un método virtual puro). No se puede crear una instancia de esa clase.

    La definición 2 es posible con herencia virtual: herencia múltiple de dos clases derivadas