Modèle de conception d’état en Java

Modèle de conception d'état en Java

1. Vue d'ensemble

Dans ce didacticiel, nous allons présenter l’un des modèles de conception comportementaux du GoF, le modèle d’État.

Dans un premier temps, nous donnerons un aperçu de son objectif et expliquerons le problème qu’il essaie de résoudre. Nous verrons ensuite le diagramme UML de l’État et la mise en œuvre de l’exemple concret.

2. Modèle de conception d'état

L'idée principale du modèle d'état est deallow the object for changing its behavior without changing its class.  De plus, en l'implémentant, le code devrait rester plus propre sans beaucoup d'instructions if / else.

Imaginons que nous ayons un colis envoyé à un bureau de poste. Le colis lui-même peut être commandé, puis livré à un bureau de poste et finalement reçu par un client. Maintenant, en fonction de l'état actuel, nous voulons imprimer son statut de livraison.

La méthode la plus simple consiste à ajouter des indicateurs booléens et à appliquer des instructions simples if / else dans chacune de nos méthodes de la classe. Cela ne compliquera pas beaucoup les choses dans un scénario simple. Cependant, cela pourrait compliquer et polluer notre code lorsque nous aurons plus d'états à traiter, ce qui entraînera encore plus d'instructions if / else.

En outre, toute logique pour chacun des États serait répartie entre toutes les méthodes. C’est à ce stade que le modèle d’État pourrait être utilisé. Grâce au modèle de conception State, nous pouvons encapsuler la logique dans des classes dédiées, appliquer lesSingle Responsibility Principle etOpen/Closed Principle, have Cleaner et un code plus maintenable.

3. Diagramme UML

UML diagram of state design pattern

Dans le diagramme UML, nous voyons que la classeContext a unState associé qui va changer pendant l'exécution du programme.

Notre contexte va àdelegate the behavior to the state implementation. En d'autres termes, toutes les requêtes entrantes seront traitées par l'implémentation concrète de l'état.

Nous voyons que la logique est séparée et l'ajout de nouveaux états est simple - cela revient à ajouter une autre implémentation deState si nécessaire.

4. la mise en oeuvre

Concevons notre application. Comme déjà mentionné, le colis peut être commandé, livré et reçu, nous allons donc avoir trois états et la classe de contexte.

Tout d'abord, définissons notre contexte, cela va être une classePackage:

public class Package {

    private PackageState state = new OrderedState();

    // getter, setter

    public void previousState() {
        state.prev(this);
    }

    public void nextState() {
        state.next(this);
    }

    public void printStatus() {
        state.printStatus();
    }
}

Comme nous pouvons le voir, il contient une référence pour gérer l'état, notez les méthodespreviousState(), nextState() and printStatus() où nous déléguons le travail à l'objet d'état. Les états seront liés les uns aux autres etevery state will set another one based on this reference transmis aux deux méthodes.

Le client interagira avec la classePackage, mais il n’aura pas à se préoccuper de définir les états, tout ce que le client a à faire est de passer à l’état suivant ou précédent.

Ensuite, nous allons avoir lePackageState qui a trois méthodes avec les signatures suivantes:

public interface PackageState {

    void next(Package pkg);
    void prev(Package pkg);
    void printStatus();
}

Cette interface sera implémentée par chaque classe d'état concrète.

Le premier état concret seraOrderedState:

public class OrderedState implements PackageState {

    @Override
    public void next(Package pkg) {
        pkg.setState(new DeliveredState());
    }

    @Override
    public void prev(Package pkg) {
        System.out.println("The package is in its root state.");
    }

    @Override
    public void printStatus() {
        System.out.println("Package ordered, not delivered to the office yet.");
    }
}

Ici, nous indiquons le prochain état qui se produira après la commande du package. L'état ordonné est notre état racine et nous le marquons explicitement. Nous pouvons voir dans les deux méthodes comment la transition entre les états est gérée.

Jetons un œil à la classeDeliveredState:

public class DeliveredState implements PackageState {

