设计模式总结

对象关系 下面两个关系好理解 泛化关系(generalization) 继承 继承关系 is-a 泛化<>实现 泛化关系用一条带空心箭头的直接表示;如下图表示(A继承自B);

实现关系(realize) 实现抽象类的接口类 继承自抽象类

下面四个关系耦合度递增 依赖关系(dependency) 访问不到私有变量 某类作为别的类的局域变量、方法的形参,或者对静态方法的调用 描述一个对象在运行期间会用到另一个对象的关系; 是一种临时性的关系,通常在运行期间产生,并且随着运行时的变化; 依赖关系也可能发生变化;

PS: 关联、聚合、组合只能配合语义,结合上下文才能够判断出来,而只给出一段代码让我们判断是无法判断的 关联关系(association) 同层次的类 可访问到私有变量 类常作为成员变量 关联关系是用一条直线表示的 关联关系是一种“强关联”的关系,静态的,运行无关的!

关联关系默认不强调方向,表示对象间相互知道;如果特别强调方向,A − − > B A-->BA−−>B,表示A知道B,但 B不知道A; 聚合关系(aggregation) 整体部分类间的关联关系 类常作为成员变量 has a 无强依赖关系的整体由部分组成的语义 学生聚合到班级上

整体和部分不是强依赖的,即使整体不存在了,部分仍然存在;例如, 部门撤销了,人员不会消失,他们依然存在。 组合关系(composition) 两类生命周期一样 整体类可以控制部分类的实例化和析构 类常作为初始化方法的参数 contains-a 有强依赖关系的整体由部分组成的语义

UML图/时序图 UML(Unified Modeling Language)统一建模语言

时序图(Sequence Diagram)是显示对象之间交互的图,这些对象是按时间顺序排列的。时序图中显示的是参与交互的对象及其对象之间消息交互的顺序。 时序图包括的建模元素主要有:对象(Actor)、生命线(Lifeline)、控制焦点(Focus of control)、消息(Message)等等。 设计理念 单一职责原则 SRP 就一个类而言,应该仅有一个引起它变化的原因 如果一个类承担职责过多,这些职责的耦合性太高,会导致设计十分脆弱! 软件设计,发现职责并分离职责! 开发-封闭原则 The Open-Closed Principle OCP 开闭原则:针对软件实体(类/模块/函数) ① Open for Extension ② Closed for modification 设计类的时候要尽可能考虑完善,新需求来的时候可以通过只增加一些类而不需要修改原类 但当变化发生的时候创建抽象类来隔离同类的变化 面对需求是通过增加代码而非更改代码实现! 但也避免刻意频繁的抽象 提高程序的可维护性,可扩展,可复用和灵活性! 依赖倒转原则 依赖倒转原则:① 高层模块不应该依赖低层模块,两个都得依赖抽象 ② 抽象不应该依赖细节,而是细节应该依赖抽象 即面向接口编程而不是对实现编程 为什么叫“倒转”:因为一般程序设计都是高层调用底层,如某项目设计了使用某个数据库的算法操作库,而你现在换了数据库,想复用高层算法代码就不方便了!!! 那为什么依赖了抽象类就不再怕更改呢??? 里氏代换原则LSP 面向对象设计的标志! 子类型必须能够替换掉它们的父类型 即 子类型拥有父类型全部非私有的行为和属性! 只要都是继承自同一个父类型,当更改子类型时,除了更改实例化的部分,其他都不变!企鹅不能继承自鸟,因为鸟会飞而企鹅不会! 只有当子类可以替换父类,父类才能真正被复用,子类也能够在父类基础上增加新的行为!才让继承复用成为了可能,采样依赖倒转原则得以实现! 正是由于子类型的可替代性才使得使用父类型的模型在不需修改的情况下可以扩展! 迪米特法则 (最少知识原则) LoD 最少知识原则 ① 如果两个类不必彼此直接通信,那这两个类不应当发生直接的相互作用。② 如果其中一个类需要调用另一个类的方法,可以使用第三者进行转发! 即每个类要保护好自己的私有行为和变量,强调两类之间的松耦合,不需要让别的类知道的就不要公开! 越弱的耦合越有利于复用!封装的思想! 组成/聚合复用原则 CARP 尽量使用组成和聚合,而避免使用继承 即优先使用组成和聚合,有利于保持每个类被封装,这样类和类继承层次会保留较小规模!即变宽以变浅

