模式:每个模式描述了一个在咱们周围不断重复发生的问题,以及该问题的解决方案的核心。”
这是关于模式最经典的定义,做者是建筑大师Christopher Alexander。若是是第一次看到这句话,多数人会以为有些抽象难懂。其实“模式”两个字只是一个代号,就像个人英文名字叫Justin,若是我改叫Tom也没什么问题,只是我更喜欢Justin这个名字,因此从Christopher开始,有了“模式”这个词,人们也都把关于“重复发生的问题的描述和解决办法”统称为模式。
“模式”这个词是不局限于软件开发行业的,它几乎无处不在,它其实就是一种经验的积累,就象大多数人的教育经历都是从小学到初中再到高中再到大学,这也是一种模式,是中国的教育模式;如今愈来愈火的出国热,也是另外一种模式:海外留学模式。由于GOF的《设计模式:可复用面向对象软件的基础》一书描述的23种经典设计模式,奠基了模式在软件行业的地位,今后人们提到“设计模式”就是默指“面向对象设计模式”,可是如前文所述,模式绝对不局限于软件行业,即便在软件行业,也不局限于GOF描述的23种设计模式,例如最著名的Martin Flower的《企业架构模式》,还有咱们经常使用的MVC、IOC等架构模式。
由于模式是一种经验的积累和总结,因此经过模式,咱们能够站在巨人的肩膀上去思考问题、解决问题,熟练使用设计模式能够提升咱们的工做效率,改善产品质量,最终带来经济效益。所以对于任何想开发出灵活高效、健壮的软件产品的我的或团体,熟练掌握并正确使用设计模式都是必须掌握的基本技能。数据库
要学习设计模式,有些基础知识是咱们必需要先知道的,设计模式是关于类和对象的一种高效、灵活的使用方式,也就是说,必须先有类和对象,才能有设计模式的用武之地,不然一切都是空谈,那么类和对象是从那冒出来的呢?这时就须要比23种设计模式更重要更经典的GRASP模式登场了。编程
GRASP,全称为General Responsibility Assignment Software Pattern,即通用职责分配软件模式,它由《UML和模式应用》(Applying UML and Patterns)一书做者Craig Larman提出。与其将它们称之为设计模式,不如称之为设计原则,由于它是站在面向对象设计的角度,告诉咱们怎样设计问题空间中的类与分配它们的行为职责,以及明确类之间的相互关系等,而不像GoF模式同样是针对特定问题而提出的解决方案。所以GRASP站在一个更高的角度来看待面向对象软件的设计,它是GoF设计模式的基础。GRASP是对象职责分配的基本原则,其核心思想是职责分配(Responsibility Assignment),用职责设计对象(Designing Objects with Responsibilities)。它包含以下9个基本特征或原则:设计模式
(1) 问题:给对象分配职责的通用原则是什么?服务器
(2) 解决方案:将职责分配给拥有履行一个职责所必需信息的类,即信息专家。架构
(3) 分析:信息专家模式是面向对象设计的最基本原则。通俗点来说,就是一个类只干该干的事情,不应干的事情不干。在系统设计时,须要将职责分配给具备实现这个职责所须要信息的类。信息专家模式对应于面向对象设计原则中的单一职责原则。ide
例如:常见的网上商店里的购物车(ShopCar),须要让每种商品(SKU)只在购物车内出现一次,购买相同商品,只须要更新商品的数量便可。以下图:模块化
针对这个问题须要权衡的是,比较商品是否相同的方法须要放到那里类里来实现呢?分析业务得知须要根据商品的编号(SKUID)来惟一区分商品,而商品编号是惟一存在于商品类里的,因此根据信息专家模式,应该把比较商品是否相同的方法放在商品类里。函数
(1) 问题:谁应该负责产生类的实例?学习
(2) 解决方案:若是符合下面的一个或者多个条件,则可将建立类A实例的职责分配给类B:spa
此时,咱们称类B是类A对象的建立者。若是符合多个条件,类B聚合或者包含类A的条件优先。
(3) 分析:若是一个类建立了另外一个类,那么这两个类之间就有了耦合,也能够说产生了依赖关系。依赖或耦合自己是没有错误的,可是它们带来的问题就是在之后的维护中会产生连锁反应,而必要的耦合是逃不掉的,咱们能作的就是正确地建立耦合关系,不要随便创建类之间的依赖关系,那么该如何去作呢?就是要遵照建立者模式规定的基本原则,凡是不符合以上条件的状况,都不能随便用A建立B。建立对象是面向对象系统中最广泛的活动之一,所以,肯定一个分配建立对象的通用职责很是重要。若是职责分配合理,设计就能下降耦合,提升设计的清晰度、封装性和重用性。一般状况下,若是对象的建立过程不是很复杂,则根据上述原则,由使用对象的类来建立对象。可是若是建立过程很是复杂,并且可能须要重复使用对象实例或者须要从外部注入一个对象实例,此时,能够委托一个专门的工厂类来辅助建立对象。建立者模式与各类工厂模式(简单工厂模式、工厂方法模式和抽象工厂模式)相对应。例如:由于订单(Order)是商品(SKU)的容器,因此应该由订单来建立商品。以下图:
这里由于订单是商品的容器,也只有订单持有初始化商品的信息,因此这个耦合关系是正确的且没办法避免的,因此由订单来建立商品。
(1) 问题:怎样支持低的依赖性,减小变动带来的影响,提升重用性?
(2) 解决方案:分配一个职责,使得保持低耦合度。
(3) 分析:耦合是评价一个系统中各个元素之间链接或依赖强弱关系的尺度,具备低耦合的元素不过多依赖其余元素。此处的元素能够是类,也能够是模块、子系统或者系统。具备高耦合的类过多地依赖其余类,这种设计将会致使:一个类的修改致使其余类产生较大影响;系统难以维护和理解;系统重用性差,在重用一个高耦合的类时不得不重用它所依赖的其余类。所以须要对高耦合的系统进行重构。
类A和类B之间的耦合关系体现以下:A具备一个B类型的属性;A调用B的方法;A的方法包含对B的引用,如方法参数类型为B或返回类型为B;A是B的直接或者间接子类;B是一个接口,A实现了该接口。低耦合模式鼓励在进行职责分配时不增长耦合性,从而避免高耦合可能产生的不良后果。在进行类设计时,须要保持类的独立性,减小类变动所带来的影响,它一般与信息专家模式和高内聚模式一块儿出现。为了达到低耦合,咱们能够经过以下方式对设计进行改进:
例如:Creator模式的例子里,实际业务中须要另外一个出货人来清点订单(Order)上的商品(SKU),并计算出商品的总价,可是因为订单和商品之间的耦合已经存在了,那么把这个职责分配给订单更合适,这样能够下降耦合,以便下降系统的复杂性。以下图:
这里咱们在订单类里增长了一个TotalPrice()方法来执行计算总价的职责,没有增长没必要要的耦合。
(1) 问题:怎样使得复杂性可管理?
(2) 解决方案:分配一个职责,使得保持高内聚。
(3) 分析:内聚是评价一个元素的职责被关联和关注强弱的尺度。若是一个元素具备不少紧密相关的职责,并且只完成有限的功能,则这个元素就具备高内聚性。此处的元素能够是类,也能够是模块、子系统或者系统。
在一个低内聚的类中会执行不少互不相关的操做,这将致使系统难于理解、难于重用、难于维护、过于脆弱,容易受到变化带来的影响。所以咱们须要控制类的粒度,在分配类的职责时使其内聚保持为最高,提升类的重用性,控制类设计的复杂程度。为了达到低内聚,咱们须要对类进行分解,使得分解出来的类具备独立的职责,知足单一职责原则。在一个类中只保留一组相关的属性和方法,将一些须要在多个类中重用的属性和方法或完成其余功能所需的属性和方法封装在其余类中。类只处理与之相关的功能,它将与其余类协做完成复杂的任务。
例如:一个订单数据存取类(OrderDAO),订单便可以保存为Excel模式,也能够保存到数据库中;那么,不一样的职责最好由不一样的类来实现,这样才是高内聚的设计,以下图:
这里咱们把两种不一样的数据存储功能分别放在了两个类里来实现,这样若是将来保存到Excel的功能发生错误,那么就去检查OrderDAOExcel类就能够了,这样也使系统更模块化,方便划分任务,好比这两个类就能够分配个不一样的人同时进行开发,这样也提升了团队协做和开发进度。
(1) 问题:谁应该负责处理一个输入系统事件?
(2) 解决方案:把接收或者处理系统事件消息的职责分配给一个类。这个类能够表明:
(3) 分析:一个控制器是负责接收或者处理系统事件的非图形用户界面对象。一个控制器定义一组系统操做方法。在控制器模式中,要求系统事件的接收与处理一般由一个高级类来代替;一个子系统须要定义多个控制器,分别对应不一样的事务处理。一般,一个控制器应当把要完成的功能委托给其余对象,它只负责协调和控制,自己不完成太多的功能。它能够将用户界面所提交的请求转发给其余类来处理,控制器能够重用,且不能包含太多业务逻辑,一个系统一般也不能设计一个统一的控制器。控制器模式与MVC模式相对应,MVC是一种比设计模式更加高级的架构模式。
(1) 问题:如何处理基于类型的不一样选择?如何建立可嵌入的软件组件?
(2) 解决方案:当相关选择或行为随类型(类)变化而变化时,用多态操做为行为变化的类型分配职责。
(3) 分析:由条件变化引起同一类型的不一样行为是程序的一个基本主题。若是用if-else或switch-case等条件语句来设计程序,当系统发生变化时必须修改程序的业务逻辑,这将致使很难方便地扩展有新变化的程序。另外对于服务器/客户端结构中的可视化组件,有时候须要在不影响客户端的前提下,将服务器的一个组件替换成另外一个组件。此时可使用多态来实现,将不一样的行为指定给不一样的子类,多态是设计系统如何处理类似变化的基本方法,基于多态分配职责的设计能够方便地处理新的变化。在使用多态模式进行设计时,若是须要对父类的行为进行修改,能够经过其子类来实现,不一样子类能够提供不一样的实现方式,将具体的职责分配给指定的子类。新的子类增长到系统中也不会对其余类有任何影响,多态是面向对象的三大基本特性之一(另外两个分别是封装和继承),经过引入多态,子类对象能够覆盖父类对象的行为,更好地适应变化,使变化点可以“经得起将来验证”。多态模式在多个GoF设计模式中都有所体现,如适配器模式、命令模式、组合模式、观察者模式、策略模式等等。
例如:咱们想设计一个绘图程序,要支持能够画不一样类型的图形,咱们定义一个抽象类Shape,矩形(Rectangle)、圆形(Round)分别继承这个抽象类,并重写(override)Shape类里的Draw()方法,这样咱们就可使用一样的接口(Shape抽象类)绘制出不一样的图形,以下图:
这样的设计更符合高内聚和低耦合原则,虽而后来咱们又增长了一个菱形(Diamond)类,对整个系统结构也没有任何影响,只要增长一个继承Shape的类就好了。
(1) 问题:当不想破坏高内聚和低耦合的设计原则时,谁来负责处理这种状况?
(2) 解决方案:将一组高内聚的职责分配给一个虚构的或处理方便的“行为”类,它并非问题域中的概念,而是虚构的事务,以达到支持高内聚、低耦合和重用的目的。
(3) 分析:纯虚构模式用于解决高内聚和低耦合之间的矛盾,它要求将一部分类的职责转移到纯虚构类中,在理想状况下,分配给这种虚构类的职责是为了达到高内聚和低耦合的目的。在实际操做过程当中,纯虚构有不少种实现方式,例如将数据库操做的方法从数据库实体类中剥离出来,造成专门的数据访问类,经过对类的分解来实现类的重用,新增长的数据访问类对应于数据持久化存储,它不是问题域中的概念,而是软件开发者为了处理方便而产生的虚构概念。纯虚构能够消除因为信息专家模式带来的低内聚和高耦合的坏设计,获得一个具备更好重用性的设计。在系统中引入抽象类或接口来提升系统的扩展性也能够认为是纯虚构模式的一种应用。纯虚构模式一般基于相关功能的划分,是一种以功能为中心的对象或行为对象。在不少设计模式中都体现了纯虚构模式,例如适配器模式、策略模式等等。
例如:上面多态模式的例子,若是咱们的绘图程序须要支持不一样的系统,那么由于不一样系统的API结构不一样,绘图功能也须要不一样的实现方式,那么该如何设计更合适呢?以下图:
这里咱们能够看到,由于增长了纯虚构类AbstractShape,不管是哪一个系统均可以经过AbstractShape类来绘制图形,咱们即没有下降原来的内聚性,也没有增长过多的耦合,可谓鱼肉和熊掌兼得!
(1) 问题:如何分配职责以免两个(或多个)事物之间的直接耦合?如何解耦对象以下降耦合度并提升系统的重用性?
(2) 解决方案:分配职责给中间对象以协调组件或服务之间的操做,使得它们不直接耦合。中间对象就是在其余组件之间创建的中介。
(3) 分析:要避免对象之间的直接耦合,最经常使用的作法是在对象之间引入一个中间对象或中介对象,经过中介对象来间接相连。中介模式对应于面向对象设计原则中的迪米特法则,在外观模式、代理模式、中介者模式等设计模式中都体现了中介模式。
“中介”顾名思义,就是这个事不能直接来办,须要绕个弯才行。绕个弯的好处就是,原本直接会链接在一块儿的对象彼此隔离开了,一个的变更不会影响另外一个。就像前面的低耦合模式里说的同样,“两个不一样模块的内部类之间不能直接链接”,可是咱们能够经过中间类来间接链接两个不一样的模块,这样对于这两个模块来讲,他们之间仍然是没有耦合/依赖关系的。
(1) 问题:如何分配职责给对象、子系统和系统,使得这些元素中的变化或不稳定的点不会对其余元素产生不利影响?
(2) 解决方案:找出预计有变化或不稳定的元素,为其建立稳定的“接口”而分配职责。
(3) 分析:受保护变化模式简称PV,它是大多数编程和设计的基础,是模式的基本动机之一,它使系统可以适应和隔离变化。它与面向对象设计原则中的开闭原则相对应,即在不修改原有元素(类、模块、子系统或系统)的前提下扩展元素的功能。开闭原则又可称为“可变性封装原则(Principle of Encapsulation of Variation, EVP)”,要求找到系统的可变因素并将其封装起来。如将抽象层的不一样实现封装到不一样的具体类中,并且EVP要求尽可能不要将一种可变性和另外一种可变性混合在一块儿,这将致使系统中类的个数急剧增加,增长系统的复杂度。在具体实现时,为了符合受保护变化模式,咱们一般须要对系统进行抽象化设计,定义系统的抽象层,再经过具体类来进行扩展。若是须要扩展系统的行为,无须对抽象层进行任何改动,只须要增长新的具体类来实现新的业务功能便可,在不修改已有代码的基础上扩展系统的功能。大多数设计原则和GoF模式都是受保护变化模式的体现。