GRASP模板:信息专家

嗨哈布罗夫斯克 联系弗拉迪斯拉夫·罗丹(Vladislav Rodin)。我目前正在OTUS门户上教授有关软件体系结构和高负载软件体系结构的课程。这次,我决定为即将开始的新课程“建筑与设计模式”写一些版权材料享受阅读。





介绍


在克雷格·拉曼(Craig Larman)的《应用UML和模式》第三版中描述了GRASP模式是GoF模式的概括,也是OOP原理的直接结果。它们补充了逻辑阶梯中缺少的步骤,使您可以从OOP原理中获取GoF模式。GRASP模板很可能不是设计模式(如GoF的),而是类之间责任分配的基本原则。实践表明,它们并不是很流行,但是,使用完整的GRASP模式集对设计的类进行分析是编写良好代码的前提。

GRASP模板的完整列表包含9个元素:

  • 信息专家
  • 创作者
  • 控制者
  • 低耦合
  • 高凝聚力
  • 多态性
  • 纯加工
  • 间接的
  • 受保护的版本

我建议考虑清单中最明显,最重要的模式:信息专家。

信息专家


措辞


避免科学的表述,这种模式的实质可以表示如下:信息应在包含信息的地方进行处理。

违例


尽管表面上看起来很简单明了,但我可以肯定,在任何项目的代码中,您都会发现许多违反该原理的行为。

考虑最简单的类系统:Order(订单),其中包含OrderItem'ov(订单行)的列表,其元素依次包含Good(产品)及其数量,产品可能包含例如价格,名称等:

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


我们有一个简单的任务:计算订单金额。如果您对解决此问题的想法不是很周到,则可以立即在与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;
    }
}


让我们分析这个解决方案。

首先,如果我们开始添加与定价相关的业务逻辑,则Client :: getOrderPrice方法代码不仅将不可避免地增长,而且还将被各种if-s包围(养老金领取者折扣,假期折扣,批发采购),最终将导致无法读取此代码,更不用说更改。

其次,如果构建UML图,则可以发现Client类最多依赖于3个类:Order,OrderItem和Good。它绘制了使用这些类的所有业务逻辑。这意味着如果我们要与Order分开重用OrderItem或Good(例如,计算仓库中剩余的货物价格),我们根本无法做到这一点,因为业务逻辑位于客户代码中,这将不可避免地导致代码重复。

在此示例中,就像几乎所有存在get'ov链的地方一样,违反了Information Expert原则,因为客户端代码处理信息并包含其Order。

应用实例


让我们尝试根据以下原则重新分配责任:

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


现在信息在包含它的类中进行处理,客户端代码仅取决于Order,而不怀疑其内部结构,并且Order,OrderItem和Good或OrderItem和Good类可以组装到一个单独的库中,该库可用于项目的各个部分。

结论


封装带来的信息专家是GRASP责任分担的最基本原则之一。通过提高对代码的感知的简易性(最少惊奇的原理),增加重用的可能性并减少类之间的连接数,可以轻松地确定并消除它的违反。

我们邀请您参加一个免费的网络研讨会,在该框架内可以研究整体应用程序,多层和无服务器架构的功能。仔细研究事件驱动系统,面向服务的系统和微服务体系结构。

All Articles