CSC/ECE 517 Fall 2007/wiki1b 1 as: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
 
(20 intermediate revisions by 2 users not shown)
Line 3: Line 3:
= Background =
= Background =


Ruby does not implement true multiple inheritance, but provides Modules as a way to reuse chunks of codes in many classes.  
Ruby does not implement true multiple inheritance, but provides Modules as a way to reuse chunks of codes in many classes. Modules, unlike classes in OO languages such as Java, cannot be instantiated or sub-classed. Modules are included in class definitions by using the ‘include’ method which will mix that module’s methods into the calling class. The module’s methods will then become instance methods.  


Modules, unlike like classes in OO languages such as Java, cannot be instantiated or sub-classed. Modules are included in class definitions by using the ‘include’ method which will mix that module’s methods into the calling class. The module’s methods will then become instance methods.
A class can include several modules within the class definition. However, a problem exists when a class includes multiple modules that contain a method of the same name. Since the class will have access to both of these methods, unexpected behavior may occur when a call is made to a method with an ambiguous name.
 
A class can include several modules within the class definition. However, a problem exists when a class includes multiple modules that contain a method of the same name. Since the class will have access to both of these methods, unexpected behavior may occur when the names of the methods conflict.


Let’s look at a few examples!
Let’s look at a few examples!
Line 14: Line 12:


== 1. Method Name Conflict ==
== 1. Method Name Conflict ==
module Foo
 
In the following example, we have the class Zap. This class includes the module Foo and the module Test. Both modules contain a method named bar.
  module Foo
       def bar
       def bar
         "hello"
         "hello"
      end
      def baz
        "world"
       end
       end
   end
   end
Line 25: Line 22:
       def bar
       def bar
         "goodbye"
         "goodbye"
      end
      def blah
        "new york"
       end
       end
   end   
   end   
Line 35: Line 29:
   end
   end


z = Zap.new
If a call is made to Zap's bar method, the caller is unsure whether the result will be "hello" or "goodbye".
puts z.bar
  z = Zap.new
  puts z.bar


We might expect it come out "Hello" but it products:
The result of this call is:


goodbye
  goodbye
 
This is because Ruby will first search the last module included, and continue in a descending order until it finds a method with that name. Since in this case, Test was the last module included, its method was the one invoked. The method that is invoked may not be the method that the caller expected to run. Therefore, precaution should be taken to eliminate ambiguity.


== 2. Using Namespaces to Avoid Conflict ==
== 2. Using Namespaces to Avoid Conflict ==
Using namespaces within the modules will make the method names unique and help disambiguate any conflicts. The namespace is created simply by prefixing the method name with the module name and a period (i.e., Module.method).


module Grouchy
Let's modify the previous example to include namespaces.
def Grouchy.say_hello(string='somebody')
  puts "#{string} says: Don't tell me what to do!"
end
end


Grouchy.say_hello is the class method of the module Grouchy
  module Foo
We have a class Person which includes the module Grouchy
    def Foo.bar #the method name is qualified
 
        "hello"
class Person
    end
 
  require "grouchy"  
 
  attr_accessor :name
 
  def initialize(name='somebody')
    @name = name
   end
   end
  module Test
    def Test.bar #the method name is qualified
        "goodbye"
    end
  end 
  class Zap
  include Foo
  include Test


end
Zap class defines a method named bar, and makes a call to both bar methods in Foo and Test. However, if the programmer knew for sure that any call to the bar method should invoke only one of the modules, this could be handled here by only calling the module of choice.
person = Person.new('Charlie')
  def bar
Grouchy.say_hello(person.name)
      puts Foo.bar
      puts Test.bar
  end
  z = Zap.new
  puts z.bar


It products:  
This will produce:
  Hello
  Goodbye


Charlie says: Don't tell me what to do!
== 3. Using Aliases to Avoid Conflict ==
An alternative to using namespaces is to use alias to disambiguate method names in different modules. Again, looking at the first example, we have modified it to show the use of aliases.


When facing the name conflicts problem, we can use namespace to tell the different of two methods.
  module Foo
 
      def bar
