Design Patterns in Ruby: Adapter

Questo secondo post della serie abbandona momentaneamente i creational patterns e tratta uno degli structural pattern più importanti: l’Adapter.

Lo scopo di un adapter è quello di convertire l’interfaccia di una classe in una richiesta dall’oggetto client.
Grazie ad un adapter è quindi possibile far interagire due classi dotate di interfacce tra loro incompatibili.

Supponiamo dunque di avere due classi, PalGame e NtscGame, che estendono una superclasse Game. Queste sottoclassi espongono rispettivamente due metodi: play e 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

Abbiamo poi una classe Console estesa da PalConsole e NtscConsole. Queste due classi si aspettano di dialogare rispettivamente con giochi di tipo PalGame e 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

Come si puo’ infatti notare, il metodo play_game di una console PalConsole chiamerà il metodo play del gioco passato come argomento; la console NtscConsole chiamerà invece il metodo run.

Il nostro obiettivo è quello di parmettere ad una PalConsole di poter lanciare dei giochi di tipo NtscGame.

L’approccio che segue la linea tracciata dai GoF prevede di costruire una classe Adapter in grado di fornire alla PalConsole l’interfaccia play di cui ha bisogno:

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

Vediamo dunque che l’adapter espone semplicemente un metodo play che invocherà il metodo run del gioco NtscGame.

Il seguente codice

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)

fornirà il seguente output:
I am the NTSC version of Final Fantasy and I am running!

A questo punto mostriamo delle alternative in grado di sfruttare maggiormente le potenzialità di Ruby.

Una prima possibilità è quella di utilizzare le “open classes” di Ruby: il linguaggio permette infatti di aggiungere metodi ad una classe già caricata, in questo modo:

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)

Come possiamo notare, abbiamo aggiunto a runtime il metodo play al gioco NtscGame.

Il codice sopra produce il seguente 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!

Notiamo però che con questa soluzione abbiamo aggiunto il metodo play all’intera classe NtscGame. Tutte le istanze che verranno create, saranno dunque dotate del metodo play.
Questo tipo di intervento è comunque molto rischioso, in quanto potrebbe poi non garantire la compatibilità con eventuali altre librerie a causa di quelche name clash.

Un’altra alternativa potrebbe essere quella di utilizzare delle “singleton classes”.
Ruby permette infatti di modificare una singola istanza di una classe, creando una classe anonima come sua superclasse che implementa il nuovo metodo dichiarato.

Esistono numerose possibilità per implementare queste singleton classes. Vediamo nello snippet sottostante le varie implementazioni.

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)

Tutte li implementazioni forniscono la stessa tipologia di 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 questo post abbiamo dunque visto l’utilità di questo design pattern e come Ruby permetta allo sviluppatore di “uscire” dall’implementazione classica suggerita in modo da poter sfruttare maggiormente le potenzialità del linguaggio.

Codice sorgente qui

Post precedenti della serie:
Design Patterns in Ruby: Introduzione
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 scrive:

    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 scrive:

    Thanks so much Sohan!

  4. Patria Scheetz scrive:

    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