设计模式-行为型设计模式(针对对象之间的交互)
针对对象之间的交互
解释器模式
解释器:对语言进行解释,根据不同语义来做不同的事情。
解释器模式的核心在于定义一个语言的文法规则,并通过递归解析来执行这些规则。这种模式特别适用于需要动态解析和执行简单语言或表达式的场景。通过定义抽象表达式和具体的终端及非终端表达式,可以构建一个表达式树,然后通过递归调用interpret方法来计算表达式的结果。
举例:双栈计算器
#include <iostream>
#include <stack>
#include <string>
#include <cmath>// 设置两栈
std::stack<char> opr;
std::stack<double> number;// 判断是否为字符
bool isChar(char c) {return c == '+' || c == '-' || c == '*' || c == '/';
}// 判断优先级是否是栈外字符小于等于栈内字符
bool isLowIn(char out, char in) {return (out == '+' || out == '-') || (in == '*' || in == '/');
}// 计算
void cal() {// 从栈内取出两个数字和一个字符double a = number.top();number.pop();double b = number.top();number.pop();char c = opr.top();opr.pop();// 根据字符c进行不同的运算switch (c) {case '+':number.push(a + b);break;case '-':number.push(b - a);break;case '*':number.push(b * a);break;case '/':number.push(b / a);break;default:std::cout << "字符输入有误" << std::endl;}
}int main() {// 接收一串字符串并转字符数组std::string str;std::getline(std::cin, str);int i = 0;while (i < str.length()) {char c = str[i];// 是+-/*字符时if (isChar(c)) {// 如果栈内有字符,则需要判断优先级,入栈字符小于等于栈内字符则需要先计算栈内字符char peek = opr.empty() ? '\0' : opr.top(); // 细节!!!这里必须先取while (peek != '\0' && isLowIn(c, peek)) {cal();peek = opr.empty() ? '\0' : opr.top(); // 细节!!这里也必须更新peek}// 入栈opr.push(c);i++;}// 字符是数字时else {double sum = 0; // 接收整数和double sum2 = 0; // 接收小数和int times = 1; // 记录当前小数位数bool flag = false; // 是否开启小数检测模式// 判断下一个是不是+-*/,不是的话就继续判断直到非数字while (i <= str.length() - 1 && !isChar(str[i])) { // 细节:括号内两者顺序不能改变// 遇到小数的情况if (str[i] == '.') {flag = true;} else {// 小数情况if (flag) {double val = str[i] - '0';for (int j = 0; j < times; j++) { // 细节!用times缩小值val /= 10.0;}times++;sum2 += val;}// 正数情况else {sum = sum * 10 + (str[i] - '0'); // 获取多位数字的关键!!!}}i++;}number.push(sum + sum2);}}// 字符都获取完了后栈内还有数字和字符的话,就计算完栈内的数据并输出最终结果while (!opr.empty()) cal();std::cout << number.top() << std::endl;return 0;
}
模板方法模式
在执行一类业务时前面有很多步骤都是相同时,就可以写一个模板抽象类,留出一个方法去给子类定义业务最后的操作。
举例:去医院看病,挂号和看医生是固定模式,但后面要不要开处方药和拿药是不一定的
from abc import ABC, abstractmethod# 模板抽象类
class AbstractDiagnosis(ABC):def test(self):print("今天头好晕,不想起床,开摆,先跟公司请个假")print("去医院看病了~")print("1 >> 先挂号")print("2 >> 等待叫号")# 由于现在不知道该开什么处方,所以只能先定义一下行为,然后具体由子类实现self.prescribe()self.medicine() # 开药同理@abstractmethoddef prescribe(self):pass # 开处方操作根据具体病症决定了@abstractmethoddef medicine(self):pass # 拿药也是根据具体的处方去拿# 实现具体业务的子类
class ColdDiagnosis(AbstractDiagnosis):def prescribe(self):print("3 >> 一眼丁真,鉴定为假,你这不是感冒,纯粹是想摆烂")def medicine(self):print("4 >> 开点头孢回去吃吧")# 主方法
if __name__ == "__main__":diagnosis = ColdDiagnosis()diagnosis.test()
责任链模式
就像闯关,一层接一层的往下进行。可以理解为报销的时候需要一层一层审批
举例:这里就使用责任链模式来模拟一个简单的面试过程,面试也是一面二面三面这样走的流程,这里先设计一下责任链上的各个处理器
from abc import ABC, abstractmethod# 设计模板抽象方法
class Handler(ABC):def __init__(self):self.successor = None # 后继节点def connect(self, successor):self.successor = successorreturn successor # 返回后继节点,方便链式调用def handle(self):self.do_handle() # 由不同的子类实现具体处理过程if self.successor: # 如果还有后继节点,就继续向下传递self.successor.handle()@abstractmethoddef do_handle(self):pass# 一面子类
class FirstHandler(Handler):def do_handle(self):print("============= 白马程序员一面 ==========")print("1. 谈谈你对static关键字的理解?")print("2. 内部类可以调用外部的数据吗?如果是静态的呢?")print("3. hashCode()方法是所有的类都有吗?默认返回的是什么呢?")print("以上问题会的,可以依次打在评论区")# 二面子类
class SecondHandler(Handler):def do_handle(self):print("============= 白马程序员二面 ==========")print("1. 如果我们自己创建一个java.lang包并且编写一个String类,能否实现覆盖JDK默认的?")print("2. HashMap的负载因子有什么作用?变化规律是什么?")print("3. 线程池的运作机制是什么?")print("4. ReentrantLock公平锁和非公平锁的区别是什么?")print("以上问题会的,可以依次打在评论区")# 三面子类
class ThirdHandler(Handler):def do_handle(self):print("============= 白马程序员三面 ==========")print("1. synchronized关键字了解吗?如何使用?底层是如何实现的?")print("2. IO和NIO的区别在哪里?NIO三大核心组件?")print("3. TCP握手和挥手流程?少一次握手可以吗?为什么?")print("4. 操作系统中PCB是做什么的?运行机制是什么?")print("以上问题会的,可以依次打在评论区")# 主方法
if __name__ == "__main__":handler = FirstHandler() # 一面首当其冲handler.connect(SecondHandler()).connect(ThirdHandler()) # 继续连接二面和三面handler.handle() # 开始面试
命令模式
命令模式,此时会有三个顶层行为:遥控器、命令、接收器。
小米家具就是典型的命令模式。只需要在手机(遥控器)上通过红外线、蓝牙等按下一些命令,家中的家具(接收器)就会执行命令。
举例:设置三个顶层的 接口/抽象类 ,遥控器、命令、接收器
#include <iostream>
using namespace std;// 接收器
class Receiver {
public:virtual void action() = 0; // 纯虚函数,相当于 Java 的接口方法virtual ~Receiver() {} // 虚析构函数,确保派生类的析构函数被正确调用
};// 具体接收器
class AirConditioner : public Receiver {
public:void action() override {cout << "空调已开启,呼呼呼" << endl;}
};// 命令
class Command {
protected:Receiver* receiver;public:Command(Receiver* receiver) : receiver(receiver) {}virtual void execute() {receiver->action();}virtual ~Command() {} // 虚析构函数
};// 具体命令
class OpenCommand : public Command {
public:OpenCommand(Receiver* receiver) : Command(receiver) {}
};// 遥控器
class Controller {
public:static void call(Command* command) {command->execute();}
};// 主方法
int main() {AirConditioner airConditioner; // 创建一个空调OpenCommand openCommand(&airConditioner); // 创建一个开启空调的命令Controller::call(&openCommand); // 通过遥控器发送命令开启空调return 0;
}
迭代器模式
每个集合类都有相应的迭代器。很少自定义,大都是用jdk定义好的迭代器
#include <iostream>
#include <vector>
using namespace std;// 自定义集合类
template <typename T>
class ArrayCollection {
private:vector<T> array; // 底层使用一个 vector 来存放数据public:ArrayCollection(const vector<T>& array) : array(array) {}static ArrayCollection<T> of(const vector<T>& array) {return ArrayCollection<T>(array);}class Iterator; // 声明嵌套的迭代器类Iterator begin() { // 返回迭代器的起始位置return Iterator(array.begin());}Iterator end() { // 返回迭代器的结束位置return Iterator(array.end());}
};// 自定义迭代器类
template <typename T>
class ArrayCollection<T>::Iterator {
private:typename vector<T>::iterator it;public:Iterator(typename vector<T>::iterator it) : it(it) {}T& operator*() { // 解引用操作符return *it;}Iterator& operator++() { // 前置递增操作符++it;return *this;}bool operator!=(const Iterator& other) const { // 不等于操作符return it != other.it;}
};// 主方法
int main() {vector<string> arr = {"AAA", "BBB", "CCC", "DDD"};ArrayCollection<string> collection = ArrayCollection<string>::of(arr);// 使用范围 for 循环for (const string& s : collection) {cout << s << endl;}// 使用迭代器for (auto it = collection.begin(); it != collection.end(); ++it) {cout << *it << endl;}return 0;
}
中介者模式
将多对多的复杂关系,变成一对多的简单明了关系
中介,第一个想到的就是房子的中介。当一堆人需要出租房屋、另一堆人又需要租房,如果没有中介,那再好的房子很难遇上租客也租不出去,此时就需要一个中介将双方的需要进行匹配从而实现房子出租的目的。
找中介:中介将双方整理好,进行需求匹配
举例:中介、出租者、租房者
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;// 用户类
class User {
public:string name;string tel;User(string name, string tel) : name(name), tel(tel) {}string toString() const {return name + " (电话:" + tel + ")";}// 声明 find 方法static User* find(const string& address, Mediator& mediator);
};// 中介类
class Mediator {
private:unordered_map<string, User*> userMap; // 存储房屋地址和用户指针public:void registerUser(string address, User* user) {userMap[address] = user;}User* find(string address) {auto it = userMap.find(address);if (it != userMap.end()) {return it->second;}return nullptr;}
};// 主方法
int main() {User user0("刘女士", "10086"); // 出租者User user1("李先生", "10010"); // 租房者Mediator mediator; // 我是中介mediator.registerUser("广州市天河区白马程序员", &user0); // 出租人先把房子给中介挂上去User* user = user1.find("广州市天河区非马程序员", mediator); // 租房者向指定中介找房子if (user == nullptr) {cout << "没有找到对应的房源" << endl;} else {cout << user->toString() << endl; // 成功找到对应房源}return 0;
}// 用户类中的 find 方法
User* User::find(string address, Mediator& mediator) {return mediator.find(address);
}
备忘录模式
我也称其为时光回溯模式。比较少用,大都是底层代码才用
这个备忘录不是我们平时用于记录容易忘记的ddl,而是保存曾经某个时刻的状态,后面有需要就恢复到该时刻的状态
举例:保存对象的状态
#include <iostream>
#include <string>
#include <random>
using namespace std;// 状态保存类
class State {
public:string currentWork;int percentage;State(const string& currentWork, int percentage) : currentWork(currentWork), percentage(percentage) {}
};// 对象实体
class Student {
private:string currentWork; // 当前正在做的事情int percentage; // 当前的工作完成百分比public:void work(const string& currentWork) {this->currentWork = currentWork;this->percentage = 随机数;}string toString() const {return "我现在正在做:" + currentWork + " (进度:" + to_string(percentage) + "%)";}State save() {return State(currentWork, percentage);}void restore(const State& state) {this->currentWork = state.currentWork;this->percentage = state.percentage;}
};// 主方法
int main() {Student student;student.work("学Java"); // 开始学Javacout << student.toString() << endl;State savedState = student.save(); // 保存一下当前的状态student.work("打电动"); // 刚打开B站播放视频,学一半开始摆烂了cout << student.toString() << endl;student.restore(savedState); // 后悔浪费时间,回到上一个保存的状态cout << student.toString() << endl; // 回到学Java的状态return 0;
}
观察者模式
观察者模式可以实现监听器机制,当对象发生改变时,观察者能够立即察觉到并进行一些联动操作。很少用,大都是直接用监听器
#include <iostream>
#include <vector>
#include <functional>
using namespace std;// 观察者接口
class Observer {
public:virtual void update() = 0; // 纯虚函数,当对象有更新时,会回调此方法virtual ~Observer() {} // 虚析构函数
};// 被观察者的实体
class Subject {
private:vector<Observer*> observerSet; // 存储观察者的指针public:void observe(Observer* observer) { // 添加观察者observerSet.push_back(observer);}void modify() { // 模拟对象进行修改for (Observer* observer : observerSet) {observer->update(); // 当对象发生修改时,会通知所有的观察者,并进行方法回调}}~Subject() { // 析构函数,释放观察者指针for (Observer* observer : observerSet) {delete observer;}}
};// 主方法
int main() {Subject subject;// 创建观察者Observer* observer1 = new Observer {[]() { cout << "我是一号观察者!" << endl; }};Observer* observer2 = new Observer {[]() { cout << "我是二号观察者!" << endl; }};// 添加观察者subject.observe(observer1);subject.observe(observer2);// 修改对象,触发观察者回调subject.modify();return 0;
}
状态模式
根据不同的状态执行不同的行为
水在不同的温度状态会随之改变,程序也可以达到某种状态后就执行不同的行为
#include <iostream>
using namespace std;// 枚举状态
enum State {NORMAL,LAZY
};// 实体类
class Student {
private:State state; // 使用一个成员来存储状态public:void setState(State state) {this->state = state;}void study() {switch (state) { // 根据不同的状态,学习方法会有不同的结果case LAZY:cout << "只要我不努力,老板就别想过上想要的生活,开摆!" << endl;break;case NORMAL:cout << "拼搏百天,我要上清华大学!" << endl;break;}}
};// 主方法
int main() {Student student;student.setState(NORMAL); // 先正常模式student.study();student.setState(LAZY); // 开启摆烂模式student.study();return 0;
}
策略模式
和状态模式代码一样。但状态模式思想是:状态是先天的设定,就像水不同温度状态不同。而策略模式思想是:策略需要根据不同情况制定的。
举例:简单的数组排列
//策略接口(模板方法模式)
public interface Strategy { //策略接口,不同的策略实现也不同Strategy SINGLE = Arrays::sort; //单线程排序方案Strategy PARALLEL = Arrays::parallelSort; //并行排序方案void sort(int[] array);
}//排序类
public class Sorter {private Strategy strategy; //策略public void setStrategy(Strategy strategy) {this.strategy = strategy;}public void sort(int[] array){strategy.sort(array);}
}//主方法
public static void main(String[] args) {Sorter sorter = new Sorter();sorter.setStrategy(Strategy.PARALLEL); //指定为并行排序方案sorter.sort(new int[]{9, 2, 4, 5, 1, 0, 3, 7});
}
访问者模式
一件事情,一个访问接口。访问者实现该接口,但不同访问者关心的事情着重点不同
#include <iostream>
#include <string>
using namespace std;// 奖实体类
class Prize {
public:string name; // 比赛名称string level; // 等级Prize(const string& name, const string& level) : name(name), level(level) {}string getName() const {return name;}string getLevel() const {return level;}
};// 访问者接口
class Visitor {
public:virtual void visit(const Prize& prize) = 0; // visit 方法来访问我们的奖项virtual ~Visitor() {} // 虚析构函数
};// 不同访问者
class Teacher : public Visitor { // 指导老师作为一个访问者
public:void visit(const Prize& prize) override { // 它只关心你得了什么奖以及是几等奖,这也关乎老师的荣誉cout << "你得奖是什么奖?" << prize.getName() << endl;cout << "你得了几等奖?" << prize.getLevel() << endl;}
};class Boss : public Visitor { // 你的公司老板作为一个访问者
public:void visit(const Prize& prize) override { // 你的老板只关心这些能不能为公司带来什么效益,奖本身并不重要cout << "你的奖项大么,能够为公司带来什么效益么?" << endl;cout << "还不如老老实实加班给我多干干,别去搞这些没用的" << endl;}
};// 主方法
int main() {Prize prize("ACM国际大学生程序设计竞赛", "一等奖");Teacher teacher;Boss boss;teacher.visit(prize);boss.visit(prize);return 0;
}