什么是GRASP模式?


GRASP模式(一般责任分配软件模式)描述了对象设计和责任分配的基本原则和模式。 确定需求并创建领域模型后,如何将方法添加到Class类中,并定义对象之间的消息传递以满足要求。
GRASP模式是一种学习辅助工具,可帮助人们理解基本对象设计,并以有条理,合理,可解释的方式应用设计推理。这种理解和使用设计原则的方法基于分配责任的模式。

责任和方法
UML将责任定义为“分类器的合同或义务”。责任与对象在其行为方面的义务有关。基本上,这些职责分为以下两种:

  • knowing知道

  • doing做

对象的doing职责包括:

  • 自己做某事,比如创建一个对象或进行计算

  • 在其他对象中启动操作

  • 控制和协调其他对象的活动

对象的knowing职责包括:
  • 了解私有封装数据

  • 了解相关对象

  • 知道它可以导出或计算的东西

在对象设计期间将责任分配给对象类。例如,我可以声明“销售是负责创建SalesLineltems”(doing),或者“销售是负责了解其总数”(knowing)。与“knowing”相关的相关责任通常可以从领域模型的属性和关联中推断出来。

POS(销售点)应用程序用于解释所有  GRASP  模式
让我们 简要理解POS (销售点)应用程序,然后将  GRASP  模式应用于POS应用程序。

销售点(POS)应用程序
让我们考虑一下销售点(POS)应用程序:POS应用程序的简要概述。

  1. 申请注册销售的商店,餐馆等。
  2. 每次销售都是一种或多种产品类型的一个或多个项目,并在某个特定日期发生。
  3. 产品具有包括描述,单一价格和标识符的规范。
  4. 该应用程序还记录与销售相关的付款(例如,现金)。
  5. 付款金额等于或大于销售总额。

如何应用GRASP模式(一般责任分配软件模式)
我们来讨论五种GRASP模式。每个GRASP模式都有一个单独的帖子

信息专家GRASP模式
将责任分配给对象的一般原则是什么?设计模型可以定义数百或数千个软件类,并且应用程序可能需要履行数百或数千个职责。在对象设计期间,当定义对象之间的交互时,我们会对软件类的职责分配做出选择。如果做得好,系统往往更容易理解,维护和扩展,并且有更多机会在未来的应用程序中重用组件。

将责任分配给信息专家 - 具有履行职责所必需信息的类。(banq注:类似DDD聚合)

在上面POS应用程序中,某些类需要知道销售的总计总和。通过信息专家类,我们应该寻找具有确定总数所需信息的那类对象。一个  销售Sale诶 是适合这一责任的一类对象, 它是这项工作的信息专家。
确定订单项总计需要哪些信息?
需要SalesLineltem.quantityProductSpecification.price
SalesLineltem知道其数量及其相关ProductSpecification ;
因此,通过专家,SalesLineltem应该确定总和; 它是信息专家。

就交互图而言,这意味着Sale需要向每个SalesLineItem发送get-Subtotal消息并对结果求和; 这个设计如图所示:为了履行知道和回答总计的责任,Sales-  Lineltem需要知道产品价格。该 ProductSpecification是在回答信息专家 的价格; 因此,  必须向其发送一条消息,询问其价格。 

总之,为了履行询问和回答销售总额的责任,三个责任被分配给三个设计类别的对象如下

Sale: knows sale total 了解销售总额
SalesLineltem: knows line item subtotal 只了解每个条目的小计
ProductSpecification: knows product price 了解产品价格

示例代码:
让我们在Sale 领模型类中编写一个示例方法:

public class Sale {
    //...
   public double getTotal(){
       double total = 0;
       for (SalesLineItem s : salesLineItem) {
           ProductSpecification prodspec
           = s.getProductSpecification();
           total += s.getQuantity()*prodspec.getPrice();
       }
       return total;
    }
}

这是上面的代码就够了吗?这是不够正确的,因为在这里我们需要   从 salesLineItem进行getSubTotal
让我们在SaleLineItem 域模型类中创建方法  :

  public class SalesLineItem {
     //...
     public double getSubTotal(){
         return this.getQuantity()
         productSpecification.getPrice();
  }
 }

观察  ProductSpecification 域模型提供了getPrice方法给出的价格。
履行责任可能需要跨不同类别的信息,每个专家都有自己的数据。
优点

  • 由于对象使用自己的信息来完成任务,因此维护了信息封装。这通常支持低耦合,这导致更强大和可维护的系统。(低耦合也是GRASP模式,将在下一节中讨论)。 

  • 行为分布在具有所需信息的类中,从而鼓励更易于理解和维护的更具凝聚力的“轻量级”类定义。通常支持高内聚(后面讨论的另一种模式)。

GRASP低耦合模式
如何支持低依赖性,低变化影响并增加重用?分配责任以使耦合保持低水平。尽量避免一个类必须knowing了解其他许多类。

关于低耦合的要点

  • “工件”(类,模块,组件)之间的低依赖关系。

  • 模块之间不应该有太多的依赖关系,即使应该通过接口存在依赖关系,也应该是最小的。

  • 避免紧密耦合以便在两个类之间进行协作(如果一个类想要调用第二个类的逻辑,那么第一个类需要第二个类的对象,这意味着第一个类创建第二个类的对象)。

  • 努力在交互的对象之间进行松散耦合设计。

  • 控制反转(IoC)/依赖注入(DI) - DI对象在创建时由某些第三方(即Java EE CDI,Spring DI ...)给出它们的依赖关系,它们协调系统中的每个对象。不期望对象创建或获取它们的依赖项 - 依赖项被注入到需要它们的对象中。DI松耦合的主要优点。

