mercredi 17 juillet 2013

Inversion des dépendances (SOLID 5/5)

Ce post fait partie de la série S.O.L.I.D.

Cette série s'achève sur le principe d'inversion des dépendances.

Inversion des dépendances... Déjà inversion, on ne l'utilise pas souvent, dépendance pas tellement plus, mais les deux ensemble, c'est un comble.

Est-ce que tu souderais directement ta lampe à la prise électrique du mur ?
Ce principe dit que les hauts modules ne doivent pas dépendre des bas modules, que les deux doivent dépendre des abstractions. Et en plus, les abstractions ne doivent pas dépendre des détails, mais que les détails doivent dépendre des abstractions. Pfiou...

En fait, ça dit d'utiliser des interfaces et des classes abstraites. Le plus possible. Tout en respectant les 4 premiers principes. Cela clôt un peu le puzzle SOLID. Chaque principe s'imbriquant les uns dans les autres.

Imaginons que vous voulez commander une bière. Comme d'habitude, vous allez dans votre bar favori et vous commandez la dite bière. Puis vous avez un algorithme pour la boire. Le plus immédiat serait d'écrire :

public void DrinkBeer(FavoriteBar bar)
{
    Beer beer = bar.OrderBeer();
    this.RaiseElbow();
    this.Drink(beer);
}

Ok, super, vous avez votre méthode pour boire une bière uniquement dans votre bar favori. Donc si demain vous souhaitez tester un nouveau bar, c'est mort. Il vas falloir modifier votre algorithme, et ça c'est mal (principe d'ouvert fermé). Il faut dépendre des abstractions ! Au lieu de prendre en paramètre une classe spécifique, prenons plutôt une abstraction : une interface !

Abstrayons notre bar :

interface IBar
{
    Beer OrderBeer();
}
Champion de France du lever de coude

class FavoriteBar : IBar

Maintenant, on peut commander et boire de n'importe quel bar :

public void DrinkBeer(IBar bar)
{
    Beer beer = bar.OrderBeer();
    this.RaiseElbow();
    this.Drink(beer);
}

Bon, notre interface IBar qui contient juste de quoi boire une bière manque de généricité. Si demain on veut boire un pastis, comment on fait ? Générisons ! Et au passage, on ségrégationne un peu mieux notre interface pour la rendre moins global. Pour cela il suffit de la nommer correctement. Un bar fait plein de choses, mais ici, seule la caractéristique 'commande de boisson' nous intéresse :

interface IOrderableBeverage
{
    Beverage Order(BeverageType type);
}
class FavoriteBar : IOrderableBeverage

public void DrinkBeer(IBar bar)
{
    this.Drink(bar, BeverageType.Beer);
}
public void Drink(IBar bar, BeverageType type)
{
    Beverage beverage = bar.Order(type);
    this.RaiseElbow();
    this.Drink(beverage);
}

C'est beau, ça respecte plein de principe SOLID, on est fier, on est devenu un meilleur programmeur, félicitons-nous ! A votre santé !