module Debug
        "hello"
  def Debug.who_am_i
      end
    "Debug"
  end
  end
  module Test
end
      def bar
 
        "goodbye"
module Burp
      end
  def Burp.who_am_i
  end
    "Burp"
 
  end
In the Zap class, we define two aliases, one for Foo's bar method and another for Test's bar method. The alias syntax is ''alias :new_name :old_name''. So, to disambiguate, we first include Foo, and give an alias to its bar method. Then include Test and give an alias to its bar method.
end
  class Zap
 
    include Foo
class EightTrack
    alias :Foo_bar :bar
  include Debug
     include Test
  include Burp
    alias :Test_bar :bar
  def who_am_i
  end
    puts Burp.who_am_i
    puts Debug.who_am_i   
  end
end
 
et = EightTrack.new
 
et.who_am_i
 
It products:
 
Burp
Debug
 
Another way is using the alias method, we still have two modules have the same name method who_am_i.
module Debug
  def who_am_i
     "Debug"
  end
end


module Burp
The caller then can make a call to the new method names defined by the aliases to choose exactly which call they want to make.
  def who_am_i
  z = Zap.new
    "Burp"
  puts z.Foo_bar
  end
  puts z.Test_bar
end


class RubyTest 
The result of this call is:
  include Burp 
  hello
  alias :Burp_who_am_i :who_am_i 
  goodbye
  include Debug
  alias :Debug_who_am_i :who_am_i 
end


rt = RubyTest.new
Note: A call to z.bar would react the same way as shown in example one, with the result being a call to the last module included.
puts rt.Debug_who_am_i
puts rt.Burp_who_am_i
 
== 3. Using Aliases to Avoid Conflict ==


= Conclusion =
= Conclusion =
Line 135: Line 109:
The two approaches are:
The two approaches are:
#using namespaces
#using namespaces
#using an alias
#using aliases
 
With namespaces, the class controls what module will be invoked. However, with aliases, the caller controls what will be invoked. These two approaches have their advantages and disadvantages.
 
An advantage of using namespaces is that the logic that the programmer envisions is captured in the code. For example, the programmer may only be interested in Foo's bar method and not Test's bar method. This can be controlled by internally making the call to Foo.bar anytime the class's bar method is invoked. By the same token, this causes less flexibility, which is a disadvantage.


In our opinion, using namespaces is the better approach. Inheriting from the module and using the qualified names to avoid conflicts is a more efficient OO design. It is also easier to read and maintain because in viewing the call, a reader will immediately gather the expected behavior of the call, as opposed to finding the alias definition and trying to make the connection.  
When using aliases, the caller is more in control. The caller can leverage the fact that the class it is calling has access to the bar method of two different modules, thus providing more power and flexibility. However, the disadvantage of this is that the caller must know the internals of the class it is calling. It must know which aliased method calls what module, and also what that module's method does.


With the alias approach, the inheritance is limited and the programmer will need to constantly update the list of aliases for methods needed as they arise.
In our opinion, using namespaces is the more efficient object-oriented (OO) design approach. Encapsulation is an important OO concept, and taxing the caller to research the internals of the code, while powerful, is inefficient.


= References =
= References =
Line 145: Line 123:
#[http://www.recentrambles.com/pragmatic/view/69 Modules, Mixins, and Inheritance]
#[http://www.recentrambles.com/pragmatic/view/69 Modules, Mixins, and Inheritance]
#[http://www.rubyist.net/~slagell/ruby/modules.html Ruby User's Guide]
#[http://www.rubyist.net/~slagell/ruby/modules.html Ruby User's Guide]
#[http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html Programming Ruby]
#[http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/164209 Class and Mixin with same name method problem]

Latest revision as of 03:28, 13 October 2007

Background

Ruby does not implement true multiple inheritance, but provides Modules as a way to reuse chunks of codes in many classes. Modules, unlike classes in OO languages such as Java, cannot be instantiated or sub-classed. Modules are included in class definitions by using the ‘include’ method which will mix that module’s methods into the calling class. The module’s methods will then become instance methods.

