Archives de catégorie : Software

PHP – Le design pattern Flyweight

Soyons honnêtes, si vous êtes un développeur web, les chances pour que vous ayez à utiliser le design pattern Flyweight en PHP sont assez minces, en raison du fait que Flyweight est surtout utile quand vous avez un très grand nombre d’objets en RAM et qu’il vous est important d’en économiser l’instanciation, ce qui arrive rarement lorsque vous développez (bien !) une application web. Vous le trouverez davantage dans des domaines comme les traitements de texte ou les jeux vidéo qui utilisent des environnements multi-tâches (multithreaded). En PHP, le multithreading est à ce jour utilisable seulement en ligne de commande, pas dans un environnement de serveur Web.

Cela ne signifie pas pour autant que ce design pattern n’est JAMAIS utilisé, il est par exemple implémenté dans l’ORM Doctrine, pour la gestion des types.

Le principe de fonctionnement du design pattern

Flyweight signifie « poids mouche » en bon français, mais pourquoi donc ? Parce que l’idée est de garder les objets dont nous allons nous servir les plus petits possible, comme la catégorie de boxeurs à laquelle ce nom fait référence…petits certes, mais efficaces !

Ces objets vont avoir deux états:

  • un état intrinsèque, qui ne dépend pas du contexte de l’objet
  • un état extrinsèque

Le premier réside dans la classe même tandis que le second lui sera fourni lors de l’appel à une de ses méthodes.

Prenons comme objet poids mouche un produit culturel donc l’état intrinsèque, immuable, sera uniquement composé de son nom. La classe ProduitCulturel se conformera à une interface assez simple, dont l’unique méthode afficher prend en paramètre l’état extrinsèque, c’est à dire tout ce qui est susceptible de venir enrichir l’état intrinsèque de l’objet. Le contexte est passé sous forme de tableau pour garder l’exemple le plus simple possible mais un objet dédié à ces contextes serait largement préférable.

interface ProduitCulturelInterface
{
    public function affiche(array $contexte): void;
}

class ProduitCulturel implements ProduitCulturelInterface
{
    protected string $nom;

    public function __construct(string $nom)
    {
        $this->nom = $nom;
    }

    public function affiche(array $contexte): void
    {
        echo 'Nom: ' . $this->nom . PHP_EOL;

        foreach ($contexte as $cle => $valeur) {
            echo ucfirst($cle) . ': ' . $valeur . PHP_EOL;
        }
    }
}

Pour gérer nos poids mouches, nous allons devoir utiliser une fabrique, car c’est à elle que s’adressera le code client pour obtenir ces objets. Il ne doit en aucun cas réaliser lui-même les instanciations ! Voilà ladite fabrique, assez rudimentaire: elle possède une méthode getProduitCulturel qui vérifie si une instance est déjà disponible avec le nom donné. Si c’est le cas, elle la renvoie, évitant une nouvelle instanciation, sinon elle instancie la classe ciblée et la range sagement dans son tableau interne, qui sert de « parc à poids mouches » comme le nomme si joliment le livre du GoF.

class FabriqueDeProduitsCulturels
{
    protected array $produits = [];

    public function getProduitCulturel(string $nom): ProduitCulturel
    {
        if (array_key_exists($nom, $this->produits)) {
            $produit = $this->produits[$nom];
        } else {
            $produit = new ProduitCulturel($nom);
            $this->produits[$nom] = $produit;
        }

        return $produit;
    }
}

Libre à vous d’en faire une version statique, pour ma part je ne suis pas tellement en faveur des appels statiques car ils complexifient les tests unitaires.

Notre code client va utiliser une classe Commande (aucun rapport avec le design pattern du même nom !) qui proposera dans son interface publique une méthode permettant l’ajout de produits culturels. Ces produits liés à la commande seront stockés dans un tableau du même nom tandis que leur contexte ira dans un tableau dédié, au même indice. Ainsi nous avons d’un côté les poids mouches et de l’autre le contexte auquel ils sont liés. Le constructeur de notre classe Commande se verra injecter son unique dépendance, la fabrique !

Afficher les produits d’une commande consistera tout simplement à boucler sur les produits et à invoquer leur méthode d’affichage, qui requiert le contexte en paramètre.

class Commande
{
    protected FabriqueDeProduitsCulturels $fabrique;

    protected array $produits = [];

    protected array $contextes = [];

    public function __construct(FabriqueDeProduitsCulturels $fabrique)
    {
        $this->fabrique = $fabrique;
    }

    public function ajouteProduit(string $nom, array $contexte)
    {
        $produit = $this->fabrique->getProduitCulturel($nom);
        $this->produits[] = $produit;
        $this->contextes[] = $contexte;
    }

    public function afficheProduits()
    {
        foreach ($this->produits as $index => $produit) {
            $produit->affiche($this->contextes[$index]);
        }
    }
}

Voici comment tout cela sera utilisé:

$commande = new Commande(new FabriqueDeProduitsCulturels());

$commande->ajouteProduit('Livre', ['prix' => 10.99, 'titre' => '1984', 'auteur' => 'George Orwell']);
$commande->ajouteProduit('Disque', ['prix' => 19.99, 'titre' => '1984', 'auteur' => 'Van Halen']);

$commande->afficheProduits();

Pour résumer…

Flyweight n’est utile qui si vous avez un nombre trèèèèès important d’objets de type semblable (si c’est le cas, demandez-vous déjà si c’est bien normal !) car son but est d’économiser la mémoire durant le temps d’exécution d’un processus (généralement multitâches)

Il met en jeu une fabrique qui sera chargée d’instancier le cas échéant les objets poids mouches et des classes concrètes contenant des états intrinsèques. Une fois l’objet obtenu par l’utilisation de la fabrique, il ne pourra plus être modifié (il est immuable) mais le client pourra lui transmettre son état extrinsèque par l’appel d’une de ses méthodes. Ici nous avons utilisé une méthode terriblement triviale qui réalise un simple affichage mais nous aurions pu tout aussi bien invoquer un service web pour commander les produits sur une API externe.

Ce billet a été réalisé en s’inspirant librement du livre du GoF et d’un ouvrage paru chez ENI Éditions.

Ruby: design pattern Adapter

Adapt or die!

Le design pattern Adapter (ou Adaptateur en bon français) a déjà été évoqué ici-même il y a quelques années, aussi vous laisserai-je le plaisir d’aller y voir les détails d’implémentation si toutefois le code proposé ici ne vous parlait pas immédiatement !

Rafraîchissons-nous la mémoire

