作者:杨宁(来自grapecity)
第一章 引言
1. 本文不适合…
本文不适合想通过本文来装修房子的读者;
本文不适合面向对象编程高手,会浪费你的时间。如果你愿意抽出时间来阅读本文,并提出宝贵的建议,非常感谢!什么?你没有听说过设计模式?那你也敢称高手?
2. 本文适合…
如果你对面向对象编程感兴趣,而又没有时间去读Gang of Four的“Design Patterns Elements of Reusable Object-Oriented Software”(以下简称《设计模式》)。那么,本篇文章将帮助你了解23种设计模式。
我第一次读这本书是在每次晚睡之前,几乎每次都先睡着。《设计模式》以一种严谨,系统化的风格来论述23种设计模式,原书可以说是面向对象编程的一个基础教程,但是要领会其精髓,必须要花费一定的精力。本文的目的是为了帮助你更加方便地理解每一种设计模式,并不想成为原书的替代读物。
本文无意于介绍面向对象的基本知识。因此,假设本文的读者已经对面向对象的封装性、继承性和多态性有足够的了解和认识。并能够认识到可复用的面向对象设计的两个原则:
● 针对接口编程,而不是针对实现编程; |
● 优先使用对象组合,而不是类继承。 |
3. 设计模式是什么?
设计模式概念是由建筑设计师Christopher Alexander提出:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动。”上述的定义是对设计模式的广义定义。我们将其应用到面向对象软件的领域内,就形成了对设计模式的狭义定义。
我们可以简单的认为:设计模式就是解决某个特定的面向对象软件问题的特定方法。但严格的来说上述的认识是不准确的,难道面向对象软件中只有区区23个问题?当然不是。
为了能够更加准确地理解这个概念,我们引入另外一个术语:框架(Framework)。框架这个词汇在当今有了各种各样的应用和含义。在设计模式中:框架(Framework)是构成一类特定软件可复用设计的一组相互协作的类。
框架可以认为是一个适用于某个领域的软件包。这个软件包提供了相应领域的各个问题的解决方法。那么,它和设计模式有什么区别?
● 设计模式和框架针对的问题域不同: |
设计模式针对面向对象的问题域;框架针对特定业务的问题域; |
● 设计模式比框架更为抽象: |
设计模式在碰到具体问题后,才能产生代码;框架已经可以用代码表示。 |
● 设计模式是比框架更小的体系结构元素: |
框架中可以包括多个设计模式。 |
Tips:设计模式就像是在武功中基本的招式。我们将这些招式合理地组合起来,就形成套路(框架)。
4. 为什么要用设计模式?
作为程序员都知道良好程序的一个基本标准:高聚合,低耦合。面向对象语言比结构化语言要复杂的多,不良或者没有充分考虑的设计将会导致软件重新设计和开发。然而实际的设计过程中,设计人员更多的考虑如何解决业务问题,对于软件内部结构考虑较少;设计模式则补充了这个缺陷,它主要考虑如何减少对象之间的依赖性,降低耦合程度,使得系统更易于扩展,提高了对象可复用性。因此,设计人员正确的使用设计模式就可以优化系统内部的结构。
第二章 概要简介
在《设计模式》一书中,共包含23个模式。根据目的的不同,将它们分为三类:
● 创建型(Creational):解决如何创建对象的问题。 |
● 结构型(Structural):解决如何正确的组合类或对象的问题。 |
● 行为型(Behavioral):解决类或对象之间如何交互和如何分配职责的问题。 |
Tips:设计模式中经常会用到抽象(Abstract)和具体(Concrete)这两个词。抽象的含义是指它所描述的类(方法)是接口类(方法),具体的含义是指它所描述的类(方法)实现了相应的抽象类(方法)。
第三章 抽象工厂(Abstract factory)
1. 意图
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
2. 分类
创建型模式。
3. 问题是什么?
对于不熟悉这个模式的人都会对工厂(Factory)这个词感到奇怪,为什么会用这个词?之后,我们还会碰到另一个模式——工厂方法(Factory method),所以先解释一下工厂的意义:
房子是由墙,门,窗户,地板,天花板,柱子组成的。如果我们为客户编写一个建造房子的软件,我们会把墙,门,窗户,地板,天花板,柱子看成不同的类:WallClass, DoorClass, WindowClass, CeilingClass, PillarClass。
现在我们建立一个新类A,这个类中有CreateWall(), CreateDoor(), CreateFloor(), CreateCeiling(), CreatePillar()五个方法,每个方法的功能就是创建并返回相应的对象。如果把WallClass, DoorClass, WindowClass, CeilingClass, FloorClass, PillarClass的实例看成产品的话,那么类A就像是一个生产这些产品的工厂。这就是使用工厂这个词的原因。
Tips:A这个名字太糟,如果用HouseFactory会好一些。一般情况下,在你的系统使用某个模式,最好使用模式相应的关键字作为类或者方法的名字的一部分,这样,你的同伴或者系统的代码维护者就会明白你的用意。
我们的软件完成了,客户非常满意。不过,我们的客户想把这个软件出口,他发现一个问题,这个软件太本地化了,建造出来的都是中国式的房屋。因此他希望我们的软件能够建造出不同地域风格的房子。
这就是我们的问题!我们需要重新设计原系统,而且一次完成世界各地不同建筑风格是不可能的。我们会先完成部分风格(客户第一要投放软件的国家的),然后再增加其他的…
4. 解决方法
1) 建立一个抽象工厂(Abstract Factory)类HouseFactory,在这个类中声明:
CreateWall () |
CreateDoor () |
CreateFloor () |
CreateCeiling () |
CreatePillar () |
2) 建立相应的抽象产品(Abstract Product)类集:
Wall, Door, Floor, Ceiling, Pillar
3) 为不同风格建立相应的具体工厂(Concrete Factory)类(不要忘了实现关系),例如:
ChinaHouseFactory : HouseFactory |
GreeceHouseFactory : HouseFactory |
… |
4) 为不同的风格建立相应的具体产品(Concrete Product)类(实现相应的抽象产品),例如:
ChinaWall : Wall |
ChinaDoor : Door |
… |
GreeceWall : Wall |
GreeceDoor : Door |
… |
5. 杂谈
我想你一定明白如何灵活的创建和使用上面的一大堆类:
● 重复最后两个步骤,就可以方便的增加新风格; |
● 使用前面两个步骤中声明的抽象类来实现操作。 |
抽象工厂模式的重点不是声明的那个抽象工厂类,而是它声明的一系列抽象产品类,我们通过使用这些抽象产品类可以操作我们已经实现或者还未实现的具体产品类,并且保证了它们的一致性。
你可能已经发现这个软件不能建造你的两层别墅,因为它没有楼梯。为此,我们要定义Stair抽象类,还要增加CreateStair抽象方法;最重要的是我们要在已经完成的76种风格中增加相应的Stair类,这可是个大麻烦。确实,抽象工厂模式在适应新的产品方面的能力是比较弱的。这是它的一个缺点。
第四章 生成器(Builder)
1. 意图
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
2. 分类
创建型模式。
3. 问题是什么?
在抽象工厂一章中,我们了解到了在全球建房系统中如何支持多种房屋的风格。新的问题是:用户希望看到同样结构下,不同风格房屋的外观。比如:这个房子有一个地板,一个天花板,四面墙,一个门,一个窗户。他希望看到中国风格房屋和希腊风格房屋的相应式样。
4. 解决方法
1) 创建一个生成器(Buider)类:
Class HouseBuider { BuildHouse(){} BuildWall(int){} BuildDoor(int){} BuildWindow(int) {} BuildFloor(){} BuildCeiling() {} BuildPillar(){} GetHouse() { return Null;} } |
在这个类中,我们对每一种房屋组成元素都定义了一个Build方法。并且定义了一个返回构造结果的方法GetHouse。
2) 对每一种风格都定义一个具体的生成器(Concrete Builder)类:
ChinaHouseBuilder: HouseBuilder GreeceHouseBuilder: HouseBuilder … |
并且在这些类中重载父类的方法。
3) 还有,各种类型的房屋类:
ChinaHouse GreeceHouse … |
5. 如何使用
我们可以用下面的方法使用上面的类来解决我们的问题。
Class HosueSystem { object Create( builder HouseBuilder) { builder.BuildHouse(); builder.BuildFloor(); builder.BuildCeiling(); builder.BuildWall(1); builder.BuildWall(2); builder.BuildWall(3); builder.BuildWall(4); builder.Door(1); // 在Wall1上建一个门 builder.Window(2); // 在Wall2上建一个窗户 return builder.GetHouse(); } } |
只要向通过HouseSystem.Create方法传入不同的生成器就可以得到不同风格的结果。
事实上,HouseSystem也是生成器模式的一个参与者,叫做导向者(Director)。注意的问题:
● 生成器(HouseBuilder)类中要包含每一种构件元素的生成方法。比如:楼梯这个元素在某些建筑风格中没有,在其他风格中有,也要加上BuilderStair方法在HouseBuilder中。 |
● 生成器(HouseBuilder)类不是抽象类,它的每一个生成方法一般情况下什么都不做。这样,具体生成器就不必考虑和它无关的元素的生成方法。 |
6. 杂谈
1) 也许你有这样的想法:生成器模式似乎过于复杂,可以直接在具体House类中定义CreateHouse方法就可以了,例如:
Class ChinaHouse { ChinaHouse CreateHouse() { ChinaHouse house; house = new ChinaHouse(); house.Add(new Floor()); house.Add(new Ceiling()); house.Add(new Wall(1)); house.Add(new Wall(2)); house.Add(new Wall(3)); house.Add(new Wall(4)); house.Add(new Door(1)); // 在Wall1上建一个门 house.Add(new Window(2)); // 在Wall2上建一个窗户 return house; } } |
而生成器模式至少用了两个类来解决这个问题:导向者类和具体生成器类。
那么,生成器模式好在哪里?答案就是职责分配。生成器模式将一个复杂对象的生成这一职责作了一个很好的分配。它把构造过程放到导向者的方法中,把装配过程放到具体生成器类中。我们看看下面的说明。
2) HouseSystem类(导向者)可以非常精细的来构造House。而这个生成过程,对于产品类(ChinaHouse, GreeceHouse …)和生成器类 (ChinaHouseBuilder, GreeceHouseBuilder)都没有必要关心。具体生成器类则考虑装配元素的问题。
7. 活用
生成器模式可以应用在以下的问题上:
● 将系统的文档格式转换到其他的格式上(每一种格式的文档都相当于一个产品)。
● 编译问题(语法分析器是导向者,编译结果是产品)。
关于作者:
杨宁是GrapeCity公司海外应用开发部技术骨干。从事多年的程序开发,有Unix,Windows平台上的开发经验。对VB,C#, VB.Net,XML有比较丰富的认识。喜爱研究OO的编程,分析,设计,项目管理等相关技术。喜欢学习新的技术。