软件构造|GRASP模式

GRASP模式

GRASP,全称为General Responsibility Assignment Software Pattern,即通用职责分配软件模式,它由Aplying UML and Pallerns(UML 和模式应用)一书作者Craig Larman提出。与其将它称为设计模式,不如称为设计原则,因为它是站在面向对象设计的角度,告诉我们怎样设计问题空间中的类与分配它们的行为职责,以及明确类之间的相互关系等,而不像GoF模式一样是针对特定问题而提出的解决方案。因此GRASP站在一一个更高的角度来看待面向对象软件的设计,它是GoF设计模式的基础。
GRASP是对象职责分配的基本原则,其核心思想是职责分配(Responsibility Assignment) ,用职责设计对象(Designing Objects with Responsibilities)。它包含如下9个基本模式。

1.信息专家模式(Information Expert Pattern)

(1)问题:给对象分配职责的通用原则是什么?

(2)解决方案:将职责分配给拥有履行一个职责所必需信息的类,即信息专家。

(3)分析:信息专家模式是面向对象设计的最基本原则。通俗地讲,就是一个类只干该干的事情,不该干的事情不干。在系统设计时,需要将职责分配给具有实现这个职责所需要信息的类。信息专家模式对应于面向对象设计原则中的单一职责原则。

2.创造者模式(Creator Pattern)

(1)问题:谁应该负责产生类的实例?

(2)解决方案:如果符合下面的一个或者多个条件,则可将创建类A实例的职责分配给类B:

  • B包含A。
  • B聚合A。
  • B拥有初始化A的数据并在创建类A的实例时将数据传递给类A。
  • B记录A的实例。
  • B频繁使用A。

此时,我们称类B是类A对象的创建者。如果符合多个条件,类B聚合或者包含类A的条件优先。

(3)分析:创建对象是面向对象系统中最普遍的活动之一,因此,确定一个分配创建对象的通用职责非常重要。如果职责分配合理,设计就能降低耦合,提高设计的清晰度性和重用性。通常情况下,如果对象的创建过程不是是很复杂,则根据上述原则,由使用的类来创建对象。但是如果创建过程非常复杂,而且可能需要重复使用对象实例或者对象从外部注入一个对象实例,此时,可以委托一个专门的工厂类来辅助创建对象。创建者需与各种工厂模式(简单工厂模式、工厂方法模式和抽象工厂模式)相对应。

3.低耦合模式(Low Coupling Pattern)

(1)问题:怎样支持低的依赖性,减少变更带来的影响,提高重用性?

(2)解决方案:分配一个职责,使得保持低耦合度。

(3)分析:耦合是评价一个系统中各个元素之间连接或依赖强弱关系的尺度,具有台耦合的元素不过多依赖其他元素。此处的元素可以是类,也可以是模块、子系统或者系统、具有高耦合的类过多地依赖其他类,这种设计将会导致:一个类的修改导致其他类产生较大影响;系统难以维护和理解;系统重用性差,在重用一个高耦合的类时不得不重用它所依赖的其他类。因此需要对高耦合的系统进行重构。

类A和类B之间的耦合关系体现如下: A具有一个B类型的属性; A调用B的方法,A的方法包含对B的引用,如方法参数类型为B或返回类型为B; A是B的直接或者间接子类;B是一个接口,A实现了该接口。低耦合模式鼓励在进行职责分配时不增加耦合性从而避免高耦合可能产生的不良后果。在进行类设计时,需要保持类的独立性,减少类变更所带来的影响,它通常与信息专家模式和高内聚模式一起出现。为了达到低耦合,我们可以通过如下方式对设计进行改进:

  • 在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用,一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及。
  • 在类的设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限。
  • 在类的设计上,只要有可能,一个类型应当设计成不变类。
  • 在对其他类的引用上,一个对象对其他对象的引用应当降到最低。

4.高内聚模式(High Cohesion Pattern)

(1)问题:怎样使得复杂性可管理?

(2)解决方案:分配一个职责,使得保持高内聚。

(3)分析:内聚是评价一个元素的职责被关联和关注强弱的尺度。如果一个元素具有很多紧密相关的职责,而且只完成有限的功能,则这个元素就具有高内聚性。此处的元素可以是类,也可以是模块、子系统或者系统。

在一 个低内聚的类中会执行很多互不相关的操作,这将导致系统难于理解、难于重用、难于维护过于脆弱,容易受到变化带来的影响。因此我们需要控制类的粒度,在分配类的职责时使其内聚保持为最高,提高类的重用性,控制类设计的复杂程度。为了达到低内聚,我们需要对类进行分解,使得分解出来的类具有独立的职责,满足单一职责原则。在一个类中只保留一组相关的属性和方法,将一些需要在多个类中重用的属性和方法或完成其他功能所需的属性和方法封装在其他类中。类只处理与之相关的功能,它将与其他类协作完成复杂的任务。

