GRASP设计模式及OO设计原则浅谈

说到设计模式,更为人所知的当然是GoF(Gang of Four)的23种设计模式。

与GoF的23种设计模式不同的是,GRASP设计模式描述的是在OO设计中为互相协作的类分配职责的原则或者建议,而GoF的设计模式则是在更高的层次上描述一个OO系统或者其局部系统的行为以及结构上的抽象。

 

GRASP设计模式的全称是General Responsibility Assignment Software Patterns,即通用职责分配软件模式。它定义了9个基本的OO设计原则或基本的设计构件。这9个设计模式分别是:创建者(Creator)、信息专家(Information Expert)、低耦合(Low Coupling)、控制器(Controller)、高内聚(High Cohesion)、多态性(Polymorphism)、纯虚构(Pure Fabrication)、间接性(Indirection)、防止变异(Protected Variations)。

 

一、创建者(Creator

实际应用中,符合下列任一条件的时候,都应该由类 A 来创建类 B,这时 A 是 B 的创建者:
a、A 是 B 的聚合
b、A 是 B 的容器
c、A 持有初始化 B 的信息(数据)
d、A 记录 B 的实例
e、A 频繁使用 B

如果一个类创建了另外一个类,那么这两个类之间就有了耦合,也可以说产生了依赖关系。依赖或耦合本身是没有错误的,但是他们带来的问题就是在以后的维护中产生连锁反应,而必要的耦合是逃不掉的,我们能做的就是正确的创建耦合关系,不要随便建立类之间的依赖关系,那么该如何去做呢?就是要遵守创建者模式规定的基本原则,凡是不符合以上条件的,都不能随便用 A 创建 B。

例如:因为订单(Order)是商品(SKU)的容器,所以应该由订单来创建商品。如下图:
这里写图片描述

这里因为订单是商品的容器,也只有订单持有初始化商品的信息,所以这个耦合关系是正确的且没有办法避免的,所以由订单来创建商品。

使用创建者模式的好处是不会增加系统的耦合度,因为根据创建者模式的建议,类的实例的创建者已经与这个类存在着某种形式的耦合。因此该模式支持低耦合的设计,能产生具有较低的维护依赖性与较高的复用性的系统

在一个设计灵活的OO系统中,对象的创建方式往往非常复杂。比如,有些系统需要为了更好的性能而集中创建或者使用回收的实例(线程池、连接池、对象池等);有些系统需要根据某些条件来创建一族类的实例;甚至有些框架系统在框架的编写过程中根本不知道需要实例化哪一个类,等等。在这些情况下,最好的方法是将创建类的实例的职责委派给抽象工厂(Abstract Factory)、具体工厂(ConcreteFactory)、创建器(Builder)等等辅助类,而不是创建者模式所建议的类。

 

二、信息专家(InformationExpert

信息专家模式是面向设计的最基本原则,是我们平时使用最多,应该跟我们的思想融为一体的原则。也就是说,我们设计对象(类)的时候,如果某个类拥有完成某个职责所需要的所有信息,那么这个职责就应该分配给这个类来实现。这时,这个类就是相对于这个职责的信息专家。
例如: 常见的网上商店的购物车(ShopCar),需要让每种商品(SKU)只在购物车内出现一次,购买相同商品,只需要更新商品的数量即可。如下图:
这里写图片描述

针对这个问题需要权衡的是,比较商品是否相同的方法需要放到哪个类里来实现呢?分析业务得知需要根据商品的编号(SKUID)来唯一区分商品,而商品编号是唯一存在于商品类的,所以根据信息专家模式,应该把比较商品是否相同的方法放在商品类里。

在信息专家模式给出的建议中,由于对象可以使用自身的信息来完成它的职责,因此信息的封装性得以维护,从而支持了低耦合;同时,由于类的职责都根据自身所拥有的信息来分配,因而该模式也支持高内聚的设计。

当然,在某些特殊的情况下,信息专家模式也许并不适用,这通常是由于内聚与耦合的问题所产生的。比如许多后台系统都有把模型(Model)类的数据存入数据库的功能。这一职责的履行所必需的信息显然是存在于各个模型类中,按照信息专家模式给出的建议,应该让这些模型类来完成把自身的数据保存到数据库中的功能。但是,这样的设计会导致内聚与耦合方面的问题。首先,在这样的设计中,所有的模型类都必须包含与数据库处理相关的逻辑,如与JDBC相关的处理逻辑。这使得模型类由于其他职责的存在而降低了它的内聚。其次,这样的设计也为所有的模型类都引入了与JDBC的耦合关系,使得系统的耦合度上升。甚至,这种设计也会导致大量重复、冗余的数据库逻辑存在于整个系统中的各个角落,这也违反了设计要分离主要的系统关注的基本架构原则。因此,在这种情况下,信息专家模式需要我们结合整个系统的耦合和内聚做出另外的考虑。

 

三、低耦合(LowCoupling

低耦合模式的意思就是要我们尽可能地减少类之间的连接。

其作用非常重要:

a、低耦合降低了因一个类的变化而影响其他类的范围。

b、低耦合使用类更容易理解,因为类会变得简单,更内聚。

下面这些情况会造成类 A、B 之间的耦合:

a、A 是 B 的属性
b、A 调用 B 的实例的方法
c、A 的方法中引用的 B,例如 B 是 A 方法的返回值或参数。
d、A 是 B 的子类,或者 A 实现 B

关于低耦合,还有下面一些基本原则:

a、Don’t Talk to Strangers 原则

意思就是说,不需要通信的两个对象之间,不要进行无谓的连接,连接了就有可能产生问题,不连接就一了百了了。

b、如果 A 已经和 B 有连接,如果分配 A 的职责给 B 不合适的话(违反信息专家模式),那么就把 B 的职责分配给 A。

c、两个不同模块的内部类之间不能连接,否则比招报应!

例如:Creator 模式的例子里,实际业务中需要另一个出货人来清点订单(Order)上的商品(SKU),并计算出商品的总价,但是由于订单和商品之间的耦合已经存在了,那么把这个职责分配给订单更合适,这样可以降低耦合,以便降低系统的复杂性。如下图:
这里写图片描述

这里我们在订单类里增加了一个 TotalPrice() 方法来执行计算总价的职责,没有增加不必要的耦合。

当然,没有绝对的度量标准来衡量耦合程度的高低。使用低耦合模式的目的是为了创建一个可灵活伸缩的、可维护的、可扩展的系统。在这个目的之下,低耦合不能脱离信息专家和高内聚等其他模式孤立地考虑,而是应该同时权衡耦合与内聚。高耦合本身也并不是问题之所在,问题是与某些方面不稳定的元素之间的高耦合,这种高耦合会严重影响系统将来的维护性和扩展性。而比如所有的Java系统都能安全地将自己去Java库(java.lang,java.util等)进行耦合,因为Java库是稳定的,与Java库的耦合不会给系统的灵活性、维护性、扩展性带来什么问题。

 

四、控制器(Controller

用来接受和处理系统事件的职责,一般应该分配给一个能够代表整个系统的类,这样的类通常被命名为“XX处理器”、“XX协调器”或“XX会话”。

关于控制器类,有如下原则:

a、系统事件的接收与处理通常由一个高级类来代替。

b、一个子系统会有很多控制类,分别处理不同的事务。

控制器设计中的常见缺陷是分配的职责过多。这时,控制器会具有不好的低内聚。因而存在这样一条准则:正常情况下,控制器应当把需要完成的工作委派给其他的对象。控制器只是协调或控制这些活动,本身并不完成大量工作。

 

五、高内聚(HighCohesion

高内聚的意思是给类尽量分配内聚的职责,也可以说成是功能性内聚的职责。即功能性紧密相关的职责应该放在一个类里,并共同完成有限的功能,那么就是高内聚合。这样更有利于类的理解和重用,也便于类的维护。

高内聚也可以说是一种隔离,就像人体由很多独立的细胞组成,大厦由很多砖头、钢筋、混凝土组成,每一个部分(类)都有自己独立的职责和特性,每一个部分内部发生了问题,也不会影响其他部分,因为高内聚的对象之间是隔离开的。

例如:一个订单数据存取类(OrderDAO),订单即可以保存为 Excel 模式,也可以保存到数据库中;那么,不同的职责最好由不同的类来实现,这样才是高内聚的设计,如下图:
这里写图片描述

这里我们把两种不同的数据存储功能分别放在了两个类里来实现,这样如果未来保存到 Excel 的功能发生错误,那么就去检查 OrderDAOExcel 类就可以了,这样也使系统更模块化,方便划分任务,比如这两个类就可以分配到不同的人同时进行开发,这样也提高了团队协作和开发进度。

在少数情况下,较低的内聚也是被接受的。比如,为了方便专门的数据库逻辑人员统一管理SQL语句,系统中往往可以将与SQL语句相关的操作都放在一个独立的全能类中;另外,出于性能的考虑,在RPC中使用一个粗粒度的RPC服务器类,既可以减少服务器上对象的数目,可以减少网络请求和连接的数目,从而提高系统的性能。这些,都需要从系统的全局出发,结合多种设计的原则进行权衡考虑。

 

六、多态性(Polymorphism

这里的多态跟 OO 三大基本特征之一的“多态”是一个意思。

例如:我们想设计一个绘画程序,要支持可以画不同类型的图形,我们定义一个抽象类 Shape,矩形(Rectangle)、圆形(Round)分别继承这个抽象类,并重写(override)Shape 类里的Draw() 方法,这样我们就可以使用同样的接口(Shape抽象类)绘制出不同的图形,如下图:
这里写图片描述

这样的设计更符合高内聚和低耦合原则,虽然后来我们又增加了一个菱形(Diamond)类,对整个系统结构也没有任何影响,只要增加一个继承 Shape 类就行了。

多态性模式是OO设计的一个基本原则。

 

七、纯虚构(PureFabrication

这里的纯虚构跟我们常说说的纯虚构函数意思相近。高内聚低耦合,是系统设计的终极目标,但是内聚和耦合永远都是矛盾对立的。高内聚以为这拆分出更多数量的类,但是对象之间需要协作来完成任务,这又造成了高耦合,反过来依然。该如何解决这个矛盾呢?这个时候就需要纯虚构模式,由一个纯虚构的类来协调内聚和耦合,可以在一定程度上解决上述问题。

例如:上面多态模式的例子,如果我们的绘图程序需要支持不同的系统,那么因为不同系统的API结构不同,绘图功能也需要不同的实现方式,那么该如何设计更合适呢?如下图:
这里写图片描述

这里我们可以看到,因为增加了纯虚构类AbstractShape,不论是哪个系统都可以通过AbstractShape 类来绘制图形,我们即没有降低原来的内聚性,也没有增加过多的耦合。

 

八、间接性(Indirection

间接性模式关注这样一个问题:为了避免两个或多个事务之间直接耦合,应该如何分配职责?如何使对象解耦合,以支持低耦合并提高复用性潜力?

间接性模式对此的回答是:将职责分配给中介对象,使其作为其他构件或服务之间的媒介,以避免它们之间的直接耦合。中介则实现了其他构件之间的间接性。

间接性模式的思想比较简单,即通过一个中介就能消除许多的耦合。在GoF的23种设计模式中,有许多模式都利用到了间接性的思想。比如桥接模式中,设计将抽象部分与其实现部分相分离,利用的就是在客户与实现之间增加了一个抽象层次。外观模式则是在整个子系统与客户之间增加了一个便于用户使用的外观类作为中介。而中介者模式中的中介者则更是典型的例子。

 

九、防止变异(ProtectedVariations

防止变异模式关注这样一个问题:如何设计对象、子系统和系统,使其内部的变化或不稳定性不会对其他元素产生不良影响?

防止变异模式的回答是:识别预计变化或不稳定之处,分配职责用以在这些变化之外创建稳定的接口。

防止变异(PV)是非常重要和基本的软件设计原则,几乎所有的软件或架构设计技巧都是防止变异的特例。PV是一个根本原则,它促成了大部分编程和设计的模式和机制,用来提供灵活性和防止变化。在软件设计中,除了数据封装、接口、多态、间接性等机制是PV的核心机制之外,没有一种固定的或者是通用的办法能够防止一切变化的产生。因此PV的实现依赖的是一系列的OO设计方面的经验性原则,用以产生一个设计良好的高内聚、低耦合的系统,从而支持PV。

 

要真正发挥OO的强大的作用,关键是要深刻理解以上的GRASP模式和设计原则,在此基础上去再深入理解设计模式,并在实践中不断磨练。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章