创建型模式 创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中 对象的创建 和 使用 分离 创建型模式在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。 创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。 简单工厂模式 Simple Factory Pattern 4 又称为静态工厂方法(Static Factory Method)模式 思路 简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。 结构

时序图

import public

public class OperationFactory { public static OperationFactory(string operate) { Operation oper = null; switch (operate) { case "+"; oper = new OperationFactory(); break; case "-"; oper = new OperationFactory(); break; case "*"; oper = new OperationFactory(); break; case "/"; oper = new OperationFactory(); break; } return oper; } }

优缺点 优点:① 简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象 ② 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可 ③ 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,灵活性。 缺点:① 简单工厂模式最大的问题在于工厂类的职责相对过重 ② 增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背的 ③ 使用简单工厂模式将会增加系统中类的个数,更复杂冗余 ④ 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构 工厂方法模式 Factory Method 5 思路 定义一个用于创建对象的接口,让子类觉得实例化哪一个类。即将类的实例化延迟到其子类 结构

时序图

interface IFactory{ Operation CreateOperation(); }

class AddFactory : IFactory{ public Operation CreateOperation() { return new OperationAdd(); } } class SubFactory : IFactory{ public Operation CreateOperation() { return new OperationSub(); } } class MulFactory : IFactory{ public Operation CreateOperation(){ return new OperationMul(); } } class DivFactory : IFactory{ public Operation CreateOperation() { return new OperationDiv(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 优缺点 优点 ① 由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点 ② 在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做,隐藏了哪种具体产品类将被实例化这一细节 ③ 这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品 缺点 ① 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销 ② 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度 抽象工厂模式 Abstract Factory Pattern 5 思路 动机:一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象 因此生成两个概念:产品等级结构(产品的继承结构)产品族(产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品一个牌子的) 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。 提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式 和工厂方法的区别:① 工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。② 当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。 结构

优缺点 优点:① 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。② 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。 缺点:① 在添加新的产品对象时,这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便 ② 开闭原则的倾斜性(1增加新的工厂和产品族容易:增加一个新的具体工厂即可,2增加新的产品等级结构麻烦:需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法) 反射+抽象工厂+配置文件 设计模式 反射:反射是程序获取自身信息的能力,可以用于动态创建类型,跨语言跨平台数据交互,持久化,序列化等等! 反射包含功能:枚举所有member;获取member的name和type;能够get/set member 反射的实现方法:结构化语言 / 运行期反射 / 编译器反射 Python中的反射神器!通过字符串映射object对象的方法或者属性 hasattr(obj,name_str) 判断objec是否有name_str这个方法或者属性 getattr(obj,name_str) 获取object对象中与name_str同名的方法或者函数 setattr(obj,name_str,value) 为object对象设置一个以name_str为名的value方法或者属性 delattr(obj,name_str) 删除object对象中的name_str方法或者属性 JAVA原生支持反射机制,就可以把类名写在配置文件中,读取配置文件完成灵活的软件模式! 建造者模式(生成器模式) Builder 设计游戏常用的模式 2 思路 将复杂对象的构建与其表示之间分离,使得同样的构建可以创建不同的表示 建造者模式是一步一步创建一个复杂的对象,允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节 结构

指挥者的作用:① 它隔离了客户与生产过程;② 负责控制产品的生成过程。 指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象 优缺点 优点: 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象 用户使用不同的具体建造者即可得到不同的产品对象 可以更加精细地控制产品的创建过程 清晰化 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”。 缺点: 如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大 扩展 省略抽象生成或指挥者,或Build将二者综合 和抽象工厂的区别 建造者模式返回一个组装好的完整产品 ,而 抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族。 如果将抽象工厂模式看成 汽车配件生产工厂 ,生产一个产品族的产品,那么建造者模式就是一个 汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。 原型模式 Prototype 3 思路 用原型实例指定创建对象的种类,通过拷贝这些原型创建新的对象 就是从一个对象在创建另外一个可定制的对象,而且不需要知道任何创建细节 在初始化信息不发生变化的时候,克隆是最好的办法!即原型模式就是可以不用重新初始化对象,而是动态的获取对象运行时的状态! 结构

声明一个抽象基类,并定义clone()函数为纯虚函数。 实例化各个子类,并且实现复制构造函数,并实现clone()函数 //首先抽象一个基类 class resume { protected: char *name; public: resume() {} virtual ~resume() {} virtual void set(const char str) {} virtual void show() {} virtual resume clone() { return 0; } }; class ResumeA : public resume { public: ResumeA(const char str); //构造函数 ResumeA(const ResumeA &r); //拷贝构造函数 ~ResumeA(); //析构函数 ResumeA clone(); //克隆,关键所在 void show(); //显示内容 }; ... ... ResumeA* ResumeA::clone() { return new ResumeA(*this); } // Client resume *r1 = new ResumeA("A"); resume *r3 = r1->clone();

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 深复制和浅复制 浅复制只复制指针而共享内存,深复制不再共享内存 当数据成员中没有指针时,浅拷贝是可行的;但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象,所以,此时,必须采用深拷贝。 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。 深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝 C++浅复制 CResume* clone() { // 调用拷贝构造函数 浅拷贝 CResume* resume = new CResume(this); return resume; } 1 2 3 4 5 C++深复制 CResume clone() { CResume* resume = new CResume; resume->setInfo(m_name, m_sex, m_age); // 重新赋值类变量 resume->setExperience(m_experience.getCompany(), m_experience.getWorkTime()); return resume; } 1 2 3 4 5 6 优缺点 单例模式 Singleton 4 思路 保证一个类仅有一个实例,并提供一个访问它的全局访问点 为了避免实例化多个对象,最好就让类自己负责保存自己的唯一实例,只提供一个访问该实例的方法 可以严格控制客户怎样访问和何时访问这个唯一实例 结构

Lazy Singleton可能存在内存泄露和通常需要加锁来保证线程安全(单例实例在第一次被使用时才进行初始化) 下面代码只有当第一次访问getInstance()方法时才创建实例。这种方法也被称为Meyers’ Singleton class Singleton { private: Singleton() { }; ~Singleton() { }; Singleton(const Singleton&); Singleton& operator=(const Singleton&); public: static Singleton& getInstance() { static Singleton instance; return instance; } }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Eager Singleton虽然是线程安全的,但存在潜在问题: static Singleton instance;和static Singleton& getInstance()二者的初始化顺序不确定(在main函数前面初始化完实例 Singleton Singleton::instance) 优缺点 优点:① 控制访问,共享 ② 节约资源,尤一些需要频繁创建和销毁的对象 ③ 可推广到指定个数对象实例的模式 缺点:① 无抽象层导致扩展困难 ② 单例职责过重,单一工厂和产品的集成 ③ 共享连接池对象的程序过多而出现连接池溢出 ④ 如果实例化的对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致对象状态的丢失 结构型模式 结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过 简单积木的组合形成复杂的、功能更为强大的结构。 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系 对象结构型模式关心类与对象的组合,通过关联关系使得在一 个类中定义另一个类的实例对象,然后通过该对象调用其方法。 根据“合成复用原则”,在系统中尽量使用关联关系来替代继 承关系,因此大部分结构型模式都是对象结构型模式。 适配器模式 Adapter 4 思路 将一个类接口转换为客户希望的另外一个接口,可使得原本由于接口不兼容而不能一起工作的那些类可以一起工作 适合希望复用一些现有的类,但是接口又与复用环境要求不一样的时候! 结构

优缺点 优点:① 目标类(Target)和适配者(Adaptee)类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码 ② 将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性 ③ 通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则” ④ 对象适配器 同一个适配器可以把适配者类和它的子类都适配到目标接口 ⑤ 类适配器 可以在适配器类(Adapter)中置换一些适配者(Adaptee)的方法,使得适配器的灵活性更强。 缺点:对象适配器模式:如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。 类适配器模式:对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。 桥接模式 Bridge 3 思路 基于组成/聚合复用原则,尽量不要使用继承 将抽象部分和它的实现部分分离,使它们都可以独立的变化 实现系统可以从多个角度分类,每一种分类都可能变化,那么就把这种多角度分离出来让他们变化,再使用组成或者聚合来共同构建好类! 又称为柄体(Handle and Body)模式或接口(Interface)模式。 结构

int main(int argc, char *argv[]) { Implementor * pImp = new ConcreteImplementorA(); Abstraction * pa = new RefinedAbstraction(pImp); pa->operation();

Abstraction * pb = new RefinedAbstraction(new ConcreteImplementorB());
pb->operation();    
​
delete pa;
delete pb;
​
return 0;

} 1 2 3 4 5 6 7 8 9 10 11 12 13 理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化 抽象化Abstraction:在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程 实现化Implementation:针对抽象化给出的具体实现 脱耦:将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。从而使两者可以相对独立地变化,这就是桥接模式的用意。 桥接模式和适配器模式结合使用(设计的不同阶段):桥接模式用于系统的初步设计,对于存在两个独立变化维度的类可以将其分为抽象化和实现化两个角色,使它们可以分别进行变化;而在初步设计完成之后,当发现系统与已有类无法协同工作时,可以采用适配器模式。但有时候在设计初期也需要考虑适配器模式,特别是那些涉及到大量第三方应用接口的情况。

优缺点 优点:① 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法 ② 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。 缺点:① 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程 ② 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性 组合模式 Composite 4 思路 将对象组合成树形结构表示“部分-整体”的层次结构,组合模式使用户对单个对象和组合对象的使用具有一致性 结构

优缺点 优点:拆分思想;可以将结构和单个对象(叶节点)看作一致处理 装饰模式 Decorator 3 思路 动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更加灵活 装饰器只考虑自己的装饰过程,不需要关心如何被添加到对象链,即不考虑添加的先后逻辑! 结构

优缺点 优点:① 可以有效的把类的核心职责和装饰功能区分开,简化类的表述,去除重复的装饰逻辑 ② 与继承关系相比,关联关系的主要优势在于不会破坏类的封装性,而且继承是一种耦合度较大的静态关系,无法在程序运行时动态扩展 ③ 在软件开发阶段,关联关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于关联关系使系统具有较好的松耦合性,因此使得系统更加容易维护 ④ 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则” 缺点:① 这些装饰类和小对象的产生将增加系统的复杂度,加大学习与理解的难度 ② 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。 外观模式 Facade 5 思路 为子系统中一组接口提供一个一致的界面,此模式定义了一个高层接口,让子系统更加容易使用! 结构

class Facade { public: Facade(); virtual ~Facade(); void wrapOpration(); private: SystemC *m_SystemC; SystemA *m_SystemA; SystemB *m_SystemB; }; 1 2 3 4 5 6 7 8 9 10 模式扩展 一个系统有多个外观类 / 不要试图通过外观类为子系统增加新行为 / 抽象外观类的引入(对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的)为了满足了”开闭原则“! 优缺点 优点:① 设计初期,将外观模式作为数据访问/业务逻辑/表示层之间的联接,降低层间的耦合度;② 开发阶段:降低子系统之间的耦合依赖关系;③ 维护阶段:为一个庞大粗糙的遗留项目设计一个清晰有效的外观接口,也是很好的! 缺点:① 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性 ② 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则” 享元模式 Flyweight 1 思路 运用共享技术有效的支持大量细粒度的对象 当对象数量太多时,将导致运行代价过高,带来性能下降等问题。 在享元模式中可以共享的相同内容称为内部状态(IntrinsicState),而那些需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。 在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象。 在享元模式中共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为细粒度对象 结构

享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。 享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。 模型扩展 单纯享元模式和复合享元模式 单纯享元模式:在单纯享元模式中,所有的享元对象都是可以共享的,即所有抽象享元类的子类都可共享,不存在非共享具体享元类。 复合享元模式:将一些单纯享元使用组合模式加以组合,可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。 享元模式与其他模式的联用 在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象。 在一个系统中,通常只有唯一一个享元工厂,因此享元工厂类可以使用单例模式进行设计。 享元模式可以结合组合模式形成复合享元模式,统一对享元对象设置外部状态。 优缺点 优点:① 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。② 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。 缺点:① 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。② 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。 代理模式 Proxy/Surrogate 4 思路 为其他对象提供一种代理以控制对这个对象的访问 结构

class Proxy : public Subject { public: Proxy(); virtual ~Proxy(); void request(); private: void afterRequest(); void preRequest(); RealSubject *m_pRealSubject; }; / int main(int argc, char *argv[]) { Proxy proxy; proxy.request();

return 0;

} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 应用场景 远程(Remote)代理 为一个位于不同的地址空间的对象提供一个本地 的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在 另一台主机中,远程代理又叫做大使(Ambassador) 虚拟(Virtual)代理 如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建 Copy-on-Write代理 它是虚拟代理的一种,把复制(克隆)操作延迟 到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个 开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆 保护(Protect or Access)代理 控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限 缓冲(Cache)代理 为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果 防火墙(Firewall)代理 保护目标不让恶意用户接近 同步化(Synchronization)代理 使几个用户能够同时使用一个对象而没有冲突 智能引用(Smart Reference)代理 当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等(如智能指针) 优缺点 优点: 代理模式能够协调调用者和被调用者,在一定程度上降低了系 统的耦合度。远程代理使得 客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 统资源的消耗,对系统进行优化并提高运行速度。 保护代理可以控制对真实对象的使用权限。 缺点: 由于在客户端和真实主题之间增加了代理对象,因此 有些类型的代理模式可能会造成请求的处理速度变慢。 实现代理模式需要额外的工作,有些代理模式的实现 非常复杂。 行为型模式(1) 行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化 行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用 研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。 观察者模式 Observer 5 定义一对多依赖关系,当一个对象发生变化,其他对象需要作出反应。发生变换的取名 目标,被通知或者发现变换的对象成为 观察者 思路 定义一对多的对象依赖关系,让观察者同时监听观察目标。目标对象状态发生变化的时候会通知到观察者对象,进而自动更新 这种交互也称为发布-订阅(publishsubscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通 结构

优缺点 优点: 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。 观察者模式在观察目标和观察者之间建立一个抽象的耦合。 观察者模式支持广播通信。 观察者模式符合“开闭原则”的要求。 缺点: 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。 模板方法模式 Template Method 3 思路 定义操作中算法的骨架,将一些步骤延迟到子类中。即使得子类可以在不改变算法结构情况下重定义算法的特定结构 结构

模板方法模式 通过把不变行为搬移到超类,去重以体现优势 代码复用平台 优缺点 TODO

命令模式 Command 4 思路 将一个请求封装为一个对象,排队+记录日志,支持可恢复(Redo)可撤销(Undo)操作 又叫动作(Action)模式或事务(Transaction)模式 结构

客户端处代码 Receiver * pReceiver = new Receiver(); ConcreteCommand * pCommand = new ConcreteCommand(pReceiver); Invoker * pInvoker = new Invoker(pCommand); pInvoker->call(); 1 2 3 4 命令模式的本质是对命令进行封装,将 发出命令的责任 和 执行命令的责任 分割开 组合模式 + 命令模式 = 宏命令 优缺点 优点: 降低系统的耦合度。 新的命令可以很容易地加入到系统中。 可以比较容易地设计一个命令队列和宏命令(组合命令)。 可以方便地实现对请求的Undo和Redo。 缺点: 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。 状态模式 State 3 有状态的(stateful)对象:一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态 针对 行为 取决于 状态 的对象! 思路 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类 状态对象(Objects for States) 结构

共享状态 / 简单状态模式 / 可切换状态模式 优缺点 优点: 封装了转换规则。 枚举可能的状态,在枚举状态之前需要确定状态种类。 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。 缺点: 状态模式的使用必然会增加系统类和对象的个数。 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。 职责链模式 Chain of Responsibility 3 思路 ① 多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。② 将这些个对象连成一条链,沿着链传递请求,直到有个对象可以处理 结构

优缺点 优点: 职责链可以简化对象的相互连接(只保持一个后续者),随时增加或修改一个请求 缺点 要保证能处理请求,即在到达链尾之前可以处理掉请求,考虑全面 行为型模式(2) 解释器模式 Interpreter 1 思路 定义文法的表示 + 定义解释器,解释器使用该表示来解释语言中的文字 如 正则表达式 问题,音乐解释器 当一个语言需要解释执行,可将语言中的句子表示为一个抽象语法树,按照解释器模式完成 结构

好处 优点:很容易改变和扩展文法,定义抽象语法树的相关节点 缺点:对文法中的每一条规则至少定义了一个类,那么包含很多规则的文法可能难以管理和维护。所以当文法十分复杂的情况下,建议采用语法分析程序或编译器生成器等技术来处理! 中介者模式 Mediator 2 对于一个模块,可能由很多对象构成,而且这些对象之间可能存在相互的引用,为了减少对象两两之间复杂的引用关系,使之成为一个松耦合的系统,我们需要使用中介者模式,这就是中介者模式的模式动机 思路 用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互 又称为调停者模式 结构

ConcreteColleagueA * pa = new ConcreteColleagueA(); ConcreteColleagueB * pb = new ConcreteColleagueB(); ConcreteMediator * pm = new ConcreteMediator(); pm->registered(1,pa); pm->registered(2,pb);

// sendmsg from a to b pa->sendmsg(2,"hello,i am a"); // sendmsg from b to a pb->sendmsg(1,"hello,i am b"); 1 2 3 4 5 6 7 8 9 10 中介者模式可以使对象之间的关系数量急剧减少 中介者的职责 中转作用(结构性):结构上,使得同事对象不需要显示的引用其他同事,通信都经过中介者即可 协调作用(行为性):行为上,中介者对关系进行封装和分离 中介者模式是迪米特法则的一个典型应用 优缺点 优点:简化对象地交互 / 各同事解耦 / 减少子类生成 / 简化了同事类之间地设计和实现 缺点:可能因为关系过于复杂导致具体中介者过于复杂,使得系统难于维护 访问者模式 Visitor 1 思路 对于作用于某对象结构中的操作,可使得不改变对象(数据结构)结构的前提下定义新操作 适合数据结构相对稳定的系统 结构

ObjectStructure o = new ObjectStructure(); o.Attach(new ConcreteElementA()); o.Attach(new ConcreteElementB()); ConcreteElementA v1 = new ConcreteElementA(); ConcreteElementB v2 = new ConcreteElementB(); a.Accept(v1); a.Accept(v2); 1 2 3 4 5 6 7 优缺点 优点:对于有较为稳定数据的结果,而有易于变化的算法,就比较适合访问者模式 缺点:使得增加新的数据结构变得复杂了 策略模式 Strategy 4 对于一个对象中有很多的行为,采用硬编码(Hard Coding)的话,导致类代码十分复杂,维护困难 思路 定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化 也称为政策模式(Policy) 定义一些独立的类来封装不同的算法,称为 策略 结构

Strategy * s1 = new ConcreteStrategyA(); Context * cxt = new Context(); cxt->setStrategy(s1); cxt->algorithm(); 1 2 3 4 策略模式 / 状态模式: 通过环境类状态的个数来决定是使用策略模式还是状态模式。 使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。 如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。 优缺点 优点: 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系的基础上选择算法或行为,也可以灵活地增加新的算法或行为。 策略模式提供了管理相关的算法族的办法。 策略模式提供了可以替换继承关系的办法。 使用策略模式可以避免使用多重条件转移语句。 缺点 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。 备忘录模式 Memento 2 思路 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样之后就可以将该对象恢复到原先保存的状态 如游戏保存进度点实现 结构

Originator 0 = new Origibator(); // 初始状态 o.State = "On"; o.Show(); Caretaker c = new Caretaker(); c.Memento = o.CreateMemento(); // 保存状态 o.State = "off"; // 改变状态 o.Show(); o.SetMemento(c.Memento); // 恢复到原状态 o.Show(); 1 2 3 4 5 6 7 8 9 优缺点 优点:对于功能复杂而需要(频繁)记录维护属性历史的类很方便 / 需要恢复属性时也可以很好的向其他对象屏蔽。 缺点:需要保存恢复的属性数据很大很全,会导致比较高的资源消耗! 迭代器模式 Iterator 5 思路 提供一种方法,以提供一种方法顺序访问一个聚合对象中的元素,但是也不必暴露对象的内部细节 为了遍历而设计:只有开始,下一个,结束和当前项内容这四个交互内容! 结构

ConcreteAggregate a = new ConcreteAggregate(); // 对a赋值, a[0] = ... Iterator i = 呢哇ConcreteIterator(a); object item = i.First(); while (!i.IsDone()) { // Console. ... i.Next(); } // Console. ...

优缺点 优点: 当需要对聚集有多种方式遍历时候,可以采用迭代器模式,为其实现多种遍历方式 迭代器模式分离了集合对象的遍历行为,抽象出一个迭代器用于遍历任务,① 不暴露集合的内部结构 ② 外部代码透明的访问集合内部的数据 缺点:太过常见而导致人们忽略掉迭代器模式是多么重要了?(笑哭) 模式总结

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值