Interface-Based Programming & Code Contracts
Dans le post précédent, on a vue que les contrats définis sur une classe étaient reportés sur ses classes dérivés. Cela nous permet de mieux respecter le principe LSP.
Ce poste ne va pas porter sur les différentes features proposées par CC, vous les trouverez très bien documentées ici.
Mais l’utilisation de Code Contracts peut également nous permettre également d’améliorer l’utilisation d’une autre “technique” de programmation qui est très à la mode:l’Interface-based Programming.
IBS va nous permettre par exemple d’implémenter différents patterns d’injection de dépendances, mais cela permet également :
- d’abaisser le couplage entre nos entités :on limite le lien aux contrat définis dans l’interface.
- d’augmenter la modularité : on est en mesure d’intervertir les classes utilisés par d’autres (qui implémentent la même interface)
- et ca nous évite d’utiliser l’héritage (en .Net, l’héritage multiple est impossible).
L’idée est donc de se concentrer sur l’utilisation d’objets qui implémentent un comportement au lieu de se baser sur l’appartenance à une hiérarchie de classe.
Dans cette affaire, l’utilisation de Code Contracts peut nous être assez utile. L’idée étant d’ouvrir aux clients de nos entités la possibilité d’implémenter leur propre code pour nos traitement doit bien sûr passer par un certains nombre de contrôle. Si on reprend l’exemple utilisé dans le post précédent, On se retrouve dans un scénario qui peut être le suivant:
J’ai un module de calcul d’augmentation avec un traitement par défaut :
- Récupération des informations
- calcul du nouveau salaire(salaire + augmentation)
- impression d’une lettre modèle
et je souhaite permettre aux différents services de pouvoir ajuster uniquement la partie qui calcul le nouveau salaire.
Cela nous donne ca:
1: public class EnvoiCourrierAugmentation
2: {
3: private ICalculAugmentation _CalculAugmentation;
4:
5: public ICalculAugmentation CalculAugmentation
6: {
7: get
8: {
9: if (_CalculAugmentation == null)
10: _CalculAugmentation = new DefaultCalcul();
11: return _CalculAugmentation;
12: }
13: }
14:
15: public EnvoiCourrierAugmentation(ICalculAugmentation calculAugmentation)
16: {
17: _CalculAugmentation = calculAugmentation;
18: }
19:
20: public void ExecuteTask( )
21: {
22: int salaire = GetSalaire(50);
23: int augmentation = GetAugmentation(2);
24:
25: int nouveauSalaire = _CalculAugmentation.Augmentation(salaire
, augmentation);
26:
27: ImprimerInfos();
28: }
29:
30: private void ImprimerInfos()
31: {
32: //
33: }
34:
35: private int GetAugmentation(int p)
36: {
37: return p;
38: }
39:
40: private int GetSalaire(int s)
41: {
42: return s;
43: }
44: }
On a une classe qui effectue le traitement à qui on injecte un objet implémentant l’interface ICalculAugmentation (Injection par Constructeur).
Si l’objet est null, on utilise une classe par défaut qui fait ca:
1: public class DefaultCalcul : ICalculAugmentation
2: {
3:
4: public virtual int Augmentation(int salaire, int augmentation)
5: {
6: //précondition:
7: Contract.Requires(augmentation > 0, "Mon augmentation ne peut pas etre null!!");
8: //Post condition:
9: Contract.Ensures(Contract.Result<int>() > salaire);
10:
11: return salaire + augmentation;
12: }
13: }
Ici on a bien l’utilisation de CodeContract qui impose certaines conditions…mais pour pouvoir les appliquer sur d’autres classes, on va devoir hériter de cette classe…ce qu’on veut pas faire.
De plus j’ai la possibilité d’implémenter cette interface sans utiliser les contrats et donc planter le traitement (et bien sûr remettre la faute sur la personne qui à fait la classe de traitement
).
Une solution serait d’ajouter des contrats avant et après l’appel à notre calcul d’augmentation…mais cela baisse la lisibilité du code.
Code Contracts nous offre une solution “élégante” pour régler ce problème: implémenter les conditions sur l’interface et pas sur la classe! Du coup, toutes classes qui implémentera notre classe va pouvoir récupérer nos Contrats de validité.
Pour cela, on va ajouter une classe spécifique qui va définir nos contrats, on obtient donc:
Notre interface:
1: [ContractClass(typeof(DefaultOperationClass))]
2: public interface ICalculAugmentation
3: {
4:
5: int Augmentation(int salaire, int augmentation);
6:
7: }
Notre classe avec nos contrats:
1: [ContractClassFor(typeof(ICalculAugmentation))]
2: internal class DefaultOperationClass : ICalculAugmentation
3: {
4:
5: public int Augmentation(int salaire, int augmentation)
6: {
7: //précondition:
8: Contract.Requires(augmentation > 0, "Mon augmentation ne peut pas etre null!!");
9: //Post condition:
10: Contract.Ensures(Contract.Result<int>() > salaire);
11:
12:
13: return 0;
14: }
15:
16: }
Je met ma classe internal pour limiter sa porté…mais si vous essayer d’utiliser cette classe directement, vous aurez une erreur à la compilation: cette classe est donc réservé à la définitions des contrats…ca pour le coup c’est moche car ca pourrait être une classe de calcul par défaut par exemple.
Ma classe de calcul par défaut devient donc:
1: public class DefaultCalcul : ICalculAugmentation
2: {
3:
4: public virtual int Augmentation(int salaire, int augmentation)
5: {
6: return salaire + augmentation;
7: }
8: }
Ma classe custom (la même chose)
1: public class SuperOperation : ICalculAugmentation
2: {
3: public int Augmentation(int salaire, int augmentation)
4: {
5: return salaire / augmentation;
6: }
7: }
…qui plante quand je l’exécute:
1: EnvoiCourrierAugmentation e = new EnvoiCourrierAugmentation(new SuperOperation());
2: e.ExecuteTask();

Magique!
Un petit bémol tout de même pour ceux qui utilisent les extensions CC comme la possibilité de factoriser les conditions communes à plusieurs méthodes (ContractAbbreviator), cela ne fonctionne pas (erreur à la compilation) lorsque l’on définis des CC sur une interface…
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :