2009-08-15 13 views
113

En Ruby, dado que puede incluir varias mixinas pero solo extender una clase, parece que las mixins serían preferibles a la herencia.ruby ​​inheritivity vs mixins

Mi pregunta: si está escribiendo código que debe ser extendido/incluido para ser útil, ¿por qué alguna vez lo haría una clase? O dicho de otra manera, ¿por qué no siempre lo convertirías en un módulo?

Solo puedo pensar en una razón por la que querrías una clase, y eso es si necesitas instanciar la clase. En el caso de ActiveRecord :: Base, sin embargo, nunca se crea una instancia directamente. Entonces, ¿no debería haber sido un módulo?

Respuesta

156

I simplemente leer sobre este tema en The Well-Grounded Rubyist (gran libro, por cierto). El autor hace un mejor trabajo de explicar lo que me así que voy a citarlo:


No hay una única regla o fórmula siempre resulta en el diseño correcto. Pero es útil para mantener un par de consideraciones en mente cuando usted está tomando decisiones clase-contra-módulo:

  • módulos no tienen instancias. Se deduce que las entidades o cosas generalmente son las mejores modeladas en clases, y las características o propiedades de las entidades o cosas son mejor encapsuladas en los módulos. En consecuencia, como se señala en la sección 4.1.1, los nombres de la clase tienden a ser sustantivos, mientras que los nombres de los módulos suelen ser adjetivos (Stack frente a Stacklike).

  • Una clase puede tener solo una superclase, pero puede mezclarse en tantos módulos como desee. Si está usando la herencia, dé prioridad a la creación de una relación sensata de superclase/subclase . No use la única y única relación de superclase de una clase con dotando a la clase con lo que podría ser solo uno de varios conjuntos de características.

Resumiendo estas reglas en un ejemplo, esto es lo que no debe hacer:

module Vehicle 
... 
class SelfPropelling 
... 
class Truck < SelfPropelling 
    include Vehicle 
... 

Por el contrario, usted debe hacer esto:

module SelfPropelling 
... 
class Vehicle 
    include SelfPropelling 
... 
class Truck < Vehicle 
... 

La versión segundas modelos de las entidades y propiedades mucho más ordenadamente. El camión desciende del vehículo (lo cual tiene sentido), mientras que el autopropulsado es una característica de los vehículos (al menos, todos los que nos importan en este modelo del mundo), una característica que se transmite a los camiones en virtud de que el camión es un descendiente , o forma especializada , del Vehículo.

+5

+1 para la respuesta articulada –

+0

Gran respuesta +1 –

+0

El ejemplo lo muestra claramente - El camión ES un vehículo - no hay un camión que no sea un vehículo. –

-1

En este momento, estoy pensando en el patrón de diseño template. Simplemente no se sentiría bien con un módulo.

9

Mi opinión: Los módulos son para compartir el comportamiento, mientras que las clases sirven para modelar las relaciones entre los objetos. Técnicamente podrías simplemente hacer de todo un ejemplo de Object y mezclar los módulos en los que desees obtener el conjunto deseado de comportamientos, pero ese sería un diseño pobre, aleatorio y bastante ilegible.

+0

Responde a la pregunta de forma directa: la herencia impone una estructura organizativa específica que puede hacer que tu proyecto más legible – emery

9

La respuesta a su pregunta es en gran medida contextual. Según la observación de Bremen, la elección depende principalmente del dominio en cuestión.

Y sí, ActiveRecord debería haber sido incluido en lugar de extendido por una subclase. Otro ORM - datamapper - ¡lo logra con precisión!

4

Me gusta mucho la respuesta de Andy Gaskell, solo quería agregar que sí, ActiveRecord no debería usar la herencia, sino incluir un módulo para agregar el comportamiento (principalmente la persistencia) a un modelo/clase. ActiveRecord simplemente está usando el paradigma equivocado.

Por la misma razón, me gusta mucho MongoId sobre MongoMapper, porque deja al desarrollador la oportunidad de usar la herencia como una forma de modelar algo significativo en el dominio del problema.

Es triste que prácticamente nadie en la comunidad de Rails esté usando la "herencia de Ruby" de la forma en que se supone que debe usarse: para definir las jerarquías de clases, no solo para agregar comportamientos.

38

Creo que los mixins son una gran idea, pero aquí hay otro problema que nadie ha mencionado: las colisiones del espacio de nombres. Considere:

module A 
    HELLO = "hi" 
    def sayhi 
    puts HELLO 
    end 
end 

module B 
    HELLO = "you stink" 
    def sayhi 
    puts HELLO 
    end 
end 

class C 
    include A 
    include B 
end 

c = C.new 
c.sayhi 

¿Cuál gana? En Ruby, resulta ser el último, module B, porque lo incluyó después de module A. Ahora, es fácil evitar este problema: asegúrese de que todas las constantes y métodos de module A y module B se encuentren en espacios de nombres poco probables. El problema es que el compilador no te advierte en absoluto cuando ocurren las colisiones.

Yo argumento que este comportamiento no se escala a grandes equipos de programadores. No debe suponerse que la persona que implementa class C conoce todos los nombres en el alcance. Ruby incluso le permitirá anular una constante o método de tipo diferente. No estoy seguro de que alguna vez se considere un comportamiento correcto.

+4

este comportamiento no es aceptable en la mayoría de los entornos –

+2

Esta es una sabia advertencia. Con reminiscencias de las múltiples trampas de herencia en C++. –

+1

¿Hay alguna buena mitigación para esto? Esta parece una razón por la cual la herencia múltiple de Python es una solución superior (no tratando de iniciar un emparejamiento p * ssing de idioma, simplemente comparando esta característica específica). – Marcin