Sharing Functionality: Inheritance, Modules, and Mixins

1.   When puts needs to convert an object to a string, it calls that object’s to_s method.

 

2.  I nheritance allows you to create a class that is a refinement or specialization of another class. This class is called a subclass of the original, and the original is a superclass of the subclass. The child inherits all of the capabilities of its parent class—all the parent’s instance methods are available in instances of the child:

 

class Child < Parent

end 
 

 

3.   The superclass method returns the parent of a particular class.

 

4.  I f you don’t define an explicit superclass when defining a class, Ruby automatically makes the built-in class Object that class’s parent.

 

5.   class BasicObject was introduced in Ruby 1.9 as the parent of Object . It is used in certain kinds of metaprogramming acting as a blank canvas. BasicObject is the root class of the hierarchy of classes.

 

6.   to_s is actually defined in class Object .

 

7.  R uby comes with a library called GServer that implements basic TCP server functionality. The GServer class handles all the mechanics of interfacing to TCP sockets. When you create a GServer object, you tell it the port to listen on. When a client connects, the GServer object calls its serve method to handle that connection.

 

8.   When you invoke super , Ruby sends a message to the parent of the current object, asking it to invoke a method of the same name as the method invoking super . It passes this method the parameters that were passed to super .

 

9.   A Ruby class has only one direct parent. However, Ruby classes can include the functionality of any number of mixins (a mixin is like a partial class definition).

 

10.  Modules are a way of grouping together methods, classes, and constants. Modules give you two major benefits:

    a)   Modules provide a namespace and prevent name clashes.

    b)   Modules support the mixin facility.

 

11.  Modules define a namespace, a sandbox in which your methods and constants can play without having to worry about being stepped on by other methods and constants.

 

12.  Module constants are named just like class constants, with an initial uppercase letter. module methods are defined just like class methods. You call a module method by preceding its name with the module’s name and a period, and you reference a constant using the module name and two colons:

#trig.rb

module Trig

  PI = 3.141592654

  def Trig.sin(x)

    # ..

  end

  def Trig.cos(x)

    # ..

  end

end

#moral.rb

module Moral

  VERY_BAD = 0

  BAD = 1

  def Moral.sin(badness)

    # ...

  end

end

 

#pinhead.rb

require_relative 'trig'

require_relative 'moral'

y = Trig.sin(Trig::PI/4)

wrongdoing = Moral.sin(Moral::VERY_BAD)

 

 

13.  A module can’t have instances, because a module isn’t a class. However, you can 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. They get mixed in. In fact, mixed-in modules effectively behave as superclasses:

 

 

module Debug

  def who_am_i?

    "#{self.class.name} (id: #{self.object_id}): #{self.name}"

  end

end

 
class Phonograph

  include Debug

  attr_reader :name

  def initialize(name)

    @name = name

  end

  # ...

end

 

class EightTrack

  include Debug

  attr_reader :name

  def initialize(name)

    @name = name

  end

  # ...

end

ph = Phonograph.new("West End Blues")

et = EightTrack.new("Surrealistic Pillow")

ph.who_am_i? # => "Phonograph (id: 2151894340): West End Blues"

et.who_am_i? # => "EightTrack (id: 2151894300): Surrealistic Pillow" 
 

 

14.  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. Instance variables are always per object.

 

15.  The Comparable mixin adds the comparison operators (< , <= , == , >= , and > ), as well as the method between? , to a class. For this to work, Comparable assumes that any class that uses it defines the operator <=> . So, as a class writer, you define one method, <=> ; include Comparable ; and get six comparison functions for free.

 

16.  If you write an iterator called each , which returns the elements of your collection in turn.Mix in Enumerable , and suddenly your class supports things such as map , include? , and find_all? . If the objects in your collection implement meaningful ordering semantics using the <=> method, you’ll also get methods such as min, max, and sort.

 

17.  Because inject is made available by Enumerable , we can use it in any class that includes the Enumerable module and defines the method each :

 

class VowelFinder

  include Enumerable

  def initialize(string)

    @string = string

  end

  def each

    @string.scan(/[aeiou]/) do |vowel|

      yield vowel

    end

  end

end

vf = VowelFinder.new("the quick brown fox jumped")

vf.inject(:+) # => "euiooue" 
 

 

18.  The module you mix into your client class (the mixee? ) may create instance variables in the client object and may use attr_reader and friends to define accessors for these instance variables. For instance, the Observable module in the following example adds an instance variable @observer_list to any class that includes it:

 

module Observable

  def observers

    @observer_list = []

  end

  def add_observer(obj)

    observers << obj

  end

  def notify_observers

    observers.each {|o| o.update }

  end

end 
 

 

19.  A mixin’s instance variables can clash with those of the host class or with those of other mixins. 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:

module Test

  State = {}

  def state=(value)

    State[object_id] = value

  end

  def state

    State[object_id]

  end

end

class Client

  include Test

end

c1 = Client.new

c2 = Client.new

c1.state = 'cat'

c2.state = 'dog'

c1.state # => "cat"

c2.state # => "dog"

 

A downside of this approach is that the data associated with a particular object will not get automatically deleted if the object is deleted. In general, a mixin that requires its own state is not a mixin—it should be written as a class.

 

 

20.  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.

When you’re looking for subclassing relationships while designing your application, be on the lookout for the is-a relationships. We need to be using composition wherever we see a case of A uses a B, or A has a B. In this case mixin can help.

你可能感兴趣的:(Ruby)