C++ 设计模式 4:行为型模式
0 行为型模式
类或对象怎样交互以及怎样分配职责,这些设计模式特别关注对象之间的通信。
1 模板模式
模板模式(Template Pattern)定义:一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
1.1 模板模式中的角色和职责
AbstractClass(抽象类):在抽象类中定义了一系列基本操作(PrimitiveOperations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
ConcreteClass(具体子类):它 是抽象类的子类,用于 实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现具体基本操作。
1.2 案例
示例代码:
#include <iostream>
using namespace std;
// AbstractClass(抽象类),制作饮料
class MakeDrink
{
public:
//1 煮开水
void boil()
{
cout << "煮开水" << endl;
}
//2 冲泡
virtual void brew() = 0;
//3 倒入杯中
void putInCup()
{
cout << "倒入杯中" << endl;
}
//4 加料
virtual void addThings() = 0;
// hook 方法,决定某些算法步骤是否挂钩在本算法中
virtual bool CustomWantAddThings()
{
return true;
}
// 模板方法(Template Method),制作饮料
void make()
{
boil();
brew();
putInCup();
if (CustomWantAddThings() == true)
{
addThings();
}
}
};
// ConcreteClass(具体子类),制作咖啡
class MakeCoffee :public MakeDrink
{
public:
MakeCoffee(bool isAdd)
{
this->isAdd = isAdd;
}
//2 冲泡
virtual void brew()
{
cout << "冲泡 咖啡豆" << endl;
}
//4 加料
virtual void addThings()
{
cout << "添加 糖 和 牛奶" << endl;
}
virtual bool CustomWantAddThings()
{
return isAdd;
}
private:
bool isAdd;
};
// ConcreteClass(具体子类),冲泡茶叶
class MakeTea :public MakeDrink
{
public:
MakeTea(bool isAdd)
{
this->isAdd = isAdd;
}
//2 冲泡
virtual void brew()
{
cout << "冲泡 茶叶" << endl;
}
//4 加一些酌料
virtual void addThings()
{
cout << "添加 柠檬 或者 菊花" << endl;
}
virtual bool CustomWantAddThings()
{
return isAdd;
}
private:
bool isAdd;
};
int main(void)
{
MakeDrink *makeCoffee = new MakeCoffee(true);
makeCoffee->make();
delete makeCoffee;
cout << " ------ " << endl;
MakeDrink *makeTea = new MakeTea(false);
makeTea->make();
delete makeTea;
return 0;
}
运行结果:
1.3 优缺点
**优点: **
(1)在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在
子类实现详细的处理算法时并不会改变算法中步骤的执行次序。
(2)模板方法模式是一种代码复用技术,它在类库设计中尤为重要,它提取
了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行
为,它鼓励我们恰当使用继承来实现代码复用。
(3)可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特
定步骤是否需要执行。
(4)在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可
以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则
和开闭原则。
缺点:
需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象。
1.4 适用场景
(1)具有统一的操作步骤或操作过程。
(2)具有不同的操作细节。
(3)存在多个具有同样操作步骤的应用场景,但某些具体的操作细节却各
不相同。
2 命令模式
命令模式(Command Pattern)定义:将一个请求封装为一个对象,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
命令模式,别名为动作(Action)模式或事务(Transaction)模式。
意义:命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
本质:是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。
2.1 命令模式中的角色和职责
Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的 execute()
等方法,通过这些方法可以调用请求接收者的相关操作。
ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现 execute()
方法时,将调用接收者对象的相关操作(Action)。
Invoker(调用者,即请求发送者):它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的 execute()
方法,从而实现间接调用请求接收者的相关操作。
Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。
2.2 案例
示例:
联想路边撸串烧烤场景,有烤羊肉,烧鸡翅命令,有烤串师傅,和服务员。根据命令模式,设计烤串场景。
示例代码:
#include <iostream>
#include <list>
using namespace std;
// Receiver(接收者),烤串师傅
class Cooker
{
public:
// 烤串
void makeChuaner()
{
cout << "烤串师傅进行了烤串" << endl;
}
// 烤鸡翅
void makeChicken()
{
cout << "烤串师傅进行了烤鸡翅" << endl;
}
};
//Command(抽象命令类),烤串的抽象的菜单
class Command
{
public:
Command(Cooker *cooker)
{
this->cooker = cooker;
}
~Command()
{
if (this->cooker != NULL)
{
delete this->cooker;
this->cooker = NULL;
}
}
// 调用接收者对象的相关操作
virtual void execute() = 0;
protected:
Cooker *cooker;
};
// ConcreteCommand(具体命令类),烤串的菜单
class CommandChuaner :public Command
{
public:
CommandChuaner(Cooker *cooker) : Command(cooker)
{
}
virtual void execute()
{
// 命令 最终让接收者干的工作
this->cooker->makeChuaner();
}
};
// ConcreteCommand(具体命令类),烤鸡翅的菜单
class CommandChicken :public Command
{
public:
CommandChicken(Cooker * cooker) : Command(cooker)
{
}
virtual void execute()
{
// 命令 最终让接收者干的工作
this->cooker->makeChicken();
}
};
// Invoker(调用者,即请求发送者),服务员
class Waitress
{
public:
// 给服务员 添加菜单的方法
void setCmd(Command *cmd)
{
this->cmd_list.push_back(cmd);
}
// 让服务员 下单
void notify()
{
list<Command *>::iterator it = cmd_list.begin();
for (; it != cmd_list.end(); it++)
{
(*it)->execute();
}
}
private:
list<Command *> cmd_list;
};
int main(void)
{
Waitress *mm = new Waitress;
Command *chuanger = new CommandChuaner(new Cooker);
Command *chicken = new CommandChicken(new Cooker);
// 把订单都给服务员
mm->setCmd(chuanger);
mm->setCmd(chicken);
// 让服务员下单,最终让师傅干活
mm->notify();
delete mm;
return 0;
}
运行结果:
2.3 优缺点
优点:
(1)降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
(2)新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
(3)可以比较容易地设计一个命令队列或宏命令(组合命令)。
缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。
2.4 适用场景
(1)系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
(2)系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。
(3)系统需要将一组操作组合在一起形成宏命令。
3 策略模式
策略模式定义:创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
3.1 策略模式中的角色和职责
Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。
3.2 案例
示例:商场促销有策略A(0.8折) 策略B(消费满200,返现100),用策略模式模拟场景。
示例代码:
#include <iostream>
using namespace std;
// Strategy(抽象策略类),销售策略
class AbstractStrategy
{
public:
//商品具体的销售策略计算方式
virtual double getPrice(double price) = 0;
};
// ConcreteStrategy(具体策略类),策略A :商品打八折
class StrategyA :public AbstractStrategy
{
public:
virtual double getPrice(double price)
{
return price*0.8;
}
};
//ConcreteStrategy(具体策略类),策略B:如果商品超过200,减100
class StrategyB :public AbstractStrategy
{
public:
virtual double getPrice(double price)
{
if (price > 200)
{
price = price - 100;
}
return price;
}
};
// Context(环境类),商品
class Item
{
public:
Item(string name, double price)
{
this->name = name;
this->price = price;
}
//提供一个可以更换策略的方法
void setStrategy(AbstractStrategy *strategy)
{
this->strategy = strategy;
}
//最终获得商品的价格的方法
double SellPrice()
{
return this->strategy->getPrice(this->price);
}
private:
string name;
double price;
//销售的策略
AbstractStrategy *strategy;
};
int main(void)
{
Item it("nike鞋", 201);
AbstractStrategy *sA = new StrategyA;
AbstractStrategy *sB = new StrategyB;
cout << "上午 商场执行 销售策略A,全场 八折" << endl;
it.setStrategy(sA);
cout << "nike鞋 应该卖" << it.SellPrice() << endl;
cout << "-----" << endl;
cout << "下午 商场执行 销售策略B,全场 超 200 减 100" << endl;
it.setStrategy(sB);
cout << "nike鞋 应该卖" << it.SellPrice() << endl;
return 0;
}
运行结果:
3.3 优缺点
优点:
(1)策略模式提供了对“开闭原则”的完美支持,**用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。 **
(2)使用策略模式可以避免多重条件选择语句。多重条件选择语句不易维护,它把采取哪一种算法或行为的逻辑与算法或行为本身的实现逻辑混合在一起,将它们全部硬编码(Hard Coding)在一个庞大的多重条件选择语句中,比直接继承环境类的办法还要原始和落后。
(3)策略模式提供了一种算法的复用机制。由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。
缺点:
(1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
(2)策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
3.4 适用场景
准备一组算法,并将每一个算法封装起来,使得它们可以互换。
4 观察者模式
观察者模式是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。
在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
4.1 命令模式中的角色和职责
Subject(抽象被观察者):被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象。Subject需要维持(添加,删除,通知)一个观察者对象的队列列表。
ConcreteSubject(具体被观察者):被观察者的具体实现。包含一些基本的属性状态及其他操作。
Observer(抽象观察者):接口或抽象类。当 Subject 的状态发生变化时,Observer 对象将通过一个 callback 函数得到通知。
ConcreteObserver(具体观察者):观察者的具体实现。得到通知后将完成一些具体的业务逻辑处理。
4.2 案例
示例代码:
#include <iostream>
#include <string>
#include <list>
using namespace std;
// Observer(抽象观察者)
class Listenner
{
public:
// 老师来了,应该做的动作
virtual void onTeacherComming() = 0;
virtual void doBadThing() = 0;
virtual void stopBadThing() = 0;
};
// 抽象被观察者
class Notifier
{
public:
// 添加观察者的方法
virtual void addListenner(Listenner *listenner) = 0;
// 删除观察者的方法
virtual void delListenner(Listenner *listenner) = 0;
// 通知所有观察者的方法
virtual void notify() = 0;
};
// ConcreteObserver(具体观察者),学生
class Student : public Listenner
{
public:
Student(string name, string badthing)
{
this->name = name;
this->badthing = badthing;
}
// 老师来了学生该怎么办
virtual void onTeacherComming()
{
stopBadThing();
}
virtual void doBadThing()
{
cout << "学生 " << name << "目前正在 " << badthing << endl;
}
virtual void stopBadThing()
{
cout << "学生 " << name << "发现班长给我使眼神了,停止 " << badthing << endl;
}
private:
string name;
string badthing;
};
// ConcreteSubject(具体被观察者),班长
class Monitor : public Notifier
{
public:
// 添加观察者的方法
virtual void addListenner(Listenner *listenner)
{
this->l_list.push_back(listenner);
}
// 删除观察者的方法
virtual void delListenner(Listenner *listenner)
{
this->l_list.remove(listenner);
}
// 通知所有观察者的方法
// 班长使眼神的方法
virtual void notify()
{
// 广播信息,让每一个学生都执行各自的重写的 onTeacherComming 方法
for (list<Listenner *>::iterator it = l_list.begin(); it != l_list.end(); it++)
{
(*it)->onTeacherComming();
}
}
private:
list<Listenner *> l_list; //班长手中所有的学生(观察者)
};
int main(void)
{
Student zhangsan("张三", "抄作业");
Student lisi("李四", "打lol");
Monitor monitor;
// 将所有的学生列表告知具体被观察者,好让具体被观察者进行通知
monitor.addListenner(&zhangsan);
monitor.addListenner(&lisi);
cout << "教室一片和谐,老师没有来 " << endl;
zhangsan.doBadThing();
lisi.doBadThing();
cout << "班长突然发现老师来了,给学生们使了一个眼神" << endl;
monitor.notify();
return 0;
}
运行结果:
4.3 优缺点
优点:
(1)观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
(2)观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
(3)观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
(4)观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。
缺点:
(1)如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
(2)**如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 **
(3)观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
4.4 适用场景
(1)一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。
(2)一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
(3)需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
C++ 设计模式 4:行为型模式的更多相关文章
- Java设计模式之行为型模式
行为型模式共11种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 策略模式:策略模式的决定权在用户,系统本身提供不同 ...
- Java设计模式之创建型模式
创建型模式分为五类:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式 一.工厂方法模式:接口-实现类.工厂类
- Java设计模式之职责型模式总结
原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6548127.html 所谓职责型模式,就是采用各种模式来分配各个类的职责. 职责型模式包括 ...
- GoF的23种设计模式之行为型模式的特点和分类(2)
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配. 行为型模式分为类行为模式和对象行为模式,前者采用继 ...
- GoF的23种设计模式之行为型模式的特点和分类(1)
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配. 行为型模式分为类行为模式和对象行为模式,前者采用继 ...
- GoF的23种设计模式之创建型模式的特点和分类
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”.这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建由相关的工厂来完成.就像我们去商场购买商品时, ...
- Typescript玩转设计模式 之 创建型模式
作者简介 joey 蚂蚁金服·数据体验技术团队 前言 我们团队的工作是用单页面应用的方式实现web工具.涉及到数万到十数万行的前端代码的管理,而且项目周期长达数年. 怎么样很好地管理好这种量级的前端代 ...
- Python与设计模式之创建型模式及实战
用Python学习一下设计模式,如果很枯燥的话,就强行能使用的就用一下.设计模式参考Python与设计模式-途索 1. 单例模式 保证一个类仅有一个实例,并提供一个访问它的全局访问点. import ...
- Java经典23种设计模式之行为型模式(三)
本文接着介绍11种行为型模式里的备忘录模式.观察者模式.状态模式. 一.备忘录模式 在不破坏封装性的前提下,捕获一个对象的内部状态.并在该对象之外保存这个状态.这样以后就能够将该对象恢复到原先保存的状 ...
- Java经典23种设计模式之行为型模式(二)
本文接着介绍行为型模式里的解释器模式.迭代器模式.中介者模式. 一.解释器模式Interpret 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言的中的句子. 1 ...
随机推荐
- 使用react Context+useReducer替代redux
首先明确一点,Redux 是一个有用的架构,但不是非用不可.事实上,大多数情况,你可以不用它,只用 React 就够了. 曾经有人说过这样一句话. "如果你不知道是否需要 Redux,那就是 ...
- jvm堆内存和GC简介
最近经常遇到jvm内存问题,觉得还是有必要整理下jvm内存的相关逻辑,这里只描述jvm堆内存,对外内存暂不阐述. jvm内存简图 jvm内存分为堆内存和非堆内存,堆内存分为年轻代.老年代,非堆内存里只 ...
- 【7】进大厂必须掌握的面试题-Java面试-Jsp
1. jsp的生命周期方法是什么? 方法 描述 公共无效的jspInit() 与servlet的init方法相同,仅被调用一次. 公共无效_jspService(ServletRequest requ ...
- 【C语言/C++编程学习笔记】:通俗易懂讲解 - 链表!学不会?不存在的!
C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现 ...
- go 正则 爬取邮箱代码
package main import ( "net/http" "fmt" "io/ioutil" "regexp" ...
- pycharm2018.3.5 下载激活(windows平台)
软件下载: 百度网盘下载 提取码: 73p7 激活操作: 1.下载jar包 JetbrainsCrack-4.2-release-enc.jar 链接:https://pan.baidu.com/s/ ...
- jdk可视化工具系列——检视阅读
jdk可视化工具系列--检视阅读 参考 java虚拟机系列 RednaxelaFX知乎问答 RednaxelaFX博客 JConsole--Java监视与管理控制台 jconsole介绍 JConso ...
- Linux操作系统的介绍和安装教程(Centos6.4)
路漫漫其修远兮,吾将上下而求 Linux的简单介绍 Linux最初是由芬兰赫尔辛基大学学生Linus Torvalds开发的,由于自己不满意教学中使用的MINIX操作系统, 所以在1990年底由于个人 ...
- spring cloud gateway网关路由分配
1, 基于父工程,新建一个模块 2,pom文件添加依赖 <dependencies> <dependency> <groupId>org.springframewo ...
- docker部署nginx服务器
1,下载nginx镜像 docker pull nginx 2,启动 docker run --name runoob-nginx-test -p 8081:80 -d nginx 3,创建本地目录 ...