设计模式(Design Pattern)是对设计经验的显式表示。每个设计模式描述了一个反复出现的问题及其解法的核心内容它命名、抽象并标识了一个通用设计结构的关键部分使之可用来创建一个可复用的设计。程序员使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码的可靠性和程序的重用性。在介绍设计模式之前本章先介绍C的核心——类的设计。本章主要讲解类的构造和UML类图的组成引用高焕堂老师提出的EIT(Engine Interface Tire)造型最后通过EIT造型拼接出设计模式为读者进行设计模式的学习奠定基础。1.1 类方法本章的内容从小码路买的第一辆汽车DZ说起。DZ由引擎提供动力假设引擎是不会坏的。汽车行驶两年后轮胎轻微变形这时小码路想给汽车换一套新的轮胎于是一个汽车类就产生了。//汽车类 class Car { public: Car(std::string en): m_engineName(en) { } void setCommonEngine() //设置发动机 { std::cout commonEngine is: m_engineName std::endl; } virtual void setDiffTire(std::string tire) 0; //设置轮胎 std::string getDiffTire() { return m_tire; } protected: std::string m_engineName; std::string m_tire; };DZ的原装“miqilin”轮胎质量相当好。可是小码路买了车之后生活拮据所以准备换相对便宜的“weichai”轮胎。小码路考虑到两年后又要为DZ换轮胎所以上面程序中提供的轮胎接口SetDiffTire(string tire)就显得相当重要了改写后的程序如下。#include iostream using namespace std; //DZ继承自汽车类 class DZ : public Car { public: DZ(std::string en): Car(en) { } void setDiffTire(std::string tire) { m_tire tire; } }; int main() { Car* car new DZ(weichai); car-setCommonEngine(); car-setDiffTire(Michelin); std::cout car-getDiffTire() std::endl; car-setDiffTire(Goodyear); std::cout car-getDiffTire() std::endl; delete car; car nullptr; return 0; }编译运行1.2 类间关系类图表示的是类与类之间的关系此种关系通常用UMLUnified Modeling Language统一建模语言表示。类图在软件设计及应用架构前期设计中是不可或缺的一部分它的主要组成部分包括类名、类方法也叫成员方法、成员函数和成员变量其中类方法包含返回值类型成员变量包含数据类型这些组成部分由一个矩形框包围起来。一个UML类图的组成部分的完整表达式如下。[是否可见] [成员变量名称/类方法名称][数据类型/返回值类型] [ 默认值可选]其中部分符号的含义如下。可见(public)。−自身可见(private)。#继承可见(protected)。因此前面提到的Car类的UML类图如图1-1所示。图1-1 Car类的UML类图图1-1完整表示了一个UML类图的组成汽车类名称Car正上居中虚接口SetDiffTire()返回string类型参数公有成员函数SetCommonEngine()无返回值Car(string en)为自身构造函数公有成员变量engineName的类型是string。在软件设计或架构设计中类通常不是单独存在的如上文提到的DZ类与Car类存在继承关系这里说的继承是一种泛指的关系。之所以说是泛指关系是因为类之间的关系根据耦合度由强到弱又分为接口实现关系、继承泛化关系、不可分离组合关系、可分离聚合关系、关联关系和依赖关系。下面分别阐述各个类间关系的UML类图的表示方式。1.2.1 接口实现关系接口实现关系就是派生类必须重写接口中的所有方法在UML类图中用“虚线空心箭头”表示其中箭头指向基类。例如不同品牌计算机的售价不同用UML类图表示的小米笔记本电脑和华为笔记本电脑各自实现的笔记本电脑类的售价接口如图1-2所示。图1-2 接口实现关系图1-2说明了接口实现关系的UML类图的组成基类笔记本电脑Computer包含一个笔记本电脑售价的公有虚方法SalePrice(Computer*sonCom)小米笔记本电脑类XiaoMiComputer和华为笔记本电脑类HuaWeiComputer均继承基类Computer并实现各自的具体售价接口SalePrice(Computer *sonCom)返回int类型的笔记本电脑售价参数完成接口的实现。注函数有时也称为方法。1.2.2 继承泛化关系继承泛化关系就是常说的继承关系派生类继承基类基类被看作“一般设计”派生类被看作“特殊设计”因此继承泛化关系也被看作一般与特殊的关系在UML类图中用“实线实心箭头”表示其中箭头指向基类。例如动物类会走路、吃东西和发声猫类和狗类继承动物类猫类会爬树而狗类会看门并且它们发声的方式也不一样。用UML类图表示的动物类、猫类和狗类的关系如图1-3所示。图1-3 继承泛化关系图1-3说明了继承泛化关系的UML类图的组成基类动物类Animal含有保护(protected)类型的成员变量animalName和animalColor走路方法OnFoot()和吃东西方法Eat()是动物的共性不同的发声方式Say()被定义为虚接口。派生类猫类Cat和狗类Dog继承自动物类Animal实现具体的Say()方法并且Cat类有自身独有的爬树方法OnTree()Dog类有自身独有的看门方法LookDoor()这样就完成了继承并且实现了扩展的功能。1.2.3 不可分离组合关系不可分离组合关系可以用整体和部分之间的关系来解释部分是不能脱离整体单独存在的。部分对象与整体对象是不可分离的一旦整体对象析构部分对象就会随之消失它们属于同一个生命周期。不可分离组合关系在UML类图中用“实线实心菱形”表示其中实心菱形指向整体。例如一个人的身体包含脚部、手部和头部等各个部分实现各自的功能但各个部分又不能脱离身体而单独存在用UML类图表示的身体类、脚部类、头部类、手部类的关系如图1-4所示。图1-4 不可分离组合关系图1-4说明了不可分离组合关系的UML类图的组成整体身体类Body由私有成员变量头部类对象Head、脚部类对象Feet、手部类对象Hand和私有成员方法ComBody()组成脚部类Feet实现走路方法Onfoot()头部类Head实现吃饭方法Eat()和观察世界方法See()手部类Hand实现操作方法OnHand()。各个部分对象不能独立于整体对象存在部分与整体是一种不可分离的组合关系。注类对象是类的实例。1.2.4 可分离聚合关系可分离聚合关系也可以说成是整体与部分的关系它与不可分离组合关系的区别是这种整体与部分是可以分离的也就是说部分是可以脱离整体单独存在的。UML类图中用“实线空心菱形”表示这种关系其中空心菱形指向整体。例如一所学校中有教师和学生教师和学生都是可以作为个体存在的用UML类图表示的学校类、教师类、学生类的关系如图1-5所示。图1-5说明了可分离聚合关系的UML类图的组成学校类School包含私有成员变量教师类对象集合setTeacher、学生类对象集合listStudent公有成员方法招聘教师方法RecruitedTeacher (setTeacher)、学生考试方法ExamStudent(listStudent)等。教师类Teacher含有教书方法Teach()学生类Student含有学习方法Study()和玩耍方法Play()。值得注意的是部分是可以单独存在的部分可以脱离整体这种关系为可分离聚合关系。图1-5 可分离聚合关系1.2.5 关联关系关联关系顾名思义就是一个类与另一个类在对象之间的联系联系可以是双向的也可以是单向的。在UML类图中双向关联关系用没有箭头的实线表示单向关联关系用“实线箭头”表示箭头指向被关联的类。例如医生与病人之间的关系个体与自身手机号、身份证号之间的关系。在代码中将一个类的对象作为另一个类的成员变量来达到两者关联的目的。其中医生与病人双向关联关系的UML类图如图1-6所示。图1-6 双向关联关系图1-6说明了双向关联关系的UML类图的组成医生类Doctor和病人类Patient为双向关联医生类Doctor包含私有成员变量病人类对象集合listPatient、医生姓名doctorName和公有类方法医生诊断方法Diagnosis()病人类Patient包含私有成员变量医生类对象集合listDoctor、病人姓名patientName和公有类方法病人看病方法SeeADoctor()Doctor类和Patient类分别包含对方的类对象作为成员变量从而实现双向关联关系。其中个体与手机号、身份证号单向关联关系的UML类图如图1-7所示。图1-7说明了单向关联关系的UML类图的组成个体类People包含手机号类Phone和身份证号类Identity这两个私有成员变量People类实现个体标志方法IdPeople()Phone类实现设定手机号方法SetPhoneNum(int* pn)Identity类实现设定身份证号方法SetIdNum(int* in)People类指向Phone类和Identity类实现了一种单向的关联关系。图1-7 单向关联关系1.2.6 依赖关系只有在一个类依赖另一个类中的方法时才存在依赖关系一般将类作为参数传递通过对方法的调用实现一个类访问另一个类的功能。在UML类图中使用带箭头的虚线表示这类关系箭头指向被依赖的类。例如同事之间通过邮件进行工作交流用UML类图表示的同事类、邮件类的关系如图1-8所示。图1-8 依赖关系图1-8说明了依赖关系的UML类图的组成同事类Colleague由私有成员变量同事名称collName、同事工号numId和工作方法Work(Mail mail)组成其中Work(Mail mail)中的形参是依赖关系实现的关键邮件类Mail包含私有成员变量邮件主题mailTopic和发送时间sendTime并且实现发送消息方法TransferMessage()Colleague类中的Work(Mail mail)方法完成对TransferMessage()的调用Colleague类只有依赖Mail类才能工作。1.3 EIT造型EIT造型是高焕堂老师在讲解Android架构时提到的一种用于表述类与类之间关系纽带的概念这种纽带把本无关系的单个类变成了联系密切的“亲戚”。1.3.1 EIT是什么从类方法与UML类图中可以抽离出一个标准模板暂且称这个标准模板为公式。只要有公式就能很好地理解自变量与因变量之间的关系。例如力矩与力臂的关系公式M FL可以理解为力臂(L)一定的时候力(F)越大力矩(M)越大又如力与加速度的关系公式F ma可以理解为物体加速度(a)跟作用力(F)成正比跟物体的质量(m)成反比且与物体质量的倒数成正比。把这种自变量与因变量之间的关系应用于软件设计是否也能找到一种类似的“公式”以便我们理解代码与框架之间是如何“沟通”的呢答案是肯定的这就是高焕堂老师讲解Android架构时提出的EIT造型这种造型也是代码设计时用的一种标准“公式”。EIT造型由以下3部分组成。E : Engine即引擎基类。I : Interface即接口。T : Tire即轮胎派生类。引擎通过接口驱动轮胎带动整辆车往前行驶EIT造型的形象表示如图1-9所示。图1-9 EIT造型图1-9形象地描述了EIT造型的3种组成部分之间的关系中间的接口I用以联系引擎E和轮胎T应用在软件设计中则是基类E和派生类T之间的联系通过接口I来实现。1.3.2 程序应用EIT造型迁移到编程实践中E是基类、I是接口、T是派生类应用到1.1节的Car类的案例中Car是E、SetDiffTire(string tire)是I、DZ是T用UML类图表示的EIT造型如图1-10所示。图1-10说明了EIT造型的程序应用基类Car是EIT造型中的E基类中的虚方法SetDiffTire (string tire)是EIT造型中的I派生类DZ是EIT造型中的T派生类实现具体的SetDiffTire(string tire)接口I。图1-10 EIT造型的程序应用图1-10中Car与DZ紧耦合在软件设计中可以继续优化将Car与DZ分离解耦增加一个接口类SetTireInterface在Car类中实现安装不同轮胎的方法DiffTire()各个派生类继承接口类例如DZInterface派生类继承自接口类SetTireInterfaceDZInterface派生类“安装”符合自身应用需求的Tire具体代码如下。#include iostream using namespace std; //新增接口类 class SetTireInterface { public: SetTireInterface(string tn): m_tireName(tn) {} virtual void setDiffTire(string tireName) 0; string getDiffTire() { return m_tireName; } protected: string m_tireName; //轮胎名 }; //新增接口派生类 class DZInterface : public SetTireInterface { public: DZInterface(string tn): SetTireInterface(tn) {} void setDiffTire(string tireName) { m_tireName tireName; } }; //Car与DZ解耦 class Car { public: Car(std::string en): m_engineName(en) { m_interface new DZInterface(Michelin); //米其林 } void setCommonEngine() //设置发动机 { std::cout commonEngine is: m_engineName std::endl; } void diffTire() //设置轮胎 { m_interface-setDiffTire(Goodyear); //‌固特异 cout m_interface-getDiffTire() endl; } protected: std::string m_engineName; SetTireInterface* m_interface; }; //客户端主程序 int main() { Car* car new Car(weichai); car-setCommonEngine(); car-diffTire(); delete car; car nullptr; return 0; }编译运行1.3.3 优化设计1.3.2小节中对最初的Car类进行了设计并且完成了对应的代码根据1.3.2小节代码中的各个类的组成及类间关系绘制优化设计后的EIT造型的UML类图如图1-11所示。图1-11 优化后的EIT造型与图1-10相比优化后的EIT造型多了一个接口类对象SetTireInterfaceEIT中的I这个接口类对象替代了原来的接口方法SetDiffTire(string tire)EIT中的E和T保持不变并且在E的Car对象中包含接口类对象SetTireInterface的成员变量Car对象中的接口类对象SetTireInterface在构造Car对象的同时被赋值为m_Interface并且在调用Car对象的DiffTire()方法中完成对SetDiffTire()的间接控制。但是这个优化后的SetTireInterface与DZInterface仍然存在继承泛化关系能否将两者改成关联关系呢这里考虑将接口及派生类分离成两个独立类整个流程代码完善后的结果如下。//car3.cpp #include iostream using namespace std; class SetTireInterface; //派生类DZ对象 class DZ { public: DZ() { } void setDiffTire(string tire) { cout setDiffTire is: tire endl; } }; //派生类对象独立于接口类 class SetTireInterface { public: SetTireInterface() { m_dzInterface new DZ(); } void enableSetDiffTire(string tire) { m_dzInterface-setDiffTire(tire); } protected: DZ* m_dzInterface; }; //Car类与接口单向关联 class Car { public: Car(string en): m_engineName(en) { m_interface new SetTireInterface(); } void setCommonEngine() //设置发动机 { std::cout commonEngine is: m_engineName std::endl; } void setDiffTire(std::string tire) //设置轮胎 { m_interface-enableSetDiffTire(tire); } protected: std::string m_engineName; SetTireInterface* m_interface; }; //客户端主程序 int main() { Car* car new Car(weichai); car-setCommonEngine(); car-setDiffTire(Michelin); delete car; car nullptr; return 0; }编译运行去除继承泛化关系后的优化方案的代码如上根据以上代码绘制的UML类图如图1-12所示。图1-12说明了再次优化后由继承泛化关系变成关联关系的EIT造型的组成Car对象与SetTireInterface对象为单向关联关系Car对象包含SetTireInterface对象的成员变量成员函数与图1-11保持一致DZ对象与SetTireInterface对象为双向关联关系DZ对象包含SetTireInterface对象的成员变量SetTireInterface对象包含DZ对象的成员变量SetTireInterface对象的成员方法EnableSetTireInterface(string tire)中完成对SetDiffTire(string tire)的关联控制。图1-12中Car、SetTireInterface、DZ之间的关联替代图1-11中SetTireInterface与DZInterface之间的继承泛化使SetTireInterface与DZ实现解耦方便后续开发者对程序进行扩展和维护实现可靠、完美的软件设计。图1-12 关联关系的EIT造型1.4 组合设计模式1.1节中讲解的类方法表明了类内的关系1.2节中讲解的UML类图描述了类与类之间的关系将其抽象成一个固定公式即1.3节中讲解的EIT造型。EIT造型通过对UML类图中各个关系的排列组合与应用实现软件基本架构的搭建与设计。软件架构设计的基础就是抽离出公共部分进行复用作为E最主要的工作就是设计出接口作为I最后设计出可更换的T。两个或两个以上EIT造型组合就成了组合设计模式。从图1-13可以清晰地看出类与类、类与EIT造型、EIT造型与EIT造型等之间的进阶关系。图1-13 类、UML类图、EIT造型之间的关系图1-13说明了类、UML类图、EIT造型之间的关系类对象与类对象之间是通过UML类图进行组合、继承、关联或依赖的类对象加上接口方法构成了EIT造型各个EIT造型之间的排列组合构成了组合设计模式。将这种类、UML类图、EIT造型之间按层次递进的关系呈现出来可使读者更容易理解组合设计模式的由来和组成这三者是设计模式理论基础的核心介绍它们之间的关系可为组合设计模式的展开说明奠定基础。将组合设计模式的思想应用到1.1节介绍的类方法中Car类与DZ类构成一个EIT1造型其中SetDiffTire()传参改为Car类的成员变量代码设计如下。//car4.cpp #include iostream using namespace std; //EIT1造型Car、SetDiffTire()和DZ class Car { public: Car() { } Car(std::string en): m_engineName(en) { } void setCommonEngine() //设置发动机 { std::cout commonEngine is: m_engineName std::endl; } virtual void setDiffTire(std::string tire) 0; //设置轮胎 std::string getDiffTire() { return m_tireName; } public: std::string m_engineName; std::string m_tireName; }; //DZ继承自汽车类 class DZ : public Car { public: DZ() : Car() { } DZ(std::string en): Car(en) { } void setDiffTire(std::string tire) { m_tireName tire; } };不同种类的汽车轮胎由固定的工厂进行加工制造例如“miqilin”轮胎有自己的“miqilin”工厂工厂加工出轮胎后应用在小码路购买的DZ品牌汽车上。同理轮胎工厂和“miqilin”轮胎工厂组成一个EIT2造型代码设计如下。//EIT2造型 CreateCarTire、ReturnCarName()和 MQLTireFactory class CreateCarTire { public: Car* productCar() { return returnCarName(); } virtual Car* returnCarName() 0; }; class MQLTireFactory : public CreateCarTire { public: Car* returnCarName() { Car* car new DZ(); return car; } };如图1-13所示将不同的EIT造型组合在一起可以得到软件的基本架构本案例中将EIT1造型与EIT2造型关联在一起EIT1造型通过构造Car对象调用SetCommonEngine()方法输出轮胎类型EIT2造型通过构造CreateCarTire对象设定CreateCarTire的轮胎名称tireName最终调用SetDiffTire()方法实现“DZ使用miqilin轮胎”的过程客户端主程序如下。//客户端主程序 int main() { Car* car new DZ(weichai); car-setCommonEngine(); CreateCarTire* createCarTire new MQLTireFactory(); Car* carSetTire createCarTire-productCar(); carSetTire-m_tireName Michelin; carSetTire-getDiffTire(); std::cout carSetTire-getDiffTire() std::endl; car-setDiffTire(Michelin); std::cout car-getDiffTire() std::endl; car-setDiffTire(Goodyear); std::cout car-getDiffTire() std::endl; delete car; car nullptr; return 0; }编译运行根据以上设计流程将两个或两个以上的EIT造型以不同的方式组合在一起形成了组合设计模式。EIT1造型和EIT2造型组合构成的设计模式的UML类图如图1-14所示。图1-14 EIT造型组合构成的设计模式的UML类图图1-14说明了EIT造型组合构成组合设计模式的过程EIT1造型由类对象Car、接口方法SetDiffTire()、类对象DZ组成Car类包含公有成员变量引擎名称engineName和轮胎名称tireName、两个类构造函数Car()和Car(string en)、公有的引擎方法SetCommonEngine()DZ类实现具体的SetDiffTire()方法EIT2造型由类对象CreateCarTire、接口方法ReturnCarName()、类对象MQLTireFactory组成CreateCarTire类包含公有成员生产汽车方法ProductCar()MQLTireFactory类实现具体的ReturnCarName()。图1-14展现了组合设计模式的构成部分是后续讲解设计原则和设计模式的基础和关键。1.5 总结本章介绍了设计模式的基本理论知识。对类方法的介绍可以让读者回想起C设计的核心思想是面向对象对UML类图的详细讲解可以让读者明白类与类之间的关系用这些关系将类组合成EIT造型进而设计出符合开发者或程序维护者需求的设计模式。设计模式的最终应用正是发挥面向对象优势的最佳体现读者掌握了本章的基本知识学习后续的设计原则和设计模式将事半功倍。思而不罔在UML类图中接口实现关系和继承泛化关系是比较常用的不可分离组合关系和可分离聚合关系是难以区分的关联关系和依赖关系又是紧密相连的请说出这3对关系之间的区别。Car类的设计总共经历了3次优化请说出每次具体优化了什么以及为什么要进行这样的优化设计。温故而知新面向对象的主要特性是继承、封装与多态这些特性都建立在类方法实现的基础上只有通过对类方法的不断改造、类关系的不断升级才可将面向对象的特性发挥到极致。面对同一需求不同的开发者可能会给出不同的实现方案如何设计一套可靠、可扩展和易维护的框架本章的逐步优化设计给开发者提供了一个良好的参考思路。框架设计的改进其实已经体现出了设计模式应用的雏形这是后续内容中的重点。如何发挥面向对象的优势如何设计一套可靠的框架掌握六大设计原则和23种设计模式是关键。下面就正式开始软件设计模式学习之旅吧