L’exemple est rigoureusement le même, si l’on excepte les particularités dues au langage Ruby (pas de classe abstraite, pas d’interface), mais je vais en rappeler les contours: nous avons un inspecteur du permis de conduire qui fait faire les mêmes manœuvres à tous ses candidats. L’ennui est que ces manœuvres ne s’effectuent pas de la même façon selon que l’on conduise un bateau à voile, à moteur, une voiture, une moto etc. Ainsi, notre inspecteur, qui manifestement n’a pas très envie de changer ses pratiques, va travailler avec des adaptateurs et non plus directement avec les classes feuilles de l’arbre matérialisant la chaîne d’héritage. Il n’y verra que du feu car ce sont les adaptateurs qui feront le travail à sa place !

Allez, en voiture !

class InspecteurPermisConduire

  def initialize(conducteur)
    @candidat = conducteur
  end

  def changerCandidat(conducteur)
    @candidat = conducteur
  end

  def fairePasserExamen
    @candidat.demarrer
    @candidat.accelerer
    @candidat.tournerDroite
    @candidat.accelerer
    @candidat.tournerGauche
    @candidat.ralentir
    @candidat.reculer
    @candidat.immobiliser
  end

  private

  attr_reader :candidat
end

class Conducteur
  def initialize(conducteur)
    raise "Impossible d'instancier la classe Conducteur"
  end

  def demarrer
    raise NotImplementedError
  end

  def tournerGauche
    raise NotImplementedError
  end

  def tournerDroite
    raise NotImplementedError
  end

  def accelerer
    raise NotImplementedError
  end

  def ralentir
    raise NotImplementedError
  end

  def reculer
    raise NotImplementedError
  end

  def immobiliser
    raise NotImplementedError
  end
end

class Automobiliste

  def demarrer
    puts "tourner la clé de contact ou mettre la carte"
  end

  def tournerGauche
    puts "tourner le volant vers la gauche"
  end

  def tournerDroite
    puts "tourner le volant vers la droite"
  end

  def accelerer
    puts "appuyer sur la pédale d'accélération"
  end

  def ralentir
    puts "relâcher la pédale d'accélération et/ou " +
             "appuyer sur la pédale de frein"
  end

  def reculer
    puts "passer la marche arrière et accélérer"
  end

  def immobiliser
    puts "mettre le frein à main"
  end
end

class Navigateur
  def initialize
    raise "Impossible d'instancier la classe abstraite Navigateur"
  end

  def demarrer
    raise NotImplementedError
  end

  def reculer
    raise NotImplementedError
  end

  def tournerBabord
    raise NotImplementedError
  end

  def tournerTribord
    raise NotImplementedError
  end

  def accelerer
    raise NotImplementedError
  end

  def ralentir
    raise NotImplementedError
  end

  def jeterAncre
    raise NotImplementedError
  end
end

class Marin

  def initialize
    raise "Impossible d'instancier la classe abstraite Marin"
  end

  # méthode commune à tous les marins
  def jeterAncre
    puts "jeter l'ancre à la mer"
  end
end

class MarinVoile < Marin

  def initialize
    # vide mais on peut imaginer de la logique ici
  end

  def demarrer
    puts "Cette fonctionnalité n'est pas disponible"
  end

  def tournerBabord
    puts "diriger les voiles et la barre pour aller à babord"
  end

  def tournerTribord
    puts "diriger les voiles et la barre pour aller à tribord"
  end

  def accelerer
    puts "positionner les voiles et déterminer l'allure"
  end

  def ralentir
    puts "positionner les voiles et déterminer l'allure"
  end

  def reculer
    puts "positionner les voiles et manœuvrer pour reculer"
  end
end

class MarinMoteur < Marin

  def initialize
    # vide mais on peut imaginer de la logique ici
  end

  def demarrer
    puts "démarrer le moteur"
  end

  def tournerBabord
    puts "manoeuvrer la barre ou le volant pour aller à babord"
  end

  def tournerTribord
    puts "manoeuvrer la barre ou le volant pour aller à tribord"
  end

  def accelerer
    puts "augmenter la vitesse du moteur"
  end

  def ralentir
    puts "dimininuer la vitesse du moteur ou le couper"
  end

  def reculer
    puts "passer la marche arrière"
  end
end

class AdaptateurMarin

  def initialize(marin)
    @marin = marin
  end

  def demarrer
    @marin.demarrer
  end

  def tournerGauche
    @marin.tournerBabord
  end

  def tournerDroite
    @marin.tournerTribord
  end

  def accelerer
    @marin.accelerer
  end

  def ralentir
    @marin.ralentir
  end

  def reculer
    @marin.reculer
  end

  def immobiliser
    @marin.jeterAncre
  end

  private

  attr_reader :marin
end

puts "AUTOMOBILISTE"
inspecteur = InspecteurPermisConduire.new(Automobiliste.new)
inspecteur.fairePasserExamen
puts "MARIN MOTEUR"
adaptateur = AdaptateurMarin.new(MarinMoteur.new)
inspecteur = InspecteurPermisConduire.new(adaptateur)
inspecteur.fairePasserExamen
puts "MARIN VOILE"
adaptateur = AdaptateurMarin.new(MarinVoile.new)
inspecteur.changerCandidat(adaptateur)
inspecteur.fairePasserExamen

Ruby: design pattern Builder

Veuillez monter !

Le design pattern Builder, appelé « Monteur » en français est un design pattern de création dont le but est d’aider à créer des objets complexes. Pour cela, il intègre une classe appelée directeur qui contrôle l’algorithme de construction.

Composants du design pattern Builder

Les composants participant à ce pattern sont les suivants:

  • Le directeur, qui prend en composition un monteur
  • Une abstraction pour les monteurs
  • Des monteurs concrets
  • Des parties
  • Des produits (la cible finale !)

Dans l’exemple qui suit, nous aurons:

  • Des monteurs de camions et de voitures
  • Des produits : Camion et Voiture
  • Des parties : porte, moteur, roue
  • Un directeur qui orchestre la construction de tout ça

Nos abstractions seront des classes abstraites simulées (pas d’interface en Ruby, ni de classes abstraites stricto sensu).

class Piece
  def donnerNom
    raise "Impossible d'instancier la classe ProduitEntretien"
  end
end

class PorteCamion < Piece
  def donnerNom
    "Porte de camion"
  end
end

class PorteVoiture < Piece
  def donnerNom
    "Porte de voiture"
  end
end

class RoueCamion < Piece
  def donnerNom
    "Roue de camion"
  end
end

class RoueVoiture < Piece
  def donnerNom
    "Roue de voiture"
  end
end

class MoteurVoiture < Piece
  def donnerNom
    "Moteur de voiture"
  end
end

class MoteurCamion < Piece
  def donnerNom
    "Moteur de camion"
  end
end

class Vehicule

  def initialize
    @configuration = {}
  end

  def ajouter_piece(nom, piece)
    @configuration[nom] = piece
  end

  protected

  attr_reader :configuration
end

class Camion < Vehicule
end

class Voiture < Vehicule
end

