CSC/ECE 517 Fall 2011/ch4 4c ap: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
Line 108: Line 108:


Consider the following example to illustrate mixins.
Consider the following example to illustrate mixins.
<pre>
<pre>  
Consider the following code:
module Introspect  
module Introspect  
   def kind  
   def kind  

Revision as of 22:34, 17 October 2011

Modules and Mixins

Regular Expressions

Regular expressions are extremely powerful.Ruby was built as a better Perl hence it supports regular expressions. Regular expression is sort of a string used to match to other strings.In ruby regular expressions are written in the format /pattern/modifiers where pattern is the regular expression and modifiers are the series of characters specifying the various options. To understand the power of regular expressions here is an example.

In the following example the character 'c' is replaced with "".

irb(main):001:0> "faculty".sub(/c/, "")
=> "faulty"

In Ruby there is "Regexp" which is a Ruby object representing a Regular expression.Creating a Regexp object is similar to creating a string except for the usage of a forward slash to delimit it,rather than quote marks.

r = /my regular expression/

The regular expressions will match the string "my regular expression" anywhere in the string.Let's look at what we can put into regular expressions using Regexp. Here's an example to retrieve the first match of /w.ll/ in the string.

irb(main):001:0> string1="I will drill for a well in walla walla"
=> "I will drill for a well in walla walla"
irb(main):002:0> r=Regexp.new(/w.ll/)
=> /w.ll/
irb(main):003:0> r.match(string1)
=> #<MatchData "will">

Suppose you want to retrieve the first digit in a string.Here's an example:

irb(main):014:0> e4=Regexp.new('\d')
=> /\d/
irb(main):016:0> string="hello12 123"
=> "hello12 123"
irb(main):017:0> e4.match(string)
=> #<MatchData "1">

The above example since \d matches only digits it selects the 1 from Hello12.In order to retrieve the digits"12" from "hello12" we need to use \w which recognizes all word character[0-9A-Z a-z_] .Here's an example showing it.

irb(main):020:0> e4=Regexp.new('\d\w*')
=> /\d\w*/
irb(main):021:0> e4.match(string)
=> #<MatchData "12">

Modules

Ruby Modules are similar to classes in that they hold a collection of methods,constants and other module and class definitions. Modules definition is similar to classes just that we use the keyword module instead of the class keyword.

Unlike classes, objects cannot be created based on modules nor can it be sub classed.However, it can be specified that the functionality of one module should be added to another class, or a specific object.

Modules serve two purpose:

1.They act as namespace in C++, letting definition of methods whose names will not clash with those defined elsewhere.

2.Modules allow to share functionality between classes.

Here's an example to illustrate modules.Suppose there is a graphics library that contains classes for Windows(windows.rb) and Border(border.rb).Window.rb and border.rb have a top method,which gives the position of the top of the Windows and top of the border respectively.

To layout windows with borders,both windows.rb and border.rb have to be loaded into the program.However, as the top method is in both classes one of them will be overridden.The solution to this is use of modules. The window function and border function can go into separate modules respectively.

module Window 
def Window.top 
# .. 
end 
def Window.bottom 
# .. 
end 
end 

module Border 
def Border.top 
# ... 
end 
def Border.width 
# .. 
end
end

When a program needs to use these modules, it can simply load the two files using the Ruby require statement, and reference the qualified names.

 
require 'window' 
require 'border' 
trueTop = Window.top + Border.Top.

Mixins

The most interesting fact about the use of modules is to define mixins.

When a module is included within a class,all its functionality becomes available to the class.Modules can contain class methods and instance methods.

Let's see how mixins is contrasting to #include and multiple inheritance in other languages.#include files define methods that can be called but not applied to objects.By using mixins the same methods can be added to any number of different classes;regardless of hierarchy.

Mixins corresponds to the usage of interfaces in Java.

Consider the following example to illustrate mixins.

 
module Introspect 
  def kind 
    puts "#{self.class.name}" 
  end 
end 
class Animal 
  include Introspect 
  def initialize(name) 
     @name = name 
  end 
