At a stroke, they pretty much eliminate the need for inheritance, providing a facility called a mixin.
include
a module within a class definition.
When this happens, all the module’s instance methods are suddenly available as methods in the class as well.
DaveThomas Si un módulo define métodos de instancia en vez de métodos de clase, esos métodos de instancia pueden ser combinados/mezclados (mixin) en otras clases.Para mezclar un módulo en una clase llamamos al método include:
class Point include Enumerable end
- Thanks to the magic of mixins and module
Enumerable
. All you have to do is write an iterator calledeach
, which returns the elements of your collection in turn. Mix inEnumerable
, and suddenly your class supports things such asmap
,include?
, andfind_all?
.- If the objects in your collection implement meaningful ordering semantics using the
<=>
method, you’ll also get methods such asmin
,max
, andsort
.include es un método privado de instancia de Module:
ruby-1.9.2-head :004 > Module.private_instance_methods.select {|x| x =~ /inc/ } => [:included, :include]Es legal incluir un módulo en una clase o en un módulo. Al hacerlo los métodos de instancia del módulo incluído se convierten en métodos de instancia del incluyente.
module D def initialize(name) @name =name end def to_s @name end end module Debug include D def who_am_i? "#{self.class.name} (\##{self.__id__}): #{self.to_s}" end end class Phonograph include Debug # ... end class EightTrack include Debug # ... end ph = Phonograph.new("West End Blues") # "Phonograph (#70270868282220): West End Blues"º) et = EightTrack.new("Surrealistic Pillow") # "EightTrack (#70270868320420): Surrealistic Pillow") ph.who_am_i? et.who_am_i?We’ll make a couple of points about the
include
statement before we go on.
- First, it has nothing to do with files.
- C programmers use a preprocessor directive called
#include
to insert the contents of one file into another during compilation. The Rubyinclude
statement simply makes a reference to a module.- If that module is in a separate file, you must use
require
(or its less commonly used cousin,load
) to drag that file in before usinginclude
.- Second, a Ruby
include
does not simply copy the module’s instance methods into the class. Instead, it makes a reference from the class to the included module.- If multiple classes
include
that module, they’ll all point to the same thing.- If you change the definition of a method within a module, even while your program is running, all classes that
include
that module will exhibit the new behavior.
Dave Thomas
Variables de Instancia en Mixins
- Remember how instance variables work in Ruby: the first mention of an
@
-prefixed variable creates the instance variable in the current object,self
.
- For a mixin, this means that the module you mix into your client class may create instance variables in the client object and may use
attr_reader
and friends to define accessors for these instance variables.- However, this behavior exposes us to a risk. A mixin’s instance variables can clash with those of the host class or with those of other mixins.
The example that follows shows a class that uses ourObserver
module but that unluckily also uses an instance variable called@observer_list
.module Observable def observers @observer_list ||= [] end def add_observer(obj) observers << obj end def notify_observers observers.each {|o| o.update } end endrequire 'code/observer_impl' class TelescopeScheduler # other classes can register to get notifications # when the schedule changes include Observable def initialize @observer_list = [] # folks with telescope time end def add_viewer(viewer) @observer_list << viewer end # ... endAt runtime, this program will go wrong in some hard-to-diagnose ways.
- For the most part, mixin modules don’t use instance variables directly—they use accessors to retrieve data from the client object
- But if you need to create a mixin that has to have its own state, ensure that the instance variables have unique names to distinguish them from any other mixins in the system (perhaps by using the module’s name as part of the variable name).
- Alternatively, the module could use a module-level hash, indexed by the current object ID, to store instance-specific data without using Ruby instance variables. A downside of this approach is that the data associated with a particular object will not get automatically deleted if the object is deleted.
Dave Thomas
[~/local/src/ruby/rubytesting/programmingRuby/instance_variables_in_mixins]$ cat private_state_in_mixins.rb require 'pp' module Test State = {} def state=(value) State[__id__] = value end def state State[__id__] end def delete_state State.delete(__id__) end end class Client include Test end c1 = Client.new c2 = Client.new c1.state = 'cat' c2.state = 'dog' pp Test::State.inspect # {70259441214220=>"cat", 70259441214140=>"dog"} pp c1.state c1.delete_state pp Test::State.inspect # {70259441214140=>"dog"} pp c2.state
[~/local/src/ruby/rubytesting/programmingRuby/instance_variables_in_mixins]$ ruby private_state_in_mixins.rb "{70278990371120=>\"cat\", 70278990371100=>\"dog\"}" "cat" "{70278990371100=>\"dog\"}" "dog"
Colisiones
One of the other questions folks ask about mixins is, how is method lookup handled? In particular, what happens if methods with the same name are defined in a class, in that class’s parent class, and in a mixin included into the class?
- The answer is that Ruby looks first in the immediate class of an object,
- then in the mixins included into that class,
- and then in superclasses and their mixins.
- If a class has multiple modules mixed in, the last one included is searched first.
Dave Thomas¿Que ocurre si se incluyen dos módulos en los que existen métodos con el mismo nombre?
[~/TheRubyProgrammingLanguage/Chapter7ClassesAndModules/modules]$ cat module_collision.rb module M1 def foo puts "M1" end end module M2 def foo puts "M2" end endCuando ambos módulos son incluídos en la clase
C
, la última definición defoo
es la que domina:[~/TheRubyProgrammingLanguage/Chapter7ClassesAndModules/modules]$ cat class_require_module_collision.rb require 'module_collision' class C include M1 include M2 end C.new.foo # 'M2' puts C.ancestors.inspect # => [C, M2, M1, Object, Kernel, BasicObject]Ahora bien, si en la clase existe un método
foo
ese será encontrado primero:[~/TheRubyProgrammingLanguage/Chapter7ClassesAndModules/modules]$ cat -n module_class_collision.rb 1 module M 2 def foo 3 puts "M" 4 end 5 end 6 7 class C 8 def foo 9 puts "C" 10 end 11 include M 12 end 13 C.new.foo # => "C" 14 puts C.ancestors.inspect # => [C, M, Object, Kernel, BasicObject]
Composición frente a Herencia/Composition versus Inheritance
Inheritance and mixins both allow you to write code in one place and effectively inject that code into multiple classes.
So, when do you use each?
- First, let’s look at subclassing. Classes in Ruby are related to the idea of types.
- When we create our own classes, you can think of it as adding new types to the language.
- And when we subclass either a built-in class or our own class, we’re creating a subtype.
- Now, a lot of research has been done on type theories. One of the more famous results is the Liskov Substitution Principle. Formally, this states:
Let q(x) be a property provable about objects x of type T. Then q(y) should be true for objects y of type S where Sis a
subtype of T
- What this means is that you should be able to substitute an object of a child class wherever you use an object of the parent class—the child should honor the parent’s contract.
We’re used to saying this in English: a car
is a
vehicle, a catis a
n animal, and so on.This means that a cat should, at the very least, be capable of doing everything we say that an animal can do.
- So, when you’re looking for subclassing relationships while designing your application, be on the lookout for these
is-a
relationships.
- But...here’s the bad news. In the real world, there really aren’t that many true
is a
relationships. Instead, it’s far more common to havehas a
oruses a
relationships between things.
Dave Thomas
- Think of containment as a has a relationship. A car
has an
engine, a personhas a
name, etc.- Think of inheritance as an
is a
relationship. A caris a
vehicle, a personis a
mammal, etc.
extendAunque
Module.include
es la forma normal de mezclar un módulo también es posible mezclar conObject.
extend. Este método hace que los métodos de instancia del módulo especificado se incorporen como métodos singleton del objeto receptor. Si el objeto receptor es una clase, esto es, es un objeto de la claseClass
, entonces los métodos son métodos de clase de dicha clase receptora.
[~/srcLPP/Chapter7ClassesAndModules/modules]$ cat module_mixin_with_extend.rb module Chuchu def tutu "method tutu. self: #{self.inspect}" end end class ChanChan end p = "hello " q = "world" p.extend Chuchu puts p.tutu ChanChan.extend Chuchu puts ChanChan.tutu puts q.tutu # exceptionEjecución:[~/srcLPP/Chapter7ClassesAndModules/modules]$ ruby module_mixin_with_extend.rb method tutu. self: "hello " method tutu. self: ChanChan module_mixin_with_extend.rb:19:in `<main>': undefined method `tutu' for "world":String (NoMethodError)Casiano Rodriguez León 2015-01-07