Temps estimé: 60 minutes
Exercices préparatoires I
Mise en situation
Voici un programme qui permet de faire l'assignation d'un siège d'avion à un passager. Malheureusement, ce code n'a pas été conçu avec de bonnes pratiques de conception OO. Il présente plusieurs signes qui le rendront difficile à évoluer avec le temps.
public class AssignateurDeSiegeSimple {
public Siege assigner(int volId, PassagerType passagerType) {
String req = "SELECT * FROM t_vol WHERE id=" + volId + ";";
Vol vol = (Vol) BaseDeDonnees.getSingleton().getTransaction().selectToObject(req);
List<Siege> sieges = vol.getSieges();
for(Siege siege: sieges) {
try {
essayerAssignerCeSiege(siege, passagerType);
return siege;
} catch(SiegeNonDisponible e) {
}
}
throw new AucunSiegeDisponibleException();
}
private void essayerAssignerCeSiege(Siege siege, PassagerType passagerType) {
if(siege.getDisponible() && siege.getType() == passagerType) {
siege.setDisponible(false);
} else {
throw new SiegeNonDisponible();
}
}
}
Question 1
Le type du volId
est un nombre entier. Est-ce que cela pourrait poser un problème? Si oui, quel est-il et comment le corriger?
Cela correspond à une obsession des primitives (Primitive Obsession).
Comme l'identifiant de vol est un concept riche du domaine de réservation de sièges, le représenter par une primitive pourrait causer un cout de maintenance très élevé s'il doit changer un jour (ce qui est fortement probable). Savoir qu'il s'agit d'un entier est une fuite de connaissance et le déclarer partout amène à une forte duplication de connaissance (DRY).
Pour le corriger, il faudrait encapsuler le nombre entier dans une classe VolId
pour réduire l'effet d'avalanche en cas de changement.
Question 2
Identifier toutes les violations du SRP présentes dans la classe AssignateurDeSiegeSimple
. Il devrait y en avoir 6.
L'assignateur de siège devrait seulement avoir la responsabilité d'orchestrer et déléguer aux autres classes la logique dans le but d'avoir assigné un siège d'un vol à un passager d'un certain type. Dans ce cas, il devrait savoir quand faire quoi, mais pas comment le faire (quand/quoi/comment).
Sachant cela, voici les violations du SRP présentes:
- Lignes 4-5: Responsable du mécanisme de persistance et de la requête a effectuer pour trouver le vol
- Lignes 7-15: Responsable de la logique pour trouver un siège libre
- Ligne 17: Responsable de la logique en cas de siège non trouve
- Ligne 21: Responsable de la logique pour déterminer si un siège est disponible ou non
- Ligne 22: Responsable de la logique pour assigner un siège
- Ligne 24: Responsable de la logique en cas de siège non disponible
Question 3
Ce code présente une importante violation du DIP. Quelle est-elle est comment la corriger?
L'assignateur de siège, qui devrait être un module de haut niveau ne connaissant aucun détails d'implémentation, dépend directement de la base de données SQL qui est un module de bas niveau.
Pour corriger cette violation, nous pourrions introduire une abstraction de persistance (ex: VolRepository
) qui offre la capacité d'aller chercher un vol selon un identifiant donné, peu importe le mécanisme de persistance en arrière.
Nous aurions donc un design qui pourrait ressembler à ceci:
╭────────────────────╮ ╭───────────────╮
│ │ │ │
│ AssignateurDeSiege │───────────▶│ VolRepository │
│ │ │ < I > │
╰────────────────────╯ ╰───────────────╯
△
│
│
╭──────────────────╮
│ │
│ SqlVolRepository │
│ │
╰──────────────────╯
Question 4
Ce code n'a pas été conçu d'une manière à être testable.
Proposer une façon de le réusiner et implémenter les tests en respectant les bonnes pratiques de tests et de mocks.
Note: ceci est une proposition de réusinage et non la seule et unique solution. Il est possible de trouver d'autres façons de rendre testable le code tout en respectant les bonnes pratiques de tests et de mocks.
public class AssignateurDeSiegeSimple {
private final VolRepository volRepository;
public AssignateurDeSiegeSimple(VolRepository volRepository) {
this.volRepository = volRepository;
}
public Siege assigner(int volId, PassagerType passagerType) {
Vol vol = volRepository.findById(volId);
return vol.assignerSiege(passagerType);
}
}
public class AssignateurDeSiegeSimpleTest {
private static final int VOL_ID = 1234;
private static final PassagerType PASSAGER_TYPE = PassagerType.ECONOMIE;
private Siege siege;
private Vol vol;
private VolRepository volRepository;
private AssignateurDeSiegeSimple assignateur;
@BeforeEach
public void setUpVol() {
siege = new Siege();
vol = mock(Vol.class);
volRepository = mock(VolRepository.class);
willReturn(vol).given(volRepository).findById(VOL_ID);
}
@BeforeEach
public void setUpAssignateur() {
assignateur = new AssignateurDeSiegeSimple(volRepository);
}
@Test
public void quandAssigner_alorsTrouveLeVol() {
assignateur.assigner(VOL_ID, PASSAGER_TYPE);
verify(volRepository).findById(VOL_ID);
}
@Test
public void quandAssigner_alorsAssigneSiegeAuPassager() {
assignateur.assigner(VOL_ID, PASSAGER_TYPE);
verify(vol).assignerSiege(PASSAGER_TYPE);
}
@Test
public void quandAssigner_alorsRetourneSiegeAssigne() {
willReturn(siege).given(vol).assignerSiege(PASSAGER_TYPE);
Siege siegeRetourne = assignateur.assigner(VOL_ID, PASSAGER_TYPE);
assertSame(siege, siegeRetourne);
}
}
public class Vol {
private final List<Siege> sieges;
public Vol(List<Siege> sieges) {
this.sieges = sieges;
}
public Siege assignerSiege(PassagerType passagerType) {
for(Siege siege: this.sieges) {
return essayerAssignerSiege(siege, passagerType);
}
throw new AucunSiegeDisponibleException();
}
private Siege essayerAssignerSiege(Siege siege, PassagerType passagerType) {
try {
siege.assigner(passagerType);
return siege;
} catch(SiegeNonDisponible e) {
}
}
}
public class VolTest {
// ...
@Test
public void plusieursSiegesDisponibles_quandAssignerSiege_alorsAssignePremierSiegeLibre() {
// ...
}
@Test
public void aucunSiege_quandAssignerSiege_alorsLanceException() {
// ...
}
@Test
public void pasDeSiegeDisponible_quandAssignerSiege_alorsLanceException() {
// ...
}
}
public class Siege {
// ...
public void assigner(PassagerType passagerType) {
if (disponible && type.equals(passagerType)) {
disponible = false;
} else {
throw new SiegeNonDisponible();
}
}
}
public class SiegeTest {
@Test
public void quandAssigner_alorsPlusDisponible() {
// ...
}
@Test
public void nonDisponible_quandAssigner_alorsLanceException() {
// ...
}
@Test
public void typeDifferent_quandAssigner_alorsLanceException() {
// ...
}
}
public interface VolRepository {
Vol findById(int volId);
}
public class SqlVolRepository implements VolRepository {
public Vol findById(int volId) {
String req = "SELECT * FROM t_vol WHERE id=" + volId + ";";
return (Vol) BaseDeDonnees.getSingleton().getTransaction().selectToObject(req);
}
}
public class SqlVolRepositoryTest {
@Test
public void volExistant_quandFindById_alorsRetournVol() {
// ...
}
@Test
public void volInexistant_quandFindById_alorsQuelqueChose() {
// ...
}
}