Design pattern Proxy
🎯 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.