class Directeur

  def initialize (monteur)
    @monteur = monteur
  end

  def monter
    @monteur.creer_vehicule
    @monteur.poser_portes
    @monteur.poser_moteur
    @monteur.poser_roues
    @monteur.peindre
  end

  private

  attr_reader :monteur
end

class Monteur

  def initialize
    raise "Impossible d'instancier la classe abstraite Monteur"
  end

  def creer_vehicule
    raise NotImplementedError
  end

  def poser_portes
    raise NotImplementedError
  end

  def poser_moteur
    raise NotImplementedError
  end

  def poser_roues
    raise NotImplementedError
  end

  def peindre
    raise NotImplementedError
  end

  def donner_vehicule
    raise NotImplementedError
  end
end

class MonteurVoitures < Monteur

  def initialize(options)
    @options = options
  end

  def creer_vehicule
    @voiture = Voiture.new
  end

  def poser_portes
    @voiture.ajouter_piece('porte AVG', PorteVoiture.new)
    @voiture.ajouter_piece('porte AVD', PorteVoiture.new)
    @voiture.ajouter_piece('porte arrière', PorteVoiture.new)

    if 3 < @options['nb_portes']
      @voiture.ajouter_piece('porte ARG', PorteVoiture.new)
      @voiture.ajouter_piece('porte ARD', PorteVoiture.new)
    end
  end

  def poser_moteur
    @voiture.ajouter_piece('moteur', MoteurVoiture.new)
  end

  def poser_roues
    @voiture.ajouter_piece('roue AVG', RoueVoiture.new)
    @voiture.ajouter_piece('roue AVD', RoueVoiture.new)
    @voiture.ajouter_piece('roue ARG', RoueVoiture.new)
    @voiture.ajouter_piece('roue ARD', RoueVoiture.new)
  end

  def peindre
    puts "Je peins la voiture en " + @options['couleur_peinture']
  end

  def donner_vehicule
    @voiture
  end

  private

  attr_reader :voiture, :options
end


class MonteurCamions < Monteur

  def initialize(options)
    @options = options
  end

  def creer_vehicule
    @camion = Camion.new
  end

  def poser_portes
    @camion.ajouter_piece('porte AVG', PorteCamion.new)
    @camion.ajouter_piece('porte AVD', PorteCamion.new)
  end

  def poser_moteur
    @camion.ajouter_piece('moteur', MoteurCamion.new)
  end

  def poser_roues
    @camion.ajouter_piece('roue AVG', RoueCamion.new)
    @camion.ajouter_piece('roue AVD', RoueCamion.new)
    @camion.ajouter_piece('roue ARG', RoueCamion.new)
    @camion.ajouter_piece('roue ARD', RoueCamion.new)
  end

  def peindre
    puts "Je peins le camion en " + @options['couleur_peinture']
  end

  def donner_vehicule
    @camion
  end

  private

  attr_reader :camion, :options
end


monteur_voitures = MonteurVoitures.new({'nb_portes' => 5, 'couleur_peinture' => 'rouge'})
monteur_camions = MonteurCamions.new({'couleur_peinture' => 'bleu'})

[monteur_voitures, monteur_camions].each do |monteur|
  directeur = Directeur.new(monteur)
  directeur.monter
  vehicule = monteur.donner_vehicule
end

Ruby: design pattern Abstract Factory

Abstract Factory vs. Factory

La différence entre le design pattern Abstract Factory et Factory est très minime: Factory ne fabrique qu’un type de produit (d’objet) alors qu’Abstract Factory crée des familles de produits, fonctionnellement proches.

Choisir les abstractions

Pour que les abstractions qui sont à la racine de l’arbre représentant la hiérarchie d’héritage demeurent des classes abstraites (simulées, car de telles classes n’existent pas en Ruby), j’y ai placé le constructeur en le forçant à lever une exception pour rendre l’instanciation impossible, ce qui est le propre d’une classe abstraite. Ceci nous force à « répéter » la méthode initialize dans les classes filles, mais ce n’est pas bien grave car le jour où elles seront amenées à différer selon les cas, elles seront déjà isolées dans les bonnes sous-classes. Le but était de rester le plus fidèle possible à ce qu’est ce design pattern, tout en prenant en compte les contraintes du langage.

class ProduitEntretien
  def initialize
      raise "Impossible d'instancier la classe ProduitEntretien"
  end

  def points_vente
    raise NotImplementedError
  end

  attr_reader :caracteristiques
end

class LessiveIndustrielle < ProduitEntretien
  def initialize(caracteristiques)
    @caracteristiques = caracteristiques
  end

  def points_vente
    ['Grande Distribution']
  end
end

class ProduitVaisselleIndustriel < ProduitEntretien
  def initialize(caracteristiques)
    @caracteristiques = caracteristiques
  end

  def points_vente
    ['Grande Distribution']
  end
end

class LessiveEcologique < ProduitEntretien
  def initialize(caracteristiques)
    @caracteristiques = caracteristiques
  end

  def points_vente
    ['Grande Distribution', 'Supérettes Bio', 'Marchés']
  end
end

class ProduitVaisselleEcologique < ProduitEntretien
    def initialize(caracteristiques)
        @caracteristiques = caracteristiques
    end
  def points_vente
    ['Supérettes Bio', 'Marchés']
  end
end

class FabriqueProduitEntretien
  def initialize
    raise "Impossible d'instancier la classe FabriqueProduitEntretien"
  end

  def fabriquer_lessive
    raise NotImplementedError
  end

  def fabriquer_produit_vaisselle
    raise NotImplementedError
  end
end

class FabriqueProduitEntretienEcologique < FabriqueProduitEntretien
  def initialize
    # TODO: à compléter
  end

  def fabriquer_lessive
    LessiveEcologique.new(['tensioactifs' => 'naturels', 'colorants' => 'naturels', 'parfum' => 'huiles essentielles'])
  end

  def fabriquer_produit_vaisselle
    ProduitVaisselleEcologique.new(['base' => 'savon Marseille', 'additifs' => ['soude', 'vinaigre']])
  end
end

class FabriqueProduitEntretienIndustriel < FabriqueProduitEntretien
  def initialize
    # TODO: à compléter
  end

  def fabriquer_lessive
    LessiveIndustrielle.new(['tensioactifs' => 'chimiques', 'colorants' => 'chimiques', 'parfum' => 'synthétiques'])
  end

  def fabriquer_produit_vaisselle
    ProduitVaisselleIndustriel.new(['tensioactifs' => 'chimiques', 'colorants' => 'chimiques', 'parfum' => 'synthétiques'])
  end
end

fabrique_industrielle = FabriqueProduitEntretienIndustriel.new
lessive_industrielle = fabrique_industrielle.fabriquer_lessive
produit_vaisselle_industriel = fabrique_industrielle.fabriquer_produit_vaisselle

fabrique_ecologique = FabriqueProduitEntretienEcologique.new
lessive_ecologique = fabrique_ecologique.fabriquer_lessive
produit_vaisselle_ecologique = fabrique_ecologique.fabriquer_produit_vaisselle