5.控制器模式(Controlr Pater)

(1)问题:谁应该负责处理一个输入系统事件?

(2)解决方案:把接收或者处理系统事件消息的职责分配给一个类。这个类可以代表:

  • 整个系统、设备或者子系统;
  • 系统事件发生时对应的用例场景,在相同的用例场最中使用相同的控制器来处理所有的系统事件。

(3)分析:一个控制器是负责接收或者者处理系统事件的非图形用户界面对象。一个控制器定义一组系统操作方法。在控制器模式中,要求系统件的接收与处理通常由一个高级类来代替;一个子系统需要定义家多个控制器,分别对应不同的事务务处理。通常,一个控制器应当把要完成的功能委托给其他,它只负责协调和控制,本身不完成太多的功能。控制器可以重用,且不能包含太多业务逻辑,一个系统通常也不能设计一个统一的控制器。

6.多态模式(Polymorphism Pattern)

(1)问题:如何处理基于类型的不同选择?如何创建可嵌入的软件组件?

(2)解决方案:当相关选择或行为随类型(类)变化而变化时,用多态操作为行为变化的类型分配职责。

(3)分析:由条件变化引发同一类型的不同行为是程序的一个基本主题。如果用if-else或switch-case等条件语句来设计程序,当系统发生变化时必须修改程序的业务逻辑,这将导致很难方便地扩展有新变化的程序。另外对于服务器/客户端结构中的可视化组件,有时候需要在不影响客户端的前提下,将服务器的一个组件替换成另一个组件。此时可以使用多态来实现,将不同的行为指定给不同的子类,多态是设计系统如何处理相似变化的基本方法,基于多态分配职责的设计可以方便地处理新的变化。在使用多态模式进行设计时,如果需要对父类的行为进行修改,可以通过其子类来实现,不同子类可以提供不同的实现方式,将具体的职责分配给指定的子类。新的子类增加到系统中也不会对其他类有任何影响,多态是面向对象的三大基本特性之一(另外两个分别是封装和继承),通过引入多态,子类对象可以覆盖父类对象的行为,更好地适应变化,使变化点能够“经得起未来验证”。多态模式在多个GoF设计模式中都有所体现,如适配器模式、命令模式组合模式观察者模式策略模式等。

7.纯虚构模式(Pure Fabrication Pattern)

(1)问题:当不想破坏高内聚和低耦合的设计原则时,谁来负责处理这种情况?

(2)解决方案:将一组高内聚的职责分配给一个虚构的或处理方便的“行为“类,它并不是问题域中的概念,,而是店虚构的事务,以达到支持高内聚、低耦合和重用的目的。

(3)分析:纯虚构模式用于解解决高内聚和低耦合之间的矛盾,它要求将一部分类的职贵转移到纯虚构类中,在理想情况,分配给这种虚类的职责是为了达到到高内聚和低耦合的目的。在实际操作过程中,纯虚构有很多种实现方式,例如将数据库操作的方法从数据库实体类中剥离出来,形成专门的数据类,通对类的的分解来实现类的重用,新增加的数据访问类对应于數据持久化存储,它不是问题城中的概念,而是软件开发者为了处理方便而产生的虚构概念。纯虚构可以消除由于信息专家模式带来的低内聚和高耦合的坏设计,得到一个具有更好重用性的设计。在系统中引入抽象类或接口来提高系统的打展性也可以认为是纯虚构模式的一种应用。在很多设计模式中都体现了纯虚构模式,例如适配器模式、策略模式等。

8.中介模式(Indirection Pattern)

(1)问题:如何分配职责以避免两个(或多个)事物之间的直接耦合?如何解耦对象以降低耦合度并提高系统的重用性?

(2)解决方案:分配职责给中间对象以协调组件或服务之间的操作,使得它们不直接耦合。中间对象就是在其他组件之间建立的中介。

(3)分析:要避免对象之间的直接耦合,最常用的做法是在对象之间引人一个中间对象或中介对象,通过中介对象来间接相连。中介模式对应于面向对象设计原则中的迪米特法则,在外观模式、代理模式、中介者模式等设计模式中都体现了中介模式。

9.受保护变化模式(Protected Variations Pattern)

(1)问题:如何分配职责给对象、子系统和系统,使得这些元素中的变化或不稳定的点不会对其他元素产生不利影响?

(2)解决方案:找出预计有变化或不稳定的元素,为其创建稳定的“接口”而分配职责。

