Design pattern Adapter

code 21 juin 2025
🎯 Objectif : éviter de faire une classe "Boite à outil", cad qui fait plus que ce qu'elle doit, et qui intègre du code d'adaption et donc du savoir spécifique, on utilise le Design pattern Adapter pour limiter l'adaptation des inputs dans la classe elle même

 Anti-pattern

class Hammer    
    def swing!  
        puts "swing"  
    end  
end  
  
class Screwdriver  
    def drive!  
          puts "drive"  
    end  
end  
  
class ToolSequence  
  def initialize(tools: [])  
    @sequence = tools  
  end  
    
  def execute!  
    @sequence.map {|tool| Object.const_get("#{tool.to_s.capitalize}")::new }.each do |tool|  
        if tool.class == Screwdriver then  
            tool.drive!  
        elsif tool.class == Hammer then  
            tool.swing!  
        end  
    end  
  end  
end  
  
sequence = ToolSequence::new tools: [:hammer, :screwdriver, :hammer]  
sequence.execute!  
 👆 Constat : La ToolSequence doit "savoir" comment utiliser chaque outil

 

Pattern pur

 On va utiliser une Classe Adapter pour chaque outil :

 

class Hammer    
    def swing!  
        puts "swing"  
    end  
end  
  
class Screwdriver  
    def drive!  
          puts "drive"  
    end  
end  
  
class HammerAdapter  
    def initialize(hammer)  
      @hammer = hammer  
    end  
    def use!  
      @hammer.swing!  
    end  
end  
  
class ScrewdriverAdapter  
    def initialize(screwdriver)  
      @screwdriver = screwdriver  
    end  
    def use!  
      @screwdriver.drive!  
    end  
end  
  
class ToolSequence  
  def initialize(tools: [])  
    @sequence = tools  
  end  
    
  def execute!  
    @sequence.map {|tool|   
        adapter = Object.const_get("#{tool.to_s.capitalize}Adapter")  
        tool = Object.const_get("#{tool.to_s.capitalize}")::new  
        adapter::new(tool)  
    }.each do |adapter|  
        adapter.use!  
    end  
  end  
end  
  
sequence = ToolSequence::new tools: [:hammer, :screwdriver, :hammer]  
sequence.execute!  

 

 👆 Constat : ici on utilise un Objet Adapter pour que La Toolsequence n'est pas de savoir spécifique lié à un objet

 => Si on vient à ajouter un outil on ne remodifiera plus la classe ToolSequence, on ajoutera son outil et l’adapter qui va avec

Pattern  Ruby-way

 Si on considèrent le cas ou l'outil n'implémente qu'une méthode d'action , la cas suivant serait bien plus Ruby-way

On utilise ici une adaptation par Mixin

ℹ️ Remarque : Cet exemple pourrait marcher avec une classe avec plus de méthodes, si on suffixe la méthode d'action par !, alors on ne cherche plus la #first mais la #select {|item| item[-1] == '!'}.first
ℹ️ Remarque 2 : self.class.instance_methods(false) renvoie la list des méthodes d'instance spécifiques à la classe (le false)
class Hammer    
    def swing!  
        puts "swing"  
    end  
end  
  
class Screwdriver  
    def drive!  
          puts "drive"  
    end  
end  
  
module ToolAdapter  
    def use!  
      self.send self.class.instance_methods(false).first  
    end  
end  
  
class ToolSequence  
  def initialize(tools: [])  
    @sequence = tools  
  end  
    
  def execute!  
    @sequence.each do |tool|   
        tool = Object.const_get("#{tool.to_s.capitalize}")::new  
        tool.extend ToolAdapter  
        tool.use!  
    end  
  end  
end  
  
sequence = ToolSequence::new tools: [:hammer, :screwdriver, :hammer]  
sequence.execute!  

Là où l'adaptation pure est utile

un exemple concret

(pas l'encrypter en lui-même qui est "ugly")

class Encrypter  
    def initialize(key)()  
        @key = key  
    end  
      
    def encrypt(reader,writer)  
        key_index = 0  
        while not reader.eof?  
            clear_char = reader.getc  
            encrypted_char = clear_char ^ @key[key_index]  
            writer.putc(encrypted_char)  
            key_index = (key_index + 1) % @key.size  
        end  
    end  
end  
  
  
reader = File.open('../Cache/message.txt')  
writer = File.open('../Cache/message.enc','w')  
encrypter = Encrypter.new('my secret key')  
encrypter.encrypt(reader,writer)  
  
class StringIOAdapter  
    def initialize(string)  
        @string = string  
        @position= 0  
    end  
      
    def getc  
        if @position >= @string.length then  
            raise EOFError    
        end  
        ch = @string[@position]  
        @position += 1  
        return ch  
    end  
      
    def eof?  
        return @position >= @string.length  
    end  
end  
  
  
  
encrypter = Encrypter.new('XYZZY')  
reader = StringIOAdapter.new('a secret story')  
writer = File.open('../Cache/message.enc','w')  
encrypter.encrypt(reader,writer) 

Mots clés

Romain GEORGES

Open Source evangelist & Ruby enthousiast