p produit_vaisselle_industriel.points_vente
p produit_vaisselle_ecologique.points_vente

p produit_vaisselle_industriel.caracteristiques
p produit_vaisselle_ecologique.caracteristiques

Ruby on Rails: design pattern Factory

Pour traiter du design pattern Factory en Ruby (dans le framework Rails), j’ai repris les exemples faits en PHP il y a déjà 7 ans !

Comme les principes ne diffèrent pas d’un langage à l’autre, seul le code figurera ici. Pour les explications, je vous renvoie au lien ci-dessus !

# on importe de quoi utiliser constantize
require 'active_support/inflector'

# l'interface des fabriques (ici une classe abstraite)
class FabriqueFacture
  def initialize
    raise "Impossible d'instancier la classe abstraite FabriqueFacture"
  end

  def fabriquer
    raise NotImplementedError
  end
end

# les fabriques concrètes
class FabriqueEnteteFacture < FabriqueFacture
  CLASSE_CIBLE = 'Entete'
  private_constant :CLASSE_CIBLE

  def initialize
    # on écrase le constructeur qui lève une exception dans la classe mère
  end

  def fabriquer
    CLASSE_CIBLE.constantize.new
  end
end

class FabriqueCorpsFacture < FabriqueFacture
  CLASSE_CIBLE = 'Corps'
  private_constant :CLASSE_CIBLE

  def initialize(produits)
    @produits = produits
  end

  def fabriquer
    CLASSE_CIBLE.constantize.new(@produits)
  end

  private

  attr_accessor :produits
end

class FabriquePiedPageFacture < FabriqueFacture
  CLASSE_CIBLE = 'PiedPage'
  private_constant :CLASSE_CIBLE

  def initialize
    # même remarque que pour le constructeur de FabriqueEnteteFacture
  end

  def fabriquer
    CLASSE_CIBLE.constantize.new
  end
end

# les produits CONCRETS
class PiedPage
  def formater
    "Je formate mon pied de page"
  end
end

class Entete
  def formater()
    "Je formate mon entête"
  end
end

class Corps
  def initialize (produits)
    @produits = produits
  end

  def formater
    "Je formate mon corps avec mes #{@produits.count} produits"
  end
end

# le code client pour tester toute la chaîne

class Facturation
  def initialize(fabrique_entete, fabrique_corps, fabrique_pied)
    if !((fabrique_entete.is_a? FabriqueFacture) && 
        (fabrique_corps.is_a? FabriqueFacture) && 
        (fabrique_pied.is_a? FabriqueFacture))
      raise "Les fabriques doivent dériver FabriqueFacture"
    end
    @entete = fabrique_entete.fabriquer
    @corps = fabrique_corps.fabriquer
    @pied = fabrique_pied.fabriquer
  end

  def declencher
    puts @entete.formater
    puts @corps.formater
    puts @pied.formater
  end

  private

  attr_accessor :entete, :corps, :pied
end

produits = ['Gourde', 'Ballon', 'Pioche']

facturation = Facturation.new(
    FabriqueEnteteFacture.new,
    FabriqueCorpsFacture.new(produits),
    FabriquePiedPageFacture.new
)

facturation.declencher

Aparté – Vol au dessus d’un service informatique

Voilà quelques années j’étais en poste dans une entreprise qui avait commandé un audit à un cabinet d’expert afin de procéder à une réorganisation. Le but était fort louable : améliorer les flux d’information entre les services pour que les projets informatiques soient livrés à temps, conformes aux spécifications et sans bugs. A cette occasion, j’avais rédigé un petit mémo à l’intention des auditeurs…Il est finalement resté sur un disque dur et je ne suis retombé dessus que tout récemment.

Vous reconnaîtrez certainement des mauvaises habitudes qui ont cours dans votre entreprise car ces maux là sont hélas génériques !

Chronique d’un échec annoncé

Tout d’abord j’évoquais dans ce document le passé du service…j’aurais pu tout aussi bien parler de PASSIF !

  • L’équipe précédente, composée de 2 à 3 personnes, avait développé à la hâte un site truffé de bugs, elle avait été intégralement remerciée par la direction et nous avions été recrutés pour colmater les brèches et éteindre les incendies (XSS, injections SQL, mots de passes en clair et j’en passe) qui se déclaraient sur le site au fur et à mesure que nous découvrions le code écrit (rien n’était documenté).
  • Les délais de développement étaient sous-estimés pour faire plaisir aux services concernés. On appelle ça « vendre du rêve ». Le rêve est vite déçu.

La conséquence de tout ça a été la totale absence de crédibilité des développeurs qui passaient grosso modo pour un ramassis de fumistes incompétents. Parfois un « Pourquoi ça prend si longtemps ? La précédente équipe le faisait en 10 secondes ! » fusait, signifiant que le chemin était encore long vers la compréhension du fonctionnement normal d’un service informatique. Il fallait alors répondre calmement « Certes, mais vous avez vu ce que ça a donné ! » pour obtenir un silence gêné et contrit.

Les origines de la démotivation ou comment saborder un service en quelques leçons

  • Le pair programming était la règle, les binômes changeaient toutes les deux semaines…trop court !
  • Embauchés pour faire du Symfony 2, les développeurs se retrouvaient à corriger des bugs en PHP pur ou des requêtes SQL à longueur de journée. « Bientôt vous passerez sur des nouveaux projets ! ». Mais oui…
  • « Il est urgent d’attendre » était la règle: « Attendez l’arrivée du nouveau DSI ! », « Attendez la réorg’ ! »…Le feu ravageait le front, les projets naviguaient à vue et il fallait attendre sans cesse.
  • Les ambitions mais surtout les compétences des développeurs étaient pour le moins variées…certains refusaient de se remettre en question techniquement et livraient du junk code par bennes entières, ces trashers hermétiques à l’évolution nuisaient grandement à la qualité du code produit et les bons en avaient marre de payer pour les dilettantes…
  • Certains développeurs ont tenté d’introduire le TDD, SCRUM, les tests unitaires et fonctionnels. On leur a gentiment fait comprendre que c’était sympa tout ça mais qu’on avait pas le temps pour le moment. Mais promis plus tard on s’y intéressera…

Chacun sa place

  • Pourquoi le marketing continue de venir voir en douce les développeurs alors que des chefs de projets ont été embauchés ?
  • Pourquoi les specs sont-elles encore dictées à la machine à café, sur un coin de table ou entre deux portes alors que des process ont été mis en place, souvent après de longues négociations avec toutes les parties concernées ? L’impatience, le syndrome du gamin frustré qui n’a pas ce qu’il veut dans la seconde…
  • Les 320 allers-retours entre le marketing et le service info démotive les développeurs, qui sont bien incapables de dire quelle tâche ils auront à effectuer le lendemain, ni même si le projet sera poursuivi ou abandonné (combien de dizaines de milliers d’euros ont été foutus à la poubelle parce que finalement « ce projet n’est plus prioritaire » ?)
  • Les demandeurs pensent QUANTITÉ, les développeurs aimeraient faire de la QUALITÉ ! Ils se sentiraient bien mieux et auraient davantage envie de s’impliquer !