(3)分析:受保护变化模式简称PV,它是大多数编程和设计的基础,是模式的基本动.机之一,它使系统能够适应和隔离变化。它与面向对象设计原则中的开闭原则相对应,即在不修改原有元素(类模块、子系统或系统)的前提下扩展元素的功能。开闭原则又可称为“可变性封装原则(Principle of Eneapulation of Variation, EVP)”,要求找到系统的可变因紫并将其封装起来。如将抽象层的不同实现封装到不同的具体类中,而且EVP要求尽量不要将一种可变性和另一种可变性混合在一 起,这将导致系统中类的个数急剧增长,增加系统的复杂度。在具体实现时,为了符合受保护变化模式,我们通常需要对系统进行抽象化设计,定义系统的抽象层,再通过具体类来进行扩展。如果需要护展系统的行为,无须对抽象层进行任何改动,只需要增加新的具体类来实现新的业务功能即可,在不修改已有代码的基础上:扩展系统的功能。大多数设计原则和GoF模式都是受保护变化模式的体现。

设计模式在实际开发中的运用

场景1:

以登陆为例,比如外层需要调用一个LoginService进行登录,LoginService又需要调用其他的service来进行比如密码校验,权限校验之类的操作。时序图见下
在这里插入图片描述

问题:有需求来了,增加指纹登录类型、增加校验内容,我们的LoginService显然必须要同步改变。具体就是,逻辑内部可能需要增加很多的if else,以及一些新的外部依赖。这么做,第一,违反开闭原则,测试难度加大,需要回归整个登录功能。第二,上线后如何生产验证?发现问题怎么办?只能回滚

解决方法:

工厂方法模式
在这里插入图片描述

在设计时,增加LoginServiceFactory,按照一定的规则,生成LoginService,比如此图,LoginServiceV1和LoginServiceV2都实现LoginService接口,client调用LoginService接口即可,由LoginServiceFactory决定真正调用那个版本的login方法。

好处:增加新的LoginService实现时不用改之前的LoginService代码,降低测试难度。第二,上线后可以流控进行生产验证,发现问题,关闭即可,无需回滚。(流控逻辑写在LoginServiceFactory中)

这里只是以登陆场景为例,其他的业务场景可以参照此例自由发挥。

场景2

以下单为例,我们的下单方法OrderService中,需要去调用其他一系列的服务来进行下单校验,比如限额校验,库存校验,权限校验。所有校验都通过,才能下单成功,时序图见下
在这里插入图片描述

问题:有需求来了,需要新增一些校验,或者下单环节增加了其他的外部依赖。那么我们的OrderService是不是需要同步的增加逻辑呢?随着业务的迭代,OrderService会变的越来越臃肿。

解决方法:

责任链模式
在这里插入图片描述

所有的校验都实现orderFilter,形成一个orderFilterList(责任链),新增或者删除某个校验的时候,只需要新增或者删除这个实现,同时维护这个责任链即可,维护这个责任链的同时不改代码的方式有:1.使用配置文件2.初始化的时候动态的初始化责任链。

设计模式在实际编程中的使用

以**命令模式(Command)**为例。

将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

命令模式的结构
顾名思义,命令模式就是对命令的封装,首先来看一下命令模式类图中的基本结构:
Command类:是一个抽象类,类中对需要执行的命令进行声明,一般来说要对外公布一个execute方法用来执行命令。
ConcreteCommand类:Command类的实现类,对抽象类中声明的方法进行实现。
Client类:最终的客户端调用类。
以上三个类的作用应该是比较好理解的,下面我们重点说一下Invoker类和Recevier类。
Invoker类:调用者,负责调用命令。
Receiver类:接收者,负责接收命令并且执行命令。
所谓对命令的封装,说白了,无非就是把一系列的操作写到一个方法中,然后供客户端调用就行了,反映到类图上,只需要一个ConcreteCommand类和Client类就可以完成对命令的封装,即使再进一步,为了增加灵活性,可以再增加一个Command类进行适当地抽象,这个调用者和接收者到底是什么作用呢?
命令模式作为一种行为类模式,首先要做到低耦合,耦合度低了才能提高灵活性,而加入调用者和接收者两个角色的目的也正是为此。命令模式的通用代码如下:

class Invoker {  
   private Command;  
   public void setCommand(Command command) {  
       this.command = command;  
   }  
   public void action(){  
       this.command.execute();  
   }  
}  
  
abstract class Command {  
   public abstract void execute();  
}  
  
class ConcreteCommand extends Command{  
   private Receiver;  
   public ConcreteCommand(Receiver receiver){  
       this.receiver = receiver;  
   }  
   public void execute() {  
       this.receiver.doSomething();  
   }  
}  
  
class Receiver {  
   public void doSomething(){  
        System.out.println("接受者-业务逻辑处理");  
   }  
}  
  
public class Client {  
   public static void main(String[] args){
       Receiver = new Receiver();  
       Command = new ConcreteCommand(receiver);
       //客户端直接执行具体命令方式(此方式与类图相符)  
       command.execute();  
  
       //客户端通过调用者来执行命令
       Invoker = new Invoker();  
       invoker.setCommand(command);  
       invoker.action();  
   }  
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值