Modèles GRASP: Expert en information

Salut, Habrovsk. En contact avec Vladislav Rodin. J'enseigne actuellement des cours sur l'architecture logicielle et l'architecture logicielle à haute charge sur le portail OTUS. Cette fois, j'ai décidé d'écrire un petit matériel protégé par le droit d'auteur en prévision du début d'un nouveau cours «Architecture et modèles de conception» . Bonne lecture.





introduction


Décrits dans le livre de Craig Larman, Application de l'UML et des modèles, 3e édition, les modèles GRASP sont une généralisation des modèles GoF, ainsi qu'une conséquence directe des principes de la POO. Ils complètent l'étape manquante dans l'échelle logique, ce qui vous permet d'obtenir des modèles GoF à partir des principes de la POO. Les modèles GRASP ne sont probablement pas des modèles de conception (comme ceux du GoF), mais des principes fondamentaux pour la répartition des responsabilités entre les classes. La pratique montre qu'ils ne sont pas très populaires, cependant, l'analyse des classes conçues en utilisant l'ensemble complet des modèles GRASP est une condition préalable à l'écriture d'un bon code.

Une liste complète des modèles GRASP comprend 9 éléments:

  • Expert en information
  • Créateur
  • Manette
  • Couplage bas
  • Haute cohésion
  • Polymorphisme
  • Fabrication pure
  • Indirection
  • Variations protégées

Je suggère de considérer le modèle le plus évident et le plus important de la liste: Expert en information.

Expert en information


Formulation


En évitant les formulations scientifiques, l'essence de ce modèle peut être exprimée comme suit: les informations doivent être traitées là où elles sont contenues.

Exemple de violation


Malgré l'apparente simplicité et l'évidence, je suis sûr que dans le code de tout projet, vous pouvez trouver de nombreuses violations de ce principe.

Considérons le système de classes le plus simple: Order (order), contenant une liste de OrderItem'ov (lignes de commande), dont les éléments contiennent à leur tour Good (produit) et sa quantité, et le produit peut contenir, par exemple, le prix, le nom, etc.:

@Getter
@AllArgsConstructor
public class Order {
    private List<OrderItem> orderItems;
    private String destinationAddress;
}

@Getter
@AllArgsConstructor
public class OrderItem {
    private Good good;
    private int amount;
}

@Getter
@AllArgsConstructor
public class Good {
    private String name;
    private int price;
}


Nous avons une tâche simple: calculer le montant de la commande. Si vous n'approchez pas la solution à ce problème de manière très réfléchie, vous pouvez immédiatement écrire quelque chose comme ça dans du code client qui fonctionne avec des objets de la classe Order:

public class Client {
    public void doSmth() {
        
    }
    
    private int getOrderPrice(Order order) {
        List<OrderItem> orderItems = order.getOrderItems();
        
        int result = 0;
        
        for (OrderItem orderItem : orderItems) {
            int amount = orderItem.getAmount();
            
            Good good = orderItem.getGood();
            int price = good.getPrice();
            
            result += price * amount;
        }
        
        return result;
    }
}


Analysons cette solution.

Premièrement, si nous commençons à ajouter une logique métier liée à la tarification, le code de la méthode Client :: getOrderPrice augmentera non seulement inévitablement, mais deviendra également entouré de toutes sortes d'if-s (remise pour les retraités, remise sur les vacances, remise due à achats en gros), ce qui conduira finalement au fait que ce code sera impossible à lire, encore moins à changer.

Deuxièmement, si vous créez un diagramme UML, vous pouvez constater qu'il existe une dépendance de la classe Client sur 3 classes: Order, OrderItem et Good. Il tire toute la logique métier pour travailler avec ces classes. Cela signifie que si nous voulons réutiliser OrderItem ou Good séparément de Order (par exemple, pour calculer le prix des marchandises laissées dans les entrepôts), nous ne pouvons tout simplement pas le faire, car la logique métier réside dans le code client, ce qui entraînera une duplication inévitable du code.

Dans cet exemple, comme presque partout où il y a une chaîne de get'ov, le principe d'Information Expert est violé, car le code client traite les informations et contient sa Commande.

Exemple d'application


Essayons de redistribuer les responsabilités selon le principe:

@Getter
@AllArgsConstructor
public class Order {
    private List<OrderItem> orderItems;
    private String destinationAddress;
    
    public int getPrice() {
        int result = 0;
        
        for(OrderItem orderItem : orderItems) {
            result += orderItem.getPrice();
        }
        
        return result;
    }
}

@Getter
@AllArgsConstructor
public class OrderItem {
    private Good good;
    private int amount;

    public int getPrice() {
        return amount * good.getPrice();
    }
}

@Getter
@AllArgsConstructor
public class Good {
    private String name;
    private int price;
}

public class Client {
    public void doSmth() {
        Order order = new Order(new ArrayList<>(), "");
        order.getPrice();
    }
}


Maintenant, les informations sont traitées dans la classe qui les contient, le code client ne dépend que d'Ordre, ne soupçonnant rien de sa structure interne, et les classes Order, OrderItem et Good ou OrderItem et Good peuvent être assemblées dans une bibliothèque distincte qui peut être utilisée dans différentes parties du projet.

Conclusion


Expert en information, résultant de l'encapsulation, est l'un des principes les plus fondamentaux du partage des responsabilités GRASP. Sa violation peut être facilement déterminée et éliminée en augmentant la simplicité de perception du code (principe de la moindre surprise), en ajoutant la possibilité de réutilisation et en réduisant le nombre de connexions entre les classes.

Nous vous invitons à un webinaire gratuit dans le cadre duquel il sera possible d'étudier les fonctionnalités d'une application monolithique, d'architectures multi-niveaux et sans serveur. Examinez de plus près le système événementiel, le système orienté service et l'architecture de microservice.

All Articles