A class can include several modules within the class definition. However, a problem exists when a class includes multiple modules that contain a method of the same name. Since the class will have access to both of these methods, unexpected behavior may occur when a call is made to a method with an ambiguous name.

Let’s look at a few examples!

Examples

1. Method Name Conflict

In the following example, we have the class Zap. This class includes the module Foo and the module Test. Both modules contain a method named bar.

  module Foo
     def bar
        "hello"
     end
  end
  module Test
     def bar
        "goodbye"
     end
  end   
  class Zap
   include Foo
   include Test
  end

If a call is made to Zap's bar method, the caller is unsure whether the result will be "hello" or "goodbye".

  z = Zap.new
  puts z.bar

The result of this call is:

  goodbye

This is because Ruby will first search the last module included, and continue in a descending order until it finds a method with that name. Since in this case, Test was the last module included, its method was the one invoked. The method that is invoked may not be the method that the caller expected to run. Therefore, precaution should be taken to eliminate ambiguity.

2. Using Namespaces to Avoid Conflict

Using namespaces within the modules will make the method names unique and help disambiguate any conflicts. The namespace is created simply by prefixing the method name with the module name and a period (i.e., Module.method).

Let's modify the previous example to include namespaces.

 module Foo
    def Foo.bar #the method name is qualified
       "hello"
    end
 end
 module Test
    def Test.bar #the method name is qualified
       "goodbye"
    end
 end   
 class Zap
  include Foo
  include Test

Zap class defines a method named bar, and makes a call to both bar methods in Foo and Test. However, if the programmer knew for sure that any call to the bar method should invoke only one of the modules, this could be handled here by only calling the module of choice.

  def bar 
     puts Foo.bar
     puts Test.bar
  end
  z = Zap.new
  puts z.bar

This will produce:

  Hello
  Goodbye

3. Using Aliases to Avoid Conflict

An alternative to using namespaces is to use alias to disambiguate method names in different modules. Again, looking at the first example, we have modified it to show the use of aliases.

 module Foo
     def bar
        "hello"
     end
  end
  module Test
     def bar
        "goodbye"
     end
  end
  

In the Zap class, we define two aliases, one for Foo's bar method and another for Test's bar method. The alias syntax is alias :new_name :old_name. So, to disambiguate, we first include Foo, and give an alias to its bar method. Then include Test and give an alias to its bar method.

  class Zap
   include Foo
   alias :Foo_bar :bar
   include Test
   alias :Test_bar :bar
  end

The caller then can make a call to the new method names defined by the aliases to choose exactly which call they want to make.

  z = Zap.new
  puts z.Foo_bar
  puts z.Test_bar

The result of this call is:

  hello
  goodbye

Note: A call to z.bar would react the same way as shown in example one, with the result being a call to the last module included.

Conclusion

As illustrated in the examples, there are at least two ways to ensure that your class runs as expected even when using modules with method name conflicts.

The two approaches are:

  1. using namespaces
  2. using aliases

With namespaces, the class controls what module will be invoked. However, with aliases, the caller controls what will be invoked. These two approaches have their advantages and disadvantages.

An advantage of using namespaces is that the logic that the programmer envisions is captured in the code. For example, the programmer may only be interested in Foo's bar method and not Test's bar method. This can be controlled by internally making the call to Foo.bar anytime the class's bar method is invoked. By the same token, this causes less flexibility, which is a disadvantage.

When using aliases, the caller is more in control. The caller can leverage the fact that the class it is calling has access to the bar method of two different modules, thus providing more power and flexibility. However, the disadvantage of this is that the caller must know the internals of the class it is calling. It must know which aliased method calls what module, and also what that module's method does.

In our opinion, using namespaces is the more efficient object-oriented (OO) design approach. Encapsulation is an important OO concept, and taxing the caller to research the internals of the code, while powerful, is inefficient.

References

  1. Modules, Mixins, and Inheritance
  2. Ruby User's Guide
  3. Programming Ruby
  4. Class and Mixin with same name method problem