login

AdaptorPattern (Ruby)

HomePage | RecentChanges | Preferences | Wikis | RubyGarden | Feed-icon-16x16

This page contains changes that are awaiting review.
"Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces." [1]

There are two ways to implement the Adaptor pattern in Ruby: through delegation or through Ruby's open classes. The former is the most common implementation of the Adapter pattern and is the most general. The latter is more concise but is not always possible.

In demonstrating each type of adaption we will use the following contrived example. We want to put a SquarePeg into a RoundHole by passing it to the hole's peg_fits? method. The peg_fits? method checks the radius attribute of the peg, but a SquarePeg does not have a radius. Therefore we need to adapt the interface of the SquarePeg to meet the requirements of the RoundHole.

class SquarePeg
    attr_reader :width

    def initialize( width )
        @width = width
    end
end

class RoundPeg
    attr_reader :radius
    
    def initialize( radius )
        @radius = radius
    end
end

class RoundHole
    attr_reader :radius

    def initialize( r )
        @radius = r
    end
        
    def peg_fits?( peg )
        peg.radius <= radius
    end
end

Adaption by Delegation

To implement an Adaptor with delegation, create a new adaptor class that implements the required interface by making calls to the adapted object.

class SquarePegAdaptor
    def initialize( square_peg )
        @peg = square_peg
    end
    
    def radius
        Math.sqrt(((@peg.width/2) ** 2)*2)
    end
end

hole = RoundHole.new( 4.0 )
4.upto(7) do |i|
    peg = SquarePegAdaptor.new( SquarePeg.new(i.to_f) )
    if hole.peg_fits?( peg )
        puts "peg #{peg} fits in hole #{hole}"
    else
        puts "peg #{peg} does not fit in hole #{hole}"
    end
end

This produces the output

peg #<SquarePegAdaptor:0xa038b10> fits in hole #<RoundHole:0xa038bd0>
peg #<SquarePegAdaptor:0xa038990> fits in hole #<RoundHole:0xa038bd0>
peg #<SquarePegAdaptor:0xa0388a0> does not fit in hole #<RoundHole:0xa038bd0>
peg #<SquarePegAdaptor:0xa038720> does not fit in hole #<RoundHole:0xa038bd0>

Adaption through Open Classes

Ruby's open classes let you add the required methods to the class that you want to adapt. This approach is far more concise than the use of adaptor classes, and also has the advantage that you don't need to create another object.

class SquarePeg
    def radius
        Math.sqrt( ((width/2) ** 2) * 2 )
    end
end

hole = RoundHole.new( 4.0 )
4.upto(7) do |i|
    peg = SquarePeg.new(i.to_f)
    if hole.peg_fits?( peg )
        puts "peg #{peg} fits in hole #{hole}"
    else
        puts "peg #{peg} does not fit in hole #{hole}"
    end
end

This produces the output:

peg #<SquarePeg:0xa038618> fits in hole #<RoundHole:0xa038b88>
peg #<SquarePeg:0xa0384b0> fits in hole #<RoundHole:0xa038b88>
peg #<SquarePeg:0xa0383d8> does not fit in hole #<RoundHole:0xa038b88>
peg #<SquarePeg:0xa038270> does not fit in hole #<RoundHole:0xa038b88>

This approach is not possible if the adapted object and required interface both have a method with the same name, but different semantics. For example, if the RoundPeg class also had an width attribute that returned the diameter of the peg and was checked by the RoundHole's peg_fits? method it would have the same name as, but a different meaning from, the width parameter of the SquarePeg class. There would be no way of implementing both the RoundPeg and the SquarePeg interfaces in the same class.

-- NatPryce


Generalized adaptor

I've put some information on what I consider a generalized adaptor (one that lets you choose which interface you want to use) in AdaptorPattern/Generalized. -- MauricioFernandez


Also see: ExampleDesignPatternsInRuby

HomePage | RecentChanges | Preferences | Wikis | RubyGarden
Edit text of this page | View other revisions
Rev 16, Last edited at March 30, 2008 19:03 pm by anonymous / none (diff)
Find: