GRASP-Vorlagen: Informationsexperte

Hallo Habrowsk. In Kontakt Vladislav Rodin. Ich unterrichte derzeit Kurse über Softwarearchitektur und Hochlast-Softwarearchitektur auf dem OTUS-Portal. Dieses Mal habe ich beschlossen, ein wenig urheberrechtlich geschütztes Material zu schreiben, um den Beginn eines neuen Kurses „Architektur- und Designmuster“ zu erwarten . Viel Spaß beim Lesen.





Einführung


GRASP-Muster werden in Craig Larmans Buch Applying UML and Patterns, 3. Auflage, beschrieben und sind eine Verallgemeinerung von GoF-Mustern sowie eine direkte Folge der OOP-Prinzipien. Sie ergänzen den fehlenden Schritt in der logischen Leiter, mit dem Sie GoF-Muster aus den Prinzipien von OOP abrufen können. GRASP-Vorlagen sind eher keine Entwurfsmuster (wie GoFs), sondern grundlegende Prinzipien für die Verteilung der Verantwortung zwischen Klassen. Die Praxis zeigt, dass sie nicht sehr beliebt sind. Die Analyse entworfener Klassen unter Verwendung des vollständigen Satzes von GRASP-Mustern ist jedoch eine Voraussetzung für das Schreiben von gutem Code.

Eine vollständige Liste der GRASP-Vorlagen besteht aus 9 Elementen:

  • Informationsexperte
  • Schöpfer
  • Regler
  • Niedrige Kopplung
  • Hoher Zusammenhalt
  • Polymorphismus
  • Reine Herstellung
  • Indirektion
  • Geschützte Variationen

Ich schlage vor, das offensichtlichste und wichtigste Muster aus der Liste zu berücksichtigen: Informationsexperte.

Informationsexperte


Wortlaut


Unter Vermeidung wissenschaftlicher Formulierungen kann das Wesentliche dieses Musters wie folgt ausgedrückt werden: Informationen sollten dort verarbeitet werden, wo sie enthalten sind.

Beispiel für Verstöße


Trotz der offensichtlichen Einfachheit und Offensichtlichkeit bin ich sicher, dass Sie im Code eines jeden Projekts viele Verstöße gegen dieses Prinzip finden können.

Betrachten Sie das einfachste Klassensystem: Bestellung (Bestellung), die eine Liste von OrderItem'ov (Bestellpositionen) enthält, deren Elemente wiederum Gut (Produkt) und seine Menge enthalten, und das Produkt kann beispielsweise Preis, Name usw.: Enthalten.

@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;
}


Wir haben eine einfache Aufgabe: den Bestellbetrag zu berechnen. Wenn Sie sich der Lösung dieses Problems nicht sehr nachdenklich nähern, können Sie sofort so etwas in Client-Code schreiben, der mit Objekten der Order-Klasse funktioniert:

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;
    }
}


Lassen Sie uns diese Lösung analysieren.

Erstens, wenn wir anfangen, Geschäftslogik in Bezug auf die Preisgestaltung hinzuzufügen, wächst der Client :: getOrderPrice-Methodencode nicht nur unweigerlich, sondern wird auch von allen Arten von if-s umgeben (Rabatt für Rentner, Rabatt an Feiertagen, Rabatt aufgrund von Großhandelskäufe), was letztendlich dazu führen wird, dass dieser Code nicht mehr lesbar ist, geschweige denn geändert wird.

Zweitens können Sie beim Erstellen eines UML-Diagramms feststellen, dass die Client-Klasse von bis zu drei Klassen abhängig ist: Order, OrderItem und Good. Es zeichnet die gesamte Geschäftslogik für die Arbeit mit diesen Klassen. Dies bedeutet, dass wir, wenn wir OrderItem oder Good getrennt von Order wiederverwenden möchten (z. B. um den Preis der in Lagern verbliebenen Waren zu berechnen), dies einfach nicht tun können, da die Geschäftslogik im Kundencode liegt, was zu einer unvermeidlichen Vervielfältigung des Codes führt.

In diesem Beispiel wird, wie fast überall dort, wo es eine Kette von get'ov gibt, das Information Expert-Prinzip verletzt, da der Client-Code die Informationen verarbeitet und seine Bestellung enthält.

Anwendungsbeispiel


Versuchen wir, die Verantwortlichkeiten nach dem Prinzip neu zu verteilen:

@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();
    }
}


Jetzt werden die Informationen in der Klasse verarbeitet, in der sie enthalten sind. Der Clientcode hängt nur von der Reihenfolge ab und ahnt nichts von seiner internen Struktur. Die Klassen Order, OrderItem und Good oder OrderItem und Good können in einer separaten Bibliothek zusammengefasst werden, die in verschiedenen Teilen des Projekts verwendet werden kann.

Fazit


Informationsexperte, die sich aus der Kapselung ergibt, ist eines der grundlegendsten Prinzipien der GRASP-Verantwortungsteilung. Die Verletzung kann leicht festgestellt und beseitigt werden, indem die Einfachheit der Wahrnehmung des Codes erhöht wird (das Prinzip der geringsten Überraschung), die Möglichkeit der Wiederverwendung hinzugefügt und die Anzahl der Verbindungen zwischen Klassen verringert wird.

Wir laden Sie zu einem kostenlosen Webinar ein, in dessen Rahmen die Funktionen einer monolithischen Anwendung, mehrstufiger und serverloser Architekturen untersucht werden können. Schauen Sie sich das ereignisgesteuerte System, das serviceorientierte System und die Microservice-Architektur genauer an.

All Articles