通用职责分配软件模式(General Responsibility Assignment Software Patterns) 共包括九种模式,它们描述了对象设计和职责分配的基本原则。 也就是说,如何把现实世界的业务功能抽象成对象,如何决定一个系统有多少对象, 每个对象都包括什么职责,GRASP给出了最基本的指导原则。 它是设计一个面向对象系统的基础。 它也是学习使用设计模式的基础。
信息专家(Information expert)模式是一项用于决定如何分配职责(responsibility)的原则。 这些职责包括方法(method),计算字段(computed fields),等等。
使用信息专家这种通用的职责分配方式,首先着眼于某个职责,然后确定履行职责所需的信息, 最后确定这些信息保存在何处。
信息专家致力于将职责放在拥有最多的信息来履行这项职责的类上面。
信息专家模式是面向对象设计的最基本原则。 通俗点来讲,就是一个类只干该干的事情,不该干的事情不干。 在系统设计时,需要将职责分配给具有实现这个职责所需要信息的类。 信息专家模式对应于面向对象设计原则中的单一职责原则。
创建者(Creator),创建对象是面向对象编程里十分常规的操作之一。 由哪个类负责创建对象是类对象之前关系的一个基础属性。
通常来讲,如果应该由类B
来负责创建类A
的实例,那么应该满足下面的一条或多条:
B
的实例包含(contain)或聚合(compositely aggregate)了A
的实例B
的实例记录(record)了A
的实例B
的实例十分紧密地使用(use)了A
的实例B
的实例拥有初始化A
的实例的信息,并在创建A
的实例时传入这些信息
如果一个类创建了另一个类,那么这两个类之间就有了耦合,也可以说产生了依赖关系。 依赖或耦合本身是没有错误的,但是它们带来的问题就是在以后的维护中会产生连锁反应, 而必要的耦合是逃不掉的,我们能做的就是正确地创建耦合关系,不要随便建立类之间的依赖关系, 那么该如何去做呢? 就是要遵守创建者模式规定的基本原则,凡是不符合以上条件的情况,都不能随便用A创建B。 创建对象是面向对象系统中最普遍的活动之一,因此,确定一个分配创建对象的通用职责非常重要。 如果职责分配合理,设计就能降低耦合,提高设计的清晰度、封装性和重用性。 通常情况下,如果对象的创建过程不是很复杂,则根据上述原则,由使用对象的类来创建对象。 但是如果创建过程非常复杂,而且可能需要重复使用对象实例或者需要从外部注入一个对象实例,此时, 可以委托一个专门的工厂类来辅助创建对象。 创建者模式与各种工厂模式(简单工厂模式、工厂方法模式和抽象工厂模式)相对应。
低耦合(Low coupling)。 耦合度用于评判一个元素连接到其它元素强度,依赖其它元素的强度或了解其它元素的细节的强度。 低耦合是一种评估模式,它规定了如何分配职责以便在类之间形成更低的依赖,改变一个类能更少的影响其它类,提高重用性。
耦合是评价一个系统中各个元素之间连接或依赖强弱关系的尺度,具有低耦合的元素不过多依赖其他元素。 此处的元素可以是类,也可以是模块、子系统或者系统。 具有高耦合的类过多地依赖其他类,这种设计将会导致:
- 一个类的修改导致其他类产生较大影响;
- 系统难以维护和理解;
- 系统重用性差,在重用一个高耦合的类时不得不重用它所依赖的其他类。
因此需要对高耦合的系统进行重构。
类A和类B之间的耦合关系体现如下:
- A具有一个B类型的属性;
- A调用B的方法;
- A的方法包含对B的引用,如方法参数类型为B或返回类型为B;
- A是B的直接或者间接子类;
- B是一个接口,A实现了该接口。
低耦合模式鼓励在进行职责分配时不增加耦合性,从而避免高耦合可能产生的不良后果。 在进行类设计时,需要保持类的独立性,减少类变更所带来的影响,它通常与信息专家模式和高内聚模式一起出现。 为了达到低耦合,我们可以通过如下方式对设计进行改进:
- 在类的划分上,应当尽量创建松耦合的类,类之间的耦合度越低,就越有利于复用, 一个处在松耦合中的类一旦被修改,不会对关联的类造成太大波及;
- 在类的设计上,每一个类都应当尽量降低其成员变量和成员函数的访问权限;
- 在类的设计上,只要有可能,一个类型应当设计成不变类;
- 在对其他类的引用上,一个对象对其他对象的引用应当降到最低。
高内聚(High cohesion)是一种评估模式,它试图保持对象合理地保持专注性,可管理性和可理解性。 高内聚通常用以支持低耦合。 高内聚意味着给定元素的职责具有高关联性和高专注性。 将程序拆分成类和子系统就是一项提高内聚性的操作。 或者说,低内聚意味着某元素具有过多不相关的职责。 低内聚的元素通常难以理解,重用,维护和改动。
控制器模式将处理系统事件的职责赋予非用户界面类,这个类代表了整个系统或某个用例场景。 控制器对象是一个非用户界面对象,负责接收和处理一个系统事件。
一个用例控制器应该用于处理这个用例的所有系统事件,并且可能会被用在多个用例上
(例如,对于创建用户和删除用户用例,可以通过单一的UserController
,而不是分开的两个用例控制器。)。
它被定义为UI层之外的首个用于接收和协调(“控制”)一个系统操作的对象。 控制器应该将具体的工作代理给其它对象;它进行协调或控制这些行为。而它本身并不做太多的工作。
一个控制器是负责接收或者处理系统事件的非图形用户界面对象。 一个控制器定义一组系统操作方法。 在控制器模式中,要求系统事件的接收与处理通常由一个高级类来代替; 一个子系统需要定义多个控制器,分别对应不同的事务处理。 通常,一个控制器应当把要完成的功能委托给其他对象,它只负责协调和控制,本身不完成太多的功能。 它可以将用户界面所提交的请求转发给其他类来处理,控制器可以重用,且不能包含太多业务逻辑,一个系统通常也不能设计一个统一的控制器。 控制器模式与MVC模式相对应,MVC是一种比设计模式更加高级的架构模式。
依据多态(polymorphism)原则,当相关选择或行为随类型(类)变化而变化时, 用多态操作为行为变化的类型分配职责。
由条件变化引发同一类型的不同行为是程序的一个基本主题。
如果用if-else
或switch-case
等条件语句来设计程序,
当系统发生变化时必须修改程序的业务逻辑,这将导致很难方便地扩展有新变化的程序。
另外对于服务器/客户端结构中的可视化组件,有时候需要在不影响客户端的前提下,
将服务器的一个组件替换成另一个组件。
此时可以使用多态来实现,将不同的行为指定给不同的子类,多态是设计系统如何处理相似变化的基本方法,
基于多态分配职责的设计可以方便地处理新的变化。
在使用多态模式进行设计时,如果需要对父类的行为进行修改,可以通过其子类来实现,
不同子类可以提供不同的实现方式,将具体的职责分配给指定的子类。
新的子类增加到系统中也不会对其他类有任何影响,多态是面向对象的三大基本特性之一
(另外两个分别是封装和继承),通过引入多态,子类对象可以覆盖父类对象的行为,更好地适应变化,
使变化点能够“经得起未来验证”。
多态模式在多个GoF设计模式中都有所体现,如适配器模式、命令模式、组合模式、观察者模式、策略模式等等。
纯虚构(pure fabrication)表示一个类不是问题域中的概念,而是为了达到高内聚低耦合和重用的目的而制造出来的。 这种类在领域驱动设计中被称作“服务”。
纯虚构模式用于解决高内聚和低耦合之间的矛盾,它要求将一部分类的职责转移到纯虚构类中。 在理想情况下,分配给这种虚构类的职责是为了达到高内聚和低耦合的目的。
在实际操作过程中,纯虚构有很多种实现方式,例如将数据库操作的方法从数据库实体类中剥离出来, 形成专门的数据访问类,通过对类的分解来实现类的重用,新增加的数据访问类对应于数据持久化存储, 它不是问题域中的概念,而是软件开发者为了处理方便而产生的虚构概念。
纯虚构可以消除由于信息专家模式带来的低内聚和高耦合的坏设计,得到一个具有更好重用性的设计。 在系统中引入抽象类或接口来提高系统的扩展性也可以认为是纯虚构模式的一种应用。 纯虚构模式通常基于相关功能的划分,是一种以功能为中心的对象或行为对象。 在很多设计模式中都体现了纯虚构模式,例如适配器模式、策略模式等等。
中介(indirection)模式可以帮助降低两个元素间的耦合度(和潜在的重用性)。 它将调解这两个元素的职责交给一个中间对象。 一个例子是在MVC模式里,引入控制器组件做为模型和视图之前的调解。
要避免对象之间的直接耦合,最常用的做法是在对象之间引入一个中间对象或中介对象, 通过中介对象来间接相连。 中介模式对应于面向对象设计原则中的迪米特法则,在外观模式、代理模式、中介者模式等设计模式中都体现了中介模式。
“中介”顾名思义,就是这个事不能直接来办,需要绕个弯才行。 绕个弯的好处就是,本来直接会连接在一起的对象彼此隔离开了,一个的变动不会影响另一个。 就像前面的低耦合模式里说的一样,“两个不同模块的内部类之间不能直接连接”, 但是我们可以通过中间类来间接连接两个不同的模块,这样对于这两个模块来说, 他们之间仍然是没有耦合/依赖关系的。
受保护变化模式(protected variations)找出预计有变化或不稳定的元素,为其创建稳定的“接口”而分配职责。
受保护变化模式简称PV,它是大多数编程和设计的基础,是模式的基本动机之一,它使系统能够适应和隔离变化。 它与面向对象设计原则中的开闭原则相对应,即在不修改原有元素(类、模块、子系统或系统)的前提下扩展元素的功能。 开闭原则又可称为“可变性封装原则(Principle of Encapsulation of Variation, EVP)”,要求找到系统的可变因素并将其封装起来。 如将抽象层的不同实现封装到不同的具体类中,而且EVP要求尽量不要将一种可变性和另一种可变性混合在一起, 这将导致系统中类的个数急剧增长,增加系统的复杂度。 在具体实现时,为了符合受保护变化模式,我们通常需要对系统进行抽象化设计,定义系统的抽象层, 再通过具体类来进行扩展。如果需要扩展系统的行为,无须对抽象层进行任何改动, 只需要增加新的具体类来实现新的业务功能即可,在不修改已有代码的基础上扩展系统的功能。
大多数设计原则和GoF模式都是受保护变化模式的体现。
它指代了面向对象编程和面向对象设计的五个基本原则:
当这些原则被一起应用时,它们使得一个程序员开发一个容易进行软件维护和扩展的系统变得更加可能。 SOLID所包含的原则是通过引发编程者进行软件源代码的代码重构进行软件的代码异味清扫, 从而使得软件清晰可读以及可扩展时可以应用的指南。 SOLID被典型的应用在测试驱动开发上,并且是敏捷开发以及自适应软件开发的基本原则的重要组成部分。
单一职责原则是一项计算机 程序设计原则,它指出每个模块或类应该只负责软件提供的功能中的一项,并且这个职责应该被完全地 封装在这个类中。它提供的所有服务都应该被细化,与职责保持一致。Robert C. Martin指出“一个类 应该有且仅有一个原因能够使它改变”。
在面向对象编程里,开闭原则指的是 “软件实体(类,模块,函数等)对扩展开放,但对修改关闭;也就是说,一个实体允许在不修改它源码 的情况下扩展它的行为。
可置换性是面向对象程序设计中的一项原则,它指的是在计算机程序中,如果S
是T
的子类型,
那么T
类型的对象可以用S
类型的对象替换,而不需要修改原有程序(例如,一个T
类型
的对象可以被任何子类型S
的对象所替换)。
里氏替换原则是面向对象设计的基本原则之一。 里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时, 基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
里氏替换原则是对子类型关系的详细定义,叫做(强)行为型子类型。
接口隔离原则(ISP)指出客户对象不应该被强制地要求依赖于它不使用的方法。 接口隔离原则强调将庞大的接口拆分成小的更具体的接口,这样客户只需要了解它感兴趣的方法。 这类小的接口也被称作角色接口。 接口隔离原则的目的是系统解开耦合,从而容易重构,更改和重新部署。 接口隔离原则是在SOLID (面向对象设计)中五个面向对象设计(OOD)的原则之一,类似于在GRASP中的高内聚原则。
在面向对象编程领域中,依赖反转原则是指一种特定的解耦(传统的依赖关系建立在高层次上,而具 体的策略设置则应用在低层次的模块上)形式,使得高层次的模块不依赖于低层次的模块的实现细节, 依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。
该原则规定:
- 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口。
- 抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。