end 
class Car 
  include Introspect 
  def initialize(model) 
    @model = model 
  end 
end 
d = Animal.new("Cat") 
c = Car.new("Ferrari") 
d.kind # kind method is available through … 
c.kind # .. the mixin Introspect 
>>Animal 
>>Car 

Comparable

Composing Modules

Simulating Multiple Inheritance

Multiple Inheritance has several disadvantages that can lead to ambiguous code behavior either during compile time or run time. Ruby does not support direct Multiple Inheritance. But, Multiple Inheritance can be achieved in Ruby through Modules. Modules simulate multiple inheritance in Ruby.

Given below is the Taggable-string example taken from the Class notes of CSC517, NCSU. Suppose we want to add tags to Strings, we can define a Taggable module and include it into the class.

 require 'set'      # A collection of unordered values with no duplicates
 
 module Taggable
   attr_accessor :tags
   
   def taggable_setup
     @tags = Set.new
   end
   
   def add_tag(tag)
     @tags << tag
   end
   
   def remove_tag(tag)
     @tags.delete(tag)
   end
 end
 
 class TaggableString < String
   include Taggable
   def initialize(*args)
     super
     taggable_setup
   end
 end
 
 s = TaggableString.new('It is a bright, it is a sunny day')
 s.add_tag 'sentence'
 s.add_tag 'quotation'
 s.tags                          # =>    #<Set: {"sentence", "quotation"}>

Given below is another simple example of how modules and mixins can be used to simulate multiple inheritance in Ruby.


 module A
   def a1
   end
   def a2
   end
 end
 module B
    def b1
    end
    def b2
    end
 end
 class Sample
   include A
   include B
   def s1
   end
 end
 obj = Sample.new
 obj.a1
 obj.a2
 obj.b1
 obj.b2
 obj.s1

Advantages of Multiple Inheritance

Although multiple inheritance has its disadvantages, there are a couple of good reasons for using multiple inheritance. Generally, multiple inheritance is used in one of the following ways:

1. Multiple Independant Protocols

This is used when a class has to have features of independant classes. A class is created by inheriting or combining two or more completely different super-classes.

For example, in Eiffel, the library class WINDOW is a subclass of SCREENMAN, RECTANGLE, and TWO_WAY_TREE. Another example

2. Mix and Match

This is used when a class need to created as a combination of different super classes. Several classes are created specially for subsequent combination. There is a mix and match of super-classes combined into a single sub-class.

For example, Mixins help achieve this. Another example

3. Submodularity

Modularity of the sub-parts of the classes is noticed and factored out into subclasses. This is used when the super-classes are modular and the modularity has to be factored out into subclasses.

For example, in a class representing mortgages, one might factor out FIXED_RATE and ADJUSTABLE mortgages. Another example

4. Separation of interface and implementation

Interfaces are defined by Abstract Classes. Interfaces contain a group of related method declarations. The methods are not defined in the Interfaces. Interfaces represents the super-class and the sub-classes inherit the interfaces by implementing them. In other words, the subclasses encapsulate the implementation details of the interface.

For example, a Stack class could be created as a subclass of StackInterface and StackImplementation. Another example

Disadvantages of Multiple Inheritance

Programmers use multiple inheritance to increase re-usability and consistency in the system. Although multiple inheritance is useful, it can lead to ambiguity and increased complexity if not used carefully. For this reason, some languages like Java, Ruby etc., do not support direct multiple inheritance. They provide different ways to achieve multiple inheritance like Interfaces, Mixins etc. The problems that arise due to multiple inheritance are as follows:

1. Name collision

Two features (instance variables or methods) with the same name are inherited from different super-classes.

The super-classes may be correct and consistent. But the conflict arises when the sub-class inherit the two super-classes which have methods of the same name (for example, initialize()). In Java, some people call this situation the "Deadly Diamond of Death". This is illustrated by the figure below:

figure

2. Repeated inheritance

