Craig Larman氏在《Applying UML and Patterns》一书中提出了GRASP设计模式的概念。作者称其为设计模式,其实,更好的理解应该为设计原则。因为,与GoF等设计模式不同的是,GoF等设计模式是针对特定问题而提出的解决方法,而GRASP则是站在面向对象设计的角度,告诉我们怎么样设计问题空间中的类与它们的行为责任,以及明确类之间的相互关系等等。GRASP可以说是GoF等设计模式的基础。
GRASP是General Responsibility Assignment Software patterns(通用职责分配软件模式)的简称,它的核心思想是“职责分配(Responsibility Assignment)”,用职责设计对象:"Designing Objects with Responsibilities"。它包含了9个基本模式:
1 专家模式(Expert)
解决方案:将职责分配给具有履行职责所需要的信息的类
通俗点就是:该干嘛干嘛去,别管别人的闲事或者我的职责就是搞这个,别的事不管。
举个简单的例子,如果有一个类是专门处理字符串相关的类,那么这个类只能有字符串处理相关的方法,而不要将日期处理的方法加进来。也就是提高软件高内聚一种原则。
2 创建者(Creator)
解决方案:将创建一个类A的实例的职责指派给类B的实例,如果下列条件满足的话:
B聚合了A对象
B包含了A对象
B纪录了A对象的实例
B要经常使用A对象
当A的实例被创建时,B具有要传递给A的初始化数据(也就是说B是创建A的实例这项任务的信息专家)
B是A对象的创建者
如果以上条件中不止一条成立的话,那么最好让B聚集或包含A
通俗点就是:我要用你所以我来创建你,请不要让别人创建你
这个模式是支持低耦合度原则的一个体现
3 高聚合度或高内聚(High Cohesion)
解决方案:分配一个职责的时候要保持类的高聚合度
聚合度或内聚度(cohesion)是一个类中的各个职责之间相关程度和集中程度的度量。一个具有高度相关职责的类并且这个类所能完成的工作量不是特别巨大,那么他就是具有高聚合度。
4 低耦合度或低耦合(Low Coupling)
解决方案:在分配一个职责时要使保持低耦合度。
耦合度(coupling)是一个类与其它类关联、知道其他类的信息或者依赖其他类的强弱程度的度量。一个具有低(弱)耦合度的类不依赖于太多的其他类。
5 控制者(Controller)
解决方案:将处理系统事件消息的职责分派给代表下列事物的类:
代表整个“系统”的类(虚包控制者)
代表整个企业或组织的类(虚包控制者)
代表真实世界中参与职责(角色控制者)的主动对象类(例,一个人的角色)
代表一个用况中所有事件的人工处理者类,通常用“<用例名>处理者”的方式命名(用例控制者)
这是一个控制者角色职责分配的原则,就是哪些控制应该分派给哪个角色。
6 多态
当相关的可选择的方法或行为随着类型变化时,将行为的职责-使用多态的操作-分配给那些行为变化的类型
也就是说尽量对抽象层编程,用多态的方法来判断具体应该使用那个类,而不是用if instanceof 来判断该类是什么接来执行什么。
7 纯虚构
一个纯虚构意味着虚构某些事物,而不是到了迫不得已我们才这样做。
例,我们的Sale类的数据要存入数据库,但是他必须和数据库接口相连接,如果将接口连接放入Sale类中势必增加该类的耦合度,所以我们可以虚构一个类来处理与数据库接口连接的问题。这个类就是我们虚构出来的一个事物。
8 中介者
将职责分配给一个中间对象以便在其他构件或服务之间仲裁,这样这些构件或服务没有被直接耦合。这个中间对象(intermediary)在其他构件或服务间创建一个中介者(Indirection)。这个中间对象也就事7)中的纯虚构。
9 不要和陌生人讲话
分配职责给一个客户端的直接对象以使它与一个间接对象进行协作,这样客户端无需知道这个间接对象。
这个模式-也被叫做(Demeter)准则。
通俗点就是:只与你直接的朋友们通信
不要跟“陌生人”说话
每个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位
其他设计原则
“开-闭”原则(Open-Closed Principle,或者OCP)
一个软件实体应当对扩展开放,对修改关闭。
意思就是在设计一个模块的时候,应当使这个模块在不被修改的前提下被扩展。换言之,应当可以在不修改代码的情况下改变这个模块的行为。
里氏代换原则(Liskov Substitution Principle, 或者LSP)
这个就是尽量用多态的方法编程,也就是GRASP模式中的多态。
依赖倒转原则(Dependency Inversion Principle, 或者DIP)
依赖倒转原则讲的是:要依赖于抽象,不要依赖于具体
就是说我们尽量在抽象层进行控制编程,要针对接口编程,不要针对实现编程。
接口隔离原则(Interface Segregation Principle, 或者ISP)
使用多个专门的接口比使用单一的总接口要好。也就是,从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小的接口上的。
组合/聚合复用原则(Composition/Aggregation Principle, 或者CARP)
又叫合成复用原则。原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分:新的对象通过向这些对象的委派达到复用已有功能的目的。也就是,要尽量使用类的合成复用,尽量不要使用继承
变与不变的分离
更扩展一步,就是将不同变化的组件进行隔离.最简单的例子就是javabean中的存取器。它隔离了不变的接口和变化的内部属性。这方面体现最好的个人觉得就是eclipse,通过变化的插件,eclipse可以用来实现任何功能。
另一种角度:高内聚、低耦合(High Cohesion、Low Coupling) 在面向对象的程序设计时,小到一个类,大到一个功能模块,如果他们之间的相依性很高就会对整个软件的开发造成诸般障碍。例如:当你修改一个类或者某一个模 块的时候,相应的你要改动其他的与之相依赖的类和模块,使得程序很难维护;代码会变得很难理解,一个很单一的操作,就会涉及到很多程序之间相互调用;程序 更是难以复用,当你想复用一个类的时候,对应的与之想依赖的类或方法也会被陆陆续续的添加进来。
这就是我们为什么要遵循这一原则的原因,而高内聚和低耦合往往是伴随在一起出现的。低耦合其实就是两个类或模块之间联系的紧密程度,高内聚就是类中方法和 方法之间的职责相关性。要想避免低内聚、高耦合,解决办法就是既要降低因为一个类的变化而对另一个类产生的影响,又要保持类或模块是有重点的、可理解的、 可管理的并且支持低耦合的,也就是更加精确的给一个类或者模块分配职责。
高内聚和低耦合是软件开发中最重要的原则,grasp的其他模式也是以高内聚、低耦合原则为中心的。
信息专家(Information Expert)
如何实现高内聚,也就是如何给类分配职责?我们要遵循的原则就是把职责分配给具有完成该职责信息的那个类。
创建者(Creator)
如何分配创建对象的职责呢?原则就是当下列条件满足时(越多越好),由B创建A:
1.B频繁的使用A
2.B包含或聚合了A
举个简单的例子,如果类A实现了B接口,类C、D是类A的一个属性,那么C、D应该由A来创建,A应该由B来创建。如果C、D由B来创建,那么当C或者D改变的时候,B 和A也要跟着改变,大大增强了B和C、D的之间的耦合度,违背了低内聚的原则。通俗点说就是如果B使用的了A,那么就应该由B来创建A,而不是由其他的类来创建。
控制器(Controller)
在UI层外,应该由哪个类来处理系统操作呢?原则就是把系统事件的处理职责分配给控制器类,这个控制器类就相当于MVC中的C。这个控制器类通常是系统事件放生 的用例的控制类。
多态(Polymorphism)
根据类型的不同而发生变化的行为的定义职责,应该分配给谁?
举个简单的例子,坐车去广州,坐车算是一个行为,但是这个行为是可以变化的,比如坐飞机、坐汽车或者坐火车,那么坐车这个行为的定义应该分配给谁呢?
原则是通过多态操作把基于类型的可变行为的定义职责分配给发生该行为的类。放到JAVA当中来实现就是定义一个坐车的接口,然后具体的坐飞机、坐汽车或者坐火 车的行为分别定义一个类来实现该功能,然后让这三个具体的类去实现坐车接口。
纯虚构模式(Pure Fabrication)
非问题领域的职责应该分配给谁?
我们在设计类的时候,通常都尽量的保持和现实世界当中的对象一致,那么我们从现实世界的对象抽象出来的类就叫做问题领域里的类,那么当我们保存这个对象的 时候要操作数据库,操作数据库就是一个非现实世界存在的业务对象,他就是非问题领域的职责。
这种职责分配的原则就是将非问题领域的职责分配给人工生成的类。比如问题领域的类通常是放到PO里面的,他不应该包括CRUD等操作。那么CRUD这些操作应该放 到一个人工生成的也就是我们在业务逻辑以外加的一个类。
间接性(Indirection)
为了避免两个或多个事物之间直接耦合,应该如何分配职责?
设计原则是将职责分配给中介对象。例如类A和类B是多对多的关联关系,当A改变的时候,B需要做相应的改变,当B改变的时候,A需要做相应的改变,这是违反低耦 合原则的,解决方法就是在A和B之间加入一个C类,类C的属性只有A和B,用C来记录A和B之间的关系,当A想使用B或者B使用A的时候,他们都通过C来调用对方。
防止变异(Protected Variation)
如何设计对象、系统和子系统,使其内部的变化或者不稳定因素不会对其他元素产生不良影响?
预计识别不稳定的因素,在其外部创建稳定的接口。例如:坐汽车去广州当中的坐汽车就是一个不稳定的因素,以后也许会坐飞机或者火车,那么我们就要把坐汽车 抽象出一个坐车的接口,当有一天想坐火车的时候直接加一个实现的类就可以了。
GRASP的主要特征:
- 对象职责分配的基本原则。
- 主要应用在分析和建模上。
GRASP的核心思想的理解:
自己干自己的事(职责的分配)
自己干自己的能干的事(职责的分配)
自己只干自己的事(职责的内聚)。