Design pattern Proxy

code 22 juin 2025
🎯 Objectif : éviter encore une fois d'implémenter dans une même classe des fonctions qui ne lui sont pas intrinsèques.

Anti-pattern

 On créé une classe CompteBancaire qui intègre et implémente le contrôle d'accès

class CompteBancaire  
    attr_reader :balance  
      
    def initialize(init_balance: , utilisateur: )  
        @utilisateur = utilisateur  
        @liste_utilisateurs = ['romain','toto','titi']  
        @balance = init_balance  
    end  
  
    def controle_acces  
        unless @liste_utilisateurs.include?(@utilisateur)  
            raise "Acces interdit : utilisateur  #{@utilisateur} pas présent "  
        end  
    end  
          
    def debit(montant)  
        controle_acces  
        @balance -= montant  
    end  
      
    def credit(montant)  
        controle_acces  
        @balance += montant  
    end  
end  
  
compte = CompteBancaire::new(init_balance: 1000, utilisateur: 'romain')  
puts "Balance initiale #{compte.balance}"  
compte.debit(100)  
compte.credit(700)  
puts "Balance Actuelle #{compte.balance}"  
❌ Problème : CompteBancaire est une "boite à outils" 

Pattern

 On vient isoler le contrôle d'accès sur le proxy

Le CompteBancaire n'implémente plus que les opérations et fonction strictement lié à l'usage bancaire. Si on souhaite modifier à postériori les mécanismes de contrôle, aucun changement ne sera nécessaire sur L'objet CompteBancaire

ℹ️ Remarque : l'usage du compte sans le proxy reste possible, mais donc sur tout usage en interface extérieur (API, IHM, CLI) il sera donc indispensable de passer par le Proxy, il est donc plus prudent de faire une Factory dédié à instancier un compte bancaire selon l'usage.
class CompteBancaire  
    attr_reader :balance  
      
    def initialize(init_balance: 0)  
        @balance = init_balance  
    end  
          
    def debit(montant)  
        @balance -= montant  
    end  
      
    def credit(montant)  
        @balance += montant  
    end  
end  
  
compte = CompteBancaire::new(init_balance: 1000)  
puts "Balance initiale #{compte.balance}"  
compte.debit(100)  
compte.credit(700)  
puts "Balance Actuelle #{compte.balance}"  
  
class ProxyCompteBancaire  
    def initialize(uncompte,utilisateur)  
        @sujet = uncompte  
        @utilisateur = utilisateur  
        @liste_utilisateurs = ['romain','toto','titi']  
    end  
      
    def method_missing(name,*args)  
        controle_acces  
        @sujet.send(name,*args)  
    end  
      
    def controle_acces  
        unless @liste_utilisateurs.include?(@utilisateur)  
            raise "Acces interdit : user #{@utilisateur} pas présent "  
        end  
    end  
      
end  
  
begin  
    compteproxy = ProxyCompteBancaire::new(compte,'romaing')  
    puts "Balance Actuelle #{compte.balance}"  
rescue => exception  
    puts exception.message  
end  
  
compteproxy = ProxyCompteBancairee::new(compte,'romain')  
compteproxy.debit 500  
puts "Balance Actuelle #{compte.balance}"  
 ℹ️ Note : En Ruby le Proxy est simplifié par l'usage de method_missing, qui s'execute dès qu'on tente l'appelle à une méthode qui n'existe et que l'on implémente de façon concrète.
👆 Remarque : Même en Ruby, qui offre les mixin même en runtime, le Proxy reste la Ruby-way, car cela obligerait de toute façon à des surcharges pour exécuter en filtre sur chaque méthodes le contrôle d'accès.

Mots clés

Romain GEORGES

Open Source evangelist & Ruby enthousiast