CSC/ECE 517 Fall 2011/ch4 4a ga

From Expertiza_Wiki
Revision as of 03:06, 19 October 2011 by Jjaube (talk | contribs)
Jump to navigation Jump to search

Introduction

This article discusses the extend concept as illustrated by Ruby's extend method. We provide a definition of extend, compare it to the include method, and go over the differences between extending a class and extending an object. We then consider two practical examples of Ruby's extend method.

From there, we go on to discuss how other languages provide functionality similar to that of Ruby's extend method. We also compare these implementations to the Ruby implementation as to whether they are more or less powerful than Ruby's. Finally, we also consider prototype-based languages such as Self, which don't even provide classes.

Extend in Ruby

What is Extend?

Definition

The Ruby doc definition of the extend method is that it “Adds to obj the instance methods from each module given as a parameter.”<ref>http://www.ruby-doc.org/core-1.8.7/Object.html#M000007</ref> An example shows how this plays out:

module Mod
  def hello
    "Hello from Mod.\n"
  end
end

class Klass
  def hello
    "Hello from Klass.\n"
  end
end

k = Klass.new
k.hello         #=> "Hello from Klass.\n"
k.extend(Mod)   #=> #<Klass:0x401b3bc8>
k.hello         #=> "Hello from Mod.\n"

From this example we can see several interesting things about extend. 1. It can be applied dynamically to an object after that object has been created. 2. Methods introduced by extend will override any existing methods in that object.

Extend vs. Include

Extend is also often defined by comparing it to the include method:

module Foo
  def foo
    puts 'heyyyyoooo!'
  end
end

class Bar
  include Foo
end

Bar.new.foo # heyyyyoooo!
Bar.foo # NoMethodError: undefined method ‘foo’ for Bar:Class

class Baz
  extend Foo
end

Baz.foo # heyyyyoooo!
Baz.new.foo # NoMethodError: undefined method ‘foo’ for #<Baz:0x1e708>

In the previous example, extend was applied dynamically to an object after it had been created. In this example, both extend and include are applied inside the class.

This example also shows an important difference between include and exclude, while also making it clearer what exactly extend does.

When include is used on a class, the methods from the module are applied to instances of a class rather than to the class itself. The extend method, on the other hand, applies methods from the module to the class itself, rather than to instances of the class. <ref>http://railstips.org/blog/archives/2009/05/15/include-vs-extend-in-ruby/</ref>

Extending classes vs. Extending objects

We’ve seen examples of extend being applied both to classes and to objects (instances of classes). The syntax, however, was significantly different between the two examples. Let’s look at another example where the same syntax is used:

class C
end

module M
   def talk
     puts "hi"
   end
end

C.talk # NoMethodError: undefined method `talk' for C:Class
C.extend M
C.talk #hi

c = C.new
c.talk # NoMethodError: undefined method `talk' for #<C:0x297c918>
c.extend M
c.talk    # hi

This syntax is a variation of the syntax used the in the first example; the parentheses are optional. Notice that extend can be applied using this syntax to both classes and class instances. Note also that extending a class doesn’t extend any instances of the class. The instance was only extended after we explicated extended the instance in addition to the class. If we created a new instance of the class, we’d also have to extend that instance before we’d be able to use any methods from the module.<ref>http://www.ruby-forum.com/topic/164443</ref>

Practical Examples

It’s easy to find trivial examples of using Ruby’s extend method, especially when applying it to objects:

# Format a numeric value as a currency
module CurrencyFormatter
  def format
    "$#{@value}"
  end
end

# Add the module methods to
# this object instance, only!
bigint2.extend CurrencyFormatter
puts bigint2.format   # --> '$2'
puts bigint1.format   # will generate an error

While this is an interesting example, it seems inefficient to extend a single object in order to provide a particular format for its value. It would make much more sense to use include instead so that the method could be applied to all instances of the class for that object. Because of this, all of the practical examples that follow, and likely any practical examples one could find, will be cases where extend is used within a class rather than on an instance of a class<ref>http://juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby/</ref>

As we’ll see even in these examples, however, rather than extend being a way to accomplish something that couldn’t be accomplished easily any other way, extend is most often used as a convenience method for accomplishing a frequently needed function in an more manageable way.

Twitter Module

There is a twitter module that makes use of the extend method in an interesting way:

