Design Patterns in Ruby: Adapter

This second post of the series leaves for a moment the creational patterns and speaks about one of the most important structural pattern: the Adapter.

The purpose of an adapter is “to convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.”

Suppose therefore to have two classes, PalGame and NtscGame, that extend a superclass Game. These subclasses respectively expose two methods: play and run.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#models.rb
class Game
  attr_accessor :title
  def initialize(title)
    @title = title
  end
end

class PalGame < Game
  def play
    puts "I am the Pal version of #{@title} and I am running!"  
  end
end

class NtscGame < Game  
  def run
    puts "I am the NTSC version of #{@title} and I am running!"  
  end
end

We then subclass a Console class with PalConsole and NtscConsole. These two classes are expected to talk with, respectively, games of kind PalGame and NtscGame.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#models.rb
class Console
end

class PalConsole < Console
  def play_game(game)
    game.play  
  end
end

class NtscConsole < Console
  def run_game(game)
    game.run  
  end
end

How can see, the method play_game of a PalConsole will call the play method of the game, the NtscConsole instead will invoke the run method.

Our goal is to let a PalConsole to run games of kind NtscGame.

Below we’ll follow the line traced by the GoF and we’ll build an Adapter class that provides the play method needed by the PalConsole’s interface:

1
2
3
4
5
6
7
8
9
10
11
#adapters.rb
class NtscToPalAdatper
  attr_accessor :game
  def initialize(game)
    @game = game
  end
 
  def play
    @game.run  
  end  
end

As you can see, the adapter exposes a simple play method that calls the run method of the NTSCGame.

The following code:

1
2
3
4
5
6
7
8
9
10
11
#main.rb
require 'models.rb'
require 'adapters.rb'

console = PalConsole.new

final_fantasy = NtscGame.new("Final Fantasy")

adapter = NtscToPalAdatper.new(final_fantasy)

console.play_game(adapter)

will produce this output:
I am the NTSC version of Final Fantasy and I am running!

We see at this point some alternatives to further exploit the potential of Ruby.

One possibility is to take advantage of the Ruby’s “open classes”. The language makes possible to add methods to an already loaded class in this way:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#main2.rb
require 'models.rb'

console = PalConsole.new

class NtscGame < Game  
  def play
    run
  end
 
  # alternatively for this simple example we can define an alias:
  # alias play run
end

final_fantasy = NtscGame.new("Final Fantasy")

double_dragon = NtscGame.new("Double Dragon")

console.play_game(final_fantasy)

console.play_game(double_dragon)

As we can see, we’ve added at runtime the method play for a NtscGame game.

The above code produces the following output:
I am the NTSC version of Final Fantasy and I am running!
I am the NTSC version of Double Dragon and I am running!

Note however that with this solution we added the play method to the entire class NtscGame. All instances that are created will be equipped with the play method.
This type of action is very risky because it could not guarantee compatibility with other libraries, for example because of name clash.

Another alternative would be to use the “singleton classes”.
Ruby makes it possible to change a single instance of a class, by creating an anonymous class as its superclass that implements the new defined method.

There are several possibilities to implement these singleton classes. Let’s see in the following snippet some implementations.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#main3.rb
require 'models.rb'

console = PalConsole.new

#1 - creating a singleton class
final_fantasy = NtscGame.new("Final Fantasy")

def final_fantasy.play
  run  
end

console.play_game(final_fantasy)


#2 - adding methods opening the singleton class directly
winning_eleven = NtscGame.new("Winning Eleven")

class << winning_eleven
  def play
    run
  end
end

console.play_game(winning_eleven)


#3 - adding methods from a module
thunderforce = NtscGame.new("Thunderforce")

module Foo
  def play
    run
  end
end

thunderforce.extend(Foo)

console.play_game(thunderforce)


#4 - adding methods inside an instance_eval call
dragons_lair = NtscGame.new("Dragons Lair")

dragons_lair.instance_eval <<EOT
  def play
    run
  end
EOT


console.play_game(dragons_lair)

All implementations will provide the same type of output:
I am the NTSC version of Final Fantasy and I am running!
I am the NTSC version of Winning Eleven and I am running!
I am the NTSC version of Thunderforce and I am running!
I am the NTSC version of Dragons Lair and I am running!

In this post we have therefore seen the usefulness of this design patterns and how Ruby allows the developer to “leave” the classic implementation suggested in order to better take advantage of the potentiality of the language.

Source code here

Previous posts from this series:
Design Patterns in Ruby: Introduction
Design Patterns in Ruby: Abstract Factory


Excellent door hangers online printing available at PsPrint


Tags: ,


About Stefano

Stefano Mancini is a co-founder of DevInterface.

After graduating in Computer Science, he first specialized in Java/J2EE development by participating in several international projects in the pharmaceutical and banking ambits.

Enthusiast of agile development, like SCRUM for project management and eXtreme Programming for code writing, he then moved to dynamic languages like Ruby and Python.

About DevInterface

We are an information and communication technology agency. Our mission is to provide web application development, design services and communication strategies. We specialize in building web applications with modern and efficient frameworks.

Related Post

5 Responses to “Design Patterns in Ruby: Adapter”

  1. [...] This post was mentioned on Twitter by Jérémie Anderlin, Stefano Mancini. Stefano Mancini said: My new article on design patterns in Ruby: Adapter. http://ping.fm/fADFu [...]

  2. Sohan says:

    I linked to this post at the Drink Rails blog as one of the top Ruby on Rails related posts of the day.

  3. Stefano says:

    Thanks so much Sohan!

  4. Brilliant! I didn’t realise I had so much more to learn!

Leave a Reply

Insert code beetween <code lang="ruby"> and </code>

Copyright 2012 DevInterface s.n.c.

DevInterface Blog is proudly powered by WordPress