设计模式
一、概述
(一)、基本概念
-
底层思维(向下)
- 语言构造
- 编译转换
- 内存模型
- 运行时机制
-
抽象思维(向上)
- 面向对象
- 组件封装
- 设计模式(依赖封装,继承,多态)
- 架构模式
-
设计模式的使用依赖于某个稳定点。在设计中,要关注变化点和稳定点
-
目标:管理变化,提高复用
-
手段
- 分解:先看个性
- 抽象:再找共性
-
重构技法
- 静态 => 动态(类 => 接口)
- 早绑定 => 晚绑定(虚函数)
- 继承 => 组合
- 编译时依赖 => 运行时依赖
- 紧耦合 => 松耦合
-
常用对象模式
class A{ B* pb; //松耦合,具有灵活性 //... };
-
不用设计模式的情况
- 代码可读性很差
- 需求理解很浅(避免花大功夫,做低效率的事)
- 变化没有显现时
- 不是系统的关键已来点
- 项目没有复用价值时
- 项目将要发布时
-
经验
- 要有Framework和Application的区隔思维
- 作为Application的开发人员,要认清Framework的扩展点
- 作为Framework的开发人员,要设计Framework的接口
- 良好的设计师演化的结果
- 要有Framework和Application的区隔思维
(二)、面向对象设计原则(八)
最高层次:忘掉模式,只有原则
依赖导致原则(DIP)
- 高层模块(稳定)不应该底层模块(变化),二应该依赖于抽象(稳定)
- 改进前:
- People直接抚摸cat
- cat发出miao回应
- 改进后:
- people抚摸宠物抽象类
- cat继承抽象类中的回应虚函数发出回应
- 即people和cat之间没有直接关系
- 改进前:
- 抽象(稳定)不应该依赖于实现细节(变化),细节应该依赖于抽象(稳定)
- 举例:由Tomcat框架调用应用,而不是反过来应用调用框架(这在面向对象的设计中是极为常见的)
开放封闭原则(OCP)
- 对扩展开放,对修改封闭
单一职责原则(SRP)
- 一个类只负责一件事
- 一个类只有一个引起它变化的原因
Liskov替换原则(LSP)
- 子类必须能够替换他们的基类(IS-A)
接口隔离原则(ISP)
- 不应该强迫客户程序依赖它们不用的方法
- 接口应该小而完备
对象组合优于类继承
- 继承耦合度高,组合耦合度低
封装变化点
- 封装的作用是是一侧稳定,一侧变化
面向接口编程
- 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口
(三)、将设计原则提升为设计经验
- 设计习语:和低层语言相关
- 设计模式:代码复用
- 架构模式:子系统划分等
(四)、重构
敏捷软件开发:Refactoring to Patterns (迭代的方法)
- 常见的重构技法
- 静态 => 动态
- 早绑定 => 晚绑定(虚函数)
- 继承 => 组合
- 编译时依赖 => 运行时依赖
- 紧耦合 => 松耦合
(五)、常见分类
- 创建型:描述“怎样创建对象”
- 结构型:描述如何将类或对象按某种布局组成更大的结构
- 行为型:描述类或对象之间怎样相互协作共同玩=完成某个对象都无法单独完成的任务
二、设计模式
(一)、组件协作模式
框架-组件
模板方法(Template Method)
-
背景
- 在一段内置代码
- 有一部分是变化的
- 有一部分是不变化的
-
code
-
修改前
-
main,cpp
#include <iostream> int main() { //step1(...); 不变,可以看做是一代代码而非函数 //step2(...); 变化,可以看做是一代代码而非函数 //step3(...); 不变,可以看做是一代代码而非函数 //step4(...); 变化,可以看做是一代代码而非函数 //step5(...); 不变,可以看做是一代代码而非函数 return 0; }
-
-
修改后
-
main.cpp
#include <iostream> class BaseEvent{ public: void run(){ //关键 //step1(...); 不变,可以看做是一代代码而非函数 step2(); //变化 //step3(...); 不变,可以看做是一代代码而非函数 step4(); //变化 //step5(...); 不变,可以看做是一代代码而非函数 } ~BaseEvent(){} protected: //由于step2,step4仅供函数内部调用,故可以写作protected virtual void step2()=0; virtual void step4()=0; }; class Event:public BaseEvent{ virtual void setp2(){ } virtual void step4(){ } }; int main() { BaseEvent *e = new Event(); e->run(); return 0; }
-
-
-
类图
-
定义:定义一个基类对象,通过run函数执行代码。对于代码中变化的部分,写作虚函数的调用。子类继承基类,实现对应的变化部分的虚函数,通常由上层调用run方法
-
注:
- 在面向过程的语言中,通常由我们来调用lab中的函数;而在面向对象的思维中,通常会使用的一些框架(FrameWork),通常这个时候就变成了框架调用我们缩写的函数
- 该模式在框架中使用较多
-
典型应用:Java的
HttpServlet
中service
方法调用doGet
,doPost
等方法,多线程中的run()方法protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); long lastModified; if (method.equals("GET")) { lastModified = this.getLastModified(req); if (lastModified == -1L) { this.doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader("If-Modified-Since"); } catch (IllegalArgumentException var9) { ifModifiedSince = -1L; } if (ifModifiedSince < lastModified / 1000L * 1000L) { this.maybeSetLastModified(resp, lastModified); this.doGet(req, resp); } else { resp.setStatus(304); } } } else if (method.equals("HEAD")) { lastModified = this.getLastModified(req); this.maybeSetLastModified(resp, lastModified); this.doHead(req, resp); } else if (method.equals("POST")) { this.doPost(req, resp); } else if (method.equals("PUT")) { this.doPut(req, resp); } else if (method.equals("DELETE")) { this.doDelete(req, resp); } else if (method.equals("OPTIONS")) { this.doOptions(req, resp); } else if (method.equals("TRACE")) { this.doTrace(req, resp); } else { String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[]{method}; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(501, errMsg); } }
策略模式(Strategy Method)
-
背景
- 我们有许多宠物
- 每个宠物发出不同的叫声
-
code
-
修改前
-
main.cpp
#include <iostream> using namespace std; enum Pet{ dog,cat }pet; int main() { int petName; if(petName == dog){ cout << "wang wang" <<endl; }else if(petName == cat){ cout << "miao miao" << endl; } return 0; }
-
为了体现设计模式的优越性,显然我们应该以动态的视角去考察,假设我们现在要增加一个动物chicken
-
main.cpp
#include <iostream> using namespace std; enum Pet{ dog,cat, chicken //修改 }pet; int main() { int petName; if(petName == dog){ cout << "wang wang" <<endl; }else if(petName == cat){ cout << "miao miao" << endl; }else if(petName == chicken){ //修改 cout << "ge ge" << endl; } return 0; }
-
显然这不算是一段好的代码,因为它违反了开闭原则
-
-
修改后
-
main.cpp
#include <iostream> #include "Person.h" using namespace std; int main() { PetFactory *petFactory = new ChickenFactory(); Person p(petFactory); p.touchAnimal(); return 0; }
-
Person.h
// // Created by Arno_vc on 2022/3/15. // #ifndef DESIGNMODE_PERSON_H #define DESIGNMODE_PERSON_H #include "PetFactory.h" class Person { public: PetFactory *petFactory; Person(PetFactory * petFactory){ this->petFactory = petFactory; } void touchAnimal(){ Pro *pet = petFactory->buildPro(); pet->howl(); } ~Person(){} }; #endif //DESIGNMODE_PERSON_H
-
PetFactory.h
// // Created by Arno_vc on 2022/3/15. // #ifndef DESIGNMODE_PETFACTORY_H #define DESIGNMODE_PETFACTORY_H #include "Pro.h" class PetFactory { public: virtual Pro* buildPro()=0; ~PetFactory(){} }; class DogFactory:public PetFactory{ public: Pro* buildPro() override{ return new DogPro(); } }; class CatFactory:public PetFactory{ public: Pro* buildPro() override{ return new CatPro(); } }; class ChickenFactory:public PetFactory{ //增加了一个新的工厂 public: Pro* buildPro() override{ return new ChickenPro(); } }; #endif //DESIGNMODE_PETFACTORY_H
-
Pro.h
// // Created by Arno_vc on 2022/3/15. // #ifndef DESIGNMODE_PRO_H #define DESIGNMODE_PRO_H #include <string> #include <iostream> using namespace std; class Pro { protected: string name; public: virtual void howl()=0; }; class CatPro:public Pro{ public: void howl() override{ cout << "miao miao miao" << endl; } }; class DogPro:public Pro{ public: void howl() override{ cout << "wang wang wang" << endl; } }; class ChickenPro:public Pro{ //增加了一个新的类 public: void howl() override{ cout << "ge ge ge" << endl; } }; #endif //DESIGNMODE_PRO_H
-
注:
- 可以看到当出现新的宠物,需要增加两个类而不是修改
- 注意与工厂模式的区别:
- 策略模式强调子类之间平行的替换
- 工厂模式强调调用的无惯性
-
-
-
定义:定义一系列算法,把它们一个个封装起来,使它们可以相互替换。具体方法为子类化和扩展
-
类图
-
注:
- 搭配工厂模式使用
- 通常用于对
if-else
语句的优化 - 当
if-else
是不稳定的,可以使用策略模式(如拥有的宠物);当if-else
是稳定的,不能使用策略模式(如一星期有七天) - 性能影响:
- 若使用
if-else
,编译后必然是整块代码 - 若使用策略模式,链接器只链接所用到的相关类的部分代码
- 若使用
-
应用:Java的SPI机制
观察者模式(Observer)
-
背景
- 水果店降价
- wechat平台观察到水果店降价而发出通知(现实中当然存在水果店通知微信的关系,但我们这里仅以此为例)
-
code
-
修改前
-
main.cpp
#include <iostream> #include <vector> using namespace std; class Wechat{ public: void NotifyAccount(int num){ cout << "Now we have " << num << " account" << endl; } }; class Shop{ public: int price; Wechat *we; Shop(Wechat *we):we(we){}; void decreasePrice(int num){ price-=num; we->NotifyAccount(num); } }; int main() { Wechat *we = new Wechat(); Shop s(we); we->NotifyAccount(10); return 0; }
-
注:显然我们这里对vip做的是一个通知。可以看到这里违背了依赖倒置的关系,一方面微信的通知是依赖于价格的变化的,另一方面却是由商店主动通知微信
-
-
修改后
-
main.cpp
#include <iostream> #include <vector> using namespace std; class Platform{ //用于解耦的Observer public: virtual void NotifyAccount(int num)=0; }; class Wechat: public Platform{ public: virtual void NotifyAccount(int num){ cout << "Wechat:Now we have " << num << " account" << endl; } }; class Alipay: public Platform{ public: virtual void NotifyAccount(int num){ cout << "Alipay:Now we have " << num << " account" << endl; } }; class Shop{ public: int price; vector<Platform *> platforms; //通过vector以支持多个通知对象 void decreasePrice(int num){ price-=num; vector<Platform *>::iterator it=platforms.begin(); while(it!=platforms.end()){ (*it)->NotifyAccount(num); it++; } } }; int main() { Platform *we = new Wechat(); Platform *allipay = new Alipay(); Shop shop; shop.platforms.push_back(we); shop.platforms.push_back(allipay); shop.decreasePrice(10); return 0; }
-
注:同时支持了多平台
-
-
-
类图
-
定义:对象之间的一对多关系,当某一事物状态发生改变时,所有依赖于它的对象都能得到通知(通过Observer对象)并自动更新
-
注:
- Observer模式是基于事件的UI框架中非常常用的设计模式,也就是MVC模式的一个重要组成部分
- 实际上这里Object也可以分为抽象的Object(包含notify,以及对vector的操作)和Concrete Object,因为就代码中而言这个水果店开业一般化为商店。但就结果而言,在观察者模式下商店与特定的平台已经无关了
(二)、单一职责
划清责任,避免子类膨胀
装饰者模式与桥模式的区别:
- 装饰者模式:装饰部分可以组合
- 桥模式:装饰部分不组合
装饰模式(Decorate Mode)
-
背景
- 吹水果
- 有三种水果,苹果,香蕉,葡萄
- 有三种不同的吃法,直接吃,洗完吃,削皮吃
-
code
-
修改前
-
main.cpp
#include <iostream> #include <string> using namespace std; class Fruit{ virtual void eatFruit()=0; virtual ~Fruit(); }; class Apple:public Fruit{ virtual void eatFruit(){ cout << "Apple : Eat directly!" << endl; } virtual ~Apple(); } class Banana: public Fruit{ virtual void eatFruit(){ cout << "Banana : Eat directly!" << endl; } virtual ~Banana(); }; class Grape: public Fruit{ virtual void eatFruit(){ cout << "Grape : Eat directly!" << endl; } virtual ~Grape(); }; class WashApple: public Fruit{ virtual void eatFruit(){ cout << "wash" << endl; cout << "Apple : Eat directly!" << endl; } virtual ~WashApple(); }; class WashBanana: public Fruit{ virtual void eatFruit(){ cout << "wash" << endl; cout << "Banana : Eat directly!" << endl; } virtual ~WashBanana(); }; class WashGrape: public Fruit{ virtual void eatFruit(){ cout << "wash" << endl; cout << "Grape : Eat directly!" << endl; } virtual ~WashGrape(); }; class PeelApple: public Fruit{ virtual void eatFruit(){ cout << "Peel" << endl; cout << "Apple : Eat directly!" << endl; } virtual ~PeelApple(); }; class PeelBanana: public Fruit{ virtual void eatFruit(){ cout << "Peel" << endl; cout << "Banana : Eat directly!" << endl; } virtual ~PeelBanana(); }; class PeelGrape: public Fruit{ virtual void eatFruit(){ cout << "Peel" << endl; cout << "Grape : Eat directly!" << endl; } virtual ~PeelGrape(); }; class WashAndPeelApple{ //... } class WashAndPeelBanana{ //... } class WashAndPeelGrape{ //... }
-
存在问题:当有n个子类,m种修饰(在本例中为洗和剥,实际上它们是可以相互组合的)的情况下,我们对于同一个动作吃可以派生出$$1+n+n\frac{(m+1)m}{2}$$(在李建忠视频中给出的是$$1+n+n*m!/2$$)种类,这是非常恐怖。观察代码,我们可以发现:
- wash和peel实际上都是对eat的附加操作,借此我们可以进行优化
- 先wash,后peel
-
同时将几种修饰结合,违背了单一职责原则
-
-
修改后
-
main.cpp
#include <iostream> #include <string> using namespace std; class Fruit{ virtual void eatFruit()=0; virtual ~Fruit(); }; class Apple: public Fruit{ virtual void eatFruit(){ cout << "Apple : Eat directly!" << endl; } virtual ~Apple(); }; class Banana: public Fruit{ virtual void eatFruit(){ cout << "Banana : Peel to eat!" << endl; } virtual ~Banana(); }; class Grape: public Fruit{ virtual void eatFruit(){ cout << "Eat : Peel to eat!" << endl; } virtual ~Grape(); }; class DecorateGrape:public Fruit{ //修饰类 Fruit *fruit; DecorateGrape(Fruit *f):fruit(f){} virtual void eatFruit(){}=0; virtual ~DecorateGrape(); }; class WashFruit:public DecorateGrape{ //修饰类 WashFruit(Fruit *f):fruit(f){} virtual void eatFruit(){ cout << "Wash" << endl; fruit->eatFruit(); } virtual ~WashFruit(); }; class PeelBanana:public DecorateGrape{ //修饰类 PeelBanana(Fruit *f):fruit(f){} virtual void eatFruit(){ cout << "Peel" << endl; fruit->eatFruit(); } virtual ~PeelBanana(); }; int main(){ Fruut a = new Apple(); Fruit pa = new PeelFruit(a); Fruit wa = new WashFruit(pa); pa.eat(); //自下而上:wash => peel => eat,简直妙极了 return 0; }
-
评价
- 通过继承规范了接口
- 通过组合实现了接口
- 通过修饰与修饰间的先后关系可以方便地进行组合
- 通过修饰模式所构建的类个数为$$1+n+1+m$$
-
-
-
类图
-
定义:动态(通过组合方式)地给一个对象增加一些额外的职责,这些职责可以方便地组合在一起
-
注:
- 注意对于修饰的理解。在本例中,苹果,香蕉的吃是对于水果的吃的扩充,但洗和剥确实另一个方向的(或者说是修饰)
- 继承(重写接口)+组合
- Decorator类在接口上表现为继承关系(用于完善接口规范),在实现上表现为组合关系(用来支持实现)
桥模式(Bridge Method)
-
背景(还有一个更简单的背景理解,就是一杯咖啡,存在两个大小和添加的小料——糖等——两个维度,以大小作为主维度去思考)
- 在平台上购物
- 平台:水果,零食,衣服
- 会员与非会员
-
code
-
修改前
-
main.cpp
#include <iostream> #include <string> using namespace std; class Buyer{ public: virtual void order()=0; }; class ClothesBuyer: public Buyer{ public: virtual void order(){ cout << "order" << endl; cout << "buy clothes" << endl; } }; class FruitBuyer: public Buyer{ public: virtual void order(){ cout << "order" << endl; cout << "buy Fruit" << endl; } }; class FoodBuyer: public Buyer{ public: virtual void order(){ cout << "order" << endl; cout << "buy Food" << endl; } }; class ClothesBuyerLite: public Buyer{ //不同平台的会员与非会员的业务 public: virtual void order(){ cout << "lite account" << endl; cout << "buy clothes" << endl; } }; class ClothesBuyerPerfect: public Buyer{ //不同平台的会员与非会员的业务 public: virtual void order(){ cout << "perfect account" << endl; cout << "buy clothes" << endl; } }; class FruitBuyerLite: public Buyer{ //不同平台的会员与非会员的业务 public: virtual void order(){ cout << "lite account" << endl; cout << "buy Fruit" << endl; } }; class FruitBuyerPerfect: public Buyer{ //不同平台的会员与非会员的业务 public: virtual void order(){ cout << "perfect account" << endl; cout << "buy Fruit" << endl; } }; class FoodBuyerLite: public Buyer{ //不同平台的会员与非会员的业务 public: virtual void order(){ cout << "lite account" << endl; cout << "buy Food" << endl; } }; class FoodBuyerPerfect: public Buyer{ //不同平台的会员与非会员的业务 public: virtual void order(){ cout << "perfect account" << endl; cout << "buy Food" << endl; } }; int main() { FoodBuyerLite foodBuyerLite; foodBuyerLite.order(); return 0; }
-
存在问题:
- 在order中我们可以发现,存在两个不同的维度,即是否为会员,具体的平台。该做法违反了单一职责原则
- 将维度展开,n个平台,m个修饰(这里的修饰是不能进行组合的),需要的类个数为$$1+n+n*m$$
- 修改目标:减少组合
-
-
修改后
-
main.cpp
#include <iostream> #include <string> using namespace std; class BuyerImpl{ public: virtual void buy()=0; }; //平台实现(与Buyer无关,只包含了紧密相关的业务) class ClothesBuyer: public BuyerImpl{ public: virtual void buy(){ cout << "buy clothes" << endl; } }; class FruitBuyer: public BuyerImpl{ public: virtual void buy(){ cout << "buy fruit" << endl; } }; class FoodBuyer: public BuyerImpl{ public: virtual void buy(){ cout << "buy food" << endl; } }; class Buyer{ protected: BuyerImpl *buyer; //由于各修饰类中均包含了BuyerImpl,我们可以将其网上提 public: Buyer(BuyerImpl *buyer):buyer(buyer){} virtual void order()=0; }; class BuyerLite: public Buyer{ //无关平台的非会员的业务 public: BuyerLite(BuyerImpl *buyerImpl):Buyer(buyerImpl){} virtual void order(){ cout << "lite account" << endl; buyer->buy(); } }; class BuyerPerfect: public Buyer{ //无关平台的会员业务 public: BuyerPerfect(BuyerImpl *buyerImpl): Buyer(buyerImpl){} virtual void order(){ cout << "perfect account" << endl; buyer->buy(); } }; int main() { ClothesBuyer *clothesBuyer = new ClothesBuyer(); BuyerLite *buyerLite = new BuyerLite(clothesBuyer); buyerLite->order(); return 0; }
-
这里我们将会员与非会员作为主维度,然后将具体的平台以组合的方式嵌入
-
-
-
类图
注:
- Abstraction和Implementor分别代表了两个维度的变化,其中Abstraction代表了主维度
- 注意中间的连线是桥
-
定义:将业务部分(抽象部分)与平台实现(实现部分)分离,使它们可以独立地变化
-
注:
- 平台实现与业务抽象分开
- 业务实现通过继承实现
- 平台抽象通过组合实现
- 如果有多个维度的变化,都可以作为impl组合
(三)、对象创建
工厂模式(Factor Method)
-
背景:
- 人要抚摸小动物,小动物产生不同声音反馈
- 目前只摸猫和狗,后续可能添加其它动物
-
code
-
main.cpp
#include <iostream> #include "person.h" int main() { PetFactory *petFactory = new DogFactory(); Person p(petFactory); p.touchAnimal(); return 0; }
-
Person.h
// // Created by Arno_vc on 2022/3/15. // #ifndef DESIGNMODE_PERSON_H #define DESIGNMODE_PERSON_H #include "PetFactory.h" class Person { public: PetFactory *petFactory; //每个person有一个唯一的pet Person(PetFactory * petFactory){ this->petFactory = petFactory; } void touchAnimal(){ Pro *pet = petFactory->buildPro(); pet->howl(); } }; #endif //DESIGNMODE_PERSON_H
-
PetFactory.h
// // Created by Arno_vc on 2022/3/15. // #ifndef DESIGNMODE_PETFACTORY_H #define DESIGNMODE_PETFACTORY_H #include "Pro.h" class PetFactory { public: virtual Pro* buildPro()=0; }; class DogFactory:public PetFactory{ public: Pro* buildPro() override{ return new DogPro(); } }; class CatFactory:public PetFactory{ public: Pro* buildPro() override{ return new CatPro(); } }; #endif //DESIGNMODE_PETFACTORY_H
-
Pro.h
// // Created by Arno_vc on 2022/3/15. // #ifndef DESIGNMODE_PRO_H #define DESIGNMODE_PRO_H #include <string> #include <iostream> using namespace std; class Pro { protected: string name; public: virtual void howl()=0; }; class CatPro:public Pro{ public: void howl() override{ cout << "miao miao miao" << endl; } }; class DogPro:public Pro{ public: void howl() override{ cout << "wang wang wang" << endl; } }; #endif //DESIGNMODE_PRO_H
-
注:这里有一个前提的条件,那就是Pro的子类应该在Person中实例化,否则我们只需在Person中留下一个
Pro *p;
的指针即可,根本不需要工厂模式。更好的说法是如果对象的实例化分散在各种逻辑代码之间,那么就会非常零乱、难以维护,bug自然也多。
-
-
类图
注:可以看到Person只和抽象类部分有关
-
定义:特点:绕开new,通过一个方法返回对象,定义一个用于创建对象的接口(基础工厂),让子类(分类工厂)决定实例化哪个类
抽象工厂(Abstract Factor)
-
背景
- 我们有不同的宠物对象,如猫,狗等
- 对于每个宠物有三个相关的对象,即宠物商店(假设每种宠物对应不同的宠物商店),宠物,宠物窝
-
code
-
对于变化的三个部分,尝试用工厂模式创建
-
main.cpp
#include <iostream> #include <vector> using namespace std; //动物相关基类 class Pet{ }; class PetShop{ protected: vector<Pet *> petV; public: virtual void addPet(Pet *)=0; }; class PetHouse{ }; //工厂相关基类 class PetFactory{ public: virtual Pet* build()=0; }; class PetHouseFactory{ public: virtual PetHouse* build()=0; }; class PetShopFactory{ public: virtual PetShop* build()=0; }; class Dog: public Pet{ }; class DogFactory:public PetFactory{ public: Dog* build(){ return new Dog(); } }; class DogShop:public PetShop{ virtual void addPet(Pet* pet){ cout << "add a dog" << endl; petV.push_back(pet); } }; class DogShopFactory:public PetShopFactory{ DogShop* build(){ return new DogShop(); } }; class DogHouse:public PetHouse{ }; class DogHouseFactory:public PetHouseFactory{ DogHouse* build(){ return new DogHouse(); } }; int main() { PetShopFactory *petShopFactory = new DogShopFactory(); PetFactory *petFactory = new DogFactory(); PetHouseFactory *petHouseFactory = new DogHouseFactory(); //通常来说Factory为根据外部需求传入的参数,这里为演示方便创建 PetShop *petShop = petShopFactory->build(); Pet *pet = petFactory->build(); petShop->addPet(pet); PetHouse *petHouse = petHouseFactory->build(); return 0; }
-
存在问题:需要传入的三个参数:PetShop,Pet,PetHouse应该是有关联的,当前的模式下无法体现三者的关联性
-
-
修改后
-
main.cpp
#include <iostream> #include <vector> using namespace std; //动物相关基类 class Pet{ }; class PetShop{ protected: vector<Pet *> petV; public: virtual void addPet(Pet *)=0; }; class PetHouse{ }; class DogShop:public PetShop{ virtual void addPet(Pet* pet){ cout << "add a dog" << endl; petV.push_back(pet); } }; class Dog: public Pet{ }; class DogHouse:public PetHouse{ }; //工厂相关基类 class PetFactory{ public: virtual PetShop* petShopBuild()=0; virtual Pet* petBuild()=0; virtual PetHouse* petHouseBuild()=0; }; class DogFactory:public PetFactory{ public: virtual PetShop* petShopBuild(){ return new DogShop(); } virtual Pet* petBuild(){ return new Dog(); } virtual PetHouse* petHouseBuild(){ return new DogHouse(); } }; int main() { PetFactory *petFactory = new DogFactory(); //通常来说Factory为根据外部需求传入的参数,这里为演示方便创建 PetShop *petShop = petFactory->petShopBuild(); Pet *pet = petFactory->petBuild(); petShop->addPet(pet); PetHouse *petHouse = petFactory->petHouseBuild(); return 0; }
-
在一个抽象工厂中可以生产三个相关联的产品
-
-
-
类图
-
定义:将工厂抽象化,负责一系列相关对象的生产(即内部包含关联的子工厂)
-
注:
- 用于"一系列相互依赖的对象"的创建工作
- 工厂之间存在联系(实际上只是根据需求做了一些细微的变化罢了)
- 常见应用于对不同数据库的操作(因为一个数据库操作中包含了多个相关联的数据库对象)
原型模式(Prototype Method)
-
背景
- 我们现在有5个苹果
- 现在3个苹果要拿出来清洗, 削皮,切片,然后吃了
-
code
-
构建5个苹果,将其中3个的清洗标志位置1
-
main.cpp
#include <iostream> #include <vector> using namespace std; class Fruit{ public: virtual void eat()=0; }; class Apple{ public: int washed; int peel; int cut; Apple(){ washed=0; peel=0; cut=0; } void eat(){ cout << "eat apple" << endl; } }; int main() { Apple a[5]; a[1].washed=1; a[2].washed=1; a[3].washed=1; a[1].peel=1; a[2].peel=1; a[3].peel=1; a[1].cut=1; a[2].cut=1; a[3].cut=1; a[1].eat(); a[2].eat(); a[3].eat(); return 0; }
-
存在问题
- 在参数较多的情况小看起来很臃肿
- 看起来不是很聪明的样子
-
-
使用原型对象创建三个已经清洗了的苹果
-
main.cpp
#include <iostream> #include <vector> using namespace std; class Fruit{ public: virtual Fruit* clone()=0; virtual void eat()=0; }; class Apple: public Fruit{ public: int washed; int peel; int cut; Apple(){ washed=0; peel=0; cut=0; } virtual Fruit* clone(){ return new Apple(*this); //胜读拷贝,应该记住这个形式 } virtual void eat(){ cout << "eat apple" << endl; } }; int main() { Apple *prototype = new Apple(); //这个原型不是拿来用的,仅仅是拿来拷贝的 prototype->washed=1; prototype->peel=1; prototype->cut=1; Fruit *a1 = prototype->clone(); Fruit *a2 = prototype->clone(); Fruit *a3 = prototype->clone(); a1->eat(); a2->eat(); a3->eat(); return 0; }
-
注
- 原型对象是拿来创建对象的不是拿来使用的
- 原型对象可以修改
-
-
-
类图
-
定义:使用原型对象实例指定创建对象的种类,通过深度拷贝创建新的对象
-
注:
- 要求"原型"拥有稳定的接口
- 面对"某些结构复杂的对象"的创建工作
- 拷贝构造:在c++中主要通过深度拷贝实现,在Java中可以通过序列化来实现
- js的继承就是采用了近乎原型对象的模式
构建器模式(Builder Method)(不常用)
-
背景
- 我们要构造三个水果:苹果,梨,葡萄
- 它们都有外皮,果肉,核三个部分
- 它们的外皮,果肉,核三个部分不完全相同
-
code
-
尝试用构造函数实现一般化的构造
-
main.cpp
#include <iostream> #include <string> using namespace std; class Peel{ public: string name; }; class Flesh{ public: string name; }; class Core{ public: string name; }; class Fruit{ protected: virtual void buildPeel()=0; //子类根据需要覆盖:如苹果,构建苹果皮 virtual void buildFlesh()=0; virtual void buildCore()=0; public: Peel *peel; Flesh *flesh; Core *core; void Fruit(){ //构建失败,因为再构造函数中无法调用虚函数。原因,在初始化时,父类先于子类初始化,故父类无法调用子类覆盖的虚函数 buildPeel(); buildCore(); buildFlesh(); } }; class ApplePeel:public Peel{ }; class AppleFlesh:public Flesh{ }; class AppleCore:public Core{ }; class Apple:public Fruit{ protected: virtual void buildPeel(){ peel = new ApplePeel(); } virtual void buildFlesh(){ flesh = new AppleFlesh(); } virtual void buildCore(){ core = new AppleCore(); } }; int main() { Apple *apple = new Apple(); apple->build(); cout << apple->peel->name << endl; cout << apple->flesh->name << endl; cout << apple->core->name << endl; return 0; }
-
错误原因:
- 目标:在父类中实现一般化的构造过程
- 构建失败,因为再构造函数中无法调用虚函数。原因,在初始化时,父类先于子类初始化,故父类无法调用子类覆盖的虚函数
-
-
修改后
-
main.cpp
#include <iostream> #include <string> using namespace std; class Peel{ public: string name; }; class Flesh{ public: string name; }; class Core{ public: string name; }; class Fruit{ public: virtual void buildPeel()=0; //子类根据需要覆盖:如苹果,构建苹果皮 virtual void buildFlesh()=0; virtual void buildCore()=0; public: Peel *peel; Flesh *flesh; Core *core; }; class FruitBuilder{ Fruit *fruit; public: FruitBuilder(Fruit *fruit):fruit(fruit){} void build(){ //构建失败,因为再构造函数中无法调用虚函数。原因,在初始化时,父类先于子类初始化,故父类无法调用子类覆盖的虚函数 fruit->buildPeel(); fruit->buildCore(); fruit->buildFlesh(); } }; class ApplePeel:public Peel{ }; class AppleFlesh:public Flesh{ }; class AppleCore:public Core{ }; class Apple:public Fruit{ protected: virtual void buildPeel(){ peel = new ApplePeel(); } virtual void buildFlesh(){ flesh = new AppleFlesh(); } virtual void buildCore(){ core = new AppleCore(); } }; int main() { Apple *apple = new Apple(); FruitBuilder *fruitBuilder = new FruitBuilder(apple); cout << apple->peel->name << endl; cout << apple->flesh->name << endl; cout << apple->core->name << endl; return 0; }
-
注:
- 可以看到,对于每种水果构建的基础单元相同(Peel,Flesh,Core),故以此为动静分离点
- 通过普通的build函数代替构造函数实现了构造函数
- 使用组合的方法,将build部分作为单独的功能与Fruit中的内容分离
- 与Template模式的区别:前者用于代码执行,后者用于类的构建
-
-
-
类图
注:
- Builder:对应Fruit
- ConcreteBuilder:对应Apple
- Director:对应FruitBuilder
-
定义:将对象的构建和表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)
-
、注:
- 用于分步骤构建一个复杂的对象
- 通常有一部分稳定,有一部分变化,类似Template模式。但是,Template模式主要用于一系列相似行为(更具有普遍性),Builder模式主要用于构造
- 当类复杂的时候,可以将内容和构造分离;当类简单的时候,可以将内容和构造合并
- 需要注意语言的区别
(四)、"对象性能"模式
处理面向对象带来的成本问题
单例模式(Singleton)
-
背景:有一个西瓜的时候,大家共享;没有切开或者吃完了,就再打开一个西瓜
-
code
-
线程不安全版本(单线程版本)
-
main.cpp
#include <iostream> #include <string> using namespace std; class Watermelon{ private: Watermelon(){}; Watermelon(const Watermelon &watermelon){}; public: static Watermelon *instance; static Watermelon *getInstance(); //静态成员函数只能调用静态成员变量 }; //单线程版本 Watermelon *Watermelon::getInstance() { if(instance==NULL){ instance = new Watermelon(); } return instance; } Watermelon *Watermelon::instance = NULL; //静态成员变量需要在类外显示地初始化 int main() { Watermelon *watermelon = Watermelon::getInstance(); //::用于限定范围 return 0; }
-
注:注意类中静态成员变量的使用
-
-
线程安全版本(高代价)
-
main.cpp
#include <iostream> #include <mutex> using namespace std; class Watermelon{ private: Watermelon(){}; Watermelon(const Watermelon &watermelon){}; public: static Watermelon *instance; static Watermelon *getInstance(); //静态成员函数只能调用静态成员变量 }; //加锁版本 Watermelon *Watermelon::getInstance() { Lock lock; if(instance==NULL){ instance = new Watermelon(); } return instance; } Watermelon *Watermelon::instance = NULL; //静态成员变量需要在类外显示地初始化 int main() { Watermelon *watermelon = Watermelon::getInstance(); //::用于限定范围 return 0; }
-
注:
- lock是一个一般化的锁,而不应该仅仅把它当做c++中的锁。该锁会在函数运行完成后释放
- 无论是否需要创建新对象,代价较高。更好地方案是当确定需要创建新对象时上锁
-
-
线程安全版本(双检查锁,内存读写reorder不安全)
-
main.cpp
#include <iostream> #include <mutex> using namespace std; class Watermelon{ private: Watermelon(){}; Watermelon(const Watermelon &watermelon){}; public: static Watermelon *instance; static Watermelon *getInstance(); //静态成员函数只能调用静态成员变量 }; //加锁版本 Watermelon *Watermelon::getInstance() { if(instance==NULL){ Lock lock; if(instance==NULL){ instance = new Watermelon(); } } return instance; } Watermelon *Watermelon::instance = NULL; //静态成员变量需要在类外显示地初始化 int main() { Watermelon *watermelon = Watermelon::getInstance(); //::用于限定范围 return 0; }
-
注:
- 为什么内部还要做一次判断:若不做判断,A线程刚拿到锁,B进来判断instance没有初始化,进来等待锁,那么instance对象就会被创建两次
- 内存reorder读写不安全问题
- 编译器在编译时会对源代码进行优化
- 在本例中,new对象应该做的三个步骤:
- 申请内存空间
- 对空间初始化
- 返回头指针
- 经过reorder优化后的步骤
- 申请内存空间
- 返回头指针
- 对空间初始化
- 导致的问题:在空间没有初始化的情况下,先返回了头指针,导致该指针被错误地使用
-
-
编译器本身优化
-
Java,C#:volatile关键字,告诉编译器不需要优化
-
c++ 11版本之后:通过atomic库
std::atomic<Siingleton> Singleton::m_instance; std::mutex Singleton::m_mutex; Singleton* Singleton::getInstance(){ Singleton* tmp = m_instance.load(std::memory_order+relaxed); //load std::atomic_thread_fence(std::memory_order_acquire); //获取内存fence if(tmp == nullptr){ std::lock_guard<std::mutex> lock(m_mutex); tmp = m_instance.load(std::memory_order_relaxed); if(tmp == nullptr){ tmp = new Singleton; std::atomic_thread_fence(std::memory_order_release); //释放内存fence m_instance.store(tmp,std::memory_order_relaxed); //store } } return tmp; }
-
-
-
类图:
-
定义:保证一个类仅有一个实例,并提供一个该实例的全局访问点
-
注:
- 一个类只有一个实例。是类的设计者的责任,而不是使用者的责任。
- 绕过构造器,实现性能优化。(在之前也有绕过构造器,但是是为了解决紧耦合问题)
- 需要考虑线程安全
- 屏蔽构造构造函数和拷贝构造函数(将其私有化)
享元模式(Flyweight)
-
背景
- 有许多水果
- 我们要记录水果的类别
- 显然水果的类别与水果的关系是一对多的
-
code
使用享元实现
-
main.cpp
#include <iostream> #include <map> using namespace std; class Fruit{ public: string key; //以水果类别作为标识 Fruit(string key):key(key){} }; class FruitFactory{ map<string,Fruit*> mp; public: Fruit *getFruit(const string key){ map<string,Fruit*>::iterator item = mp.find(key); //注意这里用迭代器做判断更具有一般性 if(item!=mp.end()){ return item->second; }else{ Fruit *value = new Fruit(key); mp[key] = value; return value; } } }; int main() { FruitFactory fruitFactory; Fruit *apple = fruitFactory.getFruit("apple"); cout << apple->key << endl; apple = fruitFactory.getFruit("apple"); cout << apple->key << endl; return 0; }
-
-
类图
-
定义:运动共享技术有效地支持大量细粒度的对象
-
注:
- 集合了工厂模式与单例模式:判断工厂中是否有key(而不是单例模式中判断引用),有则给,没有则创建
- 一般用于只读
- 常用于上万的数据量
(五)、接口隔离模式
接口直接依赖存在问题,采用一层间接(稳定)接口
门面模式(Facade Method)
- 背景:
- 到商店去吃水果捞
- 我不关心水果l捞是如何制作的
- 因为到我面前的始终是一盆水果捞(接口)
- Code:无特定的代码结构,重点是作为一种设计思想去理解
- 类图:无
- 定义:为子系统中的一组接口提供一个一致(稳定)的高层接口,使得子系统更加容易使用
- 注:松耦合,高内聚
代理模式(Proxy Method)
-
背景
- 吃水果捞:自己买水果,商店帮忙做水果捞
- 我们可以做一些附加的额外操作:做成水果捞前自己挑水果,做成水果捞后可以自己加点奶昔
-
code
-
修改前
-
main.cpp
#include <iostream> #include <map> using namespace std; class doFruityMixInf{ virtual void doFruityMix()=0; }; class FruityMix{ //这一部分内容对用户来说是屏蔽的 public: void doFruityMix(){ cout << "do fruitymix" << endl; } }; int main() { cout << "buy fruit" << endl; FruityMix *fruityMix = new FruityMix(); fruityMix->doFruityMix(); cout << "add mike shake" << endl; return 0; }
-
注:
- 我们无法直接在业务代码添加附加的操作
- 然而,实际上买水果和添加奶昔都是做水果捞时间的一部分,三者应该放在同一个代码块中以满足高内聚,低耦合的要求
-
-
修改后(静态代理)
-
main.cpp
#include <iostream> #include <map> using namespace std; class FruityMixInf{ public: virtual void doFruityMix()=0; }; class FruityMix:public FruityMixInf{ //这一部分内容对用户来说是屏蔽的 public: virtual void doFruityMix(){ cout << "do fruitymix" << endl; } }; class FruityMixProxy:public FruityMixInf{ //与readObject继承同一个接口 protected: FruityMixInf *fruityMixInf; public: FruityMixProxy(FruityMixInf *fruityMixInf):fruityMixInf(fruityMixInf){}; virtual void doFruityMix(){ cout << "buy fruit" << endl; fruityMixInf->doFruityMix(); cout << "add mike shake" << endl; } }; int main() { FruityMixInf *fruityMixInf = new FruityMix(); FruityMixProxy *fruityMixProxy = new FruityMixProxy(fruityMixInf); fruityMixProxy->doFruityMix(); return 0; }
-
注:
- 原对象和代理对象都继承了相同接口
- 代理对象调用原对象,同时完成了一些附加操作
-
-
动态代理
-
显然,当有多个类的情况下,静态代理就会显得很繁琐,故可以使用动态代理只写一次代理操作(这里使用Java的反射机制举例)
-
Code
-
尝试使用反射对一个函数进行包装
package org.example; import org.omg.CORBA.portable.InvokeHandler; import redis.clients.jedis.Jedis; import java.lang.reflect.*; /** * Hello world! * */ interface DemoInf{ public void doWork(); } class Demo implements DemoInf{ public void doWork(){ System.out.println("Do something~"); } } public class App { public static void main( String[] args ) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Demo demo = new Demo(); //这里是唯一创建的实例,而不是接口 Class<?> proxyClass = Proxy.getProxyClass(DemoInf.class.getClassLoader(), DemoInf.class); Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); DemoInf DemoInf = (DemoInf)constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before methods"); method.invoke(demo, args); //通过Method类的invoke方法调用,实际的Demo对象 System.out.println("after methods"); return null; } }); DemoInf.doWork(); } }
注:
- 这里调用了两个内容:
Proxy
和InvocationHandler
Proxy
:通过getProxyClass
接口,用DemoInf
的Class
生成包含DemoInf
的Proxy
对象的Classs
(用Class生成Class
,需要了解的是在JVM中,Class
是每各类唯一的状态描述对象),进而调用构造函数生成DemoInf
的代理对象DemoProxy
InvocationHandler
:后续,我们将通过DemoProxy
调用函数。而实际上,DemoProxy
调用的是内部InvocationHandler
对象的接口下的invoke
函数。通过对invoke
函数覆写,我们可以实现AOP。
- 这里调用了两个内容:
-
将代理函数一般化
package org.example; import org.omg.CORBA.portable.InvokeHandler; import redis.clients.jedis.Jedis; import java.lang.reflect.*; /** * Hello world! * */ interface DemoInf{ public void doWork(String s); } class Demo implements DemoInf{ public void doWork(String s){ System.out.println("Do something:" + s); } } public class App { public static Object getProxy(Object demo) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class<?> proxyClass = Proxy.getProxyClass(demo.getClass().getClassLoader(), demo.getClass().getInterfaces()); //注意第二个参数获得了getClass的Interfaces Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class); Object res = constructor.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("AOP:before method"); method.invoke(demo,args); //因为我们这里添加了参数 System.out.println("AOP:after method"); return null; } }); return res; } public static void main( String[] args ) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Demo demo = new Demo(); DemoInf proxy = (DemoInf) getProxy(demo); proxy.doWork("sleep"); //proxy.doWork(args) => invoke(demo,args) } }
注:
- 这里注意一下可以通过
class
获得所有interface
invoke
中添加args
可以调用参数- 本质:和目标对象实现相同接口的实例
- 这里注意一下可以通过
-
-
-
-
类图
-
定义:为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问
-
注:
- 为对象做一些附加的,额外的操作,如日志,安全控制等
- 在实际使用时具有较高的复杂性
- 不一定要求保持接口完整的一致性,在具体实现上主要表现为加一层
- 尤其是在分布式上采用较多
- 可以分为静态代理与动态代理
适配器模式(Adapter Method)
-
背景
- 做水果捞
- 原先,我们给自己做水果捞,过程可以分为洗水果,削皮,切片等步骤(老接口)
- 现在,我们给顾客做水果捞,过程呈现给顾客的只有到手的水果(老接口)
-
code
-
main.cpp
#include <iostream> #include <map> using namespace std; class OldFruityMixInf{ public: virtual void washFruit()=0; virtual void peelFruit()=0; virtual void sliceFruit()=0; }; class FruityMix:public OldFruityMixInf{ //这一部分内容对用户来说是屏蔽的 public: virtual void washFruit(){ cout << "wash fruit" << endl; } virtual void peelFruit(){ cout << "peel fruit" << endl; } virtual void sliceFruit(){ cout << "slice fruit" << endl; } }; class NewFruityMixInf{ public: virtual void doFruit()=0; }; class Adapter:public NewFruityMixInf{ OldFruityMixInf *oldFruityMixInf; public: Adapter(OldFruityMixInf *oldFruityMixInf):oldFruityMixInf(oldFruityMixInf){} virtual void doFruit(){ oldFruityMixInf->washFruit(); oldFruityMixInf->peelFruit(); oldFruityMixInf->sliceFruit(); } }; int main() { FruityMix *fruityMix = new FruityMix(); NewFruityMixInf *newFruityMixInf = new Adapter(fruityMix); newFruityMixInf->doFruit(); return 0; }
-
注:对旧的接口进行封装,做成新的接口
-
-
类图
-
定义:将一个类的接口转换成客户希望的另一个接口
-
注:
- 适配器本身的作用也是移植不同接口的匹配需求
- 继承新接口,组合旧接口
中介者模式(Mediator Method)(不常用)
-
背景
-
依赖关系
-
使用适配器
-
-
Code:无
-
类图
-
定义:用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各对象不需要显式的相互引用(编译时依赖=>运行时依赖),从而使其耦合松散(管理变化),而且可以相互独立地改变它们之间的交互
-
注:
- 与Facade模式的区别:Facade注重区分系统内和系统外的元素,Mediator模式注重区分系统内的元素
- 作用是将一组元素之间复杂的依赖关系通过一个专门的中介去梳理
- 需要自己定义Mediator中具体的调用协议
(六)、“状态变化“模式
组件构建过程中某些对象的状态经常面临变化,又维持高层模块的稳定
状态模式(State Method)
-
背景
- 做水果捞
- 水果捞的几种状态:原生状态,洗好,剥皮,切开,加奶昔
-
code
-
修改前
-
main.cpp
#include <iostream> #include <map> using namespace std; enum State{ original,wash,peel,slice,over }; class FruityMixInf{ public: virtual void operation()=0; }; class FruityMix:public FruityMixInf{ //这一部分内容对用户来说是屏蔽的 State state; public: FruityMix(){ state = original; } virtual void operation(){ if(state == original){ cout << "wash" << endl; state = wash; }else if(state == wash){ cout << "peel" << endl; state = peel; }else if(state == peel){ cout << "slice" << endl; state = slice; }else if(state == slice){ //注意add shake对应over状态 cout << "add Shake" << endl; state = over; } } }; int main() { FruityMix fruityMix; fruityMix.operation(); fruityMix.operation(); fruityMix.operation(); fruityMix.operation(); return 0; }
-
注:当我们想要添加一个wait状态时不符合开闭原则
-
-
修改后
-
main.cpp
#include <iostream> #include <map> using namespace std; class State{ public: virtual State* operation()=0; }; class Over:public State{ public: virtual State* operation(){ cout << "over" << endl; return NULL; } }; class Slice: public State{ public: virtual State* operation(){ cout << "add shake" << endl; return new Over(); } }; class Peel: public State{ public: virtual State* operation(){ cout << "slice" << endl; return new Slice(); } }; class Wash: public State{ public: virtual State* operation(){ cout << "peel" << endl; return new Peel(); } }; class Original: public State{ public: virtual State* operation(){ cout << "wash" << endl; return new Wash(); } }; class FruityMixInf{ public: virtual void operation()=0; }; class FruityMix:public FruityMixInf{ //这一部分内容对用户来说是屏蔽的 State *state; public: FruityMix(State *state):state(state){} virtual void operation(){ state = state->operation(); } }; int main() { State *original = new Original(); FruityMixInf *fruityMix = new FruityMix(original); fruityMix->operation(); fruityMix->operation(); fruityMix->operation(); fruityMix->operation(); fruityMix->operation(); return 0; }
-
注:增加一个状态wait,只需要添加对应的状态类即可
-
-
-
类图
-
定义:允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为。
-
注:
- 与策略模式相似,通常也是在有大量if-else语句的情况下使用,不同点在于通过一个对象表示状态转移
- 虚函数:运行时的if-else语句
- 满足开闭原则
- 可以结合Singleton模式使用
Memento Method(备忘录设计模式)(不常用)
-
背景:
- 使用A对象
- 使用B对象记录A对象的状态
- A对象出问题,用B对象回复A对象
- 从行为上看,类似于windows的备份
-
code
class Memento{ string state; //.. public: Memento(const string &s):state(s){} string getState() const{return state;} void setState(const string & s){state=s;} } class Originator{ string state; public: Originator(){} Memento createMemento(){ Memento m(state); return m; } void setMemento(const Memento & m){ state = m.getState(); } } int main(){ Originator originator; Memento mem = originator.createMemento(); //生成备忘录 //... 改变originator状态 originator.setMemento(mem); }
-
类图
-
定义:在不破坏封装性的前提下,捕捉一个对象的内部状态并在该对象之外保存这个状态,这样以后可以将该对象恢复到原先保存的转态
-
注:
- 要求程序能回溯到对象之前处于某个点的状态(如果使用公有接口来让其它对象得到对象的状态,可能会暴露对象的细节实现)
- 具体实现,备忘录的实现可以用字符流,内存流,序列化(Java)的方式存储
- 核心
- 信息隐藏
- 保持封装性
- 不用深拷贝:当对象内部存在指针是不太适合用深拷贝
- 从某些地方看有点过时,主要了解其思想
(七)、”数据结构“模式
将特定数据结构封装在内部,在外部提供统一的接口
组合模式(Composite Method)
-
背景
- 在一棵树中有composite(非叶节点)和leaf两个部分
- 显然非叶节点可以向下遍历,叶节点不能向下遍历
-
code
-
main.cpp
#include <iostream> #include <string> #include <vector> using namespace std; class Component{ public: virtual void process()=0; //virtual ~Component()=0; }; class Composite:public Component{ string name; vector<Component *> nodes; public: Composite(string name):name(name){} void addVector(Component *node){ nodes.push_back(node); } virtual void process(){ cout << name << endl; for(auto it:nodes){ it->process(); } } }; class Leaf:public Component{ string name; public: Leaf(string name):name(name){} virtual void process(){ cout << name << endl; } }; int main() { Composite *c1 = new Composite("composite1"); Composite *c2 = new Composite("composite2"); Composite *c3 = new Composite("composite3"); Composite *c4 = new Composite("composite4"); Component *l1 = new Leaf("leaf1"); Component *l2 = new Leaf("leaf2"); Component *l3 = new Leaf("leaf3"); Component *l4 = new Leaf("leaf4"); c1->addVector(c2); c1->addVector(c3); c1->addVector(c4); c1->addVector(l1); c1->addVector(l2); c1->addVector(l3); c1->addVector(l4); c1->process(); return 0; }
-
注:
- 可以看到composite和leaf公用一个process接口操作
-
-
类图
-
定义:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(对待单个对象和组合对象一样的处理方式)
-
注:
- 通过多态访问树结构
- 核心:将“客户代码与复杂的对象容器结构”解耦
- 应用:文件系统
迭代器模式(Iterator Method)(不常用)
-
背景:
- 有多个不同的集合,Map,List等
- 我们希望用同一个接口遍历
-
code
//这里只是 简单介绍一下面向对象的iterator接口 class iterator{ public: virtual void first()=0; virtual void next()=0; virtual bool isDone() const=0; virtual T& current()=0; }
-
类图
-
定义:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露(稳定)该对象的内部表示
-
注:
- 和STL模板里的迭代器
- 设计目的相同
- 设计理念不同,gof提出的迭代器以面向对象为基础
- 在某些地方显得过时
- 和STL模板里的迭代器
-
c++的泛型编程通过模板的迭代器取代了面向对象的迭代器,因为面向对象使用虚函数是有额外的时间成本的(运行时多态),而模板则不会(编译时多态且基于隐式约束)
-
java等语言依然是基于面向对象的迭代器
职责链模式(Chain of Resposibility)(不常用)
-
背景:
- ui界面多组件的重叠下,点击事件的响应
-
code:无
-
类图
-
定义:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止
-
注:
- 构成了一条链表
- 应用:一个请求可能有多个接收者,但最后真正的接收者只有一个
- 看上去有点过时
- 应该有缺省机制
(八)、“行为变化”模式
将组件的行为和组件本身进行解耦,从而支持组件行为的变化
命令模式(Command Method)(不常用)
-
背景:
- 在文档中我们做了剪切,粘贴,复制等操作
- 我们需要将这些操作保存起来,以方便redo和undo
-
code
class Command{ public: virtual void execute()=0; //通过虚函数进行实现 }
-
类图
-
定义:将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作
-
注:
- 用对象来表征行为(一段代码)
- 好处
- 可以作为参数传递
- 可以存储在数据结构中
- 可以做序列化
- 可以通过使用Composite模式,将多个“命令”封装为一个“符合命令”MacroCommand
- 与c++函数对象
- 都是将函数对象化
- Command基于虚函数,接口规范严格,有性能损失;函数对象更灵活,性能更高
- 与Iterator Method模式相同,在Java等语言得到应用
“访问器”模式(Visitor)(慎用)
-
背景:
- 基类ElementInf,子类\(Elment_1,Elment_2,Elment_3\)
- 新需求,在源代码基础上给Element添加虚函数fun2,是\(Element_1,Element_2,Element_3\)实现
-
code
-
修改前:直接在源代码基础上添加则违反了开闭原则
-
修改后
-
main.cpp
//由于本地ide激活码没了,暂时跑不了 #include <iostream> using namespace std; class Visit{ virtual visitElement1(ElementInf *e)=0; virtual visitElement2(ElementInf *e)=0; } class Visit1{ //第一个附加函数 virtual visitElement1(ElementInf *e){ cout << "This Element1, fun1" << endl; } virtual visitElement2(ElementInf *e){ cout << "This Element2, fun1" << endl; } } class ElementInf{ virtual void fun1()=0; virtual void accept(Visit *v)=0; } class Element1{ virtual void fun1{ cout << "Element1 fun1"; } virtual void accept(Visit *v){ v->visitElement1(this); } } class Element2{ virtual void fun1{ cout << "Element1 fun1"; } virtual void accept(Visit *v){ v->visitElement2(this); } } int main(){ Visit *visit1 = new Visit1(); ElementInf *e1 = new Element1(); e1.accept(v1); return 0; }
-
注
- 每个visit的子类相当于一个扩充的虚函数
- visit中每个函数对应一个具体的类
-
-
-
类图
-
定义:表示一个作用于某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展)作用于这些元素的新操作(变化)
-
注:
-
Element类层次结构稳定,操作却经常面临频繁改动
-
当基类继承的每个子类都需要添加某个动作时适合使用该模式;当仅仅个别子类需要增加操作,直接在子类上添加动作即可
-
用得很少,但一旦用整个结构就会变得很笨重
-
原来是在每个子类中添加一个同名函数 => 新建一个类作为函数的执行类,传入应该执行的类执行对应的方法(Elment1 => fun1)
重点:原函数中有留下一个函数接口accept来接受访问类
-
(九)、“领域规则”模式
结合特定领域,将问题抽象为规则
解析器(Interpreter)(不常用)
-
背景:a+b-c+d
-
code(麻烦但不复杂,写一下思路)
- 定义节点:父节点 => 变量节点,符号节点 => 加法,减法节点
- 解析表达式生成符号树
- 自上而下,递归解析符号树,各节点调用自身的虚函数
-
类图
-
定义:给定一个语言,定义它的文法的一种表示,并定义一种解释器,这个解释器使用该表示来解释语言中的句子
-
注:
- 通常结合树与虚函数进行表达
- 选择应用场景是难点:满足“业务规则频繁变化,且类似的结构不断重复出现,并且容易抽象为语法规则的问题”,如简单的语法树
三、简述
-
模板方法:一段代码中有稳定的部分,也有变化的部分。将稳定的部分作为一个类中的run函数,将变化的部分写作几个函数,供run函数调用
-
策略模式:针对if-else语句块的一般性优化,可以搭配工厂模式使用
-
观察者模式:解耦事件与被通知对象,将被通知对象保存在类的vector等容器中
-
装饰者模式:使用继承,能够以一种任意的方式(附加操作)向一个对象添加特征
-
桥模式:使用接口+组合(在c++中表现为多继承),以不同维度(非附加操作)指定一个对象
补充装饰者模式与桥模式的区别:
- 装饰者模式通常是用于添加额外的功能(咖啡有不同的品种,还有加不加奶,加不加糖等选项)
- 桥模式通常与搭配选择特定的选项(咖啡的不同品种——主模式,也有不同大小杯——额外的模式)
-
简单工厂模式(查询资料后附加):在创建对象是我们可能会有一些附加操作,比如查询数据库。如果直接写在构造函数中会变得很臃肿,所以我们可以使用工厂中的一个函数创建。一个工厂可能生产不同的产品,我们可以在方法中传入参数
string type
区别,if-else
语句进行区分创建(违反了开闭原则,故提出了下面的工厂方法模式) -
工厂方法模式:基于某个初始状态的类创建对象,主要目的在于解耦
-
抽象工厂模式:将存在关联性的几个工厂合并为一个工厂
-
原型模式:基于某个中间状态的对象创建对象
-
构建器模式:与Template模式相似,但前者用于代码执行,后者用于类的构建
-
单例模式:一个系统中最多只有一个该类
-
享元模式:使用类似key-value的模式,避免创建相同key的类(只读)
-
门面模式:注重区分系统内和系统外的元素,抽象没有固定代码
-
代理模式:额外加一层,附加操作,如日志,网络连接等
-
适配器模式:额外加一层,用老接口适配新接口
-
中介者模式:额外加一层,注重区分系统内部元素间的关系
-
状态模式:针对if-else语句块的分状态讨论的优化,通常这些状态时递进的关系,解决方法和策略模式相同,都是抽象为类
-
备忘录模式:类似系统备份,目前实现方法可以用字符流,内存流,序列化等形式
-
组合模式:使用多态遍历树结构
-
迭代器:常见于STL模板
-
职责链:就是把事件可能响应的对象串成一条链
-
命令模式:函数对象
-
访问器模式:代码维护阶段,父类需要添加虚函数,子类需要实现,违反开闭原则,通过该方式可以解决(臃肿,慎用)
-
解析器模式:用于解析一些简单的语法规则
四、补充
- 虚函数:延迟到运行时
- 以上的模式分类是按照视频作者:还需将其与三种经典的分类方法比较
- 子类将有相同的字段,可以往上提到父类中
- 思考设计模式从两个角度
- 设计模式的背景
- 这个与相似设计模式的区别
- 读线程不需要加锁,有写线程就需要加锁