Multiple inheritance may result in a sub-class inheriting the same super-class more than once. Since there are multiple paths from the sub-class to its ancestor classes, a class can by mistake, end up inhering the same super-class more than once. This can go unnoticed and lead to ambiguity and increase the chances of errors. It is very difficult to trace the errors as well.

3. Method combination

This problem is similar to the name collision issue discussed above. An object may need to execute a method (for example, initialize()) which has been defined in different super-classes. The method resolution becomes a problem during compile time and can lead to run-time errors.

4. Implementation difficulties

Multiple inheritance can result it increased code complexity and implementation difficulties. In multiple inheritance classes can be combined in several different ways. It becomes difficult for the programmer to represent different objects. Finding methods needs a lot of search or redirection along the hierarchy.

5. Misuse

Multiple inheritance provides the ability to a sub-class to inherit from a parent class as many times as it wants. Also, a sub-class can inherit from as many parent classes as it wants. Therefore, there is a chance that inheritance is used more often than is needed unnecessarily.

For example, consider a new ApplePie class which has to inherit features from Apple class and Cinnamon class. Programmers may consider this multiple inheritance because ApplePie contains Apple and Cinnamon. But, this is not the right way. Whenever a class wants to inherit another class, there should be a "is-a" relationship between the sub-class and super-class. Here, there is a "has-a" relation and not "is-a" relationship.

ApplePie has-a Apple

ApplePie has-a Cinnamon

The relationship can be a composition but not inheritance.

"Is-a" relationship does not exist. The thumb rule to check if inheritance is allowed is to verify the "is-a" relationship.

Resolution of Name conflicts or Collisions

Multiple inheritance may cause name conflicts when a sub-class inherits different super-classes that contain the methods or variables with the same name. This can be resolved in many ways.

Compound selectors in Ruby

Suppose a class Sub inherits two different methods for MethodOne from two different super-classes, SuperClassOne and SuperClassTwo.

In Ruby, we can use Compound Selectors to refer to the method as shown below:

 SuperClassOne.MethodOne      #Uses the MethodOne method inherited from SuperClassOne
 SuperClassTwo.MethodOne      #Uses the MethodOne method inherited from SuperClassTwo

Renaming in Eiffel

In Eiffel language, naming conflicts are overcome in inherited features by renaming. It contains a rename clause to remove name conflicts. This is illustrated below:

 Class Sub inherit
   SuperClassOne rename x as x1, y as y1;
   SuperClassTwo rename x as x2, y as y2;
   feature....

Here, the inherit clause would be illegal without the rename clause. This ensures that name conflicts are resolved. This also allows the programmer to give meaningful and appropriate names to the inherited features.

Specifying Objects

The Object-Oriented programming paradigm has two ways for specifying objects.

Set based language - object specification

• First, a class is described which abstracts the features or properties of the object we want to specify.

• After describing the template or class, instances or objects for that class are created to perform the actual work.

• Classes can be described by Meta-classes.

• Every object instance for a class is unique and holds its internal values for all the features defined for that class.

• We can perform inheritance. More specific sub-classes for the described classes can be created that contain the properties of the parent, modify features of the parent or add additional properties.

• When the object that receives a message to perform some action, if it does not understand, it consults its ancestor classes asking each of them to handle the message along the inheritance hierarchy.

Example:

In a set-based object specification system, the objects are defined using a top-down approach by first specifying the abstract and using that abstract to create the specific.
To describe a car object, we collect properties that are common to all the cars and create a car class that represents all cars and contains features that all cars should have.
Then we use that car class to create new car objects or instances.

Protocol based language - object specification

• First, an object that is a concrete representation of the object we are trying to specify is created. This is the prototype.

• Multiple instances of that object are obtained by copying or cloning.

• Each and every instance has those properties and internal values that are specific and unique to itself and a reference to its prototype, called an extension.

• Essentially, there is no distinction between an instance and a prototype. Any instance can be copied or cloned to become the prototypical object for its duplicates.

• When an object receives a message, if it does not understand the message, the message is delegated to its prototypes asking each one of them to grant it the ability to handle the message.