这是松耦合的一个例子:  Traveler  类与Car  或  Bike  实现没有紧密结合  。相反,通过应用依赖注入机制,实现松散耦合实现,以允许与已实现Vehicle  接口的任何类  。

步骤1:  Vehicle  接口允许松散耦合实现。

interface Vehicle {
    public void move();
}

第2步:  Car  类实现Vehicle接口:

class Car implements Vehicle {
    @Override
    public void move() {
         System.out.println("Car is moving");
    }
}

第3步:  Bike  类实现Vehicle接口:

class Bike implements Vehicle {
    @Override
    public void move() {
         System.out.println("Bike is moving");
    }
}

第4步:现在创建  Traveler  类,其中包含对Vehicle接口的引用 :

class Traveler {
    private Vehicle v;
    public Vehicle getV() {
         return v;
    }
    public void setV(Vehicle v) {
        this.v = v;
    }

    public void startJourney() {
         v.move();
    }
}

步骤5:松散耦合示例的测试类 - 示例。

public static void main(String args) {
    Traveler traveler = new Traveler();
    traveler.setV(new Car()); // Inject Car dependency
    traveler.startJourney();
// start journey by Car
    traveler.setV(new Bike());
// Inject Bike dependency
    traveler.startJourney();
// Start journey by Bike
}

GRASP高凝聚模式
如何让类Class集中,易懂和易于管理?分配责任,使凝聚力保持高水平。尽量避免一个类做太多或太不同的事情。
凝聚力这个术语用来表示一个类有一个单一的,专注的责任的程度。凝聚力衡量一个类或一个模块的方法是如何有意义和强烈相关的,以及它们在为系统提供明确定义的目的方面的重点。

当一个类在其中包含许多不相关的函数时,它被识别为低内聚类。而我们需要避免的是因为具有不相关功能的大型类妨碍了他们的维护。始终使您的类变得小巧,具有精确的目的和高度相关的功能。

关于高凝聚力的要点 

  • 代码必须在其操作行为中非常具体。 

  • 责任/方法与lass / module高度相关。

  • 凝聚力这个术语用来表示一个类有一个单一的,专注的责任的程度。凝聚力衡量一个类或一个模块的方法是如何有意义和强烈相关的,以及它们在为系统提供明确定义的目的方面的重点。更为集中的一类 是 较高的凝聚力-一件好事。

  • 当一个类 在其中包含许多不相关的函数时,它被识别为低内聚类。而我们需要 避免的 是因为具有不相关功能的大型课程妨碍了他们的维护。始终使您的课程变得小巧,具有精确的目的和高度相关的功能。 

案例:
在这个例子中,MyReader类的目的是读取资源,它只是这样做。它没有实现其他无关的东西。因此它具有高度凝聚力。

class HighCohesive {
    // -------------- functions related to read resource
   
// read the resource from disk
    public String readFromDisk(String fileName) {
         return
"reading data of " + fileName;
    }

   
// read the resource from web
    public String readFromWeb(String url) {
         return
"reading data of " + url;
    }

   
// read the resource from network
    public String readFromNetwork(String networkAddress) {
         return
"reading data of " + networkAddress;
    }
}

GRASP创建者模式
谁应该负责某个类的新实例的创建?对象的创建是面向对象系统中最常见的活动之一。因此,指导如何分配创建责任的一般原则是有用的。分配好了,该设计可以支持低耦合,增加清晰度,封装和可重用性。
如果满足以下一个或多个条件,则为B类分配创建A类实例的职责:
•B聚合A。
•B包含A。
•B记录跟踪A的实例。
•B密切使用A。
•B具有初始化数据,在创建时将传递给A(因此B是创建A的专家)。
B是A的创建者。
如果适用上述多个选项,则就倾向于选择B类为聚合,代表整体,包含了A,A是B的一部分。

在POS应用程序中,谁应该负责创建  SalesLineltem 实例?通过创建者模式,我们应该寻找一个聚合,包含  SalesLineltem 实例等的类。
由于Sale包含(实际上是聚合)许多  SalesLineltem 对象,因此Creator模式表明  Sale 是负责创建SalesLineltem 实例的良好候选者  。这导致了对象交互的设计:创建SalesLineItem

class Sale {
    List<SalesLineItem> salesLineItem
    = new ArrayList<SalesLineItem>();
    //...
    public void addLineItem(ProductSpecification prodSpec,int quantity) {
        salesLineItem.add(new SalesLineItem(prodSpec, quantity);
 return salesLineItem;
    }
}

GRASP控制者模式
谁应该负责处理输入系统事件? 输入系统事件是由外部角色输入生成的事件。它们与系统的系统操作相关联以响应系统事件,就像消息和方法相关一样。
例如,当使用文字处理器的作者按下“拼写检查”按钮时,他正在生成指示“执行拼写检查”的系统事件。Controller控制器是负责接收或处理系统事件的非用户界面对象。Controller定义系统操作的方法。谁应该负责处理输入事件,与UI层以外的对象接收交互?
将接收或处理系统事件消息的责任分配给表示以下选项之一的类:

  •  表示整个系统,设备或子系统(外观控制器)。

  •  表示系统事件发生的用例场景,通常名为<UseCaseName> Handler,<UseCaseName> Coordinator或<Use-CaseName> Session(用例或会话控制器)。

  • 对同一用例场景中的所有系统事件使用相同的控制器类。

  • 非正式地,会话是与演员的对话的实例。会话可以是任何长度,但通常根据用例(用例会话)进行组织

(banq注:以上四种模式适合设计DDD聚合根)