module Twitter
  class OAuth
    extend Forwardable
    def_delegators :access_token, :get, :post
    
    # etc

    def access_token
      @access_token ||= ::OAuth::AccessToken.new(consumer, @atoken, @asecret)
    end

    #etc
    
  end
end

Forwardable is used here as a convenient way to delegate responsibility. Rather than having to define get and post within the OAuth class, those calls are passed off to access_token, which has methods with those same names.<ref>https://github.com/jnunemaker/twitter/blob/9b880c4be0a43bf3ea6fd4f5ec1d6c120e6c2b11/lib/twitter/oauth.rb</ref>

While this is a fairly practical example of using extend, in some ways it is more of a convenience than a necessity. One could just as easily create wrappers for “get” and “post” that pass the calls off to access_token. It does, however, result in cleaner and more readable code, so that is a plus<ref>http://railsmagazine.com/articles/4</ref>

Active::Concern

Consider this snippet from The RubyGem Devise:

lib/devise/controllers/helpers.rb
    module Helpers
      extend ActiveSupport::Concern
</pref><ref>http://stackoverflow.com/questions/6787876/why-do-people-use-extend-in-a-module-definition</ref>

Devise is actually one of many ruby gems and modules that extend Active::Concern. To understand what this does, it helps to take a few steps back and look at one of the most common 3rd-party gems used in Rails, ActiveRecord::Base. There’s a particular idiom that’s used for ActiveRecord::Base, which the following module illustrates:
<pre>
module TagLib
 
  module ClassMethods
    def find_by_tags()
      # ...
    end
  end
 
  module InstanceMethods
    def tags()
      # ...
    end
  end
 
  def self.included(base)
    base.send :include, InstanceMethods
    base.send :extend, ClassMethods
  end
 
end
 
class ActiveRecord::Base
  include TagLib
end

Without getting into too much detail about what this does, the main point of this is that there is a pattern of code that is being repeated again and again every time someone wants to make use of the functionality of this gem. It’s also not entirely clear what benefits are gained through this code or what it’s doing, unless you’re familiar with the idiom. This same functionality, however, can be obtained with ActiveSupport:Concern through something like the following:

module TagLib
  extend ActiveSupport::Concern
 
  module ClassMethods
    def find_by_tags()
      # ...
    end
  end
 
  module InstanceMethods
    def tags()
      # ...
    end
  end 
end
 
class ActiveRecord::Base
  include TagLib
end

Again this accomplishes exactly the same thing as before, but with much less code and all wrapped up in a nice package that people can take a look at and dive into if they want additional details about how everything works.<ref>http://www.fakingfantastic.com/2010/09/20/concerning-yourself-with-active-support-concern/</ref>

Singleton Pattern

Another common convenience that extend provides is the ability to convert all instance methods into class methods:

module Rake
  include Test::Unit::Assertions

  def run_tests # etc.
  end

  extend self
end

<ref> http://stackoverflow.com/questions/1733124/ruby-extend-self</ref>

Without extend, in order to make use of this module, one would have to include it in a class, create an instance of that class, and then call methods from that instance. With the extend, however, it’s perfectly legal to make calls like this:

Rake.run_test

In particular, using extend in this way is a more convenient way to create Singleton objects in Ruby. Observe how a Singleton object would traditionally be created:

require 'net/http'

# first you setup your singleton
class Cheat
  include Singleton
  
  def initialize
    @host = 'http://cheat.errtheblog.com/'
    @http = Net::HTTP.start(URI.parse(@host).host)
  end
  
  def sheet(name)
    @http.get("/s/#{name}").body
  end
end

# then you use it
Cheat.instance.sheet 'migrations'
Cheat.instance.sheet 'yahoo_ceo'

Including the Singleton module provides adds the “instance” method to the class Cheat, which allows it to be used as a singleton rather than having to create instances of Cheat. Using extend, however, we can execute the singleton pattern in a much tighter fashion.

require 'net/http'

# here's how we roll
module Cheat
  extend self

  def host
    @host ||= 'http://cheat.errtheblog.com/'
  end

  def http
    @http ||= Net::HTTP.start(URI.parse(host).host)
  end

  def sheet(name)
    http.get("/s/#{name}").body
  end
end

# then you use it
Cheat.sheet 'migrations'
Cheat.sheet 'singletons'