Vous reprendrez bien du GIGO (Garbage In, Garbage Out) ?

Traduction française : On ne récolte que ce que l’on sème ! Des spécifications floues donnent du code bancal et donc difficile à maintenir et encore plus à faire évoluer. On ne perd pas son temps en rédigeant une demande claire, on perd 10 fois plus de temps quand il faut réusiner un plat de spaghetti que même Chuck Norris ne saurait démêler.

Quelques portes ouvertes à enfoncer :

  • Coder c’est cool, tester c’est encore mieux ! Il nous est arrivé de tester en Prod ! Pourquoi ?
  • Mal spécifier c’est faire perdre du temps et donc de l’argent à sa boîte ! C’est provoquer de nombreux aller-retours qui font passer chaque service pour un ramassis de clowns
  • Le changement est bien mieux encaissé par les développeurs quand il n’a pas lieu 10 fois par jour !
  • Un interlocuteur et pas 25 ! Pourquoi Tic vient 5 minutes après Tac dire tout le contraire de ce sur quoi nous nous sommes mis d’accord après d’âpres négociations ?

Ruby On Rails – utiliser first_or_create

Lorsque vous utilisez ActiveRecord, vous vous intéressez forcément tôt ou tard à la fonction first_or_create et en général, vous l’utiliser comme suit:


MonJoliModele.first_or_create(attribut: "unevaleur")

Ce qu’on pense faire…

En procédant ainsi, vous pensez « Soit un enregistrement avec comme valeur du champ attribut « unevaleur » est trouvé, soit je vais en créer un ». Et bien non ! Ouvrez-vous un rails console et regardez ce qui se passe en réalité. Et oui, Rails prend le premier enregistrement de la table liée au modèle, sans même tenir compte de la valeur du champ « attribut » que vous lui donnez, pensant logiquement qu’il allait en faire quelque chose…Évidemment si votre table est vide alors Rails créera cet enregistrement avec la valeur passée en paramètre, mais c’est loin d’être tout le temps le cas.

Développeur sceptique: ne me dites pas que vous n'avez jamais fait cette tête, vous mentiriez !

Développeur sceptique: ne me dites pas que vous n’avez jamais fait cette tête, vous mentiriez !


Ce que vous vouliez faire en réalité, c’était:

MonJoliModele.where(attribut: "unevaleur").first_or_create(attribut: "unevaleur")

ou, plus élégant :

critere = {attribut: "unevaleur"}
MonJoliModele.where(critere).first_or_create(critere)

Attention donc aux effets de bord possiblement dus à la mauvaise utilisation de cette fonction. Notez qu’on peut utiliser un bloc aussi. Mais attention, il n’est exécuté qu’en cas de création d’un nouvel enregistrement et vous l’utiliserez évidemment si vous avez de nombreuses affectations à y faire, et pas forcément deux comme dans mon exemple:


critere = {attribut: "unevaleur"}
MonJoliModele.where(critere).first_or_create do |modele|
modele.autreattribut = "une autre valeur"
modele.encoreunautreattribut = "encore une autre valeur"
end

Voilà donc pour ce crash course sur l’utilisation de first_or_create, fonction dont le nom assez ambigu peut être source de confusion et qui en même temps nous épargne d’avoir à faire un find_by suivi d’un create, ce qui n’est ni très esthétique, ni très performant !

Hors micro: le télétravail

Un petit billet rapide pour démarrer cette nouvelle année (la troisième pour ce blog qui a essentiellement vocation à parler technique, vous l’aurez remarqué) avec un sujet qui me tient à cœur: le télétravail dans les NTIC en France.

Pourquoi le télétravail, qui aide à moderniser l’organisation sans toutefois trop la bousculer, est-il tellement à la traîne en France ? Les entreprises du monde des NTIC semblent toujours plus favorables à ce mode de collaboration – c’est en tous les cas ce qui ressort de chacun des sondages d’opinion faits sur le sujet tous les ans – mais dans les faits elles restent réticentes pour appliquer le changement…Qu’est-ce qui ne va pas avec le télétravail en France ? Est-il encore mal perçu en 2016 ? N’est-il pas si soluble que ça dans la culture de l’entreprise française ?

Take the turn !

Take the turn !

J’ai eu la chance de tester ce mode de collaboration il y a presque 10 ans, à la suite de la naissance de mon deuxième enfant. Mon manager de l’époque, très compréhensif, m’avait accordé de travailler 2 jours par semaine à domicile et ce alors que je résidais à 10 km de mon lieu de travail (comme quoi ce n’est pas une histoire de distance – length does NOT matter). Ce mode de fonctionnement a duré trois ans, jusqu’à mon départ de l’entreprise. Certes, il a fallu prouver ma fiabilité et mon sérieux dans le travail avant de me voir accorder ce que je considère encore à ce jour comme un énorme avantage dans la gestion personnelle du travail.

Car oui, de mon point de vue, le télétravail est beaucoup moins stressant et beaucoup plus productif que dans les locaux de l’entreprise:

  • vous n’êtes pas dérangés par le téléphone qui sonne toutes les 30 secondes ou les gens qui hurlent sans cesse à côté de vous (je m’adresse aux gens qui travaillent en open space)
  • vous n’êtes pas sollicités par vos collègues pour partir en pause chaque heure (celui de la compta, puis celui de l’info, puis celui des achats etc.)
  • vous n’êtes pas conviés à des réunions interminables où vous n’aviez pas votre place (la preuve, vous avez rempli une page de votre cahier de dessins aléatoires, ce cahier que vous emportez à chaque fois alors que vous n’y notez rien…et vous le savez pertinemment !)
  • Pour résumer : plus de concentration et de calme = plus de travail de qualité !

Pas du tout ma vision du travail

Pas du tout ma vision du travail