    @Override
    public void next(Package pkg) {
        pkg.setState(new ReceivedState());
    }

    @Override
    public void prev(Package pkg) {
        pkg.setState(new OrderedState());
    }

    @Override
    public void printStatus() {
        System.out.println("Package delivered to post office, not received yet.");
    }
}

Encore une fois, nous voyons les liens entre les États. Le paquet change son état de commandé à livré, le message dans leprintStatus() change également.

Le dernier état estReceivedState:

public class ReceivedState implements PackageState {

    @Override
    public void next(Package pkg) {
        System.out.println("This package is already received by a client.");
    }

    @Override
    public void prev(Package pkg) {
        pkg.setState(new DeliveredState());
    }
}

C'est là que nous atteignons le dernier état, nous ne pouvons que revenir à l'état précédent.

Nous constatons déjà certains avantages, puisqu'un État connaît l’autre. Nous les rendons étroitement couplés.

5. Essai

Voyons comment se comporte la mise en œuvre. Tout d'abord, vérifions si les transitions de configuration fonctionnent comme prévu:

@Test
public void givenNewPackage_whenPackageReceived_thenStateReceived() {
    Package pkg = new Package();

    assertThat(pkg.getState(), instanceOf(OrderedState.class));
    pkg.nextState();

    assertThat(pkg.getState(), instanceOf(DeliveredState.class));
    pkg.nextState();

    assertThat(pkg.getState(), instanceOf(ReceivedState.class));
}

Ensuite, vérifiez rapidement si votre paquet peut revenir avec son état:

@Test
public void givenDeliveredPackage_whenPrevState_thenStateOrdered() {
    Package pkg = new Package();
    pkg.setState(new DeliveredState());
    pkg.previousState();

    assertThat(pkg.getState(), instanceOf(OrderedState.class));
}

Après cela, vérifions le changement d'état et voyons comment l'implémentation de la méthodeprintStatus() modifie son implémentation au moment de l'exécution:

public class StateDemo {

    public static void main(String[] args) {

        Package pkg = new Package();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();
    }
}

Cela nous donnera la sortie suivante:

Package ordered, not delivered to the office yet.
Package delivered to post office, not received yet.
Package was received by client.
This package is already received by a client.
Package was received by client.

Comme nous avons changé l’état de notre contexte, le comportement a changé mais la classe reste la même. En plus de l'API que nous utilisons.

De plus, la transition entre les états s’est produite, notre classe a changé d’état et par conséquent de comportement.

6. Inconvénients

L'inconvénient du modèle d'état est le gain lors de la mise en œuvre d'une transition entre les états. Cela rend l'état codé en dur, ce qui est une mauvaise pratique en général.

Mais, selon nos besoins et exigences, cela pourrait ou non être un problème.

7. Etat vs. Modèle de stratégie

Les deux modèles de conception sont très similaires, mais leur diagramme UML est le même, avec l'idée derrière eux légèrement différente.

Tout d'abord, lesstrategy pattern defines a family of interchangeable algorithms. Généralement, ils atteignent le même objectif, mais avec une implémentation différente, par exemple, des algorithmes de tri ou de rendu.

In state pattern, the behavior might change completely, basé sur l'état réel.

Ensuite,in strategy, the client has to be aware of the possible strategies to use and change them explicitly. Alors que dans le modèle d'état, chaque état est lié à un autre et crée le flux comme dans Finite State Machine.

8. Conclusion

Le modèle de conception d'état est excellent lorsque nous voulonsavoid primitive if/else statements. Au lieu de cela, nousextract the logic to separate classes et laissons noscontext object delegate the behavior aux méthodes implémentées dans la classe state. En outre, nous pouvons tirer parti des transitions entre les états, un état pouvant modifier l’état du contexte.

En général, ce modèle de conception est idéal pour des applications relativement simples, mais pour une approche plus avancée, nous pouvons jeter un œil àSpring’s State Machine tutorial.

Comme d'habitude, le code complet est disponible surthe GitHub project.