Example:

In a prototype-based object specification system, the objects are defined using a bottom-up approach by first specifying the specific instance and modifying it as necessary to represent other instances.
Consider the same car example. To describe a car Mercedes, we create an object first that contains its properties (Mercedes logo, black, sedan, fast). If we later discover a new car Lexus (Lexus logo, white), we specify what we know about Lexus and assume that the remainder in identical to the prototypical car Mercedes. This may evolve the assumption that all cars are sedans and fast.

Extending specific objects

Ruby as a set-based language

Ruby can act as a set-based language.

We can first define classes that form the blueprint or a template.
We can then create instances for the classes. These objects contain internal values for the properties present in the class.
We can then perform inheritance and define sub-classes, modify or add more functionality and create instances for the sub-classes.
The sub-class objects receive messages. If they don't understand, they consult the ancestors in the inheritance hierarchy.

The include statement can be used to augment the class definition and add more functionality to the class.

Ruby as a prototype-based language

Ruby can also act as a prototype-based language. This is where specific instances of the class, not all, will have some unique features and properties.

There is a way in Ruby, to add instance specific functionality to just specific instances/objects of a class by using the object#extend method.

This means, some objects of a class can have values for all the properties mentioned in the class, but a few others can have additional properties specific to them. This concept is called Extending Objects.

The example below, which is taken from the Class notes of CSC517, NCSU illustrates the concept of extending specific objects.

Consider a Person class which has properties for a mild-mannered people.

 class Person
   attr_reader :name, :age, :occupation
   
   def initialize(name, age, occupation)
     @name, @age, @occupation = name, age, occupation
   end
   
   def mild_mannered?
     true
   end
 end
 
 Amy = Person.new('Amy Wilson', 23, 'student')
 John = Person.new('John Mason', 40, 'professor')
 Amy.mild_mannered?                                  # => true 
 John.mild_mannered?                                 # => true

The above class describes all the people who are mild-mannered. What if there are some other people who are not as mild-mannered as they appear. That is they have some super-powers as well.

 module SuperPowers
   def fly
     'Flying!'
   end
   
   def leap(what)
     "Leaping #{what} in a single bound!"
   end
   
   def mild_mannered?
     false
   end
   
   def superhero_name
     'Superman'
   end
 end

Here, we have two situations. Some objects of the person class are mild-mannered, while some other objects of the same person class have super powers with them.

To achieve this, if we add super power functionality to the person class itself, then all the mild-mannered person objects will also ending having values for this property. If we use include to mix the SuperPowers module into the Person class, it will give every person super powers. Some people are bound to misuse such power.

The only way to achieve this is to extend only those objects that have super powers instead of extending the entire class itself.

 Amy.extend(SuperPowers)
 puts Amy.superhero_name          # => Superman
 puts Amy.fly                     # => Flying!
 puts Amy.leap(rocks)             # => Leaping #{what} in a single bound!   
 puts Amy.mild_mannered?          # => false             
 puts John.mild_mannered?         # => true

Given below is another example that illustrates extending specific objects.

 module CarAction
   def goLeft(steps)
     Movement.new(self, steps)
   end
 end  
 
 
 #The Actions module can be included to allow a class to generate movements.
 class BigCar
   include CarAction
   def carMethod
     puts "This is a method inside BigCar class"
   end
 end
 
 #After CarActions is included goLeft can be executed by any instance of BigCar.
 car1 = BigCar.new
 car1.carMethod
 movement1 = car1.goLeft
 
 
 #The CarActions module is not included in the SmallCar class.
 class SmallCar
   def carMethod
     puts "This is a method inside SmallCar class"
   end
 end
 
 #Extend adds the methods to one instance, not to all instances.
 car2 = SmallCar.new
 car2.extend CarActions
 movement2 = car.goLeft
 
 car3 = SmallCar.new
 movement3 = car.goLeft      #Error: car3 does not extend CarActions, hence cannot call the goLeft method.

See Also

References

Class notes of CSC517, NCSU

External Links

Ruby extend and Include