Les aspects pratiques pèsent aussi dans la balance:

  • Fini le temps perdu dans les embouteillages, une douche et c’est parti, vous êtes devant votre poste de combat à 8 h 30 au lieu des 9 heures et quelques (vous êtes de toutes les manières incapables de savoir quand vous arriverez, étant à la merci de la circulation, des grèves, des travaux parfois des caprices de votre véhicule).
  • Il ne vous faut rien ou presque pour bosser : un ordinateur et une connexion SSH.
  • La communication se fait par d’autres canaux, qui forcent parfois les gens à réfléchir avant de vous déranger pour rien; le mail force les gens à savoir formuler leur besoin de manière compréhensible, claire et précise. De plus n’importe qui possédant une ligne téléphonique peut suivre un conf call depuis chez lui.
  • Certes, tout n’est pas rose :

    Il faut savoir avant tout gérer le fait d’être seul, mentalement et techniquement (ça force à être autonome). Il faut aimer être à distance et il faut pouvoir se dépatouiller en toute occasion. J’ai vu des gens s’isoler complètement dans leur coin, au point de décrocher totalement et de ne plus assurer leurs tâches (ce qui a, hélas!, entrainé leur éviction), mais pour un échec cuisant, combien de salariés ou de collaborateurs y trouvent leur compte et une certaine forme d’épanouissement ? Le télétravail influe sur le moral et sur la qualité de l’ouvrage, à condition bien sûr de se sentir bien chez soi. Si l’environnement est bruyant et pas propice à la sérénité, c’est évidemment très dur, mais un collaborateur à qui vous accordez cette confiance prendra ça comme une preuve de confiance voire un avantage en nature !

    Car oui, la confiance reste encore la principale entrave au développement de ce mode de collaboration dans notre bon vieux pays. On reste dans un schéma sclérosé où la place du collaborateur est sous les yeux du manager et nulle part ailleurs. On part du principe que le collaborateur pourrait être distrait chez lui alors que ça ne dérange personne que certains passent leur temps de travail sur les réseaux sociaux ou sur leur téléphone portable quand les accès au Web sont filtrés.
    « Du moment qu’ils sont au bureau et font leurs horaires… ». Qu’est-ce qui est important ? Faire ses horaires ou accomplir ses tâches ? La rigidité ou la flexibilité ? Le sentiment qu’on nous fait confiance ou qu’on est pris pour un garnement qu’il faut constamment surveiller ? On passe 45 ans de sa vie à travailler, autant aimer son travail et s’y impliquer, non ?

    Cela fait trois ans maintenant que je suis indépendant, travaillant uniquement depuis chez moi, à l’exception évidemment des formations, que je tiens à assurer en présentiel. Je ne donnerais ma place pour rien au monde : j’aime encore mon métier après 18 ans passés à l’exercer et j’aime le fait de rester dans mon environnement pour travailler : je ne touche plus ma voiture que quelques fois par semaine pour des trajets courts et croyez-moi, je ne m’en porte que mieux. J’entends encore des gens me dire « Je ne sais pas comment tu fais, je ne pourrais pas travailler seul » ou « Je serais trop tenté de faire autre chose que travailler ». Je ne tente pas de les convaincre qu’il s’agit de la panacée – on s’en sent capable ou pas – mais travailler depuis chez moi est un choix personnel que j’estime payant et j’encourage vivement entreprises des NTIC et collaborateurs désireux de briser la routine ou hésitants à tenter l’expérience.

    Sus à la frilosité ! Le télétravail, c’est génial !

    Si vous avez des retours d’expérience (identiques ou différents !), n’hésitez pas à les évoquer dans les commentaires

    PHP 7 : le typage strict

    Une des grandes forces de PHP – qui est aussi, hélas !, une de ses plus grandes faiblesses – est l’absence de typage fort comme c’est le cas en Java, en C#, C++ ou dans bien d’autres langages dits « orientés objet ». PHP s’occupe de faire les conversions de type qui l’arrangent, notamment lorsque l’on effectue des comparaisons entre des types différents (voilà pourquoi les opérateurs stricts ont été introduits qui comparent en valeur et surtout en type). Tout ça est décidé de façon dynamique au runtime (à l’exécution, en bon français) et non pas de façon statique, à la compilation.

    Une nouveauté qui fait des heureuses !

    Une nouveauté qui fait des heureuses !

    Depuis les versions 5.X, on pouvait déjà typer des paramètres de fonction avec certains types non-scalaires : objet depuis 5.0 et array depuis 5.1 (et callable par la suite, mais passons…). Dans des temps reculés (avant 5.X), on n’avait pas trop le choix:
    function peuimporte ($param, $reparam) {
    var_dump($param);
    var_dump($reparam);
    }

    L’appel de cette fonction pouvait se faire avec des arguments de divers types : entiers, flottants, tableaux, objets, chaînes de caractères…Pour faire court, on pouvait un peu passer tout et (surtout) n’importe quoi !

    peuimporte (1, 1.23);
    peuimporte ("a", 1);
    peuimporte (array(), new StdClass());

    Un type scalaire est un type qui ne contient qu’une valeur à la fois: un entier, une chaine de caractères, un booléen etc. Un type non scalaire peut contenir plusieurs valeurs; un tableau, un objet…ce sont des types composites – ou des collections de valeurs.

    Depuis PHP 5.1 disais-je, on pouvait forcer le typage des paramètres des fonctions avec des types non scalaires comme un tableau:


    function peuimporte (Array $param) {
    // faites ce que bon vous semble
    }

    ou bien un objet d’un type particulier:


    class UnTypeAuPif {}
    function peuimporte (UnTypeAuPif $param) {
    // faites ce que bon vous semble
    }

    ou encore d’un super-type, comme une interface:


    interface Bidon {}
    class UnTypeAuPif implements Bidon {}
    class UnAutreTypeAuPif implements Bidon {}
    function peuimporte (Bidon $param) {
    // faites ce que bon vous semble
    }

    Il suffit alors de passer en argument une instance qui ne se conforme pas aux spécifications que constitue la signature de notre fonction et VLAN!


    Catchable fatal error: Argument 1 passed to peuimporte() must be an instance of UnTypeAuPif, instance of stdClass given

    Ici j’ai tenté de réaliser l’invocation suivante :
    peuimporte(new StdClass);

    J’ai allégrement violé le contrat qui me lie à cette fonction et qui stipule qu’il ne faut passer que des instances de UnTypeAuPif (dans le premier exemple).

    Depuis mars 2015 (et une RFC qui a fait l’objet d’un débat, puis d’un vote) il est donc prévu que PHP 7 nous donne la possibilité de spécifier des types scalaires pour nos arguments…ENFIN ! Il deviendra donc possible d’écrire:


    function peuimporte (int $param) {
    // faites ce que bon vous semble
    }

    ou bien:


    function peuimporte (string $param) {
    // faites ce que bon vous semble
    }

    ou encore:


    function peuimporte (float $param) {
    // faites ce que bon vous semble
    }

    Le fait de passer en argument d’un appel une variable du mauvais type provoque une erreur fatale, par exemple:


    Fatal error: Uncaught TypeError: Argument 1 passed to peuimporte() must be of the type float, string given

    Pour activer ce typage strict sous PHP 7, il faut utiliser declare que l’on connait depuis 5.3 et qui sert lors de la compilation du fichier. Ce language construct sera placé en tout début de fichier (sous peine de provoquer une erreur fatale)  comme suit:


    declare(strict_types = 1);
    function peuimporte (float $param) {
    // TODO
    }
    peuimporte(1.23);

    Notez que le mode bloc, originellement proposé pour strict_types , a été interdit dans PHP 7. Cette fonctionnalité n’est pas forcée par défaut, nul risque donc de BC break dans le code existant. L’aspect lâche du typage en PHP reste encore la règle (pour combien de temps ?), ce qui continuera de faciliter l’intégration des gens qui ne sont pas des développeurs de formation.

    Pour les développeurs qui viennent du monde des langages fortement typés et qui se sentent frustrés par le typage faible de PHP, c’est une bonne nouvelle et sans doute une raison de plus de cesser de prendre PHP pour un langage de seconde zone.

    Il me tarde que PHP 7 fasse l’objet d’une release officielle pour bénéficier de cette tant attendue fonctionnalité !

    Testé avec un PHP7 bêta compilé sur Debian Jessie…et approuvé !

    PHP : le design pattern Proxy

    Proxy, proxy…proxy de cache, proxy Web…vous avez sans doute déjà lu ce terme quelque part, n’est-ce pas ? Si oui, alors vous avez déjà sa principale raison d’être en tête : un proxy s’intercale entre vous et…quelque chose !

    En programmation, ce quelque chose est un objet « distant »…distant parce qu’il peut se trouver ailleurs sur le réseau mais pas seulement ! Il peut très bien se trouver sur la même machine mais dans un autre espace d’adressage. En pratique, le proxy implémente la même interface que l’objet auquel il sert d’écran, car il va se substituer à lui !

    Ce design pattern fait état de relations entre des objets, voilà pourquoi on dit qu’il est structurel ! Proxy (ou Procuration ou encore Surrogate en anglais) a des similitudes avec un autre pattern structurel : Décorateur. Cependant, il convient de bien garder à l’esprit que si Décorateur a pour but d’ajouter des fonctionnalités à l’objet décoré, Proxy est souvent là pour effectuer un contrôle d’accès à un objet.

    Si vous avez déjà travaillé avec le framework Symfony et l’ORM Doctrine, cette notion de proxy ne vous est pas inconnue car vous manipulez des proxies en bien des occasions !

    Gardes du corps peu enclins à la discussion

    Les proxies du président de la République ! Pour émettre une requête à ce dernier, c’est par eux qu’il faudra passer !

    L’interprète, un mandataire idéal !

    Voilà un nouveau terme français pour notre Proxy : mandataire. Quel meilleur exemple que celui de l’interprète, par lequel il est impératif de passer lors des sommets internationaux, si l’on veut être compris des grands de ce monde (et éventuellement lui faire porter la responsabilité d’un incident diplomatique) ! Voilà un exemple très simple pour illustrer ça : des interfaces que le sujet réel (le président russe) et son proxy (l’interprète russe-français) implémentent tous deux et un président hôte qui prend en composition un interprète avec lequel il va parler tandis que celui-ci va de son côté discuter avec le sujet réel. Dans cet exemple, j’en profite pour utiliser la dérivation d’interfaces, le mot clé final, une classe abstraite…En réalité j’aurais largement pu simplifier le code, j’aurais pu aussi injecter directement l’interprète dans le constructeur du président hôte, qui mène les conversations, au lieu de mettre un setter dont je ne suis pas spécialement adepte, bref j’aurais pu faire des tas de choses autrement mais je veux garder le code efficace et amusant autant que possible !

    interface PersonneInterface
    {
        public function parlerDuTemps(): string;
    }
    
    interface PresidentInterface extends PersonneInterface
    {
        public function parlerDuRechauffementClimatique(): string;
        public function parlerDesGuerres(): string;
    }
     
    final class PresidentRusse implements PresidentInterface
    {
        public function parlerDuTemps(): string
        {
            return 'Я очень рад быть здесь, погода прекрасная в Париже';
        }
    
        public function parlerDuRechauffementClimatique(): string
        {
            return 'это очень серьезная проблема !';
        }
    
        public function parlerDesGuerres(): string
        {
            return 'какая война?';
        }
    }
    
    interface InterpreteInterface
    {
        public function boireUnVerreEau(): string;
    }
    
    abstract class Interprete implements InterpreteInterface
    {
        protected $presidentHote;
        
        protected $presidentInvite;
    
        public function __construct(PresidentInterface $presidentHote,
                                  PresidentInterface $presidentInvite)
        {
            $this->presidentHote = $presidentHote;
            $this->presidentInvite = $presidentInvite;
        }
        
        public function boireUnVerreEau(): string
        {
            return 'Glou Glou Glou'.PHP_EOL;
        }
    }
     
    final class InterpreteRusse extends Interprete implements PresidentInterface
    {
        public function parlerDuTemps(): string
        {
            $temps = 'Au sujet du temps, le président russe me dit : "';
            $temps .= $this->presidentInvite->parlerDuTemps().'"'.PHP_EOL;
            
            return $temps;
        }
    
        public function parlerDuRechauffementClimatique(): string
        {
            $climat = 'Au sujet du climat, le président russe me dit : "';
            $climat .= $this->presidentInvite->parlerDuRechauffementClimatique().'"';
            
            return $climat.PHP_EOL;
        }
    
        public function parlerDesGuerres(): string
        {
            if ($this->presidentHote instanceof PresidentFrancais) {
                return 'Le président russe ne souhaite pas évoquer le sujet 
                        avec le président français ! Prenons plutôt Vodka !'.PHP_EOL;
            }
            
            $guerre = 'Au sujet des guerres, le président russe me dit : "';
            $guerre .= $this->presidentInvite->parlerDesGuerres().'"'.PHP_EOL;
            
            return $guerre;
        }
    }
     
    final class PresidentFrancais implements PresidentInterface
    {
        private $interprete;
         
        public function attacherInterprete(InterpreteInterface $interprete): void
        {
            $this->interprete = $interprete;
        }
        
        public function parlerDuTemps(): string
        {
            return 'Président, il fait bon vivre à Paris, n'est-ce pas ?'.PHP_EOL;
        }
    
        public function parlerDuRechauffementClimatique(): string
        {
            return 'Que pensez-vous du réchauffement climatique ?'.PHP_EOL;
        }
    
        public function parlerDesGuerres(): string
        {
            return 'Que vous inspirent les conflits mondiaux ?'.PHP_EOL;
        }
        
        public function discuterSurPerron(): void
        {
            if (!$this->interprete) {
                throw new RuntimeException('Où est l\'interprète ?');
            }
             
            echo $this->parlerDuTemps();
            echo $this->interprete->parlerDuTemps();
            
            echo $this->parlerDuRechauffementClimatique();
            echo $this->interprete->parlerDuRechauffementClimatique();
            
            echo $this->parlerDesGuerres();
            echo $this->interprete->parlerDesGuerres();
        }
    }
     
    $presidentFrancais = new PresidentFrancais();
    $presidentRusse = new PresidentRusse();
    $InterpreteRusse = new InterpreteRusse($presidentFrancais, $presidentRusse);
    
    $presidentFrancais->attacherInterprete($InterpreteRusse);
     
    try {
        $presidentFrancais->discuterSurPerron();
    } catch (RuntimeException $exception) {
        echo "Allô, ici le chef du protocole !", PHP_EOL;
        echo "Le président vient de me dire '" . 
                  $exception->getMessage(), "'", PHP_EOL;
        echo "Vite, allez chercher un interprète !", PHP_EOL;
    }
    
    echo $InterpreteRusse->boireUnVerreEau();
    

    Une fois de plus, voilà un design pattern fait la part belle à l’abstraction !

    Vous notez ici que la tentation serait grande pour notre interprète de proposer des fonctionnalités que ne propose pas le président dont il assure la traduction. En faisant ainsi, nous nous éloignerions de Proxy – dont le rôle consiste majoritairement à faire de la délégation – pour adopter une approche Décorateur.

    Notre interprète russe joue également le rôle de proxy de protection en filtrant les accès au président russe sur des questions épineuses: en l’occurrence, le président russe n’a pas tellement envie de parler des guerres avec son homologue français et l’interprète a reçu lors de son briefing des instructions sans équivoque…heureusement qu’en bon russe, il a prévu un verre de vodka pour désamorcer tout début de crise diplomatique ! Faire des vérifications de droit d’accès, voilà aussi un autre aspect de ce design pattern.

    Le chargement fainéant

    Personne n’emploie ce terme en réalité, mais il me fait rire ! Le lazy loading consiste à différer le chargement du vrai objet (celui qui nous mandate, qui nous donne procuration) jusqu’au moment où l’on s’en servira effectivement ! Ceci est notamment utile lorsqu’un objet est un plutôt gourmand en ressources.

    Prenons l’exemple d’une classe qui sert habituellement à manipuler des images. Lorsqu’on l’instancie, elle range dans une variable d’instance prévue à cette effet la totalité du flux de l’image qu’on lui passe en paramètre.

    Notre proxy, qui possède le même super-type que la classe image (une classe abstraite implémentant une interface) ne va pas effectuer ce chargement mais il va attendre le dernier moment pour instancier la classe qui le mandate et appeler dessus les méthodes demandées par le client :

    interface ImageInterface
    {
        public function donnerTaille(): int;
        public function aContenu(): bool;
        public function afficherContenu(): ?string;
    }
    
    abstract class AbstractImage implements ImageInterface
    {
    	protected $cheminFichier;
    	protected $contenuFichier;
    	protected $tailleFichier;
    	
    	public function __construct($cheminFichier)
    	{
    		$this->cheminFichier = $cheminFichier;
    	}
    	
    	public function donnerTaille(): int
    	{
    		return $this->tailleFichier;
    	}
    	
    	public function aContenu(): bool
    	{
    		return null !== $this->contenuFichier;
    	}
    }
    
    class StandardImage extends AbstractImage
    {
    	public function __construct($cheminFichier)
    	{
    		parent::__construct($cheminFichier);
    		$this->contenuFichier = file_get_contents(
                                              $this->cheminFichier);
    		$this->tailleFichier = filesize(
                                              $this->cheminFichier);
    	}
    	
    	public function afficherContenu(): ?string
    	{
    		return $this->contenuFichier;
    	}
    }
    
    class ProxyImage extends AbstractImage {
    	
    	private $vraieImage;
    	
    	public function __construct($cheminFichier)
    	{
    		parent::__construct($cheminFichier);
    		$this->tailleFichier = filesize($this->cheminFichier);
    	}
    	
    	public function afficherContenu(): ?string
    	{
    		if (!$this->vraieImage) {
    			$this->vraieImage = new StandardImage(
                                                  $this->cheminFichier);
    		}
    		
    		return $this->vraieImage->afficherContenu();
    	}
    }
    
    final class GestionnaireImage
    {
    	public function traiterImage (ImageInterface $image): void
    	{
    		echo $image->donnerTaille() . ' octets';
    		echo 'Contenu présent ?'.($image->aContenu());
    		echo $image->afficherContenu();
    	}
    }
    
    $gestionnaireImage = new GestionnaireImage();
    
    $image = new StandardImage('elephant.jpg');
    echo $gestionnaireImage->traiterImage($image);
    
    $proxy = new ProxyImage('elephant.jpg');
    echo $gestionnaireImage->traiterImage($proxy);
    

    Le client (GestionnaireImage) travaille d’ordinaire avec des objets de la classe StandardImage, qui, dès qu’elle est instanciée, stocke le flux complet du fichier ciblé dans une variable d’instance. Ceci peut s’avérer extrêmement coûteux si l’image est de grande taille ou si un grand nombre d’images sont requises en même temps par différents utilisateurs de notre classe, voire les deux !

    Nous intercalons donc un objet Proxy entre le client de notre code et le sujet ciblé : ProxyImage possède le même super-type que StandardImage, il est donc tout à fait capable d’agir en qualité de mandataire ! Son rôle sera de différer la construction du sujet ciblé jusqu’au moment où son utilisation sera requise; il doit pour cela posséder une référence à ce sujet, voilà pourquoi vous voyez la donnée membre privée $vraieImage dans ProxyImage ! C’est le mandataire qui instancie le sujet, au départ il possède sur le fichier image une référence indirecte (le nom de fichier) puis finit par obtenir une référence directe (l’objet StandardImage, avec le flux complet).

    Dans le cas de StandardImage, le contenu du fichier cible est intégralement stocké dans la propriété privée dédiée à cet effet, mais pas dans le cas de ProxyImage qui n’instanciera la classe mandatée que lorsque la méthode afficherContenu sera invoquée. La valeur retournée par $image->aContenu() vaudra TRUE dans le cas de StandardImage et FALSE dans le cas du Proxy; c’est bien le signe que le Proxy fait l’économie de la lecture du flux du fichier image sur lequel nous travaillons. Cependant, lorsque le client demande l’affichage de l’image, le Proxy ne peut faire autrement que d’instancier StandardImage pour invoquer dessus la méthode qui va retourner le flux utile à cet affichage.

    Au final, le mandataire est plus efficace que le sujet réel qu’il masque puisque lors de l’appel à donnerTaille dans traiterImage, il n’a pas récupéré l’intégralité du flux binaire du fichier cible. C’est évidemment ce qu’il fait lorsqu’il doit afficher celui-ci, ne pouvant faire autrement.

    Pour utiliser cet exemple, il vous faudra bien entendu une image nommée elephant.jpg que je vous fournis un peu plus bas !

    Quand on doit différer les opérations qui s’avèrent coûteuses lors de la création d’un objet au moment où elle seront effectivement requises alors le design pattern Proxy peut s’avérer d’une aide précieuse !

    La belle image qui a servi à mes tests !

    La belle image qui a servi à mes tests !