文章目录
一、面向对象的优点和缺点
1.1 回答重点
优点:
模块化、代码重用性高,便于维护和扩展。
封装性提高了代码的安全性和稳定性。
通过多态和继承提高了代码的灵活性和扩展性。
模拟现实世界,适合处理复杂问题。
缺点:
性能开销较大,尤其在高性能要求的环境中。
代码复杂性较高,深层继承和耦合关系可能难以维护。
学习曲线较陡,面向对象的概念对于初学者较难理解。
并不适合所有问题领域,某些场景下面向过程或函数式编程更为合适。
面向对象编程在构建大型、复杂的系统中有明显优势,但开发时应避免过度设计,并在性能要求高的场合慎重选择。
1.2 扩展知识
面向对象编程的优点
模块化和代码重用:
面向对象通过类和对象来组织代码,每个类封装了特定的功能。类可以被复用,避免重复代码。这种模块化使得开发和维护更加简便。
继承(Inheritance)允许子类继承父类的属性和行为,进一步提升代码的复用性。
代码可维护性:
OOP 强调封装(Encapsulation),使得对象的内部实现细节对外界隐藏,只通过公开的接口访问对象。这降低了代码复杂性,提高了系统的可维护性。
当代码需要修改时,封装的属性和方法减少了对其他模块的影响,降低了修改代码时产生的副作用。
提高代码的灵活性和可扩展性:
通过多态性(Polymorphism),不同对象可以以一致的方式对外提供接口,而实际的行为可以根据对象的具体类型有所不同。这使得代码更加灵活,可以根据需求扩展,而不需要修改现有代码。
抽象类和接口允许程序员定义抽象的行为,从而在系统需要扩展时,提供新的实现类,保持系统的灵活性。
提高软件开发效率:
OOP 的模块化和封装使得团队协作更为高效,不同团队成员可以分别负责不同类的开发。
开发大型项目时,面向对象编程的结构化设计使得系统更加直观易懂,开发人员可以清晰地看到系统的设计结构。
自然模拟现实世界问题:
- 面向对象的模型更贴近现实世界的事物和行为,使得复杂问题的建模变得直观。开发人员可以通过对象、属性和方法更自然地解决复杂问题。
面向对象编程的缺点
性能开销:
面向对象的抽象层次较高,带来了性能上的开销。每个对象的创建和销毁都需要内存分配和垃圾回收管理,方法调用也涉及更多的间接操作,可能会影响系统的性能。
尤其是在高性能和资源受限的应用(如嵌入式系统)中,OOP 的性能可能不如基于过程的编程效率高。
复杂性:
OOP 的设计思想和机制(如继承、封装、多态等)使得代码结构复杂,尤其是在处理大型系统或深层继承关系时,理解类之间的关系可能变得困难。
过度使用继承和多态可能导致系统变得难以理解和维护(如“继承层次过深”问题)。
类的依赖性和耦合性:
类与类之间的依赖关系可能变得复杂,如果一个类的设计不合理,可能会导致高度耦合,降低了系统的灵活性和可扩展性。类之间的强耦合会导致修改一个类时,不得不修改依赖该类的其他类。
在设计大型系统时,需要仔细考虑类之间的依赖性,以避免紧耦合的结构。
学习曲线陡峭:
对于初学者来说,面向对象的概念(如类、对象、继承、多态等)并不直观,学习 OOP 需要一定的时间和经验。
与面向过程编程相比,OOP 的设计模式、抽象概念等可能更难掌握。
不适合所有问题领域:
- OOP 更适合复杂、需要模拟现实世界对象关系的问题领域,对于某些计算密集型或简单任务的应用,面向对象可能显得过于复杂,反而不如函数式编程或面向过程编程来得简洁高效。
二、面向对象的三大特点
2.1 回答重点
封装:
就是将客观事物抽象为逻辑实体,实体的属性和功能相结合,形成一个有机的整体。并对实体的属性和功能实现进行访问控制,向信任的实体开放,对不信任的实体隐藏。,通过开放的外部接口即可访问,无需知道功能如何实现。
其目的是:
可隐藏实体实现的细节。
提高安全性,设定访问控制,只允许具有特定权限的使用者调用。
简化编程,调用方无需知道功能是怎么实现的,即可调用。
继承:
在继承机制下形成有层级的类,使得低层级的类可以延用高层级类的特征和方法。
继承的实现方式有两种:实现继承、接口继承。
实现继承:直接使用基类公开的属性和方法,无需额外编码。
接口继承:仅使用接口公开的属性和方法名称,需要子类实现。
也就是说,继承有以下目的:
复用代码,减少类的冗余代码,减少开发工作量。
使得类与类之间产生关系,为多态的实现打下基础。
多态:
是指一个类的同名方法,在不同情况下的实现细节不同。多态机制实现不同的内部实现结构共用同一个外部接口。
也就是说,多态有以下目的:
一个外部接口可被多个同类使用。
不同对象调用同个方法,可有不同实现。
2.2 扩展知识
pass
三、设计模式的六大原则
3.1 回答重点
3.1.1 单一职责原则(Single Responsibility Principle, SRP)
定义:一个类应该仅有一个引起它变化的原因,即一个类只负责一项具体职责。
核心:降低类的复杂度,避免一个类承担过多职责导致的 “牵一发而动全身”。
// 错误示例:一个类同时处理订单计算和日志记录(两个职责)
class Order {
public:
void calculateTotal() { /* 计算订单总额 */ }
void saveToFile() { /* 保存订单到文件 */ }
void logOperation() { /* 记录操作日志 */ } // 不属于订单核心职责
};
// 正确示例:拆分日志职责到专门的类
class Logger { // 单一职责:仅处理日志
public:
static void log(const std::string& msg) { /* 写日志到文件 */ }
};
class Order { // 单一职责:仅处理订单业务
public:
void calculateTotal() { /* 计算总额 */ }
void saveToFile() {
Logger::log("订单已保存"); // 委托日志类处理日志
}
};
3.1.2 开放 - 封闭原则(Open-Closed Principle, OCP)
定义:软件实体(类、模块、函数等)应对扩展开放,对修改关闭。即新增功能时应通过扩展实现,而非修改原有代码。
核心:通过抽象隔离变化,提高系统稳定性。
// 抽象基类(定义稳定接口,对修改关闭)
class Payment {
public:
virtual void pay(double amount) = 0; // 支付接口
virtual ~Payment() = default;
};
// 具体实现:支付宝(现有功能)
class Alipay : public Payment {
public:
void pay(double amount) override {
std::cout << "支付宝支付:" << amount << "元\n";
}
};
// 扩展新功能:微信支付(无需修改原有代码,对扩展开放)
class WechatPay : public Payment {
public:
void pay(double amount) override {
std::cout << "微信支付:" << amount << "元\n";
}
};
// 支付管理器(依赖抽象,无需修改)
class PaymentManager {
public:
void processPayment(Payment* payment, double amount) {
payment->pay(amount); // 自动适配新支付方式
}
};
3.1.3 里氏替换原则(Liskov Substitution Principle, LSP)
定义:所有引用基类的地方必须能透明地使用其子类的对象,且替换后不改变原有程序的正确性。
核心:子类必须遵守基类的行为约定,确保继承体系的逻辑一致性。
// 基类:Rectangle(长方形)
class Rectangle {
protected:
int width, height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
virtual void setWidth(int w) { width = w; }
virtual void setHeight(int h) { height = h; }
int getArea() { return width * height; }
};
// 错误示例:Square(正方形)继承Rectangle,违反LSP
class Square : public Rectangle {
public:
Square(int s) : Rectangle(s, s) {}
// 子类修改了基类的行为约定(宽高必须相等)
void setWidth(int w) override { width = height = w; }
void setHeight(int h) override { width = height = h; }
};
// 问题:传入Square会导致逻辑错误
void testRectangle(Rectangle* r) {
r->setWidth(2);
r->setHeight(3);
// 预期面积6,但Square实际返回9(2×2被3覆盖为3×3)
std::cout << "面积:" << r->getArea() << "\n";
}
// 正确做法:Square不继承Rectangle,单独设计或引入新基类
3.1.4 依赖倒置原则(Dependency Inversion Principle, DIP)
定义:高层模块不应依赖低层模块,两者都应依赖抽象;抽象不应依赖细节,细节应依赖抽象。
核心:通过抽象接口解耦高层与低层,提高系统灵活性。
// 抽象接口(抽象,不依赖细节)
class IDataStorage {
public:
virtual void save(const std::string& data) = 0;
virtual ~IDataStorage() = default;
};
// 低层模块:具体实现(依赖抽象)
class FileStorage : public IDataStorage {
public:
void save(const std::string& data) override {
std::cout << "保存到文件:" << data << "\n";
}
};
class DatabaseStorage : public IDataStorage {
public:
void save(const std::string& data) override {
std::cout << "保存到数据库:" << data << "\n";
}
};
// 高层模块:业务逻辑(依赖抽象,不依赖具体实现)
class UserService {
private:
IDataStorage* storage; // 依赖接口,而非具体存储方式
public:
// 构造注入,灵活切换存储方式
UserService(IDataStorage* s) : storage(s) {}
void saveUser(const std::string& user) {
storage->save(user); // 无需关心具体存储细节
}
};
3.1.5 接口隔离原则(Interface Segregation Principle, ISP)
定义:客户端不应被迫依赖它不需要的接口。即应将庞大的接口拆分为多个小而专一的接口,让客户端只依赖必要的接口。
核心:避免 “胖接口” 导致的不必要依赖。
// 错误示例:臃肿的接口包含所有可能方法
class IWorker {
public:
virtual void work() = 0; // 工作
virtual void eat() = 0; // 吃饭(机器人不需要)
virtual void charge() = 0; // 充电(人类不需要)
};
// 人类被迫实现不需要的charge()
class HumanWorker : public IWorker {
public:
void work() override { /* 工作 */ }
void eat() override { /* 吃饭 */ }
void charge() override { /* 空实现或异常,不合理 */ }
};
// 正确示例:拆分接口为专用接口
class IWorkable {
public:
virtual void work() = 0;
};
class IEatable {
public:
virtual void eat() = 0;
};
class IChargeable {
public:
virtual void charge() = 0;
};
// 人类实现需要的接口
class HumanWorker : public IWorkable, public IEatable {
public:
void work() override { /* 工作 */ }
void eat() override { /* 吃饭 */ }
};
// 机器人实现需要的接口
class RobotWorker : public IWorkable, public IChargeable {
public:
void work() override { /* 工作 */ }
void charge() override { /* 充电 */ }
};
3.1.6 迪米特法则(最少知识原则)(Law of Demeter, LoD)
定义:一个对象应该对其他对象保持最少的了解,只与直接朋友(成员变量、方法参数、返回值)通信,避免访问 “朋友的朋友”。
**大话设计模式:**如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某个方法的话,可以通过第三者转发这个调用。
核心:减少对象间的耦合,降低系统复杂度。
class Department { // 部门类
private:
std::string name;
public:
Department(const std::string& n) : name(n) {}
std::string getName() const { return name; }
};
class Employee { // 员工类(直接朋友:Department)
private:
Department* dept;
public:
Employee(Department* d) : dept(d) {}
// 错误:暴露内部依赖,让外部直接访问"朋友的朋友"
Department* getDepartment() const { return dept; }
// 正确:提供高层接口,隐藏内部细节
std::string getDepartmentName() const {
return dept->getName(); // 内部处理与Department的交互
}
};
// 客户端代码
void printDept(Employee* emp) {
// 错误:访问"朋友的朋友"(Employee的朋友是Department)
// std::cout << emp->getDepartment()->getName() << "\n";
// 正确:只与直接朋友Employee通信
std::cout << emp->getDepartmentName() << "\n";
}
总结:这六大原则的核心是 “高内聚、低耦合”。单一职责和接口隔离聚焦于 “如何拆分”,开放 - 封闭和依赖倒置指导 “如何扩展”,里氏替换保证 “继承安全”,迪米特法则控制 “交互范围”,共同支撑可维护、可扩展的代码设计。
3.2 扩展知识
pass
四、单例模式
4.1 回答重点
单例模式是一种创建型设计模式,其核心是保证一个类在整个系统中仅有一个实例,并提供一个全局访问点。这种模式常用于管理共享资源(如配置文件、数据库连接池),避免资源竞争或重复初始化。
核心特点
- 唯一实例:类自身控制实例的创建,确保只有一个实例存在。
- 全局访问:提供静态方法(如
getInstance()
)供外部获取实例,无需频繁传递对象引用。 - 延迟初始化:通常在首次使用时才创建实例,避免不必要的资源消耗。
4.1.1 常见实现方式(C++)
1. 饿汉式(线程安全,但可能浪费资源)
class Singleton {
private:
// 私有构造函数:禁止外部实例化
Singleton() { /* 初始化资源 */ }
// 禁止拷贝和移动
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 静态实例(程序启动时初始化)
static Singleton instance;
public:
// 全局访问点
static Singleton& getInstance() {
return instance;
}
};
// 类外初始化静态成员
Singleton Singleton::instance;
- 优点:实现简单,线程安全(C++ 中全局变量初始化在主线程完成)。
- 缺点:无论是否使用,实例都会在程序启动时创建,可能浪费资源(如初始化耗时操作)。
2. 懒汉式(C++11 后线程安全,推荐)
class Singleton {
private:
Singleton() { /* 初始化资源 */ }
// 禁止拷贝和移动
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
// 全局访问点:局部静态变量(C++11后初始化线程安全)
static Singleton& getInstance() {
static Singleton instance; // 首次调用时初始化
return instance;
}
};
- 优点:延迟初始化(首次使用时创建),C++11 标准保证局部静态变量初始化的线程安全性,无需手动加锁。
- 缺点:依赖 C++11 及以上标准,旧编译器可能不支持。
3. 双重检查锁定(DCL,兼容旧标准)
#include <mutex>
class Singleton {
private:
Singleton() { /* 初始化资源 */ }
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 静态指针(volatile避免编译器优化)
static volatile Singleton* instance;
static std::mutex mtx;
public:
static Singleton& getInstance() {
// 第一次检查:避免每次加锁(提高性能)
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx); // 加锁
// 第二次检查:防止多线程同时通过第一次检查
if (instance == nullptr) {
instance = new Singleton();
}
}
return *const_cast<Singleton*>(instance);
}
};
// 类外初始化
volatile Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
- 优点:兼容 C++11 前标准,线程安全且延迟初始化。
- 缺点:实现复杂,需注意内存可见性(
volatile
)和锁的开销。
4.1.2 适用场景
- 资源管理器:如日志管理器、配置管理器(确保全局唯一的配置源)。
- 共享资源访问:如数据库连接池(控制连接数量,避免重复创建连接)。
- 工具类:无状态的工具方法集合(如数学工具类,无需多个实例)。
- 优点:
- 确保唯一实例,减少资源占用;
- 全局访问点简化了对象获取,避免参数传递冗余。
- 缺点:
- 违背单一职责原则(既负责自身业务,又负责实例管理);
- 可能隐藏依赖关系(代码中直接调用
getInstance()
,难以追踪依赖); - 测试困难(单例生命周期与程序一致,难以模拟不同测试环境)。
单例模式的核心是 “唯一实例 + 全局访问”,C++ 中推荐使用懒汉式(局部静态变量) 实现(C++11 及以上),简洁且线程安全。其适用场景集中在共享资源管理,但需注意避免过度使用,以免导致代码耦合度升高和测试困难。
五、工厂方法模式
5.1 回答重点
工厂方法模式是一种创建型设计模式,简单工厂模式只有一个工厂类,负责创建所有产品,如果要添加新的产品,通常需要修改工厂类的代码。而工厂方法模式引入了抽象工厂和具体工厂的概念,每个具体工厂只负责创建一个具体产品,添加新的产品只需要添加新的工厂类而无需修改原来的代码,这样就使得产品的生产更加灵活,支持扩展,符合开闭原则。
工厂方法模式分为以下几个角色:
抽象工厂:一个接口,包含一个抽象的工厂方法(用于创建产品对象)。
具体工厂:实现抽象工厂接口,创建具体的产品。
抽象产品:定义产品的接口。
具体产品:实现抽象产品接口,是工厂创建的对象。
应用场景:
工厂方法模式使得每个工厂类的职责单一,每个工厂只负责创建一种产品,当创建对象涉及一系列复杂的初始化逻辑,而这些逻辑在不同的子类中可能有所不同时,可以使用工厂方法模式将这些初始化逻辑封装在子类的工厂中。在现有的工具、库中,工厂方法模式也有广泛的应用,比如:
Spring 框架中的 Bean 工厂:通过配置文件或注解,Spring 可以根据配置信息动态地创建和管理对象。
JDBC 中的 Connection 工厂:在 Java 数据库连接中,
DriverManager
使用工厂方法模式来创建数据库连接。不同的数据库驱动(如 MySQL、PostgreSQL 等)都有对应的工厂来创建连接。
示例代码:
// 抽象积木接口
class Block {
public:
virtual void produce() = 0;
};
// 具体圆形积木实现
class CircleBlock : public Block {
public:
void produce() override {
std::cout << "Circle Block" << std::endl;
}
};
// 具体方形积木实现
class SquareBlock : public Block {
public:
void produce() override {
std::cout << "Square Block" << std::endl;
}
};
// 抽象积木工厂接口
class BlockFactory {
public:
virtual Block* createBlock() = 0;
};
// 具体圆形积木工厂实现
class CircleBlockFactory : public BlockFactory {
public:
Block* createBlock() override {
return new CircleBlock();
}
};
// 具体方形积木工厂实现
class SquareBlockFactory : public BlockFactory {
public:
Block* createBlock() override {
return new SquareBlock();
}
};
// 积木工厂系统
class BlockFactorySystem {
private:
std::vector<Block*> blocks;
public:
void produceBlocks(BlockFactory* factory, int quantity) {
for (int i = 0; i < quantity; i++) {
Block* block = factory->createBlock();
blocks.push_back(block);
block->produce();
}
}
const std::vector<Block*>& getBlocks() const {
return blocks;
}
~BlockFactorySystem() {
for (Block* block : blocks) {
delete block;
}
}
};
int main() {
// 创建积木工厂系统
BlockFactorySystem factorySystem;
// 读取生产次数int productionCount;
std::cin >> productionCount;
// 读取每次生产的积木类型和数量for (int i = 0; i < productionCount; i++) {
std::string blockType;
int quantity;
std::cin >> blockType >> quantity;
if (blockType == "Circle") {
factorySystem.produceBlocks(new CircleBlockFactory(), quantity);
} else if (blockType == "Square") {
factorySystem.produceBlocks(new SquareBlockFactory(), quantity);
}
}
return 0;
}
5.2 扩展知识
工厂模式是创建型设计模式的一类,核心是将对象的创建与使用分离,通过 “工厂” 统一管理对象创建逻辑,降低代码耦合。它包含三种具体形式:简单工厂模式、工厂方法模式和抽象工厂模式,适用场景各有不同,下面分别介绍:
5.2.1 简单工厂模式(Simple Factory)
定义:
- 由一个工厂类根据传入的参数,决定创建哪一种具体产品的实例。它不属于 GoF 的 23 种设计模式,但作为工厂模式的基础,应用广泛。
结构:
- 具体产品:实现同一接口的不同产品(如
ProductA
、ProductB
); - 工厂类:包含静态方法,根据参数创建并返回具体产品实例。
示例(计算器工厂):
// 抽象产品:运算
class Operation {
public:
virtual double calculate(double a, double b) = 0;
virtual ~Operation() = default;
};
// 具体产品:加法
class AddOperation : public Operation {
public:
double calculate(double a, double b) override { return a + b; }
};
// 具体产品:乘法
class MulOperation : public Operation {
public:
double calculate(double a, double b) override { return a * b; }
};
// 工厂类:根据参数创建产品
class OperationFactory {
public:
static Operation* createOperation(char op) {
switch (op) {
case '+': return new AddOperation();
case '*': return new MulOperation();
default: throw std::invalid_argument("无效运算符");
}
}
};
// 客户端
int main() {
Operation* op = OperationFactory::createOperation('+');
std::cout << op->calculate(2, 3) << "\n"; // 输出5
delete op;
return 0;
}
优缺点:
- 优点:简单直观,客户端无需知道产品创建细节;
- 缺点:新增产品需修改工厂类(违反开放 - 封闭原则),工厂类职责过重,适合产品类型较少的场景。
5.2.2 工厂方法模式(Factory Method)
**定义:**定义一个创建产品的抽象接口(工厂),让子类(具体工厂)决定实例化哪种产品。即 “一个产品对应一个工厂”,将创建逻辑延迟到子类。
结构:
- 抽象产品:所有产品的公共接口;
- 具体产品:实现抽象产品的具体类;
- 抽象工厂:声明创建产品的纯虚方法(工厂方法);
- 具体工厂:每个具体工厂对应一种产品,实现工厂方法。
// 抽象产品:文档
class Document {
public:
virtual void open() = 0;
virtual ~Document() = default;
};
// 具体产品:文本文档
class TextDoc : public Document {
public:
void open() override { std::cout << "打开文本文档\n"; }
};
// 具体产品:表格文档
class SheetDoc : public Document {
public:
void open() override { std::cout << "打开表格文档\n"; }
};
// 抽象工厂
class DocumentFactory {
public:
virtual Document* createDocument() = 0; // 工厂方法
virtual ~DocumentFactory() = default;
};
// 具体工厂:文本工厂
class TextFactory : public DocumentFactory {
public:
Document* createDocument() override { return new TextDoc(); }
};
// 具体工厂:表格工厂
class SheetFactory : public DocumentFactory {
public:
Document* createDocument() override { return new SheetDoc(); }
};
// 客户端
int main() {
DocumentFactory* factory = new TextFactory();
Document* doc = factory->createDocument();
doc->open(); // 输出:打开文本文档
delete doc;
delete factory;
return 0;
}
优缺点:
- 优点:新增产品只需添加具体产品和对应工厂(符合开放 - 封闭原则),职责单一;
- 缺点:类数量增多(每增一个产品对应一个工厂),适合产品类型较多且可能扩展的场景。
5.2.3 抽象工厂模式(Abstract Factory)
定义:提供一个接口,用于创建一系列相关或相互依赖的产品族,而无需指定具体类。即 “一个工厂生产多个关联产品”,解决产品族的创建问题。
结构:
- 抽象产品族:多个相关产品的接口(如
ProductA
、ProductB
组成一个产品族); - 具体产品族:不同品牌 / 风格的产品族实现(如
Brand1ProductA
、Brand1ProductB
); - 抽象工厂:声明创建每个产品的方法(覆盖产品族中的所有产品);
- 具体工厂:每个工厂对应一个产品族,实现所有产品的创建方法。
C++ 示例(跨平台 UI 组件)
// 抽象产品A:按钮
class Button {
public:
virtual void render() = 0;
virtual ~Button() = default;
};
// 抽象产品B:文本框
class TextBox {
public:
virtual void render() = 0;
virtual ~TextBox() = default;
};
// 具体产品族1:Windows风格
class WinButton : public Button {
public:
void render() override { std::cout << "Windows按钮\n"; }
};
class WinTextBox : public TextBox {
public:
void render() override { std::cout << "Windows文本框\n"; }
};
// 具体产品族2:Mac风格
class MacButton : public Button {
public:
void render() override { std::cout << "Mac按钮\n"; }
};
class MacTextBox : public TextBox {
public:
void render() override { std::cout << "Mac文本框\n"; }
};
// 抽象工厂:声明创建产品族的方法
class UIFactory {
public:
virtual Button* createButton() = 0;
virtual TextBox* createTextBox() = 0;
virtual ~UIFactory() = default;
};
// 具体工厂1:Windows工厂(生产Windows产品族)
class WinFactory : public UIFactory {
public:
Button* createButton() override { return new WinButton(); }
TextBox* createTextBox() override { return new WinTextBox(); }
};
// 具体工厂2:Mac工厂(生产Mac产品族)
class MacFactory : public UIFactory {
public:
Button* createButton() override { return new MacButton(); }
TextBox* createTextBox() override { return new MacTextBox(); }
};
// 客户端
int main() {
UIFactory* factory = new WinFactory(); // 切换为MacFactory即可换风格
Button* btn = factory->createButton();
TextBox* txt = factory->createTextBox();
btn->render(); // 输出:Windows按钮
txt->render(); // 输出:Windows文本框
delete btn;
delete txt;
delete factory;
return 0;
}
优缺点:
- 优点:保证产品族内的产品兼容性(同一工厂生产的产品必然匹配),切换产品族只需更换工厂;
- 缺点:新增产品族成员(如增加 “复选框”)需修改所有工厂类(违反开放 - 封闭原则),适合产品族固定且需整体切换的场景(如跨平台开发)。
三种模式的对比与适用场景
模式 | 核心特点 | 适用场景 | 扩展性(新增产品) |
---|---|---|---|
简单工厂模式 | 单一工厂根据参数创建产品 | 产品类型少、变化少(如工具类) | 差(需修改工厂) |
工厂方法模式 | 一个产品对应一个工厂 | 产品类型多、可能扩展(如文档类型) | 好(新增产品 + 工厂) |
抽象工厂模式 | 一个工厂生产产品族 | 需统一风格 / 品牌的相关产品(如跨平台 UI) | 产品族内扩展差,新增族好 |
面试总结:
工厂模式的核心是 “分离对象创建与使用”,三种形式从简单到复杂:
- 简单工厂用一个工厂管理所有产品,适合简单场景;
- 工厂方法用 “一对一” 的工厂 - 产品关系,平衡扩展性和复杂度;
- 抽象工厂解决 “产品族” 创建问题,适合关联产品的统一管理。
选择时需根据产品数量、关联性和扩展需求决定,核心目标是降低耦合,提高代码灵活性。
六、适配器模式(Adapter)
6.1 回答重点
**大话设计模式:**适配器模式就是,将一个类的接口转化给客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式Adapter
是一种结构型设计模式,它可以将一个类的接口转换成客户希望的另一个接口,主要目的是充当两个不同接口之间的桥梁,使得原本接口不兼容的类能够一起工作。
适配器可以分为以下几个基本角色:
目标接口
Target
: 客户端希望使用的接口适配器类
Adapter
: 实现客户端使用的目标接口,持有一个需要适配的类实例。被适配者
Adaptee
: 需要被适配的类
应用场景:
在开发过程中,适配器模式往往扮演者“补救”和“扩展”的角色:
当使用一个已经存在的类,但是它的接口与你的代码不兼容时,可以使用适配器模式。
在系统扩展阶段需要增加新的类时,并且类的接口和系统现有的类不一致时,可以使用适配器模式。
使用适配器模式可以将客户端代码与具体的类解耦,客户端不需要知道被适配者的细节,客户端代码也不需要修改,这使得它具有良好的扩展性,但是这也势必导致系统变得更加复杂。
具体来说,适配器模式有着以下应用:
不同的项目和库可能使用不同的日志框架,不同的日志框架提供的API也不同,因此引入了适配器模式使得不同的API适配为统一接口。
Spring MVC中,
HandlerAdapter
接口是适配器模式的一种应用。它负责将处理器(Handler)适配到框架中,使得不同类型的处理器能够统一处理请求。在
.NET
中,DataAdapter
用于在数据源(如数据库)和DataSet
之间建立适配器,将数据从数据源适配到DataSet
中,以便在.NET应用程序中使用。
示例代码:
#include <iostream>// USB 接口class USB {
public:virtual void charge() = 0;
};
// TypeC 接口class TypeC {
public:virtual void chargeWithTypeC() = 0;
};
// 适配器类class TypeCAdapter : public USB {
private:
TypeC* typeC;
public:
TypeCAdapter(TypeC* typeC) : typeC(typeC) {}
void charge() override {
typeC->chargeWithTypeC();
}
};
// 新电脑类,使用 TypeC 接口
class NewComputer : public TypeC {
public:
void chargeWithTypeC() override {
std::cout << "TypeC" << std::endl;
}
};
// 适配器充电器类,使用 USB 接口
class AdapterCharger : public USB {
public:void charge() override {
std::cout << "USB Adapter" << std::endl;
}
};
int main() {
// 读取连接次数int N;
std::cin >> N;
std::cin.ignore(); // 消耗换行符for (int i = 0; i < N; i++) {
// 读取用户选择int choice;
std::cin >> choice;
// 根据用户的选择创建相应对象
if (choice == 1) {
TypeC* newComputer = new NewComputer();
newComputer->chargeWithTypeC();
delete newComputer;
} else if (choice == 2) {
USB* usbAdapter = new AdapterCharger();
usbAdapter->charge();
delete usbAdapter;
}
}
return 0;
}
6.2 扩展知识
适配器模式(Adapter Pattern)是一种结构型设计模式,用于解决两个现有接口不兼容的问题。它通过创建一个中间层(适配器),将一个类的接口转换成客户端所期望的另一个接口,从而使原本因接口不匹配而无法协作的类能够一起工作。简单来说,适配器就像日常生活中的电源适配器,它能将 220V 的交流电转换为电子设备需要的低压直流电,让不同接口的设备可以正常工作。
适配器模式的核心角色:
- 目标接口(Target):客户端期望的接口标准
- 适配者(Adaptee):已存在但接口不兼容的类
- 适配器(Adapter):实现目标接口,并包装适配者,完成接口转换
#include <iostream>
#include <string>
// 1. 目标接口(新系统期望的日志接口)
class NewLogger {
public:
virtual ~NewLogger() = default;
virtual void logInfo(const std::string& message) = 0;
virtual void logError(const std::string& message) = 0;
};
// 2. 适配者(旧系统的日志类,接口不兼容)
class LegacyLogger {
public:
// 旧系统的日志方法,接口与新系统不同
void writeLog(const std::string& level, const std::string& message) {
std::cout << "Legacy Log [" << level << "]: " << message << std::endl;
}
};
// 3. 适配器(将旧日志接口转换为新日志接口)
class LoggerAdapter : public NewLogger {
private:
LegacyLogger* legacyLogger; // 持有适配者对象
public:
LoggerAdapter(LegacyLogger* logger) : legacyLogger(logger) {}
// 实现新接口的方法,内部转换为旧接口的调用
void logInfo(const std::string& message) override {
if (legacyLogger) {
legacyLogger->writeLog("INFO", message);
}
}
void logError(const std::string& message) override {
if (legacyLogger) {
legacyLogger->writeLog("ERROR", message);
}
}
};
// 4. 客户端代码(只依赖新接口)
void clientCode(NewLogger* logger) {
logger->logInfo("系统启动成功");
logger->logError("文件读取失败");
}
int main() {
// 创建旧系统日志对象
LegacyLogger* legacyLogger = new LegacyLogger();
// 创建适配器,包装旧日志对象
NewLogger* logger = new LoggerAdapter(legacyLogger);
// 客户端使用新接口,无需关心内部实现
clientCode(logger);
// 清理资源
delete logger;
delete legacyLogger;
return 0;
}
- NewLogger:这是新系统期望的日志接口,定义了
logInfo
和logError
两个方法 - LegacyLogger:这是旧系统的日志类,它只有一个
writeLog
方法,接口与新系统不兼容 - LoggerAdapter:适配器类,实现了
NewLogger
接口,并内部持有LegacyLogger
对象,在实现新接口方法时,将其转换为对旧接口方法的调用 - 客户端代码:只依赖
NewLogger
接口,完全不需要知道LegacyLogger
的存在
Legacy Log [INFO]: 系统启动成功
Legacy Log [ERROR]: 文件读取失败
这种模式的优势在于:
- 复用了现有的
LegacyLogger
类,无需修改其代码 - 客户端代码无需修改即可使用旧系统的功能
- 降低了新系统与旧系统之间的耦合度
- 符合 “开闭原则”,扩展新的适配器很方便
适配器模式特别适合在集成第三方库、重构旧系统或需要复用现有类但接口不匹配的场景中使用。
七、代理模式
7.1 回答重点
大话设计模式: 为其他对象提供一种代理,以控制这个对象的访问。
代理模式Proxy Pattern
是一种结构型设计模式,用于控制对其他对象的访问。
在代理模式中,允许一个对象(代理)充当另一个对象(真实对象)的接口,以控制对这个对象的访问。通常用于在访问某个对象时引入一些间接层(中介的作用),这样可以在访问对象时添加额外的控制逻辑,比如限制访问权限,延迟加载。
基本结构:
Subject(抽象主题): 抽象类,通过接口或抽象类声明真实主题和代理对象实现的业务方法。
RealSubject(真实主题):定义了Proxy所代表的真实对象,是客户端最终要访问的对象。
Proxy(代理):包含一个引用,该引用可以是RealSubject的实例,控制对RealSubject的访问,并可能负责创建和删除RealSubject的实例。
应用场景:
- 比如说有一个文件加载的场景,为了避免直接访问“文件”对象,我们可以新增一个代理对象,代理对象中有一个对“文件对象”的引用,在代理对象的
load
方法中,可以在访问真实的文件对象之前进行一些操作,比如权限检查,然后调用真实文件对象的load
方法,最后在访问真实对象后进行其他操作,比如记录访问日志。
示例代码:
// 抽象主题
class HomePurchase {
public:
virtual void requestPurchase(int area) = 0;
};
// 真实主题
class HomeBuyer : public HomePurchase {
public:
void requestPurchase(int area) override {
std::cout << "YES" << std::endl;
}
};
// 代理类
class HomeAgentProxy : public HomePurchase {
private:
HomeBuyer homeBuyer;
public:
void requestPurchase(int area) override {
if (area > 100) {
homeBuyer.requestPurchase(area);
} else {
std::cout << "NO" << std::endl;
}
}
};
int main() {
HomePurchase* buyerProxy = new HomeAgentProxy();
int n;
std::cin >> n;
for (int i = 0; i < n; i++) {
int area;
std::cin >> area;
buyerProxy->requestPurchase(area);
}
delete buyerProxy;
return 0;
}
7.2 扩展知识
代理模式(Proxy Pattern)是一种结构型设计模式,它通过创建一个代理对象来控制对原始对象的访问。代理对象可以在不改变原始对象接口的前提下,为其增加额外功能(如权限控制、缓存、延迟加载等),同时负责将请求转发给原始对象。
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
// 1. 抽象主题:图片接口
class Image {
public:
virtual ~Image() = default;
virtual void display() = 0; // 显示图片
virtual std::string getFileName() const = 0; // 获取文件名
};
// 2. 真实主题:大型图片(加载成本高)
class RealImage : public Image {
private:
std::string fileName;
// 模拟加载大型图片的耗时操作
void loadFromDisk() const {
std::cout << "正在从磁盘加载图片: " << fileName << " (耗时操作)...\n";
std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟延迟
}
public:
RealImage(const std::string& name) : fileName(name) {
loadFromDisk(); // 真实图片创建时立即加载
}
void display() override {
std::cout << "显示图片: " << fileName << "\n";
}
std::string getFileName() const override {
return fileName;
}
};
// 3. 代理:图片代理(实现延迟加载和日志记录)
class ImageProxy : public Image {
private:
RealImage* realImage; // 持有真实图片的引用
std::string fileName;
// 懒加载:需要时才创建真实图片
void ensureRealImageExists() {
if (!realImage) {
realImage = new RealImage(fileName);
}
}
public:
ImageProxy(const std::string& name) : realImage(nullptr), fileName(name) {
std::cout << "创建图片代理: " << fileName << " (未实际加载图片)\n";
}
~ImageProxy() {
delete realImage; // 代理负责管理真实对象的生命周期
}
void display() override {
// 1. 记录访问日志(额外功能)
std::cout << "[日志] 访问图片: " << fileName << "\n";
// 2. 确保真实图片已加载(延迟加载)
ensureRealImageExists();
// 3. 转发请求给真实对象
realImage->display();
}
std::string getFileName() const override {
return fileName;
}
};
// 客户端代码:使用代理与使用真实对象完全一致
void clientCode(Image* image) {
std::cout << "\n客户端操作: " << image->getFileName() << "\n";
image->display(); // 调用代理的display,实际会转发给真实对象
}
int main() {
// 创建代理(此时不会加载真实图片)
Image* proxy1 = new ImageProxy("风景.jpg");
Image* proxy2 = new ImageProxy("人物.png");
// 第一次显示图片:触发真实图片加载
clientCode(proxy1);
// 第二次显示同一图片:直接使用已加载的真实图片
clientCode(proxy1);
// 显示另一张图片
clientCode(proxy2);
// 清理资源
delete proxy1;
delete proxy2;
return 0;
}
简单来说,代理就像生活中的 “中介”—— 比如租房中介,它不直接提供房屋,但可以控制租客与房东的交互,并在过程中提供额外服务(筛选租客、签订合同等)。
核心角色:
- 抽象主题(Subject)
定义原始对象和代理对象共同遵循的接口,确保代理可被当作原始对象使用。 - 真实主题(Real Subject)
实际执行业务逻辑的原始对象,是代理的目标。 - 代理(Proxy)
实现抽象主题接口,内部持有真实主题的引用,负责在调用真实主题前后添加额外操作,并将请求转发给真实主题。
适用场景:
- 延迟加载:当原始对象创建成本高时,代理可在需要时才初始化真实对象(如大型图像加载)。
- 权限控制:代理可检查调用者权限,只允许授权者访问真实对象。
- 缓存:代理可缓存真实对象的计算结果,避免重复计算。
- 日志记录:代理可记录对真实对象的调用信息(如调用时间、参数等)。
C++ 代码示例
下面以 “图片查看器” 为例,展示代理模式的实现。当加载大型图片时,使用代理实现延迟加载和日志记录功能。
- Image:抽象主题接口,定义了
display()
和getFileName()
方法,保证代理和真实对象的接口一致性。 - RealImage:真实主题,代表需要加载的大型图片。其构造函数会调用
loadFromDisk()
模拟耗时的加载过程。 - ImageProxy:代理类,核心功能包括:
- 延迟加载:仅在第一次调用
display()
时才创建RealImage
对象,避免不必要的资源消耗。 - 日志记录:在转发请求前添加访问日志功能。
- 生命周期管理:代理负责创建和销毁真实对象,客户端无需关心。
- 延迟加载:仅在第一次调用
- 客户端代码:仅依赖
Image
接口,完全不知道代理的存在,符合 “依赖倒置原则”。
运行结果
创建图片代理: 风景.jpg (未实际加载图片)
创建图片代理: 人物.png (未实际加载图片)
客户端操作: 风景.jpg
[日志] 访问图片: 风景.jpg
正在从磁盘加载图片: 风景.jpg (耗时操作)...
显示图片: 风景.jpg
客户端操作: 风景.jpg
[日志] 访问图片: 风景.jpg
显示图片: 风景.jpg
客户端操作: 人物.png
[日志] 访问图片: 人物.png
正在从磁盘加载图片: 人物.png (耗时操作)...
显示图片: 人物.png
代理模式的优点
- 增强控制:可在不修改原始对象的情况下添加额外功能。
- 优化性能:通过延迟加载减少不必要的资源消耗。
- 符合开闭原则:新增代理不影响原始对象和客户端代码。
- 隔离性:客户端无需了解原始对象的实现细节,只需与代理交互。
常见的代理类型还包括远程代理(控制远程对象访问)、保护代理(权限控制)、智能引用代理(自动释放资源)等,核心思想都是通过代理对象间接访问原始对象并增强其功能。
八、观察者模式
8.1 回答重点
**大话设计模式:**观察者模式(发布-订阅模式)属于行为型模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象,当主题对象的状态发生变化时,所有依赖于它的观察者都得到通知并被自动更新。
基本结构:
主题Subject
, 一般会定义成一个接口,提供方法用于注册、删除和通知观察者,通常也包含一个状态,当状态发生改变时,通知所有的观察者。观察者Observer
: 观察者也需要实现一个接口,包含一个更新方法,在接收主题通知时执行对应的操作。具体主题ConcreteSubject
: 主题的具体实现, 维护一个观察者列表,包含了观察者的注册、删除和通知方法。具体观察者ConcreteObserver
: 观察者接口的具体实现,每个具体观察者都注册到具体主题中,当主题状态变化并通知到具体观察者,具体观察者进行处理。
应用场景:
观察者模式特别适用于一个对象的状态变化会影响到其他对象,并且希望这些对象在状态变化时能够自动更新的情况。 比如说在图形用户界面中,按钮、滑动条等组件的状态变化可能需要通知其他组件更新,这使得观察者模式被广泛应用于GUI框架,比如Java的Swing框架。
此外,观察者模式在前端开发和分布式系统中也有应用,比较典型的例子是前端框架Vue
, 当数据发生变化时,视图会自动更新。而在分布式系统中,观察者模式可以用于实现节点之间的消息通知机制,节点的状态变化将通知其他相关节点。
示例代码:
// 观察者接口class Observer {
public:virtual void update(int hour) = 0;
virtual ~Observer() = default; // 添加虚析构函数
};
// 主题接口class Subject {
public:virtual void registerObserver(Observer* observer) = 0;
virtual void removeObserver(Observer* observer) = 0;
virtual void notifyObservers() = 0;
virtual ~Subject() = default; // 添加虚析构函数
};
// 具体主题实现class Clock : public Subject {
private:
std::vector<Observer*> observers;
int hour;
public:Clock() : hour(0) {}
void registerObserver(Observer* observer) override {
observers.push_back(observer);
}
void removeObserver(Observer* observer) override {
auto it = std::find(observers.begin(), observers.end(), observer);
if (it != observers.end()) {
observers.erase(it);
}
}
void notifyObservers() override {
for (Observer* observer : observers) {
observer->update(hour);
}
}
// 添加获取观察者的函数
const std::vector<Observer*>& getObservers() const {
return observers;
}
void tick() {
hour = (hour + 1) % 24; // 模拟时间的推移notifyObservers();
}
};
// 具体观察者实现class Student : public Observer {
private:
std::string name;
public:Student(const std::string& name) : name(name) {}
void update(int hour) override {
std::cout << name << " " << hour << std::endl;
}
};
int main() {
// 读取学生数量int N;
std::cin >> N;
// 创建时钟
Clock clock;
// 注册学生观察者
for (int i = 0; i < N; i++) {
std::string studentName;
std::cin >> studentName;
clock.registerObserver(new Student(studentName));
}
// 读取时钟更新次数int updates;
std::cin >> updates;
// 模拟时钟每隔一个小时更新一次
for (int i = 0; i < updates; i++) {
clock.tick();
}
for (Observer* observer : clock.getObservers()) {
delete observer;
}
return 0;
}
8.2 扩展知识
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一对多依赖关系:当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)都会收到通知并自动更新。
这种模式就像订阅报纸 —— 你(观察者)订阅了报纸后,每当有新报纸出版(被观察者状态变化),报社就会收到报纸(自动更新),无需你主动去询问。
核心角色:
- Subject(被观察者 / 主题)
维护一个观察者列表,提供添加、删除观察者的方法,以及通知所有观察者的方法。 - Observer(观察者)
定义一个更新接口,当被观察者状态变化时,会调用此接口接收通知并更新自己。 - ConcreteSubject(具体被观察者)
实际的被观察者,当状态变化时,触发通知方法。 - ConcreteObserver(具体观察者)
实现观察者接口,定义收到通知后的具体处理逻辑。
适用场景:
- 当一个对象的变化需要联动影响多个其他对象时(如 GUI 中的事件响应)。
- 当需要降低对象间耦合度,让它们可以独立变化时。
- 当一个对象必须通知其他对象,但又不知道具体有多少对象需要通知时。
C++ 代码示例:
下面以 “气象站” 为例:气象站(被观察者)会定期更新温度,多个显示设备(观察者)需要实时显示最新温度。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
// 2. 观察者接口
class Observer {
public:
virtual ~Observer() = default;
// 接收更新的接口(参数为被观察者传递的状态)
virtual void update(float temperature) = 0;
};
// 1. 被观察者接口(主题)
class Subject {
public:
virtual ~Subject() = default;
// 添加观察者
virtual void attach(Observer* observer) = 0;
// 移除观察者
virtual void detach(Observer* observer) = 0;
// 通知所有观察者
virtual void notify() = 0;
};
// 3. 具体被观察者:气象站
class WeatherStation : public Subject {
private:
std::vector<Observer*> observers; // 观察者列表
float currentTemperature; // 当前温度(被观察的状态)
public:
void setTemperature(float temp) {
currentTemperature = temp;
notify(); // 温度变化时通知所有观察者
}
// 添加观察者
void attach(Observer* observer) override {
observers.push_back(observer);
}
// 移除观察者
void detach(Observer* observer) override {
auto it = std::find(observers.begin(), observers.end(), observer);
if (it != observers.end()) {
observers.erase(it);
}
}
// 通知所有观察者
void notify() override {
for (Observer* observer : observers) {
observer->update(currentTemperature); // 传递当前温度
}
}
};
// 4. 具体观察者1:手机显示
class PhoneDisplay : public Observer {
private:
std::string name;
public:
PhoneDisplay(const std::string& n) : name(n) {}
void update(float temperature) override {
std::cout << "[" << name << "] 手机显示 - 当前温度: " << temperature << "°C\n";
}
};
// 4. 具体观察者2:电脑显示
class ComputerDisplay : public Observer {
public:
void update(float temperature) override {
std::cout << "[电脑显示] 温度更新: " << temperature << "°C,已记录到日志\n";
}
};
// 客户端代码
int main() {
// 创建被观察者:气象站
WeatherStation weatherStation;
// 创建观察者:两个手机显示和一个电脑显示
PhoneDisplay phone1("小明的手机");
PhoneDisplay phone2("小红的手机");
ComputerDisplay computer;
// 注册观察者(订阅气象站)
weatherStation.attach(&phone1);
weatherStation.attach(&phone2);
weatherStation.attach(&computer);
// 气象站温度变化(触发通知)
std::cout << "=== 温度更新为25°C ===" << std::endl;
weatherStation.setTemperature(25.0f);
// 移除一个观察者(取消订阅)
weatherStation.detach(&phone2);
std::cout << "\n=== 温度更新为30°C ===" << std::endl;
weatherStation.setTemperature(30.0f);
return 0;
}
代码说明
- Observer(观察者接口):定义了
update()
方法,所有观察者都必须实现此接口以接收通知。 - Subject(被观察者接口):定义了
attach()
(添加)、detach()
(移除)和notify()
(通知)三个核心方法,规范被观察者的行为。 - WeatherStation(具体被观察者):
- 维护一个观察者列表
observers
。 - 当
setTemperature()
被调用(状态变化)时,立即调用notify()
通知所有观察者。 - 实现了添加 / 移除观察者的逻辑。
- 维护一个观察者列表
- 具体观察者:
PhoneDisplay
和ComputerDisplay
分别实现了update()
方法,定义了收到温度更新后的具体行为(显示方式不同)。- 观察者无需知道被观察者的具体类型,只需通过接口接收通知。
运行结果
=== 温度更新为25°C ===
[小明的手机] 手机显示 - 当前温度: 25°C
[小红的手机] 手机显示 - 当前温度: 25°C
[电脑显示] 温度更新: 25°C,已记录到日志
=== 温度更新为30°C ===
[小明的手机] 手机显示 - 当前温度: 30°C
[电脑显示] 温度更新: 30°C,已记录到日志
可以看到,当气象站温度变化时:
- 所有注册的观察者都会收到通知并更新。
- 移除观察者(小红的手机)后,它不再收到通知。
观察者模式的优点
- 松耦合:被观察者和观察者相互独立,一方变化不影响另一方。
- 可扩展性:新增观察者无需修改被观察者代码(符合开闭原则)。
- 联动性:轻松实现一个对象变化带动多个对象自动更新的场景。
这种模式在 GUI 框架(如按钮点击事件)、消息订阅系统、数据监控系统等场景中被广泛使用。
九、策略模式
9.1 回答重点
**大话设计模式:**策略模式(strategy)它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
策略模式是一种行为型设计模式,它定义了一系列算法(这些算法完成的是相同的工作,只是实现不同),并将每个算法封装起来,使它们可以相互替换,而且算法的变化不会影响使用算法的客户。
基本结构:
策略类
Strategy
: 定义所有支持的算法的公共接口。具体策略类
ConcreteStrategy
: 实现了策略接口,提供具体的算法实现。上下文类
Context
: 包含一个策略实例,并在需要时调用策略对象的方法。
应用场景:
举个例子,电商网站对于商品的折扣策略有不同的算法,比如新用户满减优惠,不同等级会员的打折情况不同,这种情况下会产生大量的if-else语句
, 并且如果优惠政策修改时,还需要修改原来的代码,不符合开闭原则。
这就可以将不同的优惠算法封装成独立的类来避免大量的条件语句,如果新增优惠算法,可以添加新的策略类来实现,客户端在运行时选择不同的具体策略,而不必修改客户端代码改变优惠策略。
示例代码:
// 抽象购物优惠策略接口
class DiscountStrategy {
public:
virtual int applyDiscount(int originalPrice) = 0;
virtual ~DiscountStrategy() = default; // 添加虚析构函数
};
// 九折优惠策略
class DiscountStrategy1 : public DiscountStrategy {
public:
int applyDiscount(int originalPrice) override {
return static_cast<int>(std::round(originalPrice * 0.9));
}
};
// 满减优惠策略
class DiscountStrategy2 : public DiscountStrategy {
private:
int thresholds[4] = {100, 150, 200, 300};
int discounts[4] = {5, 15, 25, 40};
public:
int applyDiscount(int originalPrice) override {
for (int i = sizeof(thresholds) / sizeof(thresholds[0]) - 1; i >= 0; i--) {
if (originalPrice >= thresholds[i]) {
return originalPrice - discounts[i];
}
}
return originalPrice;
}
};
// 上下文类
class DiscountContext {
private:
DiscountStrategy* discountStrategy;
public:
void setDiscountStrategy(DiscountStrategy* discountStrategy) {
this->discountStrategy = discountStrategy;
}
int applyDiscount(int originalPrice) {
return discountStrategy->applyDiscount(originalPrice);
}
};
int main() {
// 读取需要计算优惠的次数int N;
std::cin >> N;
std::cin.ignore(); // 忽略换行符
for (int i = 0; i < N; i++) {
// 读取商品价格和优惠策略int M, strategyType;
std::cin >> M >> strategyType;
// 根据优惠策略设置相应的打折策略
DiscountStrategy* discountStrategy;
switch (strategyType) {
case 1:
discountStrategy = new DiscountStrategy1();
break;
case 2:
discountStrategy = new DiscountStrategy2();
break;
default:
// 处理未知策略类型
std::cout << "Unknown strategy type" << std::endl;
return 1;
}
// 设置打折策略
DiscountContext context;
context.setDiscountStrategy(discountStrategy);
// 应用打折策略并输出优惠后的价格
int discountedPrice = context.applyDiscount(M);
std::cout << discountedPrice << std::endl;
// 释放动态分配的打折策略对象delete discountStrategy;
delete discountStrategy;
}
return 0;
}
9.2 扩展知识
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来并使它们可以相互替换。这种模式让算法的变化独立于使用算法的客户端,从而实现灵活的算法切换。
简单来说,策略模式就像手机的拍照模式 —— 你可以在 “普通模式”、“夜景模式”、“人像模式” 之间切换,每种模式都是一种拍照策略,而手机相机就是使用这些策略的客户端。
核心角色:
- Strategy(策略接口)
定义所有支持的算法的公共接口,客户端通过这个接口调用具体策略的算法。 - ConcreteStrategy(具体策略)
实现策略接口,包含具体的算法逻辑。 - Context(上下文)
持有一个策略对象的引用,负责将客户端的请求委派给当前的策略对象执行,同时提供切换策略的方法。
适用场景:
- 当一个问题有多种解决方法,且需要在运行时动态选择其中一种时。
- 当一个类中包含多种条件判断(如大量
if-else
或switch
),且这些判断对应不同算法时(可用策略模式消除条件判断)。 - 当需要隐藏算法的实现细节,只暴露其使用接口时。
C++ 代码示例:
下面以 “支付系统” 为例:一个电商平台支持多种支付方式(支付宝、微信支付、银行卡支付),使用策略模式实现支付方式的灵活切换
#include <iostream>
#include <string>
// 1. 策略接口:支付策略
class PaymentStrategy {
public:
virtual ~PaymentStrategy() = default;
// 支付接口(参数为金额)
virtual void pay(double amount) const = 0;
};
// 2. 具体策略1:支付宝支付
class AlipayStrategy : public PaymentStrategy {
private:
std::string username; // 支付宝账号
public:
AlipayStrategy(const std::string& user) : username(user) {}
void pay(double amount) const override {
std::cout << "使用支付宝支付 " << amount << " 元,账号:" << username << std::endl;
}
};
// 2. 具体策略2:微信支付
class WechatPayStrategy : public PaymentStrategy {
private:
std::string openId; // 微信openId
public:
WechatPayStrategy(const std::string& id) : openId(id) {}
void pay(double amount) const override {
std::cout << "使用微信支付 " << amount << " 元,OpenId:" << openId << std::endl;
}
};
// 2. 具体策略3:银行卡支付
class BankCardStrategy : public PaymentStrategy {
private:
std::string cardNumber; // 卡号
std::string name; // 持卡人姓名
public:
BankCardStrategy(const std::string& card, const std::string& n)
: cardNumber(card), name(n) {}
void pay(double amount) const override {
std::cout << "使用银行卡支付 " << amount << " 元,卡号:" << cardNumber
<< ",持卡人:" << name << std::endl;
}
};
// 3. 上下文:订单支付
class Order {
private:
PaymentStrategy* paymentStrategy; // 当前支付策略
double totalAmount; // 订单总金额
public:
Order(double amount) : paymentStrategy(nullptr), totalAmount(amount) {}
~Order() {
// 清理策略对象
delete paymentStrategy;
}
// 设置支付策略(切换策略)
void setPaymentStrategy(PaymentStrategy* strategy) {
// 先删除旧策略
delete paymentStrategy;
paymentStrategy = strategy;
}
// 执行支付(委托给当前策略)
void pay() const {
if (paymentStrategy) {
paymentStrategy->pay(totalAmount);
} else {
std::cout << "请先选择支付方式!" << std::endl;
}
}
};
// 客户端代码
int main() {
// 创建一个100元的订单
Order order(100.0);
std::cout << "订单金额:100元\n" << std::endl;
// 选择支付宝支付
order.setPaymentStrategy(new AlipayStrategy("user123@alipay.com"));
order.pay();
// 切换为微信支付
order.setPaymentStrategy(new WechatPayStrategy("o6_bmjrPTlm6_2sgVt7hMZOPfL2M"));
order.pay();
// 切换为银行卡支付
order.setPaymentStrategy(new BankCardStrategy("6222 **** **** 1234", "张三"));
order.pay();
return 0;
}
十、备忘录模式
10.1 回答重点
大话设计模式: 备忘录(Memento),在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后可以将该对象恢复到原来保存的状态。
备忘录模式(Memento Pattern)是一种行为型设计模式,它允许在不暴露对象实现的情况下捕获对象的内部状态并在对象之外保存这个状态,以便稍后可以将其还原到先前的状态。
基本结构:
备忘录模式包括以下几个重要角色:
发起人
Originator
: 需要还原状态的那个对象,负责创建一个【备忘录】,并使用备忘录记录当前时刻的内部状态。备忘录
Memento
: 存储发起人对象的内部状态,它可以包含发起人的部分或全部状态信息,但是对外部是不可见的,只有发起人能够访问备忘录对象的状态。(备忘录有两个接口,发起人能够通过宽接口访问数据,管理者只能看到窄接口,并将备忘录传递给其他对象。)管理者
Caretaker
: 负责存储备忘录对象,但并不了解其内部结构,管理者可以存储多个备忘录对象。客户端:在需要恢复状态时,客户端可以从管理者那里获取备忘录对象,并将其传递给发起人进行状态的恢复。
应用场景:
备忘录模式在保证了对象内部状态的封装和私有性前提下可以轻松地添加新的备忘录和发起人,实现“备份”,不过 备份对象往往会消耗较多的内存,资源消耗增加。
备忘录模式常常用来实现撤销和重做功能,比如在Java Swing GUI编程中,javax.swing.undo
包中的撤销(undo)和重做(redo)机制使用了备忘录模式。UndoManager
和UndoableEdit
接口是与备忘录模式相关的主要类和接口。
示例代码:
// 备忘录
class Memento {
private:
int value;
public:
Memento(int val) : value(val) {}
int getValue() const {
return value;
}
};
// 发起人(Originator)
class Counter {
private:
int value;
std::stack<Memento> undoStack;
std::stack<Memento> redoStack;
public:
void increment() {
redoStack = std::stack<Memento>(); // 清空 redoStack
undoStack.push(Memento(value));
value++;
}
void decrement() {
redoStack = std::stack<Memento>(); // 清空 redoStack
undoStack.push(Memento(value));
value--;
}
void undo() {
if (!undoStack.empty()) {
redoStack.push(Memento(value));
value = undoStack.top().getValue();
undoStack.pop();
}
}
void redo() {
if (!redoStack.empty()) {
undoStack.push(Memento(value));
value = redoStack.top().getValue();
redoStack.pop();
}
}
int getValue() const {
return value;
}
};
int main() {
Counter counter;
// 处理计数器应用的输入
std::string operation;
while (std::cin >> operation) {
if (operation == "Increment") {
counter.increment();
} else if (operation == "Decrement") {
counter.decrement();
} else if (operation == "Undo") {
counter.undo();
} else if (operation == "Redo") {
counter.redo();
}
// 输出当前计数器的值
std::cout << counter.getValue() << std::endl;
}
return 0;
}
10.2 扩展知识
备忘录模式(Memento Pattern)是一种行为型设计模式,用于捕获对象在某一时刻的内部状态,并将其保存起来,以便在将来需要时恢复该状态。这种模式就像游戏中的 “存档” 功能,允许你在关键时刻保存进度,之后可以随时回到该状态。
核心角色:
- Originator(发起人)
负责创建一个备忘录(Memento),用于记录自身当前的内部状态,同时也能从备忘录恢复状态。 - Memento(备忘录)
存储发起人的内部状态,且仅允许发起人访问其内容(对其他对象隐藏细节)。 - Caretaker(管理者)
负责保存备忘录,但不能对备忘录的内容进行操作或检查(仅负责存储和检索)。
适用场景:
- 需要保存和恢复对象状态的场景(如编辑器的撤销操作、游戏存档)。
- 不希望暴露对象内部状态,却需要捕获和恢复状态的场景。
- 状态变化频繁,且需要回滚到历史状态的场景。
C++ 代码示例:
下面以 “文本编辑器” 为例,实现撤销(Undo)功能,展示备忘录模式的用法:
#include <iostream>
#include <string>
#include <vector>
// 2. 备忘录:存储文本编辑器的状态
class EditorMemento {
private:
// 只有发起人才可以访问备忘录的状态(友元类)
friend class TextEditor;
std::string content; // 保存的文本内容
// 私有构造函数,防止外部创建(只能由发起人创建)
EditorMemento(const std::string& cnt) : content(cnt) {}
// 获取保存的内容(仅发起人可调用)
std::string getContent() const {
return content;
}
};
// 1. 发起人:文本编辑器
class TextEditor {
private:
std::string content; // 当前文本内容
public:
// 设置文本内容
void setContent(const std::string& cnt) {
content = cnt;
}
// 获取当前文本内容
std::string getContent() const {
return content;
}
// 创建备忘录(保存当前状态)
EditorMemento* save() {
return new EditorMemento(content); //EditorMemento的构造函数不是一看私有的吗?怎么回成功调用呢?====>friend
}
// 从备忘录恢复状态
void restore(EditorMemento* memento) {
if (memento) {
content = memento->getContent();
}
}
};
// 3. 管理者:负责保存和管理备忘录(实现撤销历史)
class History {
private:
std::vector<EditorMemento*> mementos; // 存储备忘录的容器
public:
~History() {
// 清理所有备忘录
for (auto m : mementos) {
delete m;
}
mementos.clear();
}
// 保存新的备忘录
void push(EditorMemento* memento) {
mementos.push_back(memento);
}
// 获取最近的备忘录(用于撤销操作)
EditorMemento* pop() {
if (mementos.empty()) {
return nullptr;
}
EditorMemento* last = mementos.back();
mementos.pop_back();
return last;
}
};
// 客户端代码
int main() {
// 创建文本编辑器和历史记录管理器
TextEditor editor;
History history;
// 操作1:输入第一段文本并保存状态
editor.setContent("第一段文本");
std::cout << "当前内容: " << editor.getContent() << "\n";
history.push(editor.save()); // 存档
// 操作2:输入第二段文本并保存状态
editor.setContent("第二段文本");
std::cout << "当前内容: " << editor.getContent() << "\n";
history.push(editor.save()); // 存档
// 操作3:输入第三段文本(不保存)
editor.setContent("第三段文本");
std::cout << "当前内容: " << editor.getContent() << "\n";
// 撤销1:回到上一个状态
editor.restore(history.pop());
std::cout << "撤销后内容: " << editor.getContent() << "\n";
// 撤销2:回到更早的状态
editor.restore(history.pop());
std::cout << "再次撤销后内容: " << editor.getContent() << "\n";
return 0;
}
代码说明:
- PaymentStrategy(策略接口):定义了支付的统一接口
pay()
,所有支付方式都必须实现这个接口。 - 具体策略类:
AlipayStrategy
、WechatPayStrategy
、BankCardStrategy
分别实现了不同支付方式的具体逻辑。- 每个策略类都封装了对应支付方式所需的参数(如账号、卡号)和支付流程。
- Order(上下文):
- 持有一个
PaymentStrategy
指针,代表当前使用的支付策略。 - 提供
setPaymentStrategy()
方法,允许在运行时动态切换支付方式。 pay()
方法将支付请求委派给当前策略对象执行,自身不包含具体支付逻辑。
- 持有一个
运行结果:
订单金额:100元
使用支付宝支付 100 元,账号:user123@alipay.com
使用微信支付 100 元,OpenId:o6_bmjrPTlm6_2sgVt7hMZOPfL2M
使用银行卡支付 100 元,卡号:6222 **** **** 1234,持卡人:张三
策略模式的优点:
- 消除条件判断:用策略切换替代
if-else
或switch
语句,使代码更清晰。 - 增强扩展性:新增策略只需实现策略接口,无需修改原有代码(符合开闭原则)。
- 算法复用:不同场景可复用相同的策略实现。
- 灵活性高:可在运行时动态切换算法,适应不同需求。
策略模式特别适合在需要频繁切换算法、或算法逻辑复杂且多变的场景中使用,例如排序算法、加密算法、支付方式等场景。
十一、模板模式
11.1 回答重点
**大话设计模式:**模板方法模式(Template Method Pattern)是一种行为型设计模式, 它定义了一个算法的骨架,将一些步骤的实现延迟到子类。模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
基本结构:
模板类
AbstractClass
:由一个模板方法和若干个基本方法构成,模板方法定义了逻辑的骨架,按照顺序调用包含的基本方法,基本方法通常是一些抽象方法,这些方法由子类去实现。基本方法还包含一些具体方法,它们是算法的一部分但已经有默认实现,在具体子类中可以继承或者重写。具体类
ConcreteClass
:继承自模板类,实现了在模板类中定义的抽象方法,以完成算法中特定步骤的具体实现。
应用场景:
模板方法模式将算法的不变部分被封装在模板方法中,而可变部分算法由子类继承实现,这样做可以很好的提高代码的复用性,但是当算法的框架发生变化时,可能需要修改模板类,这也会影响到所有的子类。
总体来说,当算法的整体步骤很固定,但是个别步骤在更详细的层次上的实现可能不同时,通常考虑模板方法模式来处理。在已有的工具和库中, Spring框架中的
JdbcTemplate
类使用了模板方法模式,其中定义了一些执行数据库操作的模板方法,具体的数据库操作由回调函数提供。而在Java的JDK源码中,AbstractList
类也使用了模板方法模式,它提供了一些通用的方法,其中包括一些模板方法。具体的列表操作由子类实现。
示例代码:
// 抽象类
class CoffeeMakerTemplate {
private:
std::string coffeeName;
public:// 构造函数,接受咖啡名称参数
CoffeeMakerTemplate(const std::string& coffeeName) :
coffeeName(coffeeName){};
// 模板方法定义咖啡制作过程
virtual void makeCoffee()
{
std::cout << "Making " << coffeeName << ":\n";
grindCoffeeBeans();
brewCoffee();
addCondiments();
std::cout << '\n';
}
// 具体步骤的具体实现由子类提供
virtual void grindCoffeeBeans() = 0;
virtual void brewCoffee() = 0;
// 添加调料的默认实现
virtual void addCondiments() {
std::cout << "Adding condiments\n";
}
};
// 具体的美式咖啡类
class AmericanCoffeeMaker : public CoffeeMakerTemplate {
public:
// 构造函数传递咖啡名称
AmericanCoffeeMaker() : CoffeeMakerTemplate("American Coffee") {}
void grindCoffeeBeans() override {
std::cout << "Grinding coffee beans\n";
}
void brewCoffee() override {
std::cout << "Brewing coffee\n";
}
};
// 具体的拿铁咖啡类
class LatteCoffeeMaker : public CoffeeMakerTemplate {
public:
// 构造函数传递咖啡名称
LatteCoffeeMaker() : CoffeeMakerTemplate("Latte") {}
void grindCoffeeBeans() override {
std::cout << "Grinding coffee beans\n";
}
void brewCoffee() override {
std::cout << "Brewing coffee\n";
}
// 添加调料的特定实现
void addCondiments() override {
std::cout << "Adding milk\n";
std::cout << "Adding condiments\n";
}
};
int main() {
std::unique_ptr<CoffeeMakerTemplate> coffeeMaker;
int coffeeType;
while (std::cin >> coffeeType) {
if (coffeeType == 1) {
coffeeMaker = std::make_unique<AmericanCoffeeMaker>();
} else if (coffeeType == 2) {
coffeeMaker = std::make_unique<LatteCoffeeMaker>();
} else {
std::cout << "Invalid coffee type\n";
continue;
}
// 制作咖啡
coffeeMaker->makeCoffee();
}
return 0;
}
11.2 扩展知识
模板模式(Template Pattern)是一种行为型设计模式,它定义了一个算法的骨架流程,将某些步骤的具体实现延迟到子类中。这样可以在不改变算法整体结构的情况下,让子类重写特定步骤,实现算法细节的定制化。
简单来说,模板模式就像制作蛋糕的食谱:食谱规定了 “准备材料→混合→烘烤→装饰” 的固定步骤(骨架),但具体用什么材料、如何装饰可以由不同的厨师(子类)自行实现。
核心角色:
- 抽象类(Abstract Class)
定义算法的骨架流程(模板方法),其中包含多个抽象步骤(由子类实现)和可能的具体步骤(通用实现)。 - 具体子类(Concrete Class)
继承抽象类,实现模板方法中定义的抽象步骤,完成算法的具体细节。
关键特点:
- 模板方法(Template Method):在抽象类中定义,是算法的骨架,按顺序调用各个步骤方法。
- 钩子方法(Hook Method):抽象类中定义的可选方法(通常有默认实现),子类可重写以影响模板方法的流程。
- 不可变骨架:算法的整体结构由抽象类控制,子类不能改变流程顺序,只能定制步骤实现。
C++ 代码示例
下面以 “制作饮料” 为例:冲咖啡和泡茶的流程相似(都需要 “煮水→冲泡→倒入杯子→添加调料”),但具体步骤实现不同,用模板模式统一流程并区分细节。
#include <iostream>
#include <string>
// 1. 抽象类:定义饮料制作的模板流程
class Beverage {
public:
// 修正:将模板方法声明为虚函数,再用final修饰
virtual void prepareRecipe() final { // 此处添加virtual关键字
boilWater(); // 步骤1:煮水(通用步骤)
brew(); // 步骤2:冲泡(抽象步骤,子类实现)
pourInCup(); // 步骤3:倒入杯子(通用步骤)
if (customerWantsCondiments()) { // 钩子方法:判断是否需要调料
addCondiments(); // 步骤4:添加调料(抽象步骤,子类实现)
}
}
// 抽象方法:冲泡(子类必须实现)
virtual void brew() = 0;
// 抽象方法:添加调料(子类必须实现)
virtual void addCondiments() = 0;
// 具体方法:煮水(通用实现)
void boilWater() {
std::cout << "煮水至沸腾\n";
}
// 具体方法:倒入杯子(通用实现)
void pourInCup() {
std::cout << "倒入杯子中\n";
}
// 钩子方法:是否需要添加调料(默认返回true,子类可重写)
virtual bool customerWantsCondiments() {
return true;
}
virtual ~Beverage() = default; // 虚析构函数
};
// 2. 具体子类:咖啡
class Coffee : public Beverage {
public:
// 实现咖啡的冲泡方式
void brew() override {
std::cout << "用沸水冲泡咖啡粉\n";
}
// 实现咖啡的调料添加
void addCondiments() override {
std::cout << "添加糖和牛奶\n";
}
// 重写钩子方法:询问用户是否需要调料
bool customerWantsCondiments() override {
std::string answer;
std::cout << "请问需要给咖啡加奶和糖吗?(y/n): ";
std::cin >> answer;
return answer == "y" || answer == "Y";
}
};
// 2. 具体子类:茶
class Tea : public Beverage {
public:
// 实现茶的冲泡方式
void brew() override {
std::cout << "用沸水浸泡茶叶\n";
}
// 实现茶的调料添加
void addCondiments() override {
std::cout << "添加柠檬\n";
}
};
// 客户端代码
int main() {
std::cout << "=== 制作咖啡 ===" << std::endl;
Beverage* coffee = new Coffee();
coffee->prepareRecipe();
std::cout << "\n=== 制作茶 ===" << std::endl;
Beverage* tea = new Tea();
tea->prepareRecipe();
// 清理资源
delete coffee;
delete tea;
return 0;
}
代码说明:
- Beverage(抽象类):
prepareRecipe()
是模板方法(用final
修饰,防止子类重写),定义了 “煮水→冲泡→倒入杯子→加调料” 的固定流程。- 包含两个抽象方法
brew()
和addCondiments()
,由子类实现具体细节。 - 包含通用方法
boilWater()
和pourInCup()
,所有饮料制作都共用这些步骤。 - 钩子方法
customerWantsCondiments()
提供默认实现(返回true
),子类可重写以改变流程(如咖啡询问用户是否加调料)。
- 具体子类:
Coffee
实现了咖啡特有的brew()
(冲泡咖啡粉)和addCondiments()
(加糖奶),并通过钩子方法实现了交互逻辑。Tea
实现了茶特有的brew()
(浸泡茶叶)和addCondiments()
(加柠檬),使用默认钩子方法(总是加调料)。
运行结果:
=== 制作咖啡 ===
煮水至沸腾
用沸水冲泡咖啡粉
倒入杯子中
请问需要给咖啡加奶和糖吗?(y/n): y
添加糖和牛奶
=== 制作茶 ===
煮水至沸腾
用沸水浸泡茶叶
倒入杯子中
添加柠檬
模板模式的优点:
- 代码复用:将通用流程和步骤放在抽象类中,避免重复代码。
- 控制反转:抽象类定义骨架,子类填充细节,符合 “开闭原则”(扩展容易,修改骨架难)。
- 流程固化:确保算法的关键步骤和执行顺序不被改变,只允许定制具体实现。
这种模式在框架设计中非常常见,例如:
- 测试框架中,
setUp()
→runTest()
→tearDown()
的测试流程。 - GUI 框架中,事件处理的初始化→处理→清理流程。
- 数据库访问的连接→查询→关闭流程。
通过模板模式,可以平衡 “流程标准化” 和 “细节定制化” 的需求,是代码设计中实现复用和扩展的重要手段。
十二、状态模式
12.1 回答重点
**大话设计模式:**状态模式(State),当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变其类。
状态模式(State Pattern):是一种行为型设计模式,它适用于一个对象在在不同的状态下有不同的行为时,比如说电灯的开、关、闪烁是不停的状态,状态不同时,对应的行为也不同,在没有状态模式的情况下,为了添加新的状态或修改现有的状态,往往需要修改已有的代码,这违背了开闭原则,而且如果对象的状态切换逻辑和各个状态的行为都在同一个类中实现,就可能导致该类的职责过重,不符合单一职责原则。而状态模式将每个状态的行为封装在一个具体状态类中,使得每个状态类相对独立,并将对象在不同状态下的行为进行委托,从而使得对象的状态可以在运行时动态改变,每个状态的实现也不会影响其他状态。
基本结构:
State
(状态): 定义一个接口,用于封装与Context的一个特定状态相关的行为。ConcreteState
(具体状态): 负责处理Context在状态改变时的行为, 每一个具体状态子类实现一个与Context
的一个状态相关的行为。Context
(上下文): 维护一个具体状态子类的实例,这个实例定义当前的状态。
应用场景:
状态模式将每个状态的实现都封装在一个类中,每个状态类的实现相对独立,使得添加新状态或修改现有状态变得更加容易,避免了使用大量的条件语句来控制对象的行为。但是如果状态过多,会导致类的数量增加,可能会使得代码结构复杂。
总的来说,状态模式适用于有限状态机的场景,其中对象的行为在运行时可以根据内部状态的改变而改变,在游戏开发中,Unity3D 的 Animator 控制器就是一个状态机。它允许开发人员定义不同的状态(动画状态),并通过状态转换来实现角色的动画控制和行为切换。
示例代码:
// 状态接口
class State {
public:
virtual std::string handle() = 0; // 处理状态的方法
};
// 具体状态类
class OnState : public State {
public:
std::string handle() override {
return "Light is ON";
}
};
class OffState : public State {
public:
std::string handle() override {
return "Light is OFF";
}
};
class BlinkState : public State {
public:
std::string handle() override {
return "Light is Blinking";
}
};
// 上下文类
class Light {
private:
State* state; // 当前状态
public:Light() : state(new OffState()) {} // 初始状态为关闭
void setState(State* newState) { // 设置新的状态
delete state; // 释放之前的状态对象
state = newState;
}
std::string performOperation() { // 执行当前状态的操作
return state->handle();
}
~Light() {
delete state; // 释放内存
}
};
int main() {
// 读取要输入的命令数量int n;
std::cin >> n;
std::cin.ignore(); // 消耗掉整数后的换行符// 创建一个Light对象
Light light;
// 处理用户输入的每个命令
for (int i = 0; i < n; i++) {
// 读取命令并去掉首尾空白字符
std::string command;
std::getline(std::cin, command);
// 根据命令执行相应的操作
if (command == "ON") {
light.setState(new OnState());
} else if (command == "OFF") {
light.setState(new OffState());
} else if (command == "BLINK") {
light.setState(new BlinkState());
} else {
// 处理无效命令
std::cout << "Invalid command: " << command << std::endl;
}
// 在每个命令后显示灯的当前状态
std::cout << light.performOperation() << std::endl;
}
return 0;
}
12.2 扩展知识
状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态改变时改变其行为,使对象看起来好像修改了它的类。这种模式通过将状态相关的行为封装到不同的状态对象中,实现状态与行为的绑定,以及状态之间的灵活切换。
简单来说,状态模式就像交通信号灯:信号灯有红、黄、绿三种状态,每种状态对应不同的行为(禁止通行、准备通行、允许通行),且状态之间会按特定规则切换(红→黄→绿→红…)。
核心角色:
- Context(上下文)
维护一个当前状态的引用,负责将状态相关的请求委派给当前状态对象处理,同时提供切换状态的方法。 - State(状态接口)
定义所有具体状态的公共接口,声明了上下文在特定状态下应有的行为方法。 - ConcreteState(具体状态)
实现状态接口,包含该状态下的具体行为逻辑,同时可能定义状态转换的条件和目标。
适用场景:
- 当一个对象的行为取决于其状态,且在运行时需要根据状态改变行为时(如订单状态、生命周期状态)。
- 当一个类中包含大量与状态相关的条件判断(
if-else
或switch
),导致代码臃肿难以维护时。 - 当状态转换规则复杂,需要集中管理不同状态间的切换逻辑时。
C++ 代码示例:
下面以 “订单状态管理” 为例:一个电商订单有 “待支付”、“已支付”、“已发货”、“已完成” 四种状态,每种状态下的操作(支付、发货、确认收货)有不同的行为和状态转换规则。
#include <iostream>
#include <string>
// 前向声明:上下文类
class Order;
// 2. 状态接口
class OrderState {
public:
virtual ~OrderState() = default;
// 声明各种状态下可能的行为
virtual void pay(Order* order) = 0;
virtual void ship(Order* order) = 0;
virtual void confirm(Order* order) = 0;
virtual std::string getStateName() = 0;
};
// 1. 上下文:订单
class Order {
private:
OrderState* currentState; // 当前状态
std::string orderId;
public:
Order(const std::string& id);
~Order();
// 状态转换:切换到新状态
void setState(OrderState* newState);
// 订单操作(委派给当前状态处理)
void pay() { currentState->pay(this); }
void ship() { currentState->ship(this); }
void confirm() { currentState->confirm(this); }
std::string getOrderId() const { return orderId; }
};
// 3. 具体状态1:待支付
class PendingPaymentState : public OrderState {
public:
void pay(Order* order) override;
void ship(Order* order) override {
std::cout << "错误:订单" << order->getOrderId() << "未支付,不能发货!\n";
}
void confirm(Order* order) override {
std::cout << "错误:订单" << order->getOrderId() << "未支付,不能确认收货!\n";
}
std::string getStateName() override { return "待支付"; }
};
// 3. 具体状态2:已支付
class PaidState : public OrderState {
public:
void pay(Order* order) override {
std::cout << "错误:订单" << order->getOrderId() << "已支付,不能重复支付!\n";
}
void ship(Order* order) override;
void confirm(Order* order) override {
std::cout << "错误:订单" << order->getOrderId() << "未发货,不能确认收货!\n";
}
std::string getStateName() override { return "已支付"; }
};
// 3. 具体状态3:已发货
class ShippedState : public OrderState {
public:
void pay(Order* order) override {
std::cout << "错误:订单" << order->getOrderId() << "已发货,无需支付!\n";
}
void ship(Order* order) override {
std::cout << "错误:订单" << order->getOrderId() << "已发货,不能重复发货!\n";
}
void confirm(Order* order) override;
std::string getStateName() override { return "已发货"; }
};
// 3. 具体状态4:已完成
class CompletedState : public OrderState {
public:
void pay(Order* order) override {
std::cout << "错误:订单" << order->getOrderId() << "已完成,无需支付!\n";
}
void ship(Order* order) override {
std::cout << "错误:订单" << order->getOrderId() << "已完成,无需发货!\n";
}
void confirm(Order* order) override {
std::cout << "错误:订单" << order->getOrderId() << "已完成,不能重复确认!\n";
}
std::string getStateName() override { return "已完成"; }
};
// 上下文类实现
Order::Order(const std::string& id) : orderId(id) {
// 初始状态为待支付
currentState = new PendingPaymentState();
std::cout << "创建订单 " << orderId << ",当前状态:" << currentState->getStateName() << "\n";
}
Order::~Order() {
delete currentState;
}
void Order::setState(OrderState* newState) {
delete currentState;
currentState = newState;
std::cout << "订单" << orderId << "状态变更为:" << currentState->getStateName() << "\n";
}
// 具体状态方法实现(状态转换逻辑)
void PendingPaymentState::pay(Order* order) {
std::cout << "订单" << order->getOrderId() << "支付成功!\n";
order->setState(new PaidState()); // 转换到已支付状态
}
void PaidState::ship(Order* order) {
std::cout << "订单" << order->getOrderId() << "发货成功!\n";
order->setState(new ShippedState()); // 转换到已发货状态
}
void ShippedState::confirm(Order* order) {
std::cout << "订单" << order->getOrderId() << "确认收货成功!\n";
order->setState(new CompletedState()); // 转换到已完成状态
}
// 客户端代码
int main() {
// 创建一个订单(初始状态:待支付)
Order order("ORD-12345");
// 执行一系列操作
order.pay(); // 支付订单(状态变为已支付)
order.ship(); // 发货(状态变为已发货)
order.confirm();// 确认收货(状态变为已完成)
// 尝试无效操作(已完成状态下的支付)
order.pay();
return 0;
}
代码说明:
- OrderState(状态接口):
- 定义了订单在任何状态下可能的操作(
pay
、ship
、confirm
)。 - 所有具体状态都必须实现这些方法,定义该状态下的行为逻辑。
- 定义了订单在任何状态下可能的操作(
- Order(上下文):
- 持有当前状态的引用(
currentState
),并将所有操作委派给当前状态处理。 - 提供
setState()
方法用于切换状态,封装了状态转换的细节。
- 持有当前状态的引用(
- 具体状态类:
- 每个类对应一种订单状态(
PendingPaymentState
、PaidState
等)。 - 实现了该状态下允许的操作(如
PendingPaymentState
中只有pay()
有效)和状态转换逻辑(如支付后切换到PaidState
)。 - 对无效操作(如未支付时发货)提供错误提示。
- 每个类对应一种订单状态(
运行结果:
创建订单 ORD-12345,当前状态:待支付
订单ORD-12345支付成功!
订单ORD-12345状态变更为:已支付
订单ORD-12345发货成功!
订单ORD-12345状态变更为:已发货
订单ORD-12345确认收货成功!
订单ORD-12345状态变更为:已完成
错误:订单ORD-12345已完成,无需支付!
状态模式的优点:
- 消除庞大的条件判断:将不同状态的行为分散到不同类中,替代复杂的
if-else
或switch
语句。 - 状态转换清晰:每个状态类自行控制状态转换的逻辑,职责单一,易于维护。
- 扩展性好:新增状态只需添加新的状态类,无需修改现有代码(符合开闭原则)。
- 状态封装:状态的细节对上下文和客户端透明,只通过接口交互。
状态模式特别适合实现有明确状态划分和状态转换规则的系统,如订单系统、工作流引擎、游戏角色状态等场景,能显著提高代码的可读性和可维护性。
十三、 责任链模式
13.1 回答重点
**大话设计模式:**责任链模式(Chain of Responsibility)使得多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象练成一条链,并且沿着这条链传递该请求,直到有一个对象处理它为止。
责任链模式是一种行为型设计模式,它允许你构建一个对象链,让请求从链的一端进入,然后沿着链上的对象依次处理,直到链上的某个对象能够处理该请求为止。
职责链上的处理者就是一个对象,可以对请求进行处理或者将请求转发给下一个节点,这个场景在生活中很常见,就是一个逐层向上递交的过程,最终的请求要么被处理者所处理,要么处理不了,这也因此可能导致请求无法被处理。
基本结构:
处理者
Handler
:定义一个处理请求的接口,包含一个处理请求的抽象方法和一个指向下一个处理者的链接。具体处理者
ConcreteHandler
: 实现处理请求的方法,并判断能否处理请求,如果能够处理请求则进行处理,否则将请求传递给下一个处理者。客户端:创建并组装处理者对象链,并将请求发送到链上的第一个处理者。
应用场景:
责任链模式具有下面几个优点:
降低耦合度:将请求的发送者和接收者解耦,每个具体处理者都只负责处理与自己相关的请求,客户端不需要知道具体是哪个处理者处理请求。
增强灵活性:可以动态地添加或删除处理者,改变处理者之间的顺序以满足不同需求。
但是由于一个请求可能会经过多个处理者,这可能会导致一些性能问题,并且如果整个链上也没有合适的处理者来处理请求,就会导致请求无法被处理。
责任链模式是设计模式中简单且常见的设计模式,在日常中也会经常使用到,比如Java开发中过滤器的链式处理,以及Spring框架中的拦截器,都组装成一个处理链对请求、响应进行处理。
示例代码:
class LeaveHandler {
public:
virtual void handleRequest(const std::string& name, int days) = 0;
};
class Supervisor : public LeaveHandler {
private:
static const int MAX_DAYS_SUPERVISOR_CAN_APPROVE = 3;
LeaveHandler* nextHandler;
public:
Supervisor(LeaveHandler* nextHandler) : nextHandler(nextHandler)
{}
void handleRequest(const std::string& name, int days) override {
if (days <= MAX_DAYS_SUPERVISOR_CAN_APPROVE) {
std::cout << name << " Approved by Supervisor." << std::endl;
} else if (nextHandler != nullptr) {
nextHandler->handleRequest(name, days);
} else {
std::cout << name << " Denied by Supervisor." << std::endl;
}
}
};
class Manager : public LeaveHandler {
private:
static const int MAX_DAYS_MANAGER_CAN_APPROVE = 7;
LeaveHandler* nextHandler;
public:
Manager(LeaveHandler* nextHandler) : nextHandler(nextHandler)
{}
void handleRequest(const std::string& name, int days) override {
if (days <= MAX_DAYS_MANAGER_CAN_APPROVE) {
std::cout << name << " Approved by Manager." << std::endl;
} else if (nextHandler != nullptr) {
nextHandler->handleRequest(name, days);
} else {
std::cout << name << " Denied by Manager." << std::endl;
}
}
};
class Director : public LeaveHandler {
private:
static const int MAX_DAYS_DIRECTOR_CAN_APPROVE = 10;
public:
void handleRequest(const std::string& name, int days) override {
if (days <= MAX_DAYS_DIRECTOR_CAN_APPROVE) {
std::cout << name << " Approved by Director." << std::endl;
} else {
std::cout << name << " Denied by Director." << std::endl;
}
}
};
class LeaveRequest {
private:
std::string name;
int days;
public:
LeaveRequest(const std::string& name, int days) :
name(name),
days(days)
{}
std::string getName() const {
return name;
}
int getDays() const {
return days;
}
};
int main() {
int n;
std::cin >> n;
std::cin.ignore();
LeaveHandler* director = new Director();
LeaveHandler* manager = new Manager(director);
LeaveHandler* supervisor = new Supervisor(manager);
for (int i = 0; i < n; i++) {
std::string input;
std::getline(std::cin, input);
std::istringstream iss(input);
std::string name;
int days;
if (iss >> name >> days) {
LeaveRequest request(name, days);
supervisor->handleRequest(name, days);
} else {
std::cout << "Invalid input" << std::endl;
return 1;
}
}
delete supervisor;
delete manager;
delete director;
return 0;
}
13.2 扩展知识
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它通过建立一条对象链,使请求能够沿着这条链依次传递,直到有一个对象处理它为止。这种模式避免了请求的发送者与接收者之间的紧耦合,让多个对象都有机会处理请求。
简单来说,责任链模式就像公司的审批流程:员工报销需要先由部门经理审批,部门经理无权审批的大额报销会提交给总监,总监也处理不了的会继续提交给总经理,直到有人处理或流程结束。
核心角色:
- Handler(处理者接口)
定义处理请求的接口,通常包含一个指向后继处理者的引用。 - ConcreteHandler(具体处理者)
实现处理者接口,负责处理特定范围内的请求;如果无法处理,则将请求转发给后继者。 - Client(客户端)
创建处理者链,并发起请求(无需关心具体由谁处理)。
适用场景:
- 当多个对象都可能处理同一请求,且具体处理者不确定(需运行时确定)时。
- 当需要动态指定处理请求的对象集合时。
- 当希望请求的发送者与接收者解耦时(发送者无需知道谁会处理请求)。
C++ 代码示例:
下面以 “请假审批流程” 为例:员工请假需要根据请假天数,由不同层级的领导审批(组长可批 3 天内,经理可批 7 天内,总监可批 30 天内,超过 30 天拒绝)。
#include <iostream>
#include <string>
// 1. 处理者接口:审批者
class Approver {
protected:
Approver* nextApprover; // 后继审批者
std::string name; // 审批者姓名
public:
Approver(const std::string& n, Approver* next = nullptr)
: name(n), nextApprover(next) {}
virtual ~Approver() {
// 递归销毁责任链
delete nextApprover;
nextApprover = nullptr;
}
// 处理请求的接口(纯虚函数)
virtual void approve(int days, const std::string& employee) = 0;
};
// 2. 具体处理者1:组长(审批3天以内)
class TeamLeader : public Approver {
public:
TeamLeader(const std::string& n, Approver* next = nullptr)
: Approver(n, next) {}
void approve(int days, const std::string& employee) override {
if (days <= 3) {
std::cout << "组长" << name << "批准了" << employee
<< "的" << days << "天假期。\n";
} else if (nextApprover) {
// 无法处理,转发给下一个审批者
std::cout << "组长" << name << "无法批准" << days << "天假期,提交给上级...\n";
nextApprover->approve(days, employee);
} else {
std::cout << "无人处理" << employee << "的" << days << "天假期申请。\n";
}
}
};
// 2. 具体处理者2:经理(审批7天以内)
class Manager : public Approver {
public:
Manager(const std::string& n, Approver* next = nullptr)
: Approver(n, next) {}
void approve(int days, const std::string& employee) override {
if (days <= 7) {
std::cout << "经理" << name << "批准了" << employee
<< "的" << days << "天假期。\n";
} else if (nextApprover) {
std::cout << "经理" << name << "无法批准" << days << "天假期,提交给上级...\n";
nextApprover->approve(days, employee);
} else {
std::cout << "无人处理" << employee << "的" << days << "天假期申请。\n";
}
}
};
// 2. 具体处理者3:总监(审批30天以内)
class Director : public Approver {
public:
Director(const std::string& n, Approver* next = nullptr)
: Approver(n, next) {}
void approve(int days, const std::string& employee) override {
if (days <= 30) {
std::cout << "总监" << name << "批准了" << employee
<< "的" << days << "天假期。\n";
} else if (nextApprover) {
std::cout << "总监" << name << "无法批准" << days << "天假期,提交给上级...\n";
nextApprover->approve(days, employee);
} else {
std::cout << employee << "的" << days << "天假期申请被拒绝(超过最大允许天数)。\n";
}
}
};
// 客户端代码
int main() {
// 构建责任链:组长 → 经理 → 总监
Approver* chain = new TeamLeader("张三",
new Manager("李四",
new Director("王五")));
// 发起不同的请假请求
std::cout << "=== 请假2天 ===" << std::endl;
chain->approve(2, "小明");
std::cout << "\n=== 请假5天 ===" << std::endl;
chain->approve(5, "小红");
std::cout << "\n=== 请假20天 ===" << std::endl;
chain->approve(20, "小刚");
std::cout << "\n=== 请假40天 ===" << std::endl;
chain->approve(40, "小强");
// 清理责任链
delete chain;
return 0;
}
十四、装饰模式
14.1 回答重点
大话设计模式:(Decorator),动态地给一个对象添加一些额外地指责,就增加功能来说,装饰模式比生成子类更灵活。
通常情况下,扩展类的功能可以通过继承实现,但是扩展越多,子类越多,装饰模式(Decorator Pattern
, 结构型设计模式)可以在不定义子类的情况下动态的给对象添加一些额外的功能。具体的做法是将原始对象放入包含行为的特殊封装类(装饰类),从而为原始对象动态添加新的行为,而无需修改其代码。
举个简单的例子,假设你有一个基础的图形类,你想要为图形类添加颜色、边框、阴影等功能,如果每个功能都实现一个子类,就会导致产生大量的类,这时就可以考虑使用装饰模式来动态地添加,而不需要修改图形类本身的代码,这样可以使得代码更加灵活、更容易维护和扩展。
基本结构:
组件
Component
:通常是抽象类或者接口,是具体组件和装饰者的父类,定义了具体组件需要实现的方法,比如说我们定义Coffee
为组件。具体组件
ConcreteComponent
: 实现了Component接口的具体类,是被装饰的对象。装饰类
Decorator
: 一个抽象类,给具体组件添加功能,但是具体的功能由其子类具体装饰者完成,持有一个指向Component对象的引用。具体装饰类
ConcreteDecorator
: 扩展Decorator类,负责向Component对象添加新的行为,加牛奶的咖啡是一个具体装饰类,加糖的咖啡也是一个具体装饰类。
应用场景:
装饰模式通常在以下几种情况使用:
当需要给一个现有类添加附加功能,但由于某些原因不能使用继承来生成子类进行扩充时,可以使用装饰模式。
动态的添加和覆盖功能:当对象的功能要求可以动态地添加,也可以再动态地撤销时可以使用装饰模式。
在Java的I/O库中,装饰者模式被广泛用于增强I/O流的功能。例如,BufferedInputStream
和BufferedOutputStream
这两个类提供了缓冲区的支持,通过在底层的输入流和输出流上添加缓冲区,提高了读写的效率,它们都是InputStream
和OutputStream
的装饰器。BufferedReader
和BufferedWriter
这两个类与BufferedInputStream
和BufferedOutputStream
类似,提供了字符流的缓冲功能,是Reader和Writer的装饰者。
示例代码:
// 咖啡接口
class Coffee {
public:virtual ~Coffee() {}
virtual void brew() = 0;
};
// 具体的黑咖啡类
class BlackCoffee : public Coffee {
public:void brew() override {
std::cout << "Brewing Black Coffee" << std::endl;
}
};
// 具体的拿铁类
class Latte : public Coffee {
public:void brew() override {
std::cout << "Brewing Latte" << std::endl;
}
};
// 装饰者抽象类
class Decorator : public Coffee {
protected:
std::unique_ptr<Coffee> coffee;
public:Decorator(std::unique_ptr<Coffee> coffee) : coffee(std::move(coffee)) {}
void brew() override {
if (coffee) {
coffee->brew();
}
}
};
// 具体的牛奶装饰者类
class MilkDecorator : public Decorator {
public:
MilkDecorator(std::unique_ptr<Coffee> coffee) :
Decorator(std::move(coffee))
{}
void brew() override {
Decorator::brew();
std::cout << "Adding Milk" << std::endl;
}
};
// 具体的糖装饰者类
class SugarDecorator : public Decorator {
public:
SugarDecorator(std::unique_ptr<Coffee> coffee) : Decorator(std::move(coffee))
{}
void brew() override {
Decorator::brew();
std::cout << "Adding Sugar" << std::endl;
}
};
// 客户端代码
int main() {
int coffeeType, condimentType;
while (std::cin >> coffeeType >> condimentType) {
// 根据输入制作咖啡
std::unique_ptr<Coffee> coffee;
if (coffeeType == 1) {
coffee = std::make_unique<BlackCoffee>();
} else if (coffeeType == 2) {
coffee = std::make_unique<Latte>();
} else {
std::cout << "Invalid coffee type" << std::endl;
continue;
}
// 根据输入添加调料
if (condimentType == 1) {
coffee = std::make_unique<MilkDecorator>(std::move(coffee));
} else if (condimentType == 2) {
coffee = std::make_unique<SugarDecorator>(std::move(coffee));
} else {
std::cout << "Invalid condiment type" << std::endl;
continue;
}
// 输出制作过程
coffee->brew();
}
return 0;
}
14.2 扩展知识
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许在不改变原有对象结构的前提下,动态地给对象添加额外功能。这种模式通过创建 “装饰器” 包装原始对象,以层层嵌套的方式扩展功能,比继承更加灵活。
简单来说,装饰器模式就像给手机套壳:手机(原始对象)的核心功能不变,但可以套上保护壳(基础装饰)、带支架的壳(增强装饰)、防水壳(特殊装饰)等,每个装饰都在不改变手机本身的情况下增加了新功能。
核心角色:
- Component(抽象组件)
定义原始对象和装饰器的共同接口,确保装饰器可以替代原始对象。 - ConcreteComponent(具体组件)
实现抽象组件接口,是被装饰的原始对象(如手机本身)。 - Decorator(抽象装饰器)
实现抽象组件接口,并持有一个组件对象的引用,定义所有具体装饰器的基类。 - ConcreteDecorator(具体装饰器)
继承抽象装饰器,在调用原始组件方法前后添加额外功能(如手机壳的保护功能)。
适用场景:
- 当需要给一个对象动态添加功能,且这些功能可以灵活组合时。
- 当不适合用继承(会导致类爆炸)来扩展功能时。
- 当需要在运行时动态增加或移除对象的功能时。
C++ 代码示例:
下面以 “咖啡订单” 为例:基础咖啡可以添加牛奶、糖、巧克力等配料(装饰器),每种配料会增加价格并改变描述,且配料可以任意组合
#include <iostream>
#include <string>
// 1. 抽象组件:咖啡
class Coffee {
public:
virtual ~Coffee() = default;
virtual std::string getDescription() const = 0; // 获取描述
virtual double getPrice() const = 0; // 获取价格
};
// 2. 具体组件:基础咖啡(被装饰的原始对象)
class Espresso : public Coffee {
public:
std::string getDescription() const override {
return "浓缩咖啡";
}
double getPrice() const override {
return 20.0; // 基础价格
}
};
// 3. 抽象装饰器:配料装饰器
class CoffeeDecorator : public Coffee {
protected:
Coffee* coffee; // 被装饰的咖啡对象
public:
CoffeeDecorator(Coffee* c) : coffee(c) {}
~CoffeeDecorator() override {
delete coffee; // 负责释放被装饰对象
}
};
// 4. 具体装饰器1:牛奶
class Milk : public CoffeeDecorator {
public:
Milk(Coffee* c) : CoffeeDecorator(c) {}
// 扩展描述:添加"加牛奶"
std::string getDescription() const override {
return coffee->getDescription() + ",加牛奶";
}
// 扩展价格:增加5元
double getPrice() const override {
return coffee->getPrice() + 5.0;
}
};
// 4. 具体装饰器2:糖
class Sugar : public CoffeeDecorator {
public:
Sugar(Coffee* c) : CoffeeDecorator(c) {}
std::string getDescription() const override {
return coffee->getDescription() + ",加糖";
}
double getPrice() const override {
return coffee->getPrice() + 2.0;
}
};
// 4. 具体装饰器3:巧克力
class Chocolate : public CoffeeDecorator {
public:
Chocolate(Coffee* c) : CoffeeDecorator(c) {}
std::string getDescription() const override {
return coffee->getDescription() + ",加巧克力";
}
double getPrice() const override {
return coffee->getPrice() + 8.0;
}
};
// 客户端代码
int main() {
// 点一杯纯浓缩咖啡
Coffee* coffee1 = new Espresso();
std::cout << "订单1: " << coffee1->getDescription()
<< ",价格: " << coffee1->getPrice() << "元\n";
// 点一杯加牛奶和糖的咖啡(多层装饰)
Coffee* coffee2 = new Milk(new Sugar(new Espresso()));
std::cout << "订单2: " << coffee2->getDescription()
<< ",价格: " << coffee2->getPrice() << "元\n";
// 点一杯加巧克力、牛奶和糖的咖啡(更多层装饰)
Coffee* coffee3 = new Chocolate(new Milk(new Sugar(new Espresso())));
std::cout << "订单3: " << coffee3->getDescription()
<< ",价格: " << coffee3->getPrice() << "元\n";
// 清理资源(装饰器会递归释放被装饰对象)
delete coffee1;
delete coffee2;
delete coffee3;
return 0;
}
代码说明:
- Coffee(抽象组件):定义了咖啡的基本接口(
getDescription()
和getPrice()
),确保所有咖啡(包括原始咖啡和装饰后的咖啡)都遵循同一接口。 - Espresso(具体组件):代表基础咖啡,是所有装饰的起点,实现了基础描述和价格。
- CoffeeDecorator(抽象装饰器):
- 继承
Coffee
接口,确保装饰器可以像原始咖啡一样被使用(里氏替换原则)。 - 持有一个
Coffee
指针,指向被装饰的对象(可以是原始咖啡或其他装饰器)。 - 析构函数负责释放被装饰对象,避免内存泄漏。
- 继承
- 具体装饰器:
Milk
、Sugar
、Chocolate
分别实现了添加对应配料的功能。- 每个装饰器在调用被装饰对象的方法(如
getDescription()
)基础上,添加了自己的扩展(如 “加牛奶” 和额外价格)。
运行结果
订单1: 浓缩咖啡,价格: 20元
订单2: 浓缩咖啡,加糖,加牛奶,价格: 27元
订单3: 浓缩咖啡,加糖,加牛奶,加巧克力,价格: 35元
装饰器模式的优点
- 灵活性高:可以动态组合不同的装饰器,实现多种功能组合(如牛奶 + 糖、巧克力 + 牛奶等),比继承更灵活。
- 遵循开闭原则:新增功能只需添加新的装饰器,无需修改原始对象或现有装饰器。
- 功能隔离:每个装饰器只关注单一功能(如牛奶只负责添加牛奶相关的描述和价格),职责单一。
与继承的对比
- 继承是静态的(编译时确定功能),装饰器是动态的(运行时组合功能)。
- 继承会导致类数量爆炸(如
加牛奶的咖啡
、加糖的咖啡
、加牛奶和糖的咖啡
需要多个类),而装饰器通过组合实现,类数量少。
装饰器模式在实际开发中应用广泛,例如:
- Java 的
IO流
(如BufferedInputStream
装饰FileInputStream
)。 - GUI 组件的扩展(如给按钮添加边框、阴影等装饰)。
- 日志记录、性能监控等横切关注点的实现。
通过装饰器模式,可以在不改变原始对象的情况下,灵活地扩展功能,是代码设计中实现 “组合优于继承” 的重要手段。
十五、命令模式
15.1 回答重点
**大话设计模式:**命令模式(Command),将一个请求封装为一个对象,从而使得你用不同地请求,对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销地操作。
命令模式是一种行为型设计模式,其允许将请求封装成一个对象(命令对象,包含执行操作所需的所有信息),并将命令对象按照一定的顺序存储在队列中,然后再逐一调用执行,这些命令也可以支持反向操作,进行撤销和重做。
这样一来,发送者只需要触发命令就可以完成操作,不需要知道接受者的具体操作,从而实现两者间的解耦。
基本结构:
命令接口
Command
:接口或者抽象类,定义执行操作的接口。具体命令类
ConcreteCommand
: 实现命令接口,执行具体操作,在调用execute
方法时使“接收者对象”根据命令完成具体的任务,比如遥控器中的“开机”,“关机”命令。接收者类
Receiver
: 接受并执行命令的对象,可以是任何对象,遥控器可以控制空调,也可以控制电视机,电视机和空调负责执行具体操作,是接收者。调用者类
Invoker
: 发起请求的对象,有一个将命令作为参数传递的方法。它不关心命令的具体实现,只负责调用命令对象的execute()
方法来传递请求,在本例中,控制遥控器的“人”就是调用者。客户端:创建具体的命令对象和接收者对象,然后将它们组装起来。
应用场景:
命令模式同样有着很多现实场景的应用,比如Git中的很多操作,如提交(commit)、合并(merge)等,都可以看作是命令模式的应用,用户通过执行相应的命令来操作版本库。Java的GUI编程中,很多事件处理机制也都使用了命令模式。例如,每个按钮都有一个关联的 Action
,它代表一个命令,按钮的点击触发 Action
的执行。
示例代码:
class DrinkMaker; // 前向声明
// 命令接口
class Command {
public:virtual void execute() = 0;
virtual ~Command() = default; // 添加虚析构函数
};
// 具体命令类 - 点餐命令
class OrderCommand : public Command {
private:
std::string drinkName;
DrinkMaker* receiver; // 使用前向声明
public:
OrderCommand(const std::string& drinkName, DrinkMaker* receiver);
void execute() override;
};
// 接收者类 - 制作饮品
class DrinkMaker {
public:void makeDrink(const std::string& drinkName) {
std::cout << drinkName << " is ready!" << std::endl;
}
};
// 实现 OrderCommand 的构造函数和 execute 函数
OrderCommand::OrderCommand(const std::string& drinkName, DrinkMaker* receiver) : drinkName(drinkName), receiver(receiver) {}
void OrderCommand::execute() {
receiver->makeDrink(drinkName);
}
// 调用者类 - 点餐机
class OrderMachine {
private:
Command* command;
public:
void setCommand(Command* command) {
this->command = command;
}
void executeOrder() {
command->execute();
}
};
int main() {
// 创建接收者和命令对象
DrinkMaker drinkMaker;
// 读取命令数量int n;
std::cin >> n;
std::cin.ignore(); // 消耗掉换行符
while (n-- > 0) {
// 读取命令
std::string drinkName;
std::cin >> drinkName;
// 创建命令对象
Command* command = new OrderCommand(drinkName, &drinkMaker);
// 执行命令
OrderMachine orderMachine;
orderMachine.setCommand(command);
orderMachine.executeOrder();
// 释放动态分配的命令对象delete command;
}
return 0;
}
15.2 扩展知识
命令模式(Command Pattern)是一种行为型设计模式,它将请求封装为一个对象,使你可以用不同的请求参数化其他对象,并且支持请求的排队、记录日志和撤销操作。
简单来说,命令模式就像餐厅点餐:顾客(客户端)向服务员(调用者)下达命令(点单),服务员将命令传递给厨师(接收者)执行。这里的 “订单” 就是封装后的命令对象,包含了菜品信息和执行方式。
核心角色:
- Command(命令接口)
声明执行操作的接口(通常是一个execute()
方法)。 - ConcreteCommand(具体命令)
实现命令接口,绑定接收者和具体操作,调用接收者的方法完成命令。 - Receiver(接收者)
知道如何执行命令的具体操作,是实际业务逻辑的执行者。 - Invoker(调用者)
持有命令对象,负责在合适的时机调用命令的execute()
方法。 - Client(客户端)
创建具体命令对象,并设置其接收者。
适用场景:
- 需要将请求的发送者和接收者解耦时(发送者无需知道接收者是谁以及如何处理)。
- 需要支持命令的撤销、重做、排队执行或记录日志时。
- 需要支持事务操作(一组命令要么全部执行,要么全部不执行)时。
C++ 代码示例:
下面以 “智能家居控制” 为例:遥控器(调用者)可以发送各种命令(开灯、关灯、开空调等),每个命令对应不同的设备(接收者),且支持命令撤销功能。
#include <iostream>
#include <vector>
#include <string>
// 前向声明接收者
class Light;
class AirConditioner;
// 1. 命令接口
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0; // 执行命令
virtual void undo() = 0; // 撤销命令
virtual std::string getName() = 0; // 获取命令名称
};
// 3. 接收者1:电灯
class Light {
private:
std::string location;
bool isOn;
public:
Light(const std::string& loc) : location(loc), isOn(false) {}
void on() {
isOn = true;
std::cout << location << "电灯已打开\n";
}
void off() {
isOn = false;
std::cout << location << "电灯已关闭\n";
}
bool getState() const { return isOn; }
};
// 3. 接收者2:空调
class AirConditioner {
private:
std::string location;
bool isOn;
int temperature;
public:
AirConditioner(const std::string& loc) : location(loc), isOn(false), temperature(26) {}
void on() {
isOn = true;
std::cout << location << "空调已打开,当前温度: " << temperature << "°C\n";
}
void off() {
isOn = false;
std::cout << location << "空调已关闭\n";
}
void setTemperature(int temp) {
temperature = temp;
std::cout << location << "空调温度设置为: " << temperature << "°C\n";
}
bool getState() const { return isOn; }
int getTemperature() const { return temperature; }
};
// 2. 具体命令1:开灯命令
class LightOnCommand : public Command {
private:
Light* light;
bool prevState; // 用于撤销操作
public:
LightOnCommand(Light* l) : light(l) {}
void execute() override {
prevState = light->getState();
light->on();
}
void undo() override {
if (prevState) {
light->on();
} else {
light->off();
}
std::cout << "撤销" << getName() << "\n";
}
std::string getName() override {
return "开灯命令";
}
};
// 2. 具体命令2:关灯命令
class LightOffCommand : public Command {
private:
Light* light;
bool prevState;
public:
LightOffCommand(Light* l) : light(l) {}
void execute() override {
prevState = light->getState();
light->off();
}
void undo() override {
if (prevState) {
light->on();
} else {
light->off();
}
std::cout << "撤销" << getName() << "\n";
}
std::string getName() override {
return "关灯命令";
}
};
// 2. 具体命令3:开空调命令
class AirConditionerOnCommand : public Command {
private:
AirConditioner* ac;
bool prevState;
public:
AirConditionerOnCommand(AirConditioner* a) : ac(a) {}
void execute() override {
prevState = ac->getState();
ac->on();
}
void undo() override {
if (prevState) {
ac->on();
} else {
ac->off();
}
std::cout << "撤销" << getName() << "\n";
}
std::string getName() override {
return "开空调命令";
}
};
// 4. 调用者:遥控器
class RemoteControl {
private:
std::vector<Command*> commands; // 命令列表
Command* lastCommand; // 记录最后执行的命令(用于撤销)
public:
RemoteControl(int buttonCount) {
commands.resize(buttonCount, nullptr);
lastCommand = nullptr;
}
~RemoteControl() {
for (auto cmd : commands) {
delete cmd;
}
delete lastCommand;
}
// 设置按钮对应的命令
void setCommand(int slot, Command* cmd) {
if (slot >= 0 && slot < commands.size()) {
delete commands[slot]; // 释放旧命令
commands[slot] = cmd;
}
}
// 按下按钮执行命令
void pressButton(int slot) {
if (slot >= 0 && slot < commands.size() && commands[slot]) {
commands[slot]->execute();
// 保存最后执行的命令(用于撤销)
delete lastCommand;
lastCommand = commands[slot]; // 注意:实际应用中可能需要命令克隆
} else {
std::cout << "无效的按钮或未设置命令\n";
}
}
// 撤销最后一个命令
void pressUndo() {
if (lastCommand) {
lastCommand->undo();
} else {
std::cout << "没有可撤销的命令\n";
}
}
};
// 客户端代码
int main() {
// 创建接收者(设备)
Light* livingRoomLight = new Light("客厅");
AirConditioner* bedroomAC = new AirConditioner("卧室");
// 创建具体命令
Command* lightOn = new LightOnCommand(livingRoomLight);
Command* lightOff = new LightOffCommand(livingRoomLight);
Command* acOn = new AirConditionerOnCommand(bedroomAC);
// 创建调用者(遥控器,有3个按钮)
RemoteControl* remote = new RemoteControl(3);
// 给遥控器按钮分配命令
remote->setCommand(0, lightOn); // 按钮0:开灯
remote->setCommand(1, lightOff); // 按钮1:关灯
remote->setCommand(2, acOn); // 按钮2:开空调
// 模拟操作遥控器
std::cout << "=== 按下按钮0 ===" << std::endl;
remote->pressButton(0);
std::cout << "\n=== 按下按钮2 ===" << std::endl;
remote->pressButton(2);
std::cout << "\n=== 按下按钮1 ===" << std::endl;
remote->pressButton(1);
std::cout << "\n=== 按下撤销按钮 ===" << std::endl;
remote->pressUndo();
std::cout << "\n=== 再次按下撤销按钮 ===" << std::endl;
remote->pressUndo();
// 清理资源
delete remote;
delete livingRoomLight;
delete bedroomAC;
return 0;
}d
代码说明
- Command(命令接口):定义了
execute()
(执行)和undo()
(撤销)方法,所有具体命令都必须实现这两个方法。 - 具体命令类:
- 如
LightOnCommand
、LightOffCommand
等,每个命令绑定一个接收者(如Light
、AirConditioner
)。 - 在
execute()
中调用接收者的对应方法(如light->on()
),并记录执行前的状态用于撤销。 undo()
方法根据记录的状态恢复到命令执行前的状态。
- 如
- 接收者:
Light
和AirConditioner
类,包含实际的业务逻辑(开灯、关灯、开空调等),不知道命令的存在。 - 调用者:
RemoteControl
(遥控器)类,持有命令列表,负责调用命令的execute()
和undo()
方法,不关心命令的具体实现。
运行结果:
=== 按下按钮0 ===
客厅电灯已打开
=== 按下按钮2 ===
卧室空调已打开,当前温度: 26°C
=== 按下按钮1 ===
客厅电灯已关闭
=== 按下撤销按钮 ===
客厅电灯已打开
撤销关灯命令
=== 再次按下撤销按钮 ===
卧室空调已关闭
撤销开空调命令
命令模式的优点:
- 解耦发送者和接收者:发送者(遥控器)无需知道接收者(电灯、空调)的具体实现,只需调用命令的
execute()
方法。 - 支持撤销和日志:通过记录命令历史和实现
undo()
方法,可以轻松实现撤销、重做功能,也便于记录操作日志。 - 支持批量操作:可以将多个命令组合成复合命令(如 “回家模式” 包含开灯、开空调等),实现事务性操作。
- 扩展性好:新增命令只需添加新的具体命令类,无需修改现有代码(符合开闭原则)。
命令模式在很多场景中都有应用,例如:GUI 中的菜单命令、撤销 / 重做功能、任务调度系统、事务管理等,核心是通过封装请求为对象,实现请求的灵活管理和扩展。