Seven Wonders 3 – Couche de service… ou pas ?

Modèle maigre, modèle gras ?

Là, il faut que je dise que je suis tiraillé : où mettre la logique ? Bon, le contrôleur, ce n’est évidemment pas bon : il bosse avec la vue, les interactions utilisateurs, ce n’est pas à lui de savoir si un joueur peut payer une carte. Mais faut-il mettre cela dans les objets ou mettre tout dans une couche de services ?

Pour Martin Fowler, dans http://www.martinfowler.com/bliki/AnemicDomainModel.html (un des mecs du manifeste agile) les « skinny models » sont un anti-pattern, et les objets n’étant que des getters/setters sont un viol de l’orienté objet. Bon ça date de 2003 mais on peut lire

One source of confusion in all this is that many OO experts do recommend putting a layer of procedural services on top of a domain model, to form a Service Layer. But this isn’t an argument to make the domain model void of behavior, indeed service layer advocates use a service layer in conjunction with a behaviorally rich domain model.

Bref de là j’en tire qu’il faut utiliser un service layer mais ne pas hésiter à en mettre pas mal dans les domain models, on fait de l’orienté objet et des modèles POJO restreints getters/setters sont des objets trop étriqués .

Des remarques ? (d’ailleurs la réponse n’est pas unique, ça dépend des projets etc) mais je vais ajouter de la logique à certaines de mes classes qui ne seront pas de simples POJO sans autre chose que des getters/setters (ex : le tuto spring mvc http://static.springsource.org/docs/Spring-MVC-step-by-step/part3.html où la fonction « increaseprice » du « productmanager » pourrait être aussi bien dans le « product ».

Plus précisément, nous allons mettre dans les classes tout ce qui concerne l’objet et ses sous-objets mais qui reste générique.

Prenons la Partie : nous voulons trouver le voisin gauche et droit d’un joueur, faire la rotation des cartes (chaque joueur donne ses cartes en main à un voisin) : tout cela est assez simple, nous sommes (quasiment) certains que cela n’évoluera pas même avec des extensions, et cela va dans Partie. Ajoutons-y également des fonctions « raccourci », comme récupérer le nombre de joueurs afin d’éviter des getListeJoueurs().size() dans le code.
Par contre, toujours dans la partie, nous voulons calculer le score d’un joueur, effectuer l’effet d’arrivée en jeu d’une carte ou l’action d’un joueur, compter les points, etc : cela dépend de plein de paramètres, des merveilles, des cartes en jeu etc, et je n’ai pas les ressources pour faire un moteur de jeu entièrement paramétrable, il y aura donc du nom de carte en dur dans le code : je mettrai donc cela dans un service de gestion de partie.

La Partie se voit donc adjoindre plusieurs fonctions (le Joueur aussi d’ailleurs), dont nous écrivons les tests unitaires puis les codes :

  public Joueur getJoueur(int place) {
    return listeJoueur.get(place);
  }

  public int getNbJoueurs() {
    return getListeJoueur().size();
  }

  public Joueur getVoisinGauche(Joueur joueur) {
    return getJoueur((joueur.getPlace() + getNbJoueurs() - 1) % getNbJoueurs());
  }

  public Joueur getVoisinDroite(Joueur joueur) {
    return getJoueur((joueur.getPlace() + 1) % getNbJoueurs());
  }

  public int compteNombreCartes(Joueur joueur, TypeCarte typeCarte,
      boolean gauche, boolean soi, boolean droite) {
    int i = 0;
    if (gauche) {
      i += getVoisinGauche(joueur).compteNombreCartes(typeCarte);
    }
    if (soi) {
      i += joueur.compteNombreCartes(typeCarte);
    }
    if (droite) {
      i += getVoisinDroite(joueur).compteNombreCartes(typeCarte);
    }
    return i;
  }

  public void rotationCartes(boolean sensHoraire) {
    List<Carte> l1 = getJoueur(0).getMain();
    for (int i = 0; i < getNbJoueurs() - 1; i++) {
      getJoueur(i).setMain(getJoueur(i + 1).getMain());
    }
    getJoueur(getNbJoueurs() - 1).setMain(l1);
  }

Reprenons la classe Cout : Nous allons devoir faire des opérations mathématiques dessus pour savoir si un joueur peut payer un coût, notamment faire une soustraction. On ne veut jamais pouvoir retirer trop de sous d’un coût (impossible dans le jeu d’acheter quelque chose si on n’a pas assez d’argent), par contre on veut pouvoir retirer une ressource d’un coût même si elle n’y est pas (possible d’acheter quelque chose même si on a des ressources excédentaires).

@Test
  public void testOperationRetire() throws Exception {
	  Cout cout = new Cout(5, Ressource.Bois, Ressource.Bois, Ressource.Pierre, Ressource.Pierre, Ressource.Pierre, Ressource.Papyrus, Ressource.Tissu);
	  cout.retire(2);
	  assertEquals(3, cout.getPrix().intValue());
	  cout.retire(Ressource.Bois);
	  assertEquals(6, cout.getListeRessources().size());
	  cout.retire(Ressource.Bois, Ressource.Bois, Ressource.Papyrus, Ressource.Papyrus);
	  assertEquals(4, cout.getListeRessources().size());
  }

  @Test(expected=Exception.class)
  public void testRetirePrixTropGrand() throws Exception {
	  Cout cout = new Cout(5);
	  cout.retire(8);
  }

Bon, je pense qu’on a assez vu les modèles, on va passer à la couche de service où il y aura donc la gestion des règles, des cartes spéciales et de nos bots qui, s’ils se débrouillent bien, vous mettront la patée.

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s