设计模式 - 行为型模式 下(C++版)
- [一、模板模式(Template Pattern)](#一、模板模式(Template Pattern))
- [二 、命令模式(Command Pattern)](#二 、命令模式(Command Pattern))
- [三 、责任链模式(Chain of Responsibility Pattern)](#三 、责任链模式(Chain of Responsibility Pattern))
- [四 、策略模式(Strategy Pattern)](#四 、策略模式(Strategy Pattern))
- [五 、中介者模式(Mediator Pattern)](#五 、中介者模式(Mediator Pattern))
- [六、观察者模式(Observer Pattern)](#六、观察者模式(Observer Pattern))
-
- 1、观察者模式代码实现
- 2、优缺点
-
- 2.1、优点
- [2.2 缺点](#2.2 缺点)
- 3、使用场景
- [七、备忘录模式(Memento Pattern)](#七、备忘录模式(Memento Pattern))
- [八、访问者模式(Visitor Pattern)](#八、访问者模式(Visitor Pattern))
- [九、状态模式(State Pattern)](#九、状态模式(State Pattern))
- [十、解释器模式(interpreter Pattern)](#十、解释器模式(interpreter Pattern))
- [十一、迭代器模式(Iterator Pattern)](#十一、迭代器模式(Iterator Pattern))
一、模板模式(Template Pattern)
- 介绍:模板方法模式,是一种行为型模式。通过模板模式可以把特定步骤的算法接口定义在抽象基类中,通过子类继承对抽象算法进行不同的实现来达到改变算法行为的目的。通俗来讲就是,在抽象类中定义好算法步骤并统一接口,在子类中实现接口,这就实现了算法操作步骤和算法实现的解耦合。模板模式一般应用于,具有同样的操作步骤,但是这些操作的细节不同的场景。
- 模板模式结构:
(1)抽象基类:定义了算法的框架和步骤;
(2)实体类:实现抽象基类中的方法;
1、模板模式代码实现
cpp
#include <iostream>
/*
模板模式
*/
// 抽象基类(定义算法框架流程)
class Message{
protected:
virtual void link() = 0;
virtual void send() = 0;
virtual void broken() = 0;
public:
void send_message(){
link();
send();
broken();
}
};
// 与远程通信流程(具体实现类)
class Socket : public Message{
protected:
virtual void link(){
std::cout<<"与远程建立连接\n";
}
virtual void send(){
std::cout<<"发送消息\n";
}
virtual void broken(){
std::cout<<"断开连接\n";
}
};
// 查询数据库数据流程(具体实现类)
class Database : public Message{
protected:
virtual void link(){
std::cout<<"与数据库建立连接\n";
}
virtual void send(){
std::cout<<"发送查询请求\n";
}
virtual void broken(){
std::cout<<"断开与数据库连接\n";
}
};
int main(void){
std::cout<<"与远程通信流程:\n";
Message* mess_1 = new Socket();
mess_1->send_message();
std::cout<<"查询数据库数据流程:\n";
Message* mess_2 = new Database();
mess_2->send_message();
return 0;
}
2、优缺点
2.1、优点
(1)良好的扩展性,封装不变部分,扩展可变部分,把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。例如增加一个新的功能很简单,只要再增加一个子类,实现父类的基本方法就可以了。
(2)提取公共部分代码,便于维护,减小维护升级成本,基本操作由父类定义,子类实现
(3)基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原 则。
2.2、缺点
(1)每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,但是更加符合"单一职责原则",使得类的内聚性得以提高。
3、使用场景
(1)多个子类有公有的方法,并且逻辑基本相同时。
(2)重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个 子类实现。
(3)重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子方法约束其行为。
二 、命令模式(Command Pattern)
- 介绍:是一种行为型设计模式,将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
- 命令模式结构:
(1)命令创建者抽象接口:定义了命令接口
(2)命令的创建者:实现抽象命令的接口,创建了具体命令
(3)命令的执行者:收到命令后执行相应的操作;
(4)命令的调用者:客户创建命令,并负责去调用命令中的操作;
1、命令模式代码实现
cpp
#include <iostream>
// 不同功能类(命令执行者)
class Database{
public:
void add(){
std::cout<<"添加数据\n";
}
void move(){
std::cout<<"删除数据\n";
}
void alter(){
std::cout<<"修改数据\n";
}
void query(){
std::cout<<"查询数据\n";
}
};
// 抽象命令接口
class Command{
public:
virtual void command() = 0;
};
// 添加操作(命令创建者)
class AddComand : public Command{
public:
AddComand(Database* data){
m_data = data;
}
virtual void command(){
m_data->add();
}
private:
Database* m_data;
};
// 删除操作(命令创建者)
class MoveCommand : public Command{
public:
MoveCommand(Database* data){
m_data = data;
}
virtual void command(){
m_data->move();
}
private:
Database* m_data;
};
// 修改操作(命令创建者)
class AlterCommand : public Command{
public:
AlterCommand(Database* data){
m_data = data;
}
virtual void command(){
m_data->alter();
}
private:
Database* m_data;
};
// 查询操作(命令创建者)
class QueryCommand : public Command{
public:
QueryCommand(Database* data){
m_data = data;
}
virtual void command(){
m_data->query();
}
private:
Database* m_data;
};
// 管理命令调用(命令调用者)
class Manager{
public:
Manager(Command* command){
m_command = command;
}
void order(){
m_command->command();
}
private:
Command* m_command;
};
int main(void){
// 具体执行操作
Database* p_data = new Database();
std::cout<<"查询数据:\n";
Command* p_com = new QueryCommand(p_data); // 创建命令
Manager* p_man = new Manager(p_com); // 调用命令
p_man->order();
delete p_com;
delete p_man;
std::cout<<"删除数据:\n";
p_com = new MoveCommand(p_data);
p_man = new Manager(p_com);
p_man->order();
delete p_com;
delete p_man;
std::cout<<"添加数据:\n";
p_com = new AddComand(p_data);
p_man = new Manager(p_com);
p_man->order();
delete p_com;
delete p_man;
std::cout<<"修改数据:\n";
p_com = new AlterCommand(p_data);
p_man = new Manager(p_com);
p_man->order();
delete p_com;
delete p_man;
delete p_data;
return 0;
}
2、优缺点
2.1、优点
(1)降低系统的耦合度。
(2)新的命令可以很容易地加入到系统中。
(3)可以比较容易地设计一个组合命令。
2.2、缺点
(1)使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
3、使用场景
(1)系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
(2)系统需要在不同的时间指定请求、将请求排队和执行请求。
(3)系统需要将一组操作组合在一起,即支持宏命令
三 、责任链模式(Chain of Responsibility Pattern)
- 介绍:是行为型设计模式之一。责任链模式就像一个链表,将对象连成一个链式结构,并沿着这条链传递请求,直到请求被某个对象处理。在责任链模式中,客户端只要把请求放到对象链上即可,不需关心请求的传递过程和处理细节,实现了请求发送和请求处理的解耦合。
- 责任链模式结构:
(1)抽象处理者:定义了处理请求的接口,并包含一个指向下一个对象的指针;
(2)具体处理者:负责处理请求或把请求沿着对象链传递给下一个具体处理者;
1、责任链模式代码实现
cpp
#include <iostream>
/*
责任链模式
*/
// 抽象处理者接口
class Handler{
public:
void set_next(Handler* next){
p_next = next;
}
virtual void task() = 0;
protected:
Handler* p_next;
};
// 任务1 (具体处理者)
class Task_1 : public Handler{
public:
virtual void task(){
std::cout<<"取号\n";
if(p_next != nullptr){
p_next->task();
}
}
};
// 任务2 (具体处理者)
class Task_2 : public Handler{
public:
virtual void task(){
std::cout<<"等待\n";
if(p_next != nullptr){
p_next->task();
}
}
};
// 任务3 (具体处理者)
class Task_3 : public Handler{
public:
virtual void task(){
std::cout<<"处理\n";
if(p_next != nullptr){
p_next->task();
}
}
};
int main(void){
// 创建任务
Handler* task_1 = new Task_1;
Handler* task_2 = new Task_2;
Handler* task_3 = new Task_3;
// 设置当前任务执行后,下一个要执行的任务
task_1->set_next(task_2);
task_2->set_next(task_3);
task_3->set_next(nullptr);
task_1->task();
return 0;
}
2、优缺点
2.1、优点
(1)一个对象无须知道是其他哪一个对象处理了其请求,仅需知道该请求会被处理即可,接受者和发送者都没有对方的明确信息,每个职责对象只负责自己的职责范围,链中对象不需要知道链的结构,各个组件间完全解耦,由客户端负责链的创建;
(2)职责链模式会把功能分散到单独的职责对象中,然后在使用时动态的组合形成链,可以通过在运行时对该链进行动态的增加或修改来增加或改变处理一个请求的职责。
(3)在系统中增加处理者无需修改原有代码,只需在客户端重新建链即可,符合开闭原则;
(4)每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
2.2、缺点
(1)对于较长的职责链,请求处理时需要很多的职责对象,系统性能将受影响,调试也不方便;
3、使用场景
(1)有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定;
(2)在不明确指定接受者的情况下,向多个对象中的一个提交一个请求;
(3)可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求
四 、策略模式(Strategy Pattern)
- 介绍:策略模式,行为型模式之一。策略模式可以定义一个算法族,把一系列算法封装起来并提供一个统一接口,这样算法之间的切换或其他变化不会影响客户端。其关键在于,把算法的抽象接口封装在一个类中,算法的实现由具体策略类来实现,,而算法的选择由客户端决定。
- 策略模式结构:
(1)抽象策略类:定义算法族的统一接口;
(2)具体策略类:实现了具体的算法操作;
(3)上下文:包含一个策略类的引用,根据不同策略执行不同操作,策略的选择由客户端决定;
1、策略模式代码实现
cpp
#include <iostream>
/*
策略模式
*/
// 计算接口(抽象策略类)
class Calculate{
public:
virtual void caculate(int a, int b) = 0;
};
// 加法计算(具体策略类)
class Addition : public Calculate{
public:
virtual void caculate(int a, int b){
std::cout<<"a + b = "<<a + b<<std::endl;
}
};
// 减法计算(具体策略类)
class Subtraction : public Calculate{
public:
virtual void caculate(int a, int b){
std::cout<<"a - b = "<<a - b<<std::endl;
}
};
// 根据不同选择执行不同策略(上下文类)
class Context{
public:
Context(Calculate* calcu){
p_calcu = calcu;
}
void select_calcu(int a, int b){
p_calcu->caculate(a, b);
}
private:
Calculate* p_calcu;
};
int main(void){
// 创建加法策略,执行加法
Calculate* p_cal = new Addition;
Context* p_con = new Context(p_cal);
p_con->select_calcu(3,4);
delete p_cal;
delete p_con;
// 创建减法策略,执行减法
p_cal = new Subtraction;
p_con = new Context(p_cal);
p_con->select_calcu(10,5);
delete p_cal;
delete p_con;
return 0;
}
2、优缺点:
2.1、优点
(1)策略模式提供了对"开闭原则"的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
(2)策略模式提供了管理相关的算法族的办法。
(3)策略模式提供了可以替换继承关系的办法。
(4)使用策略模式可以避免使用多重条件转移语句。
2.2、缺点
(1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
(2)策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
3、使用场景
(1)如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
(2)一个系统需要动态地在几种算法中选择一种。
(3)如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
(4)不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。
五 、中介者模式(Mediator Pattern)
- 介绍:行为型模式之一。类与类之间的交互都放在一个中介对象中进行,即类通过中介和另一个类交互,类与类之间不用互相引用就能实现交互,降低了类与类之间的耦合。但是需要通过中介者进行交互的类中包含了中介者的引用,而中介者也包含了所有需要交互的类的引用。
- 中介者模式结构:
(1)抽象中介者:定义了同事对象到中介者对象的接口;
(2)具体中介者:它包含了所有具体同事类的引用,并实现抽象中介类中的接口;
(3)抽象同事类:定义类同事类功能接口
(4)具体同事类:每个同事类只知道自己的行为;
1、中介者模式代码实现
cpp
#include <iostream>
#include <string>
/*
中介者模式
*/
struct Student{
std::string name;
std::string school;
int age;
};
// 抽象同事类
class Role{
public:
Role(Student student):m_student(student){};
virtual Student query(std::string name) = 0;
protected:
Student m_student;
};
// 服务端 (具体同事类)
class Server : public Role{
public:
Server(Student student):Role(student){};
virtual Student query(std::string name){
if(!name.empty()){
std::cout<<"在数据库查询姓名为:"<<name<<"的所有信息\n";
m_student.name = name;
m_student.age = 18;
m_student.school = "北大";
}else{
std::cout<<"查询失败\n";
m_student.age = -1;
m_student.name = "";
m_student.school = "";
}
return m_student;
}
};
// 客户端 (具体同事类)
class Client : public Role{
public:
Client(Student student):Role(student){};
virtual Student query(std::string name){
std::cout<<"向服务端查询姓名为:"<<name<<" 的所有信息\n";
m_student.name = name;
m_student.age = -1;
m_student.school = "";
return m_student;
}
};
/// 抽象中介类
class Mediator_abs{
public:
virtual void set_client(Role* client) = 0;
virtual void set_server(Role* server) = 0;
virtual int comparison(std::string name) = 0;
protected:
Role* m_client;
Role* m_server;
};
// 具体中介类
class Mediator : public Mediator_abs{
public:
virtual void set_client(Role* client){
m_client = client;
}
virtual void set_server(Role* server){
m_server = server;
}
virtual int comparison(std::string name){
Student stu = m_client->query(name);
/*对客户端信息解包,然后服务端根据解包出的信息去数据库查询数据*/
Student stu_ = m_server->query(stu.name);
/*对从数据库查询的数据进行判别*/
if(!stu_.name.empty() && (0 <= stu_.age && stu_.age <= 200) && !stu_.school.empty()){
std::cout<<"姓名为:"<<stu_.name<<"详细信息如下:\n";
std::cout<<"年龄:"<<stu_.age<<std::endl;
std::cout<<"学校:"<<stu_.school<<std::endl;
}else{
std::cout<<"没有此学生信息\n";
}
}
};
int main(void){
Student stu={};
Role* client = new Client(stu);
Role* server = new Server(stu);
Mediator* med = new Mediator;
med->set_client(client);
med->set_server(server);
med->comparison("张三");
std::cout<<"******************************\n";
med->comparison("");
delete client;
delete server;
delete med;
return 0;
}
2、优缺点
2.1、优点:
(1)简化了对象之间的交互。
(2)将各同事解耦。
(3)减少子类生成。
(4)可以简化各同事类的设计和实现。
2.2、缺点:
(1)在具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。
3、使用场景
(1)系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
(2)一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。
(3)想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象。
(4)交互的公共行为,如果需要改变行为则可以增加新的中介者类。
六、观察者模式(Observer Pattern)
- 介绍:行为型模式之一。观察者模式提供了一种一对多的模式,多个观察者对象同时监听一个主题对象,一旦主题对象发生变化,能够自动通知所有的观察者对象。它提供了一种关联对象之间的同步机制,他们之间通过通信来保持状态同步。
- 观察者模式结构:
(1)抽象被观察者,当被观察的状态发生变化时,会通知所有的观察者
(2)具体被观察者:被观察者的具体实现,当状态发生改变时,向所有观察者发出通知;
(3)抽象观察者:提供统一的接口,在得到通知时执行某些操作;
(4)具体观察者:实现抽象观察者提供的接口;
1、观察者模式代码实现
cpp
#include <iostream>
#include <vector>
#include <algorithm>
/*
观察者模式
*/
// 观察到信息做出反应(抽象观察者)
class Observer {
public:
virtual void update() = 0;
};
// 抽象管理观察者接口(抽象被观察者)
class Subject {
public:
virtual void registerObserver(Observer* ob) = 0;
virtual void removeObserver(Observer* ob) = 0;
virtual void notifyObservers() = 0;
};
// 管理观察者(注册、删除、通知观察者)(具体被观察者)
class Beobserver : public Subject {
public:
virtual void registerObserver(Observer* ob) {
observers.push_back(ob);
}
virtual void removeObserver(Observer* ob) {
observers.erase(std::remove(observers.begin(), observers.end(), ob), observers.end());
}
virtual void notifyObservers() override {
for (Observer* observer : observers) {
observer->update();
}
}
private:
std::vector<Observer*> observers;
};
// 观察者1,观察到信息更新做出决策1(具体观察者)
class Observer1 : public Observer {
public:
virtual void update() {
std::cout << "信息更新,做出决策1\n";
}
};
// 观察者2,观察到信息更新做出决策2(具体观察者)
class Observer2 : public Observer {
public:
virtual void update() override {
std::cout << "信息更新,做出决策2\n";
}
};
// 观察者3,观察到信息更新做出决策3(具体观察者)
class Observer3 : public Observer {
public:
virtual void update() override {
std::cout << "信息更新,做出决策3\n";
}
};
int main() {
// 被观察者对象
Beobserver* p_beob = new Beobserver;
// 观察者对象
Observer* p_ob1 = new Observer1;
Observer* p_ob2 = new Observer2;
Observer* p_ob3 = new Observer3;
// 观察者关注被观察者,被观察者注册了观察者,有信息更新就会通知观察者
p_beob->registerObserver(p_ob1);
p_beob->registerObserver(p_ob2);
p_beob->registerObserver(p_ob3);
// 通知观察者信息
p_beob->notifyObservers();
// 某个观察者取消关注,被观察者删除了观察者,就不会通知该观察者
p_beob->removeObserver(p_ob3);
std::cout<<"****** p_ob3取消关注,不会收到信息 ******\n";
// 通知观察者信息
p_beob->notifyObservers();
delete p_beob;
delete p_ob1;
delete p_ob2;
delete p_ob3;
return 0;
}
2、优缺点
2.1、优点
(1)观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
(2)观察者模式在观察目标和观察者之间建立一个抽象的耦合。
(3)观察者模式支持广播通信。
(4)观察者模式符合"开闭原则"的要求。
2.2 缺点
(1)如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
(2)如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
(3)观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化
3、使用场景
(1)一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
(2)一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
(3)一个对象必须通知其他对象,而并不知道这些对象是谁。
(4)需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象,可以使用观察者模式创建一种链式触发机制。
七、备忘录模式(Memento Pattern)
- 介绍:行为型模式之一。备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在外部保存,并在需要的时候恢复对象以前的状态。可以理解为,在备忘录中保存了一个对象的备份,当对象已经发生了改变,并且我们需要恢复对象以前的状态时,可以通过一个备忘录管理器来恢复以前的状态。
- 备忘录模式结构:
(1)原生者角色:需要在备忘录中保存的对象,负责创建一个备忘录,并进行保存和恢复状态的操作;
(2)备忘录:用于保存原生者角色的内部状态;
(3)管理者:包含一个备忘录的引用,负责操作和管理备忘录;
1、备忘录模式代码实现
cpp
#include <iostream>
#include <string>
#include <vector>
/*
备忘录模式
*/
// 备忘录
class Memento{
public:
Memento(std::string state):state_(state){};
std::string GetState(){return state_;}
private:
std::string state_;
};
// 原生角色
class Originator{
public:
// 设置状态
void SetState(std::string state){
state_ = state;
}
// 获取状态
std::string GetState(){
return state_;
}
// 创建备忘录
Memento*CreateMemento(){
return new Memento(state_);
}
// 恢复备忘录
void RestoreState(Memento* memento){
state_=memento->GetState();
}
private:
std::string state_;
};
// 管理者
class Careker{
public:
// 添加备忘录
void AddMemento(Memento* memento){
mementos_.push_back(memento);
}
// 获取备忘录
Memento* GetMemento(int index){
return mementos_[index];
}
private:
std::vector<Memento*>mementos_;
};
int main(void){
// 原生角色对象
Originator originator;
// 备忘录管理者对象
Careker careker;
// 设置状态1
originator.SetState("State 1");
// 备忘录管理者备份状态1
careker.AddMemento(originator.CreateMemento());
std::cout<<"当前状态:"<<originator.GetState()<<std::endl;
// 设置状态2
originator.SetState("State 2");
// 备忘录管理者备份状态2
careker.AddMemento(originator.CreateMemento());
std::cout<<"当前状态:"<<originator.GetState()<<std::endl;
// 设置状态3
originator.SetState("State 3");
// 备忘录管理者备份状态3
careker.AddMemento(originator.CreateMemento());
std::cout<<"当前状态:"<<originator.GetState()<<std::endl;
// 恢复到状态1
std::cout<<"****** 恢复到状态1 ******\n";
originator.RestoreState(careker.GetMemento(0));
std::cout<<"当前状态:"<<originator.GetState()<<std::endl;
return 0;
}
2、优缺点
2.1、优点
(1)提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。它实现了内部状态的封装,除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
(2)它简化了发起人,发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
2.2、缺点
(1)它可能会消耗大量资源。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
3、使用场景
(1)备忘录模式常用于需要保存与恢复数据的场景。
八、访问者模式(Visitor Pattern)
- 介绍:是一种行为型设计模式。访问者模式把数据结构和作用于数据结构上的操作进行了分离,在不修改已有类的前提下可以增加新的操作,而新增新的操作就相当于新增一个访问者。例如不同的班级,每个班级都有自己班的成绩单(具体元素),对成绩有不同的排序方法,比如按数学成绩排序,按语文成绩排序等等。就可以使用访问者模式,不同的排序方法就是不同的访问者,不同班级的成绩就是被访问的元素。
- 访问者模式结构:
(1)抽象访问者角色:声明了访问操作的方法,方法的参数为被访问的元素;
(2)具体访问者角色:实现抽象访问者中声明的方法;
(3)抽象元素角色:声明一个包含接受操作 accept() 的接口,其参数为访问者对象
(4)具体元素角色:实现抽象元素角色提供的 accept() 操作,另外具体元素中可能还包含本身业务逻辑的相关操作。
(5)对象结构角色:一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法
1、访问者模式代码实现
cpp
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
/*
访问者模式
*/
// 成绩结构体
struct student_data{
std::string m_name;
float m_math;
float m_chinese;
float m_english;
float m_total;
student_data(std::string name, float math, float chinese, float english){
m_name = name;
m_math = math;
m_chinese = chinese;
m_english = english;
m_total = math + chinese + english;
}
};
class Class;
// 排序算法接口类,对不同班级成绩进行排序(抽象访问者)
class Sort{
public:
virtual void sort_class_1(Class* p_class) = 0;
virtual void sort_class_2(Class* p_class) = 0;
protected:
virtual void sort(std::vector<student_data*>& vec_data) = 0;
virtual void print_score(std::vector<student_data*>& vec_data, std::string str){
std::cout<<"****************************************************\n";
std::cout<<str<<std::endl;
std::cout<<"姓名\t数学\t语文\t英语\t总分\n";
for(const auto& it : vec_data){
std::cout<<it->m_name<<"\t"<<it->m_math<<"\t"<<it->m_chinese<<"\t"<<it->m_english<<"\t"<<it->m_total<<"\n";
}
std::cout<<"****************************************************\n";
}
};
// 抽象班级接口,提供班级成绩元素(抽象元素)
class Class{
public:
virtual void accept(Sort* sort) = 0;
virtual std::vector<student_data*> get_score() = 0;
};
// 1班 提供1班成绩(具体元素类)
class Class_1 : public Class{
public:
virtual void accept(Sort* sort){
sort->sort_class_1(this);
}
virtual std::vector<student_data*> get_score(){
student_data* student_1 = new student_data("1_aaa", 89.5, 76.5, 90.6);
student_data* student_2 = new student_data("1_bbb", 66.8, 98.7, 50.3);
student_data* student_3 = new student_data("1_ccc", 90.2, 80.8, 87.9);
std::vector<student_data*> vec_student = {student_1, student_2, student_3};
return vec_student;
}
};
// 2班 提供2班成绩(具体元素类)
class Class_2 : public Class{
public:
virtual void accept(Sort* sort){
sort->sort_class_2(this);
}
virtual std::vector<student_data*> get_score(){
student_data* student_1 = new student_data("2_aaa", 77.3, 65.2, 81.4);
student_data* student_2 = new student_data("2_bbb", 73.2, 86.5, 91.9);
student_data* student_3 = new student_data("2_ccc", 88.6, 90.5, 65.5);
std::vector<student_data*> vec_student = {student_1, student_2, student_3};
return vec_student;
}
};
// 分别对1班和2班按数学成绩排序 (具体访问者)
class Sort_math : public Sort{
public:
virtual void sort_class_1(Class* p_class){
std::vector<student_data*> data = p_class->get_score();
sort(data);
print_score(data, "一班成绩按数学排序:");
for(auto da : data){
if(da != nullptr){
delete da;
}
}
}
virtual void sort_class_2(Class* p_class){
std::vector<student_data*> data = p_class->get_score();
sort(data);
print_score(data, "二班成绩按数学排序");
for(auto da : data){
if(da != nullptr){
delete da;
}
}
}
protected:
virtual void sort(std::vector<student_data*>& vec_data){
std::sort(vec_data.begin(),
vec_data.end(),
[](const student_data* a, const student_data* b){
return a->m_math > b->m_math;
});
}
};
// 分别对1班和2班按语文成绩排序 (具体访问者)
class Sort_chinese : public Sort{
public:
virtual void sort_class_1(Class* p_class){
std::vector<student_data*> data = p_class->get_score();
sort(data);
print_score(data, "一班成绩按语文排序");
for(auto da : data){
if(da != nullptr){
delete da;
}
}
}
virtual void sort_class_2(Class* p_class){
std::vector<student_data*> data = p_class->get_score();
sort(data);
print_score(data, "二班成绩按语文排序");
for(auto da : data){
if(da != nullptr){
delete da;
}
}
}
protected:
virtual void sort(std::vector<student_data*>& vec_data){
std::sort(vec_data.begin(),
vec_data.end(),
[](const student_data* a, const student_data* b){
return a->m_chinese > b->m_chinese;
});
}
};
// 提供了元素列表,接待访问者,使得访问者可以遍历元素列表进行访问(对象接口角色)
class Score{
public:
~Score(){
for(auto da : vec_class){
if(da != nullptr){
delete da;
}
}
}
void accept(Sort* p_sort){
for(auto it : vec_class){
it->accept(p_sort);
}
}
void add(Class* p_class){
vec_class.push_back(p_class);
}
void remove(Class* p_class){
for (auto it = vec_class.begin(); it != vec_class.end(); ) {
if (*it == p_class) {
it = vec_class.erase(it);
} else {
++it;
}
}
}
private:
std::vector<Class*> vec_class;
};
int main(void){
// 创建对象结构
Score* p_score = new Score;
// 添加不同元素
p_score->add(new Class_1);
p_score->add(new Class_2);
// 接待不同访问者
p_score->accept(new Sort_math);
p_score->accept(new Sort_chinese);
delete p_score;
return 0;
}
2、优缺点
2.1、优点
(1)开闭原则。 你可以引入在不同类对象上执行的新行为, 且无需对这些类做出修改。
(2)单一职责原则。 可将同一行为的不同版本移到同一个类中。
(3)访问者对象可以在与各种对象交互时收集一些有用的信息。 当你想要遍历一些复杂的对象结构 (例如对象树), 并在结构中的每个对象上应用访问者时, 这些信息可能会有所帮助。
2.2、缺点
(1)每次在元素层次结构中添加或移除一个类时, 你都要更新所有的访问者。
(2)在访问者同某个元素进行交互时, 它们可能没有访问元素私有成员变量和方法的必要权限。
3、使用场景
(1)如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式。
(2)当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式。你可将该行为抽取到单独的访问者类中, 只需实现接收相关类的对象作为参数的访问者方法并将其他方法留空即可。
九、状态模式(State Pattern)
- 介绍:是一种行为型设计模式。通过改变对象的内部状态来达到改变对象行为的目的,"这个对象表现得就好像改变了它的类一样"。其实说白了就是,根据用户是输入的条件,满足一定条件就改变对象的行为,不同条件执行不同的操作。
- 状态模式结构:
(1)抽象状态:定义了一个接口,接口声明了一个与上下文环境相关的状态的行为;
(2)具体状态:定义了本状态的行为和转换到另一个状态的判定条件;
(3)上下文环境:负责状态的转换,包含了一个表示当前状态的State类型的引用;
1、状态模式代码实现
cpp
#include <iostream>
/*
状态模式
*/
class Context;
// 抽象状态接口
class State{
public:
virtual void handle(Context* context) = 0;
};
// 复制状态转换(上下文环境)
class Context{
public:
Context(){
p_state = nullptr;
}
~Context(){
if(p_state != nullptr){
delete p_state;
p_state = nullptr;
}
}
void set_state(State* state){
if(p_state != nullptr){
delete p_state;
p_state = nullptr;
}
p_state = state;
}
State* get_state(){
return p_state;
}
void handle(){
p_state->handle(this);
}
public:
State* p_state;
};
// 关灯状态,实现关灯操作(具体状态类)
class State_off : public State{
public:
virtual void handle(Context* context);
};
// 开灯状态,实现开灯操作(具体状态类)
class State_on : public State{
public:
virtual void handle(Context* context){
std::cout<<"开灯\n";
context->set_state(new State_off);
}
};
void State_off::handle(Context* context){
std::cout<<"关灯\n";
context->set_state(new State_on);
}
int main(void){
// 创建上下文环境
Context* p_context = new Context;
// 设置初始状态为关灯
p_context->set_state(new State_off);
// 关灯操作,并把下一个状态设置为开灯
p_context->handle();
// 开灯操作,并把下一个状态设置为关灯
p_context->handle();
p_context->handle();
p_context->handle();
return 0;
}
2、优缺点
2.1、优点
(1)封装了转换规则。
(2)枚举可能的状态,在枚举状态之前需要确定状态种类。
(3)将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
(4)允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
(5)可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
2.2、缺点
(1)状态模式的使用必然会增加系统类和对象的个数。
(2)状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
(3)状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
3、使用场景
(1)对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为。
(2)代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。在这些条件语句中包含了对象的行为,而且这些条件对应于对象的各种状态。
(3)当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。状态模式让你能够生成状态类层次结构, 通过将公用代码抽取到抽象基类中来减少重复。
十、解释器模式(interpreter Pattern)
- 介绍:提供了评估语言的语法或表达式的方式,属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。常被用在 SQL 解析、符号处理引擎等。比自定义一种加减法的语法,把**-定义为 #,把 +定义为$**,正常的是(30 - 10 + 20 = 40),自定义语法是(30 # 10 $ 20 = 40)
- 解释器模式结构:
(1)上下文:包含解释器之外的一些全局信息,通常用于存储解释器需要的具体状态,或是提供解释器执行所需的特定信息。
(2)抽象表达式:定义解释操作的接口,所有具体表达式类都需要遵循这个接口。
(3)非终结符表达式:对文法的规则进行解释,通常是复合表达式。非终结符表达式可以是组合多个表达式的复杂表达式,这些表达式可以是终结符或其他非终结符表达式。
(4)终结符表达式:实现与语法中的终结符相关的解释操作。通常,每个终结符表达式都会对应语言的一个规则。
1、解释器模式代码实现
cpp
#include <iostream>
#include <string>
#include <sstream>
#include <stack>
#include <cctype>
/*
解释器模式
*/
// 抽象表达式接口
class Expression {
public:
virtual ~Expression() = default;
virtual int interpret() = 0;
};
// 数字表达式接口(终结符表达式)
class NumberExpression : public Expression {
private:
int value;
public:
NumberExpression(int value) : value(value) {}
virtual int interpret() {
return value;
}
};
// 加法($)表达式接口(非终结符表达式)
class AdditionExpression : public Expression {
private:
Expression* left;
Expression* right;
public:
AdditionExpression(Expression* left, Expression* right) : left(left), right(right) {}
~AdditionExpression() {
delete left;
delete right;
}
virtual int interpret(){
return left->interpret() + right->interpret();
}
};
// 减法(#)表达式接口(非终结符表达式)
class SubtractionExpression : public Expression {
private:
Expression* left;
Expression* right;
public:
SubtractionExpression(Expression* left, Expression* right) : left(left), right(right) {}
~SubtractionExpression() {
delete left;
delete right;
}
virtual int interpret(){
return left->interpret() - right->interpret();
}
};
// 解释器(上下文环境)
class ExpressionParser {
public:
static Expression* parse(const std::string& expression) {
std::istringstream iss(expression);
std::stack<Expression*> stack;
std::string token;
std::string symbol = "";
while(iss >> token){
if(token == " "){
continue;
}else if (isdigit(token[0])) {
stack.push(new NumberExpression(std::stoi(token)));
if(!symbol.empty()){
Expression* right = stack.top();
stack.pop();
Expression* left = stack.top();
stack.pop();
if (symbol == "$") {
stack.push(new AdditionExpression(left, right));
} else if (symbol == "#") {
stack.push(new SubtractionExpression(left, right));
}
symbol = "";
}
}
else if(token == "$" || token == "#"){
symbol = token;
}
}
if (stack.size() != 1) {
std::cout<<"error: operation fail!\n";
}
return stack.top();
}
};
int main() {
// 要被解释的语句,自定义的语法,在自定义语法中('#' 对应 '-', '$' 对应 '+')
std::string expression = "30 # 10 $ 20"; // 相当于 30 - 10 + 20
// 创建解解释器,并通过解释器解析语法
ExpressionParser par;
Expression* parsedExpression = par.parse(expression);
// 获取到解析结果
int result = parsedExpression->interpret();
std::cout << expression << " = " << result << std::endl;
delete parsedExpression;
return 0;
}
2、优缺点
2.1、优点
(1)易于改变和扩展文法,每个文法规则都对应一个类,可以方便地改变或扩展文法。
(2)实现文法较为容易,每条文法规则都可以表示为一个类,因此可以直接将规则表示为代码。
(3)增加新的解释表达式较为方便,如果需要增加新的解释表达式,只需要添加一个新的类即可。
2.2、缺点
(1)对于复杂文法难以维护,当文法规则数目太多时,管理这些类会变得非常困难。
(2)执行效率较低,解释器模式使用了大量的循环和递归调用,对于复杂的句子可能会导致效率问题。
(3)可能会引起类膨胀,每个文法规则都需要一个单独的类,可能会导致系统中类的数量急剧增加。
3、使用场景
(1)解释器模式非常适合用于实现一些简单的、可组合的语法规则。例如,计算器程序需要解析和计算数学表达式,可以使用解释器模式来实现。
(2)在某些领域,可能需要定义一个小型的语言来描述特定的任务或行为。例如,SQL查询、正则表达式、配置文件解析等,都可以使用解释器模式来实现相应的解析和执行。
(3)解释器模式可以用于文本处理和编译,例如编译器或解释器中的词法分析和语法分析。它可以将输入的文本转换为抽象语法树,并基于这个树结构执行相应的操作。
(4)一些应用程序可能需要解析和执行命令行输入或脚本语言。解释器模式可以用来定义这些命令的语法,并提供相应的解释和执行机制。
(5)在某些业务系统中,可能需要根据一系列规则来执行不同的操作。解释器模式可以用来定义这些规则的语法,并在运行时解析和执行这些规则。
(6)实现一种新的编程语言或脚本语言时,解释器模式可以用于解析和执行语言的语法。许多简单的脚本语言和教学语言都使用解释器模式来实现。
十一、迭代器模式(Iterator Pattern)
- 介绍:是行为型模式的一种。迭代器模式提供了一种从外部遍历访问一个容器的方法,并且在不需知道容器内部细节的前提下就可以完成对容器的顺序遍历。所以,创建迭代器的容器应该将自身的引用传递给迭代器,迭代器通过持有的这个容器的引用来实现对容器的遍历。
- 迭代器模式结构:
(1)迭代抽象类:用于提供实现迭代的最小方法集,一般包括获取开始对象、获取下一个对象、获取当前对象、判断是否结束这几个方法;
(2)具体的迭代器:实现抽象迭代器定义的方法;
(3)聚集抽象类:可以理解为一个容器的接口;
(4)具体聚集类:容器的实现类;
1、迭代器模式代码实现
cpp
#include <iostream>
#include <vector>
#include <stdexcept>
/*
迭代器模式
*/
class ConcreteAggregate;
// 迭代器抽象接口
class Iterator {
public:
virtual ~Iterator() = default;
virtual bool hasNext() = 0;
virtual int next() = 0;
};
// 聚集抽象类接口
class Aggregate {
public:
virtual ~Aggregate() = default;
virtual Iterator* createIterator() const = 0;
};
// 具体迭代器类
class ConcreteIterator : public Iterator {
private:
const ConcreteAggregate* aggregate;
int current;
public:
ConcreteIterator(const ConcreteAggregate* aggregate) : aggregate(aggregate), current(0) {}
virtual bool hasNext();
virtual int next();
};
// 具体聚集类
class ConcreteAggregate : public Aggregate {
private:
std::vector<int> items;
public:
ConcreteAggregate(const std::vector<int>& items) : items(items) {}
Iterator* createIterator() const override {
return new ConcreteIterator(this);
}
int getItem(int index) const {
if (index >= 0 && index < items.size()) {
return items[index];
}
throw std::out_of_range("Index out of range");
}
int getCount() const {
return items.size();
}
};
// 具体迭代器类方法实现
bool ConcreteIterator::hasNext() {
return current < aggregate->getCount();
}
int ConcreteIterator::next() {
if (!hasNext()) {
throw std::out_of_range("No more elements");
}
return aggregate->getItem(current++);
}
int main() {
// 创建一个具体的聚合对象
ConcreteAggregate aggregate({1, 2, 3, 4, 5});
// 创建一个迭代器
Iterator* iterator = aggregate.createIterator();
// 遍历聚合对象中的元素
while (iterator->hasNext()) {
int item = iterator->next();
std::cout << "Item: " << item << std::endl;
}
delete iterator;
return 0;
}
2、优缺点
2.1、优点
(1)单一职责原则。 通过将体积庞大的遍历算法代码抽取为独立的类, 你可对客户端代码和集合进行整理。
(2)开闭原则,可实现新型的集合和迭代器并将其传递给现有代码, 无需修改现有代码。
(3)可以并行遍历同一集合, 因为每个迭代器对象都包含其自身的遍历状态。
2.2、缺点
(1)如果你的程序只与简单的集合进行交互, 应用该模式可能会矫枉过正。
(2)对于某些特殊集合, 使用迭代器可能比直接遍历的效率低。
3、使用场景
(1)当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时, 可以使用迭代器模式。迭代器封装了与复杂数据结构进行交互的细节, 为客户端提供多个访问集合元素的简单方法。 这种方式不仅对客户端来说非常方便, 而且能避免客户端在直接与集合交互时执行错误或有害的操作, 从而起到保护集合的作用。
(2)使用该模式可以减少程序中重复的遍历代码。 重要迭代算法的代码往往体积非常庞大。 当这些代码被放置在程序业务逻辑中时, 它会让原始代码的职责模糊不清, 降低其可维护性。 因此, 将遍历代码移到特定的迭代器中可使程序代码更加精炼和简洁。