Plantillas GRASP: experto en información

Hola habrovsk En contacto Vladislav Rodin. Actualmente estoy impartiendo cursos sobre arquitectura de software y arquitectura de software de alta carga en el portal OTUS. Esta vez decidí escribir un poco de material con derechos de autor en previsión del inicio de un nuevo curso "Arquitectura y patrones de diseño" . Disfruta leyendo.





Introducción


Descrito en el libro de Craig Larman Aplicando UML y patrones, 3ra edición, los patrones GRASP son una generalización de los patrones GoF, así como una consecuencia directa de los principios de la POO. Complementan el paso que falta en la escalera lógica, lo que le permite obtener patrones de GoF de los principios de OOP. Es más probable que las plantillas GRASP no sean patrones de diseño (como los de GoF), sino principios fundamentales para la distribución de la responsabilidad entre las clases. La práctica muestra que no son muy populares, sin embargo, el análisis de las clases diseñadas utilizando el conjunto completo de patrones GRASP es un requisito previo para escribir un buen código.

Una lista completa de plantillas GRASP consta de 9 elementos:

  • Experto en información
  • Creador
  • Controlador
  • Bajo acoplamiento
  • Alta cohesión
  • Polimorfismo
  • Fabricación pura
  • Indireccion
  • Variaciones protegidas

Sugiero considerar el patrón más obvio e importante de la lista: experto en información.

Experto en información


Fraseología


Evitando formulaciones científicas, la esencia de este patrón se puede expresar de la siguiente manera: la información debe procesarse donde está contenida.

Ejemplo de violación


A pesar de la aparente simplicidad y obviedad, estoy seguro de que en el código de cualquier proyecto puede encontrar muchas violaciones de este principio.

Considere el sistema de clases más simple: Order (order), que contiene una lista de OrderItem'ov (líneas de orden), cuyos elementos a su vez contienen Good (producto) y su cantidad, y el producto puede contener, por ejemplo, precio, nombre, 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;
}


Tenemos una tarea simple: calcular el importe del pedido. Si enfoca la solución a este problema sin mucha consideración, puede escribir inmediatamente algo como esto en el código del cliente que funciona con objetos de la clase 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;
    }
}


Analicemos esta solución.

En primer lugar, si comenzamos a agregar lógica comercial relacionada con los precios, el código del método Client :: getOrderPrice no solo crecerá inevitablemente, sino que también estará rodeado de todo tipo de if-s (descuento para pensionistas, descuento en vacaciones, descuento debido a compras al por mayor), lo que al final conducirá al hecho de que este código será imposible de leer, mucho menos cambiar.

En segundo lugar, si crea un diagrama UML, puede encontrar que hay una dependencia de la clase Cliente en hasta 3 clases: Order, OrderItem y Good. Dibuja toda la lógica de negocios para trabajar con estas clases. Esto significa que si queremos reutilizar OrderItem o Good por separado de Order (por ejemplo, para calcular el precio de los bienes que quedan en los almacenes), simplemente no podemos hacer esto, porque la lógica de negocios radica en el código del cliente, lo que conducirá a una duplicación inevitable del código.

En este ejemplo, como en casi todas partes donde hay una cadena de get'ov, se viola el principio del experto en información, porque el código del cliente procesa la información y contiene su orden.

Ejemplo de aplicación


Intentemos redistribuir las responsabilidades de acuerdo con el principio:

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


Ahora la información se procesa en la clase que la contiene, el código del cliente depende solo de Order, sin sospechar nada sobre su estructura interna, y las clases Order, OrderItem y Good, o OrderItem and Good se pueden ensamblar en una biblioteca separada que se puede usar en diferentes partes del proyecto.

Conclusión


El experto en información, resultante de la encapsulación, es uno de los principios más fundamentales del intercambio de responsabilidades GRASP. Su violación se puede determinar y eliminar fácilmente aumentando la simplicidad de percepción del código (el principio de menor sorpresa), agregando la posibilidad de reutilización y reduciendo el número de conexiones entre clases.

Lo invitamos a un seminario web gratuito en cuyo marco será posible estudiar las características de una aplicación monolítica, arquitecturas multinivel y sin servidor. Observe más de cerca el sistema basado en eventos, el sistema orientado a servicios y la arquitectura de microservicios.

All Articles