Notice how the awkward “instance” is no longer necessary, nor even is the Singleton module itself. Instead, method calls can be made directly to the Cheat module in a very readable fashion. As a side benefit, it’s still possible to include the module in a class if necessary and create instances just as you would normally. In typical Ruby fashion, you aren’t restricted from creating instances, as you might be if you implemented the singleton pattern in a language like Java, but the intention to create a Singleton is very clear and easy to use.<ref>http://ozmm.org/posts/singin_singletons.html</ref>

Extend in other languages

Extend in Java

Java, like other object-orientated languages, supports class inheritance. Inheritance allows one class to "inherit" the properties of another class. Inheritance offers a radically different outlook on the re-use of code. Extend is a functionality that provides a way to inherit the fields and methods of a class by other class. This is done by extending the the class or implementing interfaces. This concept of extending the functionality is called Inheritance. In Java, when we wish to extend the usefulness of a class, we can create a new class that inherits the attributes and methods of another. We don't need a copy of the original source code to extend the usefulness of a library. We simply need a compiled '.class' file, from which we can create a new enhancement. Furthermore, instead of re-writing a class to provide a new function, we can simply declare we wish to extend a class, and add a few lines of code to get an enhancement. It makes the process of re-using code much simpler, and code easier to read. When we extend an existing class, we 'inherit' all of the attributes, and methods, of the parent class which are not static, final or private. This makes programming much easier, and simpler, as we don't re-invent the wheel each time we extend other classes.

For example, all Java objects are inherited from the java.lang.Object class. This means that we can call the toString() method inherited from java.lang.Object, and get a string representation of any java object, such as an Integer, a Float, a Double, etc. Take a look at the following example, which demonstrates the use of the 'extends' keyword.

public class A extends java.lang.Object {
        public int number;

        public String toString() {
                return new String("Value : " + number);
        }
}

In this example, we implicitly state that we extend java.lang.Object, and override the functionality of the toString() method (present in java.lang.Object class) by providing our own function. Note that all classes, whether they state so or not, will be inherit from java.lang.Object. Our next example shows a more common use of the keyword 'extends'. In this example, we inherit from class A, which means that B will also contain a field called number, and a function called toString().

public class B extends A {
        public void increment() {
                number++;
        }
}

public class ABDemo {

        public static void main (String args[]) {
                // Create an instance of B
                B counter = new B();

                // Increment B
                counter.increment();

                // Call toString() method
                System.out.println ( counter.toString() );
        }
}

Even though we never defined a toString() method, or added a number field to the B class, it has inherited this information from A. This is a powerful feature of object-orientated programming, and can save significant time when developing classes.

In the above case, we can say that Object is base class and A is derived class or A is base class for B and B is derived class. Derived class always extends base class.An interface can also extend another interface. Extending a class is closely related to implementing an interface. A new class can extend at most one superclass, but it may implement several interfaces. Extending means adding new method definitions. Implementing means satisfying the existing interface contract by writing the proscribed method bodies. When a class or abstract class implements or extends, those methods are are available in all descendants. All descendant classes automatically implement that interface too.

The below program explains the ability of derived class to extend the fields and methods of base class.

class Box {

        double width;
        double height;
        double depth;
        Box() {
        }
        Box(double w, double h, double d) {
                width = w;
                height = h;
                depth = d;
        }
        void getVolume() {
                System.out.println("Volume is : " + width * height * depth);
        }
}

public class MatchBox extends Box {

        double weight;
        MatchBox() {
        }
        MatchBox(double w, double h, double d, double m) {
                super(w, h, d);

                weight = m;
        }
        public static void main(String args[]) {
                MatchBox mb1 = new MatchBox(10, 10, 10, 10);
                mb1.getVolume();
                System.out.println("width of MatchBox 1 is " + mb1.width);
                System.out.println("height of MatchBox 1 is " + mb1.height);
                System.out.println("depth of MatchBox 1 is " + mb1.depth);
                System.out.println("weight of MatchBox 1 is " + mb1.weight);
        }
}

Output

Volume is : 1000.0
width of MatchBox 1 is 10.0
height of MatchBox 1 is 10.0
depth of MatchBox 1 is 10.0
weight of MatchBox 1 is 10.0

Conclusion

References

<references></references>

Additional Resources