设计模式
介绍
设计模式是什么:
- 设计模式是用于解决在开发过程中产生的设计问题,每种设计模式都解决特定的问题,并且在适合的场景下使用能够带来明显的设计优势,提高了项目的健壮性、可理解性,易维护性。
- 简单来说,设计模式是一种模板结构,每种设计模式都有其优点和适用的场景,当我们实现某个功能按照适应场景选择使用相应的设计模式来构建我们的代码结构,能够提高代码的可复用性等好处
设计模式目的:
可复用
:通过这些设计模式,我们可以在多个项目重用这些模式,利于项目后期维护升级,可复用前版本代码
标准化
:在复杂系统的设计过程中,设计模式提供了一个标准化的方法,使得代码更加统一和规范
设计模式原则:
依赖倒置原则
:高层次的代码(稳定)不应该依赖低层次的代码(变化)、抽象的代码不应该依赖具体的代码,避免依赖具体的类,尽量依赖抽象
开放封闭原则
:类模块应该开放扩展的,而其原先的代码尽量封闭不可改变
单一职责原则
:一个类应该仅有一个变化的原因,该变化隐含了它的职责,职责太多时会导致扩展时对代码东拉西扯,造成混乱
替换原则
:子类必须能够替换它的基类(IS-A),继承可以表达类型抽象
接口隔离原则
:接口应该小而完备,不该强迫用户使用多余的方法
优先使用组合而不是继承
:继承通常会让子类和父类的耦合度增加、组合的方式只要求组件具备良好定义的接口
封装变化点
针对接口编程,而不是针对实现编程
设计模式分类:
创建型模式
:创建对象的机制,通过”对象创建”模式绕开new,**来避免对象创建过程中所导致的紧耦合(依赖具体类)**,从所需要实例化的对象中解耦(解耦就是降低模块之间的依赖程度)
结构型模式
:将对象或类组装到更大的结构中
行为模式
:负载对象间的交互和分配职责
UML类图
UML
:统一建模语言,经常用于来描述类或者类与类之间的关系
关系词描述:
依赖
:对类B进行修改会影响到类A
关联
:对象A知道对象B,类A依赖于类B
聚合
:对象A知道对象B且由B构成。类A依赖于类B
组合
:对象A知道对象B且由B构成,且管理着B的生命周期。类A依赖于类B
实现
:类A定义的方法由接口B声明(B is A接口)。对象A可被视为对象B。类A依赖于类B
继承
:类A继承类B的接口和实现(B is A父类),但是可以对其进行扩展。对象A可被视为对象B。类A依赖于类B
工厂模式
概念
工厂模式(Factory pattern)
:是一种创建型设计模式,旨在通过定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类。这种模式使得代码更加灵活、可扩展,并且遵循“开闭原则”(对扩展开放,对修改封闭)
种类:
简单工厂模式
工厂方法模式
抽象工厂模式
解决的问题:
解耦对象的创建与使用
: 工厂模式将对象的创建过程从客户端代码中抽离出来,客户端只需要通过工厂接口获取对象,而不需要知道具体的创建细节。这降低了客户端与具体实现之间的耦合度。
增强系统的可扩展性
: 通过引入工厂接口和具体工厂类,可以方便地扩展新产品类型,而无需修改现有的客户端代码。这符合“开闭原则”,即对扩展开放,对修改封闭。
集中管理对象创建
: 将对象创建的逻辑集中在工厂类中,便于统一管理和维护。例如,可以在工厂类中添加日志、缓存、单例等功能,而无需在多个客户端中重复实现。
提高代码的可维护性和可读性
: 工厂模式通过明确的接口和类结构,使代码的职责分明,增强了代码的可读性和可维护性。
注意:在设计模式的上下文中,“客户端”指的是使用或调用某个设计模式提供的接口或功能的代码部分
简单工厂模式
概念:
简单工厂模式
:定义一个工厂类,根据传入的标识来决定创建哪一种产品类的实例
优点:
缺点:
- 工厂类集中了所有产品的创建逻辑,违背了单一职责原则
- 难以扩展新产品,如果需要扩展则需要修改工厂类创建产品的代码实现,违背了开放封闭原则
示例代码:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include <iostream>
class product{ public: virtual ~product(){} virtual void use()=0; };
class productA:public product{ public: void use() override{ std::cout<<"使用产品A"<<std::endl; } };
class productB:public product{ public: void use() override{ std::cout<<"使用产品B"<<std::endl; } };
class factory{ public: static product* createproduct(char type){ product* p=nullptr; switch(type){ case 'A': p=new productA(); break; case 'B': p=new productB(); break; } return p; } };
int main (int argc, char *argv[]) { auto producta=factory::createproduct('A'); if(producta){ producta->use(); delete producta; }
auto productb=factory::createproduct('B'); if(productb){ productb->use(); delete productb; } return 0; }
|
工厂方法模式
概念:
工厂方法模式
:用于定义一个创建对象的接口,但由子类决定实例化哪个类,工厂方法让类把实例化推迟到子类。是一个类创建型模式,也就是用类解决问题
例子
问题:一个物流公司最初只使用卡车运输,现需要增加轮船运输业务,目前的程序代码与卡车关联。
解决方案:定义一个用于创建对象的接口,让子类决定实例化哪一个类。factory method使一个类的实例化延迟到子类
优点:
- 遵循开放封闭原则,易于扩展新产品
- 每个具体工厂只负责创建一种产品,符合单一职责原则
缺点:
适用场景:
- 当一个类不知道它所必须创建的对象的类的时候。
- 当一个类希望由其子类来指定所创建的对象的时候。
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
实现: 定义一个抽象工厂类,具体工厂类继承抽象工厂类并实现创建具体产品的方法。
示例代码:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| #include <iostream>
class product{ public: virtual ~product(){} virtual void use()=0; };
class productA:public product{ public: void use() override{ std::cout<<"使用产品A"<<std::endl; } };
class productB:public product{ public: void use() override{ std::cout<<"使用产品B"<<std::endl; } };
class creator{ public: virtual ~creator(){} virtual product* createproduct()=0; void douse(){ product* p=createproduct(); p->use(); delete p; } };
class creatorA:public creator{ public: product* createproduct() override{ return new productA(); } };
class creatorB:public creator{ public: product* createproduct() override{ return new productB(); } };
int main (int argc, char *argv[]) { creator* creatora=new creatorA(); creatora->douse(); creator* creatorb=new creatorB(); creatorb->douse(); delete creatora; delete creatorb; return 0; }
|
解析:
- 产品基类将会对接口进行声明
- 具体产品是产品接口的不同实现
- 创建者声明返回产品对象的
工厂方法
- 具体创建者将会重写基础工厂方法,使其返回不同类型的产品
注意:为什么不叫工厂的原因在于这是工厂方法模式,工厂方法就是生产产品的方法而不是工厂类,为了区分其他工厂模式,这里叫作创建者
抽象工厂模式
概念:
抽象工厂模式
:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。是一个对象创建型模式,也就是用对象解决问题
例子
问题:家具店有沙发、椅子、茶几等产品。产品有不同的风格,如现代、北欧、工业。希望确保客户端收到的产品风格统一,并可以方便的添加新产品和风格
解决方案:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类
优点:
- 生成的产品是一系列相关的对象,保证了产品的一致性
- 易于交换产品系列
缺点:
- 增加了系统的复杂性,尤其是当需要增加新的产品系列时,需要修改抽象工厂和所有具体工厂
适用场景:
- 一个系统要独立于它的产品的创建、组合和表示时。
- 一个系统要由多个产品系列中的一个来配置时。
- 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
- 当你提供一个产品类库,而只想显示它们的接口而不是实现时。
实现: 定义一个抽象工厂接口,具体工厂类实现该接口并创建一系列相关产品。
示例代码:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
| #include <iostream>
class productA{ public: virtual ~productA(){} virtual void use()=0; };
class productB{ public: virtual ~productB(){} virtual void use()=0; };
class productA1:public productA{ public: virtual ~productA1(){} void use() override{ std::cout<<"使用产品类A的产品A1,风格为1"<<std::endl; } };
class productA2:public productA{ public: virtual ~productA2(){} void use() override{ std::cout<<"使用产品类A的产品A2,风格为2"<<std::endl; }
};
class productB1:public productB{ public: virtual ~productB1(){} void use() override{ std::cout<<"使用产品类B的产品B1,风格为1"<<std::endl; } };
class productB2:public productB{ public: virtual ~productB2(){} void use() override{ std::cout<<"使用产品类B的产品B2,风格为2"<<std::endl; } };
class factory{ public: virtual productA* createproductA() const=0; virtual productB* createproductB() const=0; };
class factory1:public factory{ public: productA* createproductA() const override{ return new productA1(); } productB* createproductB() const override{ return new productB1(); } };
class factory2:public factory{ public: productA* createproductA() const override{ return new productA2(); } productB* createproductB() const override{ return new productB2(); } };
class client{ private: factory* m_factory; public: client(factory* fy){ m_factory=fy; } void douse(){ productA* pa=m_factory->createproductA(); productB* pb=m_factory->createproductB(); pa->use(); pb->use(); delete pa; delete pb; }
};
int main (int argc, char *argv[]) { factory1 fy1; client cli1(&fy1); cli1.douse(); factory2 fy2; client cli2(&fy2); cli2.douse(); return 0; }
|
解析:
- 抽象产品:构成系列产品的一组不同但相关的产品声明接口
- 具体产品:抽象产品的多重不同类型实现
- 抽象工厂:声明类了彝族创建各种抽象产品的方法
- 具体工厂:实现抽象工厂的构架方法
- 客户端:只需通过抽象接口调用工厂和产品对象
建造者模式
概念:
建造者模式(Builder pattern)
:是一种对象创建型模式,旨在将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。这种模式通过逐步构建对象的各个部分,允许客户端通过指挥者(Director)或直接使用建造者接口来创建所需的对象。
例子
问题:当需要构建一个房屋时,房屋可能包含不同的组件,如地砖、衣柜、空调、游泳池等。直接在房屋类(House
)中添加所有可能的参数会导致类的复杂性增加,特别是当需要创建具有不同参数组合的房屋时,可能需要大量的子类或复杂的构造函数,这既不方便扩展,也不利于维护。
解决方案:采用建造者模式,将房屋的构建过程与其表示分离。通过分步骤构建房屋的各个部分,使得客户端可以灵活地选择需要的组件,而无需关心房屋的具体构建细节。
解决的问题:
- 创建复杂对象的分步骤构建: 当一个对象的创建过程涉及多个步骤,并且这些步骤需要按照特定的顺序执行时,建造者模式能够将这些步骤组织起来,使得创建过程更加清晰和灵活。
- 同一构建过程创建不同表示: 通过不同的具体建造者,可以使用相同的构建过程来创建不同的对象表示。
- 隐藏对象的创建细节: 客户端代码无需了解对象创建的具体步骤和内部结构,只需通过建造者接口进行操作。这提高了代码的封装性和可维护性。
- 促进代码的可扩展性: 当需要添加新的对象类型时,只需创建新的具体建造者,而无需修改已有的构建过程或客户端代码,符合开闭原则。
优点:
- 分离复杂构建过程和表示:使得构建过程独立于产品的具体表示。
- 易于扩展:可以通过添加新的具体建造者来创建不同的产品表示,无需修改现有代码。
- 提高代码的可读性和可维护性:将对象的构建过程封装起来,使代码更加清晰。
缺点:
- 增加了系统的复杂性:引入了多个角色(建造者接口、具体建造者、指挥者等),增加了类的数量。
- 对于简单对象的创建可能过于复杂:如果对象的创建过程简单,使用建造者模式可能显得冗余。
- 产品差异不能过大:使用得到限制
示例代码:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
| #include <iostream> #include <string> #include <vector>
class house{ private: std::vector<std::string> m_parts; public: void setbase(std::string s){ m_parts.emplace_back(s); } void setgarage(std::string s){ m_parts.emplace_back(s); } void setpool(std::string s){ m_parts.emplace_back(s); } void describe() const{ std::cout<<"House Details:\n"; for(auto& s:m_parts){ std::cout<<s<<std::endl; } } };
class builder{ public: virtual ~builder(){} virtual void reset()=0; virtual void buildbase()=0; virtual void buildgarage()=0; virtual void buildpool()=0; };
class simplehousebuilder:public builder{ private: house* simplehouse; public: simplehousebuilder(){ simplehouse=new house(); } ~simplehousebuilder(){ delete simplehouse; } void reset() override{ simplehouse=new house(); } void buildbase() override{ simplehouse->setbase("simple basehouse"); } void buildgarage() override{ simplehouse->setgarage("simple garage"); } void buildpool() override{ simplehouse->setpool("simple swimming pool"); } house* getresult(){ house* result=std::move(simplehouse); reset(); return result; } };
class luxuryhousebuilder:public builder{ private: house* luxuryhouse; public: luxuryhousebuilder(){ luxuryhouse=new house(); } ~luxuryhousebuilder(){ delete luxuryhouse; } void reset() override{ luxuryhouse=new house(); } void buildbase() override{ luxuryhouse->setbase("luxury basehouse"); } void buildgarage() override{ luxuryhouse->setgarage("luxury garage"); } void buildpool() override{ luxuryhouse->setpool("luxury swimming pool"); } house* getresult(){ house* result=luxuryhouse; reset(); return result; } };
class director{ private: builder* m_builder; public: void setbuilder(builder* builder_){ m_builder=builder_; } void buildhouse(){ m_builder->buildbase(); m_builder->buildgarage(); m_builder->buildpool(); } };
int main (int argc, char *argv[]) { std::cout<<"客户自己设计:\n"; simplehousebuilder* simplebuilder=new simplehousebuilder(); simplebuilder->buildbase(); simplebuilder->buildgarage(); simplebuilder->buildpool(); house* simplehouse=simplebuilder->getresult(); simplehouse->describe(); std::cout<<"指挥者建造:\n"; std::cout<<"简单房子:\n"; director director_; director_.setbuilder(simplebuilder); director_.buildhouse(); simplehouse=simplebuilder->getresult(); simplehouse->describe(); delete simplehouse; delete simplebuilder; std::cout<<"-----------------------\n"; std::cout<<"豪华房子:\n"; luxuryhousebuilder* luxurybuilder=new luxuryhousebuilder(); director_.setbuilder(luxurybuilder); director_.buildhouse(); house* luxuryhouse=luxurybuilder->getresult(); luxuryhouse->describe(); delete luxuryhouse; delete luxurybuilder; return 0; }
|
解析:
- 建造者:声明通用的产品构造步骤
- 具体建造者:提供构造过程的不同实现,具体建造者也可以构造不遵循通用接口的产品
- 产品:最终生成的对象,由不同建造者构造的产品无需属于同一类层次结构或接口
- 指挥者:定义调用构造步骤的顺序,这样可以创建和复用特定的产品配置
- 客户端:必须将某个建造者对象与指挥者类关联,一般情况下,只需通过指挥者类构造函数的参数进行一次性关联即可
原型模式
概念:
原型模式(protoytpe pattern)
:是一种对象创建型模式,,旨在通过复制现有的实例来创建新的对象,而不是通过新建实例。
- 定义:定义了一个用于创建对象的接口,通过拷贝现有的实例(原型),来生成新的对象。这种模式允许系统在运行时动态地创建新的对象,而无需依赖具体类的实例化过程
- 适用场景:适用于创建过程复杂、代价高昂或需要大量资源的对象,因为它能够通过克隆现有对象来节省创建新对象的时间和资源。
例子
问题:希望复制一个状态完全相同的对象。首先,新建一个相同的类的对象。然后,复制所有成员变量。但是,有时候不知道具体类型,而且成员变量可能是私有的(从外部复制对象并非总是可行的)
解决方案:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。即复制已有对象,而无需使代码依赖他们所属的类
解决的问题:
- 对象创建开销大:当创建一个对象需要耗费大量资源或时间(如复杂的初始化过程、大量的数据加载等)时,直接克隆一个现有对象比重新创建一个新对象更高效。
- 动态类型:在某些情况下,程序在编译时无法确定需要创建的具体类类型,而原型模式允许在运行时通过克隆现有对象来创建所需类型的实例。
- 避免依赖具体类:原型模式通过接口或抽象类进行对象的克隆,减少了代码对具体类的依赖,提高了系统的灵活性和可扩展性。
- 配置对象:在需要根据用户输入或配置文件动态生成对象的场景中,原型模式可以通过克隆预配置好的原型对象来快速生成所需对象。
优点:
- 性能优化:克隆对象通常比重新创建对象更高效,特别是当对象创建开销大时。
- 灵活性和可扩展性:通过原型接口,可以轻松地增加新的具体原型类,而无需修改客户端代码。
- 减少代码冗余:避免了重复的初始化代码,通过克隆现有对象简化了对象的创建过程。
缺点:
- 实现复杂性:对于复杂对象,深拷贝(deep copy)可能需要额外的处理,以确保对象内部所有引用的对象也被正确克隆。
- 资源消耗:虽然克隆比新建高效,但在某些情况下,频繁的克隆操作可能会消耗大量内存。
- 隐藏类结构:客户端通过克隆获取对象,可能对对象的内部结构缺乏了解,增加了系统的复杂性。
示例代码:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
| #include <iostream> #include <string>
class protoytpe{ protected: std::string m_name=""; public: protoytpe(const std::string& val):m_name(val){} virtual ~protoytpe(){} virtual protoytpe* clone() const=0; virtual void getname() const=0; virtual void settype(int)=0; };
class protoytpe1:public protoytpe{ private: int m_type1; public: virtual ~protoytpe1(){} protoytpe1(const std::string& val,int type):protoytpe(val),m_type1(type){} protoytpe* clone() const override{ return new protoytpe1(*this); } void getname() const override{ std::cout<<"------------------------------"<<std::endl; std::cout<<"protoytpe_type:"<<m_type1<<std::endl; std::cout<<"protoytpe_name:"<<m_name<<std::endl; std::cout<<"------------------------------"<<std::endl; } void settype(int type) override{ m_type1=type; } };
class protoytpe2:public protoytpe{ private: int m_type2; public: virtual ~protoytpe2(){} protoytpe2(const std::string& val,int type):protoytpe(val),m_type2(type){} protoytpe* clone() const override{ return new protoytpe2(*this); } void getname() const override{ std::cout<<"------------------------------"<<std::endl; std::cout<<"protoytpe_type:"<<m_type2<<std::endl; std::cout<<"protoytpe_name:"<<m_name<<std::endl; std::cout<<"------------------------------"<<std::endl; } void settype(int type) override{ m_type2=type; } };
int main (int argc, char *argv[]) { protoytpe1 original1("Original1", 100); protoytpe2 original2("Original2", 200);
std::cout << "原型对象1:" << std::endl; original1.getname();
std::cout << "原型对象2:" << std::endl; original2.getname();
protoytpe* clone1 = original1.clone(); protoytpe* clone2 = original2.clone();
clone1->settype(101); clone2->settype(201);
std::cout << "克隆对象1:" << std::endl; clone1->getname();
std::cout << "克隆对象2:" << std::endl; clone2->getname();
std::cout << "验证原型对象1是否未被修改:" << std::endl; original1.getname();
std::cout << "验证原型对象2是否未被修改:" << std::endl; original2.getname();
delete clone1; delete clone2;
return 0; }
|
解析:
- 原型:接口将对克隆方法进行声明
- 具体原型:类将实现克隆方法。除了将原始对象的数据复制到克隆体中之外,该方法有时还需处理克隆过程中的极端情况,例如克隆关联对象和梳理递归以来等等
- 客户端:可以复制实现了原型接口的任何对象
单例模式
概念
概念:
单例模式(Singleton pattern)
:是一种对象创建型模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点来获取该实例。单例模式在需要控制资源的访问或确保全局状态的一致性时非常有用
- 定义:保证一个类仅有一个实例,并提供一个访问该实例的全局点。这意味着,无论系统中有多少个对象需要使用该类,都将共享同一个实例,因此我们单例类的构造函数必须对客户端代码隐藏,而提供一个静态方法来作为全局公共访问点来获取对象
例子
问题:对于一些类来说,只有一个实例是很重要的。例如数据库或其共享资源的访问权限。并且这个实例需要易于被访问
解决方案:保证一个类只有一个实例,并提供一个访问它的全局访问点
解决的问题:
- 控制资源的访问:当某个资源(如数据库连接、日志文件)需要全局唯一时,单例模式可以确保对该资源的唯一访问,避免资源冲突和竞争条件。
- 全局状态管理:在应用程序中需要维护一个全局状态或配置,如配置管理器、线程池等,单例模式确保这些管理器只有一个实例,保证状态的一致性。
- 避免重复实例化:当创建对象的开销较大,且只需要一个实例时,使用单例模式可以避免不必要的重复实例化,节省资源和提高性能。
- 实现全局对象:单例模式提供了一个全局访问点,便于在整个应用程序中访问该对象,而无需传递引用或指针。
优点:
- 确保唯一性:保证系统中只有一个实例,避免了资源冲突和不一致性。
- 全局访问:提供了一个全局访问点,便于在整个应用程序中访问该实例。
- 延迟实例化(如果实现了懒加载):实例在首次使用时创建,节省了系统资源。
- 易于实现:在大多数编程语言中,单例模式的实现相对简单。
缺点:
- 违背单一职责原则:单例类既负责自己的业务逻辑,又负责管理其唯一实例,可能导致职责不清。
- 难以测试:单例模式可能导致代码之间的紧密耦合,增加单元测试的复杂性。
- 隐藏的依赖性:全局访问点可能隐藏类之间的依赖关系,降低代码的可维护性和可读性。
- 并发问题:在多线程环境下,需要确保单例实例的创建是线程安全的,否则可能创建多个实例。
饿汉式
饿汉式单例
:在程序启动时就创建单例实例,线程安全但可能导致资源浪费
优点:
缺点:
实现:
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 31 32 33 34 35
| #include <iostream>
class singleton{ private: static singleton instance;
singleton(){}
singleton(const singleton&)=delete; singleton& operator=(const singleton&)=delete;
public: static singleton& getinstance(){ return instance; }
void showmess(){ std::cout<<"hello!"<<std::endl; } };
singleton singleton::instance;
int main (int argc, char *argv[]) { singleton &s=singleton::getinstance(); s.showmess(); return 0; }
|
懒汉式
懒汉式单例(不加锁)
:**在第一次使用时创建实例,节省资源(延迟加载)**。多线程下不安全
懒汉式单例(加锁)
:在第一次使用时创建实例,节省资源(延迟加载)。线程安全,但是性能较低,因为每次获取实例都需要加锁
优点:
缺点:
实现:
未加锁
:
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 31 32 33 34 35 36 37 38 39
| #include <iostream>
class singleton{ private: static singleton* instance;
singleton(){}
singleton(const singleton&)=delete; singleton& operator=(const singleton&)=delete;
public: static singleton* getinstance(){ if(instance==nullptr){ instance=new singleton(); } return instance; }
void showmess(){ std::cout<<"hello!"<<std::endl; } };
singleton* singleton::instance=nullptr;
int main (int argc, char *argv[]) { singleton* s=singleton::getinstance(); s->showmess(); return 0; }
|
加锁
:
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 31 32 33 34 35 36 37 38 39 40 41 42
| #include <iostream> #include <mutex>
class singleton{ private: static singleton* instance; static std::mutex mtx;
singleton(){}
singleton(const singleton&)=delete; singleton& operator=(const singleton&)=delete;
public: static singleton* getinstance(){ std::lock_guard<std::mutex> lock(mtx); if(instance==nullptr){ instance=new singleton(); } return instance; }
void showmess(){ std::cout<<"hello!"<<std::endl; } };
singleton* singleton::instance=nullptr; std::mutex singleton::mtx;
int main (int argc, char *argv[]) { singleton* s=singleton::getinstance(); s->showmess(); return 0; }
|
双重检查锁定单例
双重检查锁定单例
:结合懒汉式和饿汉式的优点,减少锁的开销,线程安全
问题:因为懒汉式的锁只有第一次有用,但是剩下的每次都要进行加锁,很浪费性能
解决方案:选择多加一次检查,可以避免后面每次加锁
优点:
- 延迟加载,节省资源
- 保证只使用了一次锁,大大减少性能消耗
- 线程安全
缺点:
稳定问题:双重检查锁定(DLC)单例模式,避免在每次访问单例实例时都进行锁操作。但在实际实现中,双重检查锁定单例模式在某些编程语言(尤其是 C++)中存在一些显著的问题
双重检查锁定在C++中的问题:C++编译器和处理器可能会对指令重排序
,以优化性能,这就代表着即使程序代码按顺序编写,实际执行的顺序可能不同。在双重检查锁定中,这种重排序可能导致单例实例在完全构造之前就被其他线程看到
具体问题
:
- 当一个线程(线程 A)正在执行
instance = new Singleton();
时,可能会发生以下步骤:
- 分配内存。
- 初始化
Singleton
对象。
- 将
instance
指向新分配的内存。
- 由于指令重排序,步骤 3 可能会在步骤 2 之前完成。这意味着另一个线程(线程 B)可能会看到
instance
不为空,但 Singleton
对象尚未完全构造。
结果
:
- 线程 B 可能会获取一个部分构造的
Singleton
实例,导致未定义行为和潜在的崩溃。
解决方案
:
- 使用
std::call_once
和std::once_flag
保证instance = new Singleton()
代码块只执行一次即可
- 推荐使用局部静态变量单例
实现:
双重检查锁定单例
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <mutex>
class singleton{ private: static singleton* instance; static std::mutex mtx;
singleton(){}
singleton(const singleton&)=delete; singleton& operator=(const singleton&)=delete;
public: static singleton* getinstance(){ if(instance==nullptr){ std::lock_guard<std::mutex> lock(mtx); if(instance==nullptr){ instance=new singleton(); } } return instance; }
void showmess(){ std::cout<<"hello!"<<std::endl; } };
singleton* singleton::instance=nullptr; std::mutex singleton::mtx;
int main (int argc, char *argv[]) { singleton* s=singleton::getinstance(); s->showmess(); return 0; }
|
使用call_once单例
:
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 31 32 33 34 35 36 37 38 39 40 41
| #include <iostream> #include <mutex>
class singleton{ private: static singleton* instance; static std::once_flag flag_;
singleton(){}
singleton(const singleton&)=delete; singleton& operator=(const singleton&)=delete;
public: static singleton* getinstance(){ std::call_once(flag_,[](){ instance=new singleton(); }); return instance; }
void showmess(){ std::cout<<"hello!"<<std::endl; } };
singleton* singleton::instance=nullptr; std::once_flag singleton::flag_;
int main (int argc, char *argv[]) { singleton* s=singleton::getinstance(); s->showmess(); return 0; }
|
局部静态变量单例(推荐)
局部静态变量单例
:利用C++11的线程安全静态变量初始化,实现懒加载且线程安全(推荐使用)
优点:
- 线程安全
- 延迟加载,节省资源
- 无锁,性能强
- 自动管理内存,无需手动释放
- 简洁且易于实现
缺点:
实现:
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 31
| #include <iostream>
class singleton{ private: singleton(){}
singleton(const singleton&)=delete; singleton& operator=(const singleton&)=delete;
public: static singleton& getinstance(){ static singleton instance; return instance; }
void showmess(){ std::cout<<"hello!"<<std::endl; }
};
int main (int argc, char *argv[]) { singleton& s=singleton::getinstance(); s.showmess(); return 0; }
|
适配器模式
概念:
适配器模式(Adapter pattern)
是一种结构型设计模式,它允许不兼容的接口之间进行协作。通过引入一个适配器类,适配器模式将一个类的接口转换成客户端所期望的另一个接口,从而使得原本由于接口不兼容而无法一起工作的类可以协同工作
例子
问题:开发一款股票市场监测程序,会从不同来源下载XML格式的股票数据,然后向用户呈现出分析图表。但是,分析函数库只兼容JSON格式的数据
解决方案:将一个类的接口转换为客户希望的另一个接口,使得原本不兼容的一些类可以一起工作。即创建一个适配器,这是一个特殊的对象,能够转换对象接口
解决问题:
- 接口不兼容:在多个类或系统之间进行集成时,可能会遇到接口不兼容的问题。适配器模式提供了一种解决方案,使得这些不兼容的接口可以无缝地工作在一起。
- 代码重用:通过使用适配器模式,现有的类可以被复用而不需要修改。这样可以避免对已有代码的改动,从而降低维护成本和风险。
- 灵活性和可扩展性:适配器模式允许在不修改客户端代码的情况下,添加新的适配者或更改适配的实现。这样使得系统更加灵活和易于扩展。
结构:
优点:
- 单一职责原则:可以将接口或数据转换代码从程序主要业务逻辑中分离
- 开闭原则:只要客户端代码通过客户端接口与适配器进行交互,就能在不修改现有客户端代码的情况下运行新类型接口,只需要增加新类型的适配器即可
缺点:
- 代码复杂:代码整体复杂度增加,因为需要新增一系列接口和类,有时直接更改服务类使其与其他代码兼容会更简单
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <iostream> #include <string>
class jsoninfo{ public: virtual ~jsoninfo(){} virtual std::string request() const{ return "some json information..."; } };
class xmlinfo{ public: std::string xmlrequest() const{ return "some xml information..."; } };
class adapter:public jsoninfo{ private: xmlinfo* m_adptee; public: adapter(xmlinfo* adaptee):m_adptee(adaptee){} std::string request() const override{ std::string str=m_adptee->xmlrequest(); return "xml转换json适配结果:"+str; } };
void client(const jsoninfo* info){ std::cout<<"股票分析软件运行json:"<<info->request()<<std::endl; std::cout<<"--------------------------"<<std::endl; }
int main (int argc, char *argv[]) { jsoninfo jsinfo; client(&jsinfo); xmlinfo xlinfo; adapter jsonadapter(&xlinfo); client(&jsonadapter); return 0; }
|
桥接模式
概念:
桥接模式(Bridge pattern)
:是一种对象结构型设计模式,旨在将抽象部分与其实现部分分离,使它们可以独立地变化。通过这种方式,桥接模式允许在不影响其他部分的情况下,扩展或修改系统的抽象和实现部分,从而提高系统的灵活性和可维护性
例子
问题:假设你正在开发一个远程控制系统,用于控制不同品牌的电器(如电视、音响等)。不同品牌的电器有不同的操作接口和实现方式,而远程控制器需要能够控制这些电器而无需了解具体的实现细节
解决方案:桥接模式可以将远程控制器(抽象部分)与电器的具体实现(实现部分)分离,使得远程控制器可以独立于电器品牌进行扩展。即使用组合的方式来替代继承,将一个类层次转化为多个相关的类层次,避免单个类层次的失控。在抽象和实现间架起一座桥梁
结构:
- **抽象部分(Abstraction)**:提供高层控制逻辑,依赖于完成底层实际工作的实现对象
- **实现部分(Implementation)**:为所有具体实现声明通用接口
- **具体实现(Concrete Implementations)**:特定于平台的实现代码
- **扩展抽象(Refined Abstraction)**:提供控制逻辑的变体
- 客户端:仅关心如何与抽象部分合作。但是,客户端需要将抽象对象与一个实现对象连接起来
优点:
- 分离抽象与实现:使两者可以独立变化,增强系统的灵活性。
- 减少类的数量:避免了因多维度变化导致的类爆炸问题。
- 提高可扩展性:易于在抽象和实现层面进行扩展。
- 单一职责原则:抽象部分专注于处理高层逻辑,实现部分处理平台细节
缺点:
高内聚:模块中的依赖关系很强
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| #include <iostream>
class device{ public: virtual ~device(){} virtual void turnon() const=0; virtual void turnoff() const=0; virtual void mute() const=0; };
class sonydevice:public device{ public: ~sonydevice(){} void turnon() const override{ std::cout<<"sonydevice:开机...\n"; } void turnoff() const override{ std::cout<<"sonydevice:关机...\n"; } void mute() const override{ std::cout<<"sonydevice:静音...\n"; } };
class samsungdevice:public device{ public: ~samsungdevice(){} void turnon() const override{ std::cout<<"samsungdevice:开机...\n"; } void turnoff() const override{ std::cout<<"samsungdevice:关机...\n"; } void mute() const override{ std::cout<<"samsungdevice:静音...\n"; } };
class remotecontroller{ protected: device* m_impl; public: remotecontroller(device* dev):m_impl(dev){} virtual ~remotecontroller(){} virtual void sendturnon(){ std::cout<<"基础遥控器发出turnon命令:\n"; m_impl->turnon(); } virtual void sendturnoff(){ std::cout<<"基础遥控器发出turnoff命令:\n"; m_impl->turnoff(); } };
class advanceremote: public remotecontroller{ public: advanceremote(device* dev):remotecontroller(dev){} ~advanceremote(){} void sendturnon() override{ std::cout<<"高级遥控器发出turnon命令:\n"; m_impl->turnon(); } void sendturnoff() override{ std::cout<<"高级遥控器发出turnoff命令:\n"; m_impl->turnoff(); } void sendmute(){ std::cout<<"高级遥控器发出mute命令:\n"; m_impl->mute(); } };
int main (int argc, char *argv[]) { sonydevice sydev; remotecontroller brc(&sydev); brc.sendturnon(); brc.sendturnoff();
samsungdevice ssdev; advanceremote arc(&ssdev); arc.sendturnon(); arc.sendmute(); arc.sendturnoff(); return 0; }
|
组合模式
概念:
组合模式(Composite pattern)
:是一种对象结构型设计模式,旨在将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户能够以统一的方式对待单个对象(叶子节点)和对象组合(组合节点),从而简化客户端代码的复杂性
透明式组合模式
:会将叶节点和组合节点各自特有的接口也一起声明在组件接口中,但是默认实现是抛出错误,这样防止非叶节点/组合节点调用该方法
安全式模式
:组件接口中只包含叶节点和组合节点所共有的接口方法
例子
问题:在文件系统中,用户既可以对单个文件执行操作,也可以对整个文件夹及其包含的文件执行操作。如果不使用组合模式,客户端代码需要分别处理单个对象和组合对象,导致代码复杂且难以维护
解决方案:将对象组成树形结构以表示”部分-整体”的层次结构。使得用户对单个对象和组合对象的使用具有一致性
结构:
- **组件(Component)**:定义对象的接口,声明叶节点和组合节点所共有的操作
- **叶节点(Leaf)**:代表组合结构中的叶子对象,不能有子节点
- **组合节点(Composite)**:代表组合对象,内部可包含子节点
适用场景:
- 需要体现部分与整体的树状层次结构时,可以使用组合模式。
- 希望客户端忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
优点:
- 一致处理: 客户端可以一致地对待单个对象和对象组合,减少了条件判断和复杂的处理逻辑。
- 可以利用多态和递归机制更方便地使用复杂树结构
- 开闭原则:在组合体内加入新的对象,客户端不会更改源代码,可以一致地处理单个对象和组合对象。
缺点:
- 设计上的复杂性: 对于简单的层次结构,使用组合模式可能会增加不必要的类和接口,导致系统设计复杂。
- 不适合所有场景: 当对象层次结构不适合树形结构时,组合模式可能不适用。
- 对于功能差异较大的类,提供公共接口或许会有困难,在特定情况下,需要过度一般化组件接口,使其变得令人难以理解
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| #include <iostream> #include <string> #include <list>
class filesystemcomponent{ protected: std::string m_name; public: filesystemcomponent(const std::string& str):m_name(str){} virtual ~filesystemcomponent(){} virtual void display(int idx=0) const=0;
};
class file:public filesystemcomponent{ public: file(const std::string &str):filesystemcomponent(str){} ~file(){} void display(int idx=0) const override{ for(int i = 0; i < idx; ++i) std::cout << " "; std::cout << "- File: " << m_name << std::endl; } };
class directory:public filesystemcomponent{ private: std::list<filesystemcomponent*> children; public: directory(const std::string &str):filesystemcomponent(str){} ~directory(){ for(auto child : children) { delete child; } } void display(int idx = 0) const override { for(int i = 0; i < idx; ++i) std::cout << " "; std::cout << "+ Directory: " << m_name << std::endl; for(auto child : children) { child->display(idx + 1); } } void add(filesystemcomponent* component){ children.emplace_back(component); } void remove(filesystemcomponent* component){ children.remove(component); } };
int main (int argc, char *argv[]) { directory* root=new directory("root"); file* f1=new file("f1.txt"); file* f2=new file("f2.txt"); directory* subdir=new directory("subdir"); file* f3=new file("f3.txt"); root->add(f1); root->add(subdir); subdir->add(f2); subdir->add(f3); f1->display(); root->display(); delete root; return 0; }
|
装饰模式
概念:
装饰模式(Decorator pattern)
:是一种对象结构型设计模式,允许在不修改现有对象结构的情况下,动态地为对象添加新的功能。
做法
:通过创建一个装饰类包装原有的类,并在保持原有类接口的前提下,提供额外的功能,装饰模式实现了对对象功能的灵活扩展
例子
问题:饮料店订单系统,饮料有多种,并且可以选择加牛奶、冰淇淋、巧克力等配料
解决方案:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰模式相对于生成子类更为灵活。找出基本组件和可选层次
结构:
- **组件(Component)**:声明封装器和被封装对象的公用接口
- **具体组件(Concrete Component)**:是被封装对象所属的类。它定义了基础行为,但装饰类可以改变这些行为
- 基础装饰类(Base Decorator):拥有指向被封装对象的引用成员变量。装饰基类会将所有操作委派给被封装的对象
- **具体装饰类(Concrete Decorators)**:定义了可动态添加到组件的额外行为。具体装饰类会重写装饰基类的方法,并在调用父类方法之前或之后进行额外的行为
解决的问题:
避免类爆炸:使用继承来扩展类功能时,每增加一个新功能,都需要创建一个新的子类,导致类的数量迅速增长(类爆炸问题)。
动态功能添加:有时需要在运行时动态地为对象添加或撤销功能,继承机制无法满足这一需求,因为继承是在编译时确定的。
保持单一职责:将不同功能分散到不同的装饰类中,可以保持每个类的职责单一,提升代码的可维护性和可扩展性。
优点:
灵活性高:可以在运行时动态地组合不同的装饰器,灵活地为对象添加多种功能。
遵循开闭原则:对扩展开放,对修改关闭。通过添加新的装饰类,不需要修改现有的类。
增强单一职责:每个装饰器负责添加一种功能,保持类的职责单一。
缺点:
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| #include <iostream> #include <string>
class Beverage{ public: virtual ~Beverage(){} virtual std::string operation() const=0; };
class Americano:public Beverage{ public: ~Americano(){} std::string operation() const override{ return "美式咖啡"; } };
class Basewrapper:public Beverage{ protected: Beverage* m_beverage; public: Basewrapper(Beverage* beverage):m_beverage(beverage){} ~Basewrapper(){} std::string operation() const override{ return m_beverage->operation(); } };
class Whip:public Basewrapper{ public: Whip(Beverage* beverage):Basewrapper(beverage){} ~Whip(){} std::string operation() const override{ return "奶昔("+Basewrapper::operation()+")"; } };
class Moca:public Basewrapper{ public: Moca(Beverage* beverage):Basewrapper(beverage){} ~Moca(){} std::string operation() const override{ return "摩卡("+Basewrapper::operation()+")"; } };
void client(Beverage* bev){ std::cout<<"执行结果:"<<bev->operation()<<std::endl; }
int main (int argc, char *argv[]) { std::cout<<"来一杯美式咖啡\n"; Beverage* americano=new Americano();
std::cout<<"来一杯双份摩卡+奶昔的美式咖啡\n"; Beverage* whip=new Whip(americano); Beverage* moca1=new Moca(whip); Beverage* moca2=new Moca(moca1); client(moca2);
delete americano; delete whip; delete moca1; delete moca2; return 0; }
|
外观模式
概念:
外观模式(Facade pattern)
:是一种对象结构型设计模式,它为复杂子系统提供一个统一的接口,从而使得子系统更容易使用。外观模式通过封装多个类的复杂性,向客户端提供一个简单的接口,减少了客户端与多个子系统之间的交互
例子
问题:假设我们有一个家庭影院系统,包括多个子系统(如音响、投影仪、DVD播放器等)。用户希望通过一个简单的接口来控制这些设备
解决方案:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得子系统更加容易使用。开放用户真正关心的功能
解决的问题:
复杂性管理:客户端需要直接与多个子系统交互,导致代码复杂、难以维护。
依赖性问题:客户端与多个子系统的紧耦合,增加了系统之间的依赖性,降低了系统的灵活性。
学习曲线:新开发人员在理解复杂的子系统时,学习曲线较陡,增加了上手难度。
结构:
- 外观(Facade):提供一个简单的接口来访问特定子系统功能的便捷方式,封装了复杂子系统的调用逻辑。
- **附加外观(Additional Facade)**:可以避免多种不相关的功能污染单一外观,使其变成又一个复杂结构,客户端和其他外观都可使用附加外观
- 子系统(Subsystem Classes):实现系统的功能,但通常不对外暴露接口。客户端与这些类的交互通过外观进行(但客户端也可以直接操作子系统)。
- **客户端(Client)**:使用外观代替对子系统对象的直接调用
优点:
缺点:
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #include <iostream> #include <string>
class videosystem{ public: std::string init() const{ return "视频系统:Ready!\n"; } std::string play() const{ return "视频系统:播放\n"; } std::string operationx() const{ return "视频系统:不常用的功能\n"; } };
class radiosystem{ public: std::string init() const{ return "音频系统:Ready!\n"; } std::string play() const{ return "音频系统:播放\n"; } std::string operationx() const{ return "音频系统:不常用的功能\n"; } std::string mute() const{ return "音频系统:静音!\n"; } };
class controller{ protected: videosystem* m_video; radiosystem* m_radio; public: controller(videosystem* video=nullptr,radiosystem* radio=nullptr){ m_video=video?video:new videosystem(); m_radio=radio?radio:new radiosystem(); } ~controller(){ delete m_video; delete m_radio; } std::string start(){ std::string str="控制器启动系统:\n"; str+=m_video->init(); str+=m_radio->init(); str+="控制器发送播放命令:\n"; str+=m_video->play(); str+=m_radio->play(); return str; } std::string mute(){ return m_radio->mute(); } };
int main (int argc, char *argv[]) { videosystem* video=new videosystem(); radiosystem* radio=new radiosystem(); controller ctler(video,radio); std::cout<<ctler.start(); std::cout<<ctler.mute(); return 0; }
|
享元模式
概念:
享元模式(Flyweight pattern)
:是一种对象结构型设计模式,旨在通过共享对象来减少内存使用和提高性能,特别是在需要大量相似对象的情况下。享元模式通过将对象的状态分为可共享的内部状态和不可共享的外部状态,来实现对象的高效复用。
例子
问题:做一个车管所系统,将会产生大量的车辆实体,如果每一个实例都保存自己的所有信息,将会需要大量内存,甚至导致程序崩溃
解决方案:运用共享技术有效的支持大量细粒度的对象,共享部分单独抽出来,特有部分保留给个体
解决的问题:
内存消耗:大量相似对象的创建会导致显著的内存开销。通过共享相同的对象,享元模式减少了内存使用。
性能问题:频繁地创建和销毁对象会增加系统的负担,影响性能。享元模式通过复用对象,降低了对象创建的开销。
管理复杂性:当对象数量庞大时,管理这些对象会变得复杂。享元模式提供了统一的管理机制,使得对象的创建和共享更加高效。
结构:
享元(Flyweight):定义了可以共享的接口,通常用于实现可共享对象的公共部分。
具体享元(Concrete Flyweight):实现享元接口的具体类,包含共享的状态。
外部状态(Extrinsic State):享元对象不存储的状态,由客户端管理并传递给享元对象。外部状态可以是与上下文相关的数据
优点:
缺点:
- 增加复杂性:引入外部状态和共享机制,可能增加系统的复杂性。
- 管理难度:需要有效管理共享对象的生命周期,确保其正确性。
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <iostream> #include <string> #include <unordered_map>
struct shared_state { std::string brand; std::string model; };
struct unique_state { std::string owner; std::string num; };
class Vehicle { private: shared_state m_shared; public: Vehicle():m_shared({"",""}){} Vehicle(const std::string& b, const std::string& m) : m_shared({b, m}) {}
Vehicle(const Vehicle& other) : m_shared(other.m_shared) {}
void display() const { std::cout << "Brand: " << m_shared.brand << ", Model: " << m_shared.model << std::endl; } };
class VehicleFactory { private: std::unordered_map<std::string, Vehicle> vehicles; public: Vehicle& getVehicle(const std::string& brand, const std::string& model) { std::string key = brand + "_" + model; if (vehicles.find(key) == vehicles.end()) { vehicles[key] = Vehicle(brand, model); } return vehicles[key]; } };
class IndividualVehicle { private: Vehicle vehicle; unique_state m_unique; public: IndividualVehicle(const Vehicle& v, const std::string& o, const std::string& n) : vehicle(v), m_unique({o, n}) {}
void display() const { vehicle.display(); std::cout << "Owner: " << m_unique.owner << ", num: " << m_unique.num << std::endl; } };
int main(int argc, char *argv[]) { VehicleFactory factory;
IndividualVehicle car1(factory.getVehicle("Toyota", "Camry"), "Alice", "ABC123"); IndividualVehicle car2(factory.getVehicle("Toyota", "Camry"), "Bob", "XYZ789"); IndividualVehicle car3(factory.getVehicle("Honda", "Civic"), "Charlie", "LMN456");
car1.display(); car2.display(); car3.display();
return 0; }
|
代理模式
概念:
代理模式(Proxy pattern)
:是一种对象结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。通过代理模式,客户端可以通过代理对象间接地访问目标对象,而不需要直接与目标对象交互。
- 场景:这种模式在需要对目标对象的访问进行控制、延迟加载、记录日志、权限检查等场景下非常有用
例子
问题:系统需要访问数据库,但需要对数据的访问做一些优化,例如缓存查询结果、生成访问日志、访问控制。
解决方案:为其他对象提供一种代理以控制这个对象的访问。即新建一个与原服务器对象接口相同的代理类,代理将自己伪装成数据库对象,对用户而言是透明的
结构:
- **服务接口(Service Interface)**:声明服务接口。代理须遵循该接口进行伪装。
- **服务类(Service)**:提供了一些使用的业务逻辑
- **代理类(Proxy)**:包含一个指向服务对象的引用成员变量,代理完成其任务(如延迟初始化、记录日志、访问控制和缓存等)后会将请求传递给服务对象
- **客户端(Client)**:能通过同一接口与服务或代理进行交互,可在一切需要服务对象的代码中使用代理
优点:
- 透明性:可以在客户端毫无察觉的情况下控制服务对象
- 如果客户端对服务对象的生命周期没有特殊要求,可以对生命周期进行管理
- 即使服务对象还未准备好或不存在,代理也可以正常工作
缺点:
- 增加系统复杂性:引入代理对象会增加系统的类的数量和复杂性,可能导致系统结构过于庞大
- 性能影响:由于代理对象需要转发请求,并可能执行额外的操作,服务响应可能会延迟
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include <iostream>
class Database{ public: virtual void request() const=0; };
class RealDatabase:public Database{ public: void request() const override{ std::cout<<"真实数据库: 处理业务请求.\n"; } };
class ProxyDatabase:public Database{ private: RealDatabase* m_rdb; bool check_access() const{ std::cout<<"代理数据库: 在发出请求之前,检查以下.\n"; return true; } void log() const{ std::cout<<"代理数据库: 处理日志.\n"; } public: ProxyDatabase(RealDatabase* rdb):m_rdb(rdb){} void request() const override{ if(!check_access()){ std::cout<<"代理数据库: 数据库访问请求被驳回.\n"; return; } std::cout<<"代理数据库: 通过代理处理数据库请求.\n"; m_rdb->request(); log(); } };
void client(const Database& database){ database.request(); }
int main (int argc, char *argv[]) { RealDatabase rdb; client(rdb); std::cout<<"----------------"<<std::endl; ProxyDatabase pdb(&rdb); client(pdb); return 0; }
|
责任链模式
概念:
责任链模式(Chain of Responsibility pattern)
:是一种对象行为型设计模式,旨在通过创建一条处理请求的链,使多个对象都有机会处理该请求,从而避免请求的发送者与接收者之间的耦合关系。请求在链上传递,直到有一个对象处理它为止。
例子
问题:开发一款故障保修系统,不同的业务员处理的故障不同,如何确保客户的维修请求得到妥善的处理(比方说打维修电话,客服开始可能并非人工而是ai,如果处理不了,就转人工,人工还处理不了交给更高的人处理)
解决方案:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
结构:
- **处理者(Handler)**:声明了所有具体处理者的通用接口
- **基础处理者(Base Handler)**:是一个可选的类,你可以将所有处理者共用的样本代码放置在其中,可以与处理者合并成抽象处理者类
- **具体处理者(Concrete Handlers)**:包含处理请求的实际代码。每个处理者接收到请求后,都必须决定是否进行处理,以及是否沿着链传递请求
- **客户端(Client)**:可根据程序逻辑一次性或者动态地生成链请求可发送给链上的任意一个处理者
优点:
- 灵活性:可以控制请求处理的顺序
- 单一职责原则:你可对发起操作和执行操作的类进行解耦
- 开闭原则:可以在不更改现有代码的情况下在程序中新增处理者
缺点:
- 部分请求可能未被处理
- 性能问题:如果责任链过长,可能会影响系统性能,因为请求需要经过多个处理者的检查。
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| #include <iostream> #include <list> #include <string>
class Handler { private: Handler* m_next; public: Handler() : m_next(nullptr) {} virtual ~Handler() {}
virtual Handler* setnext(Handler* handler) { m_next = handler; return m_next; }
virtual std::string handle(std::string request) { if (m_next) { return m_next->handle(request); } return ""; } };
class AIHandler : public Handler { public: std::string handle(std::string request) override { if (request == "使用向导") { return "AI客服: 我来处理-" + request + "\n"; } else { return Handler::handle(request); } } };
class HumanHandler : public Handler { public: std::string handle(std::string request) override { if (request == "常见问题") { return "人工客服: 我来处理-" + request + "\n"; } else { return Handler::handle(request); } } };
class ExpertHandler : public Handler { public: std::string handle(std::string request) override { if (request == "疑难杂症") { return "专家: 我来处理-" + request + "\n"; } else { return Handler::handle(request); } } };
void client(Handler& handler) { std::list<std::string> problems = { "使用向导", "疑难杂症", "常见问题", "无法解决问题" }; for (auto& problem : problems) { std::string res = handler.handle(problem); if (res.empty()) { std::cout << "处理结果: 无人能够处理该问题\n"; } else { std::cout << "处理结果: " << res; } } }
int main(int argc, char* argv[]) { AIHandler ai; HumanHandler human; ExpertHandler expert;
ai.setnext(&human)->setnext(&expert);
std::cout << "Chain: AI客服->人工客服->专家\n"; client(ai);
return 0; }
|
命令模式
概念:
命令模式(Command pattern)
:是一种对象行为型设计模式,旨在将请求(操作)封装为对象,从而使你可以使用不同的请求、队列或日志来参数化其他对象,并支持可撤销的操作。
例子
问题:模拟小餐馆点餐,客户提交订单给服务员,服务员将需求提交给大厨,由大厨完成食物的准备工作
解决方案:将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作
结构:
- **发送者/调用者(Invoker)**:负责对请求进行初始化,包含命令对象的引用。负责调用命令对象执行请求
- **命令(Command)**:定义命令的接口,通常包括执行操作的方法
- **具体命令(Concrete Command)**:实现各种类型的请求(实现命令接口)。自身并不完成工作,而是委派给业务逻辑对象(调用接收者相应的操作来实现命令功能)
- **接收者(Receiver)**:包含部分业务逻辑。知道如何执行与请求相关的操作,绝大部分命令只处理如果将请求传递到接收者,接收者会完成实际的工作
- **客户端(Client)**:会创建并配置具体命令对象
优点:
- 单一职责原则:可以解耦触发和执行操作的类
- 开闭原则:可以在不修改已有客户端代码的情况下在程序中创建新的命令
- 可以实现撤销和恢复功能
- 延迟执行:可以实现操作的延迟执行
- 可以将一组简单命令组合成一个复杂命令
缺点:
- 复杂性提高:引入了大量的命令类,可能会使系统变得复杂,尤其是在命令数量较多时
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| #include <iostream> #include <string> #include <vector>
class Kitchen{ private: Kitchen(){} Kitchen(const Kitchen&)=delete; Kitchen& operator=(const Kitchen&)=delete; public: static Kitchen* getinstance(){ static Kitchen* instance=new Kitchen(); return instance; }
void preparefood(const std::string& food){ std::cout<<"厨房: 正在准备("<<food<<".)\n"; } };
class Command{ public: virtual ~Command(){} virtual void execute() const=0; };
class Order:public Command{ private: Kitchen* m_kitchen; std::vector<std::string> m_foods; public: Order(Kitchen* kitchen,std::vector<std::string> foods):m_kitchen(kitchen),m_foods(foods){} ~Order(){ delete m_kitchen; } void execute() const override{ std::cout<<"订单: 需要厨房进行处理...\n"; for(auto& food:m_foods){ m_kitchen->preparefood(food); } } };
class Waitress{ private: Command* m_command; public: ~Waitress(){ delete m_command; } void orderup(Command* command){ std::cout<<"服务员:...提交订单...\n"; m_command=command; m_command->execute(); } };
class Client{ public: void orderfood(Waitress& waitress,std::vector<std::string> foods){ waitress.orderup(new Order(Kitchen::getinstance(),foods)); } };
int main (int argc, char *argv[]) { Client client; Waitress waitress; client.orderfood(waitress,{"波士顿大龙虾","雪顶咖啡"}); return 0; }
|
迭代器模式
概念:
迭代器模式(Iterator pattern)
:一种对象行为型设计模式,它允许你以一致的方式访问集合对象(如列表、数组、集合等)中的元素,而无需暴露集合的内部结构。换句话说,迭代器模式提供了一种顺序访问集合元素的机制,可以在不暴露集合的实现细节的情况下,遍历其元素。
例子
问题:两个餐厅要合并,虽然两家菜单系统类似,但底层使用的数据结构不同,如何能让客户端方便的遍历
解决方案:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示
结 构:
- **迭代器(Iterator)**:声明了遍历集合所需的操作:获取下一个元素、是否有下一个元素等
- **具体迭代器(Concrete Iterators)**:实现遍历集合的一种特定算法。迭代器对象必须跟踪自身遍历的进度
- **集合(Collection)**:声明获取与集合兼容的迭代器的方法
- **具体集合(Concrete Collections)**:在客户端请求迭代器时返回一个特定的具体迭代器类实体
- **客户端(Client)**:通过集合和迭代器的接口与两者进行交互,允许同一客户端代码使用各种不同的集合和迭代器
优点:
- 单一职责原则:将庞大的遍历算法代码抽取为独立的类
- 开闭原则:可实现新型的集合和迭代器,无需修改现有代码
- 可以并行遍历同一集合,因为每个迭代器对象都包含其自身的遍历状态,并可以暂停遍历并在需要的时候继续
缺点:
- 如果程序只与简单的结合进行交互,应用该模式可能适得其反
- 对于某些特殊集合,使用迭代器可能比直接遍历的效率低
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| #include <iostream> #include <list> #include <string>
template <class T,class U> class Iterator{ public: typedef typename std::list<T>::iterator Pointer; Iterator(U* p_container,bool reverse=false):m_container(p_container){} void first(){ m_iter=m_container->m_data.begin(); } void next(){ m_iter++; } bool isend(){ return (m_iter==m_container->m_data.end()); } Pointer current(){ return m_iter; } private: U* m_container; Pointer m_iter; };
template <class T> class Container{ friend class Iterator<T,Container>; public: Iterator<T,Container>* createIterator(){ return new Iterator<T,Container>(this); } void add(T data){ m_data.push_back(data); } private: std::list<T>m_data; };
class MenuItem{ public: virtual ~MenuItem(){} virtual void setData(std::string data1,std::string data2="")=0; virtual std::string data() const=0; };
class Beverage:public MenuItem{ public: ~Beverage(){} Beverage(std::string data1,std::string data2):m_data1(data1),m_data2(data2){} virtual void setData(std::string data1,std::string data2) override{ m_data1=data1; m_data2=data2; } std::string data() const override{ return m_data1+"_"+m_data2; } private: std::string m_data1; std::string m_data2; };
class Pizza:public MenuItem{ public: ~Pizza(){} Pizza(std::string data):m_data(data){} void setData(std::string data,std::string data2) override{ m_data=data; } std::string data() const override{ return m_data; } private: std::string m_data; };
template<class T> void client(T* iter){ for(iter->first();!iter->isend();iter->next()){ std::cout<<iter->current()->data()<<std::endl; } }
int main (int argc, char *argv[]) { Container<Beverage> beverageMenu; Beverage coffee("美式","奶茶"),bubbleTea("珍珠","奶茶"); beverageMenu.add(coffee); beverageMenu.add(bubbleTea);
Container<Pizza> pizzaMenu; Pizza a("意大利香肠"),b("海鲜披萨"),c("榴莲披萨"); pizzaMenu.add(a); pizzaMenu.add(b); pizzaMenu.add(c); std::cout<<"Iterator用于遍历Beverage菜单:\n"; auto it=beverageMenu.createIterator(); client(it); std::cout<<"Iterator用于遍历Pizza菜单:\n"; auto it2=beverageMenu.createIterator(); client(it2); return 0; }
|
中介者模式
概念:
中介者模式(Mediator pattern)
:一种对象行为型设计模式,旨在通过一个中介对象来封装一系列对象之间的交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,并且可以独立地改变它们之间的交互
例子
问题:实现一个机场的起飞管理系统,如果由驾驶员们讨论谁先起飞,后果可能是灾难性的
解决方案:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示地相互引用,从而使得其耦合松散
结构:
- **组件(Component)**:各种包含业务逻辑的类,每个组件都有一个指向中介者的引用,可通过将其连接到不同的中介者使其内容能在其他程序中复用,类似于同事类
- **中介者接口(Mediator)**:声明了与组件交流的方法,但通常仅包括一个通知方法
- **具体中介者(Concrete Mediator)**:封装了多种组件间的关系,具体中介者通常会保存所有组件的引用并对其进行管理
- 组件并不知道其他组件的情况。如果组件内发生了重要事件,它只能通知中介者,中介者收到通知后能轻易地确定发送者。
优点:
- 无需修改实际组件就能增加新的中介者
- 可以减轻应用中多个组件间的耦合情况
- 可以更方便地复用各个组件
缺点:
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| #include <iostream> #include <string>
class Staff;
class ControlTower{ public: virtual void notify(Staff* sender,std::string event) const=0; };
class Staff{ protected: std::string m_name; ControlTower* m_controltower; public: Staff(std::string name,ControlTower* controltower=nullptr):m_name(name),m_controltower(controltower){} std::string name(){ return m_name; } void setMediator(ControlTower* controltower){ m_controltower=controltower; } };
class Pilot:public Staff{ public: Pilot(std::string name):Staff(name){} void takeOff(){ std::cout<<name()+":请求起飞.\n"; m_controltower->notify(this,"起飞请求"); } void copy(){ std::cout<<name()+":收到.\n"; } };
class GroundGrew:public Staff{ public: GroundGrew(std::string name):Staff(name){} void takeOff(){ std::cout<<name()+":请求维护.\n"; m_controltower->notify(this,"维护请求"); } void copy(){ std::cout<<name()+":收到.\n"; } };
class ConcreteControlTower:public ControlTower{ private: Pilot* m_pilot1; Pilot* m_pilot2; GroundGrew* m_ground1; public: ConcreteControlTower(Pilot* p1,Pilot* p2,GroundGrew* g1):m_pilot1(p1),m_pilot2(p2),m_ground1(g1){ m_pilot1->setMediator(this); m_pilot2->setMediator(this); m_ground1->setMediator(this); }
void notify(Staff* sender,std::string event) const override{ std::cout<<"控制塔:收到"+sender->name()+event<<std::endl; if(event=="起飞请求"){ m_ground1->copy(); if(sender!=m_pilot1) m_pilot1->copy(); if(sender!=m_pilot2) m_pilot2->copy(); } if(event=="维护请求"){ if(sender!=m_pilot1) m_pilot1->copy(); if(sender!=m_pilot2) m_pilot2->copy();
} } };
int main (int argc, char *argv[]) { Pilot* p1=new Pilot("空军一号"); Pilot* p2=new Pilot("空军二号"); GroundGrew* g1=new GroundGrew("地勤1"); ConcreteControlTower* controltower=new ConcreteControlTower(p1,p2,g1); p1->takeOff(); g1->takeOff(); delete p1; delete p2; delete g1; return 0; }
|
备忘录模式
概念:
备忘录模式(Memento pattern)
:是一种对象行为型设计模式,它允许在不暴露对象实现细节的情况下,保存和恢复对象的状态。简单来说,备忘录模式可以让你在某个时刻保存对象的状态,并在以后需要的时候恢复到这个状态。
例子
问题:实现一个游戏的存档功能,希望在保存状态的同时可以不破坏类的封装
解决方案:在不破坏封装性的前提下,捕获一个对象的内部状态。并在该对象之外保留这个状态。这样以后就可将该对象恢复到原先保存的状态
结构:
- **原发器类(Originator)**:可以生成自身状态的快照(备忘录对象),也可以在需要时通过快照恢复自身状态
- **备忘录类(Memento)**:存储对象状态(快照)的对象,通常做法是将备忘录设为不可变的,并通过构造函数一次性传递数据
- **负责人类(Caretaker)**:仅知道”何时”和”为何”创建原发器快照,以及何时恢复状态
- 可以通过友元,使得Memento中的状态只能被Originator所拿到
优点:
- 可以在不破坏对象封装情况的前提下创建对象状态快照
- 可以通过负责人维护原发器状态历史记录,简化原发器代码
缺点:
- 负责人必须跟踪原发器的生命周期,这样才能销毁启用的备忘录
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| #include <cstdlib> #include <ctime> #include <iostream> #include <vector>
class Memento{ friend class Hero; private: unsigned int m_state; std::string m_date; public: Memento(unsigned int state):m_state(state){ std::time_t now=std::time(0); m_date=std::ctime(&now); } std::string getdate(){ return m_date; } };
class Hero{ private: unsigned int m_state; public: Hero(unsigned int state):m_state(state){ std::cout<<"英雄:我的初始状态为:"<<m_state<<std::endl; } void fight(){ std::cout<<"英雄:被砍了一刀\n"; m_state-=rand()%20; std::cout<<"当前状态为:"<<m_state<<std::endl; } Memento* save(){ return new Memento(m_state); }
void restore(Memento* memento){ m_state=memento->m_state; std::cout<<"英雄:当前状态恢复为:"<<m_state<<std::endl; } };
class Caretaker{ private: Hero* m_hero; std::vector<Memento*> m_mementos; public: Caretaker(Hero* hero):m_hero(hero){} ~Caretaker(){ for(auto item:m_mementos) delete item; } void backup(){ std::cout<<"管理者:正在保存英雄状态...\n"; m_mementos.emplace_back(m_hero->save()); } void undo(){ if(m_mementos.empty()) return; Memento* memento=m_mementos.back(); m_mementos.pop_back(); std::cout<<"管理者:英雄状态恢复为"<<memento->getdate()<<std::endl; m_hero->restore(memento); delete memento; } };
int main (int argc, char *argv[]) { std::srand(static_cast<unsigned int>(std::time(NULL))); Hero* hero=new Hero(100); Caretaker* caretaker=new Caretaker(hero); caretaker->backup(); hero->fight(); hero->fight(); caretaker->undo(); delete hero; delete caretaker; return 0; }
|
观察者模式
概念:
观察者模式(Observer pattern)
:是一种对象行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,所有依赖于它的观察者都会得到通知并自动更新。这个模式使得主题与观察者之间的耦合度降低,从而提高系统的灵活性和可扩展性
例子
问题:开发一个购物平台,当商品缺货时如何处理?如果客户经常主动查看,无疑是浪费客户的时间。如果平台主动发送到货通知给所有客户,那么对于不需要的客户来说并不友好,缺货商品一多消息也容易泛滥
解决方案:定义对象间的一种一对多关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到同志并自动更新
结构:
- **发布者/主题接口(Publisher/Subject)**:被观察对象,维护一系列观察者,提供注册、注销观察者的方法。会向其他对象发送值得关注的事件,支持订阅。当新事件发生时,发布者会遍历订阅者对象,调用通知方法。该方法是在订阅者接口中声明的
- **具体发布者/具体主题(ConcretePublisher/ConcreteSubject)**:实现了主题接口,存储与核心业务相关的状态。当具体主题的状态发生变化时,会通知所有观察者
- **订阅者接口/观察者接口(Subscriber)**:声明了通知接口。多数情况下,该接口仅包含一个update更新方法
- **具体订阅者/具体观察者(Concrete Subscribers)**:执行一些操作来回应发布者的通知,订阅者通常需要一些上下文信息来正确地处理更新
- **客户端(Client)**:会分别创建发布者和订阅者对象,然后为订阅者注册发布者更新
优点:
- 开闭原则:你无需修改发布者代码就能引入新的订阅者类(如果是发布者接口则可轻松引入发布者类)
- 动态管理:可以在运行时建立对象之间的联系,动态地添加或移除观察者,无需修改主题的代码
缺点:
- 通知顺序一致性:订阅者的通知顺序是随机的
- 循环依赖:在实现观察者模式时,如果不当心,可能会导致循环依赖的问题,特别是当观察者和被观察者相互持有对方的引用时
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| #include <iostream> #include <list>
class IObserver{ public: virtual void update(const std::string& message)=0; };
class ISubject{ public: virtual void attach(IObserver* observer)=0; virtual void detach(IObserver* observer)=0; virtual void noify()=0; };
class Mate50:public ISubject{ private: std::list<IObserver*> m_observerlist; std::string m_message; public: void attach(IObserver *observer) override{ m_observerlist.emplace_back(observer); } void detach(IObserver *observer) override{ m_observerlist.remove(observer); } void noify() override{ for(auto &ob:m_observerlist){ ob->update(m_message); } } void createmess(std::string message=""){ m_message=message; noify(); } };
class Customer:public IObserver{ private: ISubject* m_mate50; std::string m_message; int m_numer; static int m_static_num; public: Customer(ISubject* subject):m_mate50(subject){ m_mate50->attach(this); std::cout<<"我是"<<++Customer::m_static_num<<"号订阅者.\n"; m_numer=m_static_num; } void update(const std::string &message) override{ m_message=message; printinfo(); } void printinfo(){ std::cout<<m_numer<<"号订阅者:接收到新的消息:"+m_message; } void remove(){ m_mate50->detach(this); std::cout<<m_numer<<"号订阅者:从列表中移除.\n"; } };
int Customer::m_static_num=0;
int main (int argc, char *argv[]) { Mate50* mate50=new Mate50(); Customer* c1=new Customer(mate50); Customer* c2=new Customer(mate50); Customer* c3=new Customer(mate50); Customer* c4; mate50->createmess("Mate50即将到店...\n"); c4=new Customer(mate50); mate50->createmess("Mate50明天到店...\n"); c2->remove(); mate50->createmess("Mate50即将售完...\n"); delete mate50; delete c1; delete c2; delete c3; delete c4; return 0; }
|
状态模式
概念:
状态模式(State pattern)
:是一种对象行为型设计模式,它允许一个对象在其内部状态变化时,改变其行为。换句话说,状态模式使得对象在不同的状态下,表现出不同的行为,而无需在代码中使用条件语句来切换状态
核心思想
:将一个对象的状态抽象成不同的状态类,当对象的状态发生变化时,直接切换到相应的状态类,通过状态类来实现行为的切换。每个状态类都代表对象的某种状态,状态的转换是通过状态对象之间的切换来完成的
作用
:避免了大量的条件判断语句,简化状态机的处理代码,增加系统的可扩展性
例子
问题:开发一个糖果贩卖机,当投入硬币按下按钮,糖果机将掉落一枚糖果,当没有投入硬币,直接按下按钮,将会得到请投币的提示(如果有多种状态,并且需要后期扩展)
解决方案:允许一个对象在其内部状态发生改变时改变它的行为
结构:
- **上下文(Context)**:上下文持有当前的状态,并且提供切换状态的方法。上下文的行为会随着状态的变化而发生改变
- **状态接口(State)**:声明特定于状态的方法,用于封装具体状态类的行为
- 具体状态:实现状态接口,定义不同状态下具体行为
注意:上下文和具体状态都可以设置上下文的下一个状态,并通过替换连接到上下文的状态对象来完成实际的状态转换
优点:
- 单一职责原则:将与特定状态相关的代码放在单独的类中
- 开闭原则:无需修改已有状态类和上下文就能引入新状态
- 简化代码:通过消除臃肿的状态机条件语句简化上下文代码
缺点:
- 过度设计:如果状态机只有很少的几个状态,或者很少发生改变,那么应用该模式可能会显得小题大做
- 类数量增加:状态模式会引入很多状态类,可能导致系统中类的数量显著增加,从而增加了系统的复杂性
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| #include <iostream>
class CandyMachine;
class State{ protected: public:
virtual ~State(){} virtual void getcandy()=0; virtual void returncoin()=0; virtual void putcoin()=0; };
class NoCoinState:public State{ public: void getcandy() override{ std::cout<<"请先投币.\n"; } void returncoin() override{ std::cout<<"你并没有投币.\n"; } void putcoin() override{ std::cout<<"投币成功.\n"; } };
class HaveCoinState:public State{ public: void getcandy() override{ std::cout<<"糖果投放中....\n"; } void returncoin() override{ std::cout<<"硬币退回中....\n"; } void putcoin() override{ std::cout<<"重复投币.\n"; } };
class CandyMachine{ private: State* m_curstate; State* m_havecoin; State* m_nocoin; public: CandyMachine(){ m_havecoin=new HaveCoinState(); m_nocoin=new NoCoinState(); m_curstate=m_nocoin; std::cout<<"糖果机:当前状态为"<<typeid(*m_curstate).name()<<std::endl; } ~CandyMachine(){ delete m_nocoin; delete m_havecoin; } void putcoin(){ std::cout<<"糖果机:putcoin\n"; m_curstate->putcoin(); transit(m_havecoin); } void getcandy(){ std::cout<<"糖果机:getcandy\n"; m_curstate->getcandy(); transit(m_nocoin); } void returncoin(){ std::cout<<"糖果机:returncoin\n"; m_curstate->returncoin(); transit(m_nocoin); } private: void transit(State* state){ m_curstate=state; std::cout<<"糖果机:切换状态为"<<typeid(*m_curstate).name()<<std::endl; } };
int main (int argc, char *argv[]) { CandyMachine* candymachine=new CandyMachine(); candymachine->getcandy(); candymachine->returncoin(); candymachine->putcoin(); candymachine->getcandy(); candymachine->returncoin(); delete candymachine;
return 0; }
|
策略模式
概念:
策略模式(Strategy pattern)
:是一种对象行为型设计模式,它定义了一系列算法,并使得它们可以相互替换。策略模式使得算法的变化独立于使用算法的客户端。换句话说,策略模式允许你在运行时选择不同的算法来完成某个任务,从而避免了在代码中大量的条件语句
核心思想
:将算法封装在独立的策略类中,通过上下文(Context)对象来控制算法的切换,使得客户端能够灵活地选择不同的策略,而不需要修改客户端代码
例子
问题:做一款打斗游戏,玩家使用的英雄使用不同的武器将会产生不同的损伤效果
解决方案:定义一系列算法,把他们一个个封装起来。并且使它们可以相互替换。使得算法可独立于使用它的客户而变化
区分:
状态模式
:状态模式的本质是在对象内部状态改变时,改变其行为,也就是说状态模式允许对象根据当前内部状态的不同,而表现出不同的行为
策略模式
:策略模式的本质是在运行中可以选择不同的算法,也就是说相同的任务,策略模式允许对象选择不同的算法来进行执行这个任务,不同的算法所得出的结果不同
区分
:策略模式是改变的是算法或行为,状态模式是改变其内部状态(间接改变行为),策略模式是外部控制改变行为,而状态模式是内部改变状态以改变行为
结构:
- **上下文(Context)**:维护指向具体策略的引用,并通过该对象来执行具体的算法(上下文通常会根据外部条件来选择使用哪个具体策略)。
- **策略接口(Strategy)**:所有具体策略的通用接口,定义了所有支持的算法或行为(所有具体策略类都需要实现这个接口)
- **具体策略(Concrete Strategies)**:实现了上下文所用算法(策略接口)的不同变体
- **客户端(Client)**:会创建一个特定策略对象并将其传递给上下文,上下文则会提供一个设置器以便客户端在运行时替换相关联的策略
优点:
- 动态选择:可在运行时切换对象内的算法
- 代码隔离:可将算法的实现和使用算法的代码隔离
- 组合代替:可使用组合代替继承
- 开闭原则:无需对上下文进行修改就能够引入新的策略
缺点:
- 过度设计:如果算法极少发生改变,那么没有任何理由引入新的类和接口,这样只会让程序过于复杂
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <iostream>
class Weapon{ public: virtual ~Weapon(){} virtual std::string fightAlgorithm() const=0; };
class Dagger:public Weapon{ public: std::string fightAlgorithm() const override{ return "使用匕首计算损伤.\n"; } };
class Axe:public Weapon{ public: std::string fightAlgorithm() const override{ return "使用斧头计算损伤.\n"; } };
class Hero{ private: Weapon* m_weapon; public: Hero(Weapon* weapon=nullptr):m_weapon(weapon){} void setWeapon(Weapon* weapon){ m_weapon=weapon; } void fight(){ std::cout<<m_weapon->fightAlgorithm(); } };
int main (int argc, char *argv[]) { std::cout<<"客户端:英雄使用匕首攻击.\n"; Dagger dagger; Hero hero(&dagger); hero.fight(); std::cout<<"客户端:英雄使用斧头攻击.\n"; Axe axe; hero.setWeapon(&axe); hero.fight(); return 0; }
|
模板方法模式
概念:
模板方法模式(Template Method pattern)
:是一种类行为型设计模式,它定义了一个算法的骨架,将一些步骤的实现推迟到子类中。模板方法模式让子类可以在不改变算法结构的情况下,重定义算法中的某些步骤
- 工厂方法是模板方法的一种特例
例子
问题:做一款数据挖掘的程序,需要支持不同格式的数据文件,虽然文件格式不同,实现步骤基本一致
解决方案:定义一个算法骨架,而将一些步骤延迟到子类。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
结构:
- **抽象类(AbstractClass)**:定义一个模板方法,控制算法的执行顺序,模板方法通常是一个 final 方法,防止子类修改它的执行流程。模板方法中包含一些抽象步骤,这些步骤由子类去实现。
- **具体类(ConcreteClass)**:继承抽象类,提供实现细节,重写抽象步骤,继承并完成模板方法中的抽象部分
- **模板方法(Template Method)**:抽象类中定义的公共算法流程,由具体子类实现特定的步骤
优点:
- 代码复用:大部分算法结构在父类中实现,子类只需要实现某些步骤,从而避免了重复代码
- 控制算法顺序:父类可以控制算法的结构和顺序,子类只能修改某些具体步骤,保证了算法的一致性
- 开闭原则:通过让子类扩展或实现某些步骤,而不是修改父类的结构,实现了对扩展的开放,对修改的封闭
缺点:
- 维护困难:模板方法中的步骤越多,其维护工作就可能会越困难
- 限制灵活性:子类只能扩展或修改父类定义的步骤,无法自由改变算法结构,因此模板方法模式不适用于所有情况
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include <iostream> #include <string>
class DataMiner{ public: void mineData(std::string path){ openfile(path); extractData(); makereport(); hook1(); closefile(); } protected: virtual void openfile(std::string path)=0; void extractData() const{ std::cout<<"DataMiner:从字符串中提取相关数据.\n"; } void makereport() const{ std::cout<<"DataMiner:生成数据分析报告.\n"; } virtual void closefile() const=0; virtual void hook1() const{} protected: std::string m_str=""; };
class PDFDataMiner:public DataMiner{ public: void openfile(std::string path) override{ m_str="openfile(path)"; std::cout<<"PDFDataMiner:打开PDF文件,转换为字符串序列.\n"; } void closefile() const override{ std::cout<<"PDFDataMiner:关闭PDF文件.\n"; } };
class WordDataMiner:public DataMiner{ public: void openfile(std::string path) override{ m_str="openfile(path)"; std::cout<<"WordDataMiner:打开Word文件,转换为字符串序列.\n"; } void closefile() const override{ std::cout<<"WordDataMiner:关闭Word文件.\n"; } void hook1() const override{ std::cout<<"WordDataMiner:给Word文件添加水印.\n"; } };
void client(DataMiner* dataminer,std::string path){ dataminer->mineData(path); }
int main (int argc, char *argv[]) { PDFDataMiner pdfdataminer; client(&pdfdataminer,"aa.pdf"); WordDataMiner worddataminer; client(&worddataminer,"bb.doc"); return 0; }
|
访问者模式
概念:
访问者模式(Visitor pattern)
:是一种对象行为型设计模式,其核心思想是将对一组对象的操作从对象结构中分离出来,放入到访问者对象中。通过这种方式,你可以在不修改元素类的情况下,向元素添加新的操作
具体操作
:具体来说,访问者模式提供了一个 访问者(Visitor) 类,通过这个访问者类来访问一组元素对象。每个元素对象会通过 accept()
方法接收访问者,从而触发访问者的操作方法。访问者模式让你可以在不改变元素类的前提下,添加新的操作
例子
问题:做一款生成冰激凌和雪糕的程序,现在希望为其添加糖霜,巧克力粉
解决方案:保证元素类不变,为元素类定义作用于该元素类的新操作
结构:
- **访问者接口(Visitor)**:声明了一系列以对象结构的具体元素为参数的访问者方法
- **具体访问者类(Concrete Visitor)**:会为不同的具体元素类实现相同行为的不同版本
- **元素接口(Element)**:声明了一个方法来”接收”访问者(允许访问者访问该元素)
- **具体元素(Concrete Element)**:实现元素接口的接收方法,根据当前元素类将其调用重定向到相应访问者的方法
- **客户端(Client)**:通常会作为集合或其他复杂对象(例如一个组合树)的代表,
优点:
- 操作集中管理:所有操作都在访问者类中进行,这样避免了每个元素类都实现重复的操作
- 开闭原则:在不修改元素类情况下,添加新的操作(访问者)
- 如果对象结构比较复杂,比如树形结构或多层次结构,它可以遍历整个对象结构,对每个元素进行相应的操作
缺点:
- 元素类扩展困难:访问者模式的前提就是元素类不变,如果要添加新的元素类,所有访问者类都得修改,添加对新元素的支持
- 不适用频繁变化元素结构:如果元素种类频繁变化(增加和删除),都需要更新所有的访问者类
实现:
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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| #include <iostream> #include <string> #include <vector>
class IceCream; class Popsicle;
class Visitor{ public: virtual void visitIce(const IceCream* elem) const=0; virtual void visitPopsicle(const Popsicle* elem) const=0; };
class Component{ public: virtual void accept(Visitor* visitor) const=0; };
class IceCream:public Component{ public: void accept(Visitor *visitor) const override{ visitor->visitIce(this); } std::string getIce() const{ return "冰激凌"; } };
class Popsicle:public Component{ public: void accept(Visitor *visitor) const override{ visitor->visitPopsicle(this); } std::string getPopsicle() const{ return "雪糕"; } };
class Frosting:public Visitor{ public: void visitIce(const IceCream *elem) const override{ std::cout<<elem->getIce()<<"+糖霜\n"; } void visitPopsicle(const Popsicle *elem) const override{ std::cout<<elem->getPopsicle()<<"+糖霜\n"; } };
class Chocolate:public Visitor{ public: void visitIce(const IceCream *elem) const override{ std::cout<<elem->getIce()<<"+巧克力\n"; } void visitPopsicle(const Popsicle *elem) const override{ std::cout<<elem->getPopsicle()<<"+巧克力\n"; } };
void client(std::vector<Component*> component,Visitor* visitor){ for(auto& it:component){ it->accept(visitor); } }
int main (int argc, char *argv[]) { std::vector<Component*> components={new IceCream,new Popsicle,new Popsicle}; Frosting frosting; client(components,&frosting); Chocolate chocolate; client(components,&chocolate); for(auto it:components) delete it; return 0; }
|
MVC模式
概念:
MVC模式
:是一种经典的设计模式,可以理解为一种复合型设计模式,常用于开发图形用户界面(GUI)应用程序。分为Model、View和Control部分,每个部分负责不同的功能,从而实现应用程序的分层架构
组成
:
- Model(模型):负责处理数据、状态和应用业务逻辑,只提供数据接口,通常包含应用程序的核心类和数据结构
- View(视图):显示模型的数据,并根据用户的交互更新界面,表示用户界面,用于展示数据给用户,视图通常不直接处理数据或业务逻辑,而是负责展示数据
- Control(控制器):处理用户的请求,协调模型和视图之间的交互。作为用户输入和视图之间的中介,控制器接收用户的输入,并调用模型和视图进行相应的操作。控制器会根据用户的行为更新模型,模型的更新又会通过视图进行展示
总结
范围 / 类型 |
创建型 |
结构型 |
行为型 |
类 |
工厂方法 |
适配器(类) |
模板方法 |
对象 |
抽象工厂,建造者,原型,单例 |
适配器(对象),桥接,组合,装饰,外观,享元,代理 |
责任链,命令,迭代器,中介者,备忘录,观察者,状态,策略,访问者 |