编程作业02
-
(论述题)小王正在编写一个简单的计算器程序,要求输入两个整数和运算符号(加、减、乘、除),输出计算结果。小王用面向过程方法编写了下面的代码。请采用面向对象方法通过恰当的设计模式对小王的代码进行重构。请写出你所选择的设计模式,画出类图,并给出核心代码。
int main() {
int numa,numb;
char oper;
double result;
cin>>numa>>numb;
cin>>oper;
switch(oper) {
case '+': result=numa+numb; break;
case '-': result=numa-numb; break;
case '': result=numanumb; break;
case '/': result=numa/(double)numb; break;
}
cout<
}
简单工厂模式 (Simple Factory Pattern)
对于这种需要根据不同的条件(这里是不同的运算符号)来实例化不同对象的场景,最适合的设计模式是简单工厂模式 (Simple Factory Pattern)。

cpp
// 1. 抽象产品类:运算类 (Operation)
class Operation {
protected:
double numberA = 0;
double numberB = 0;
public:
void setNumberA(double num) { numberA = num; }
void setNumberB(double num) { numberB = num; }
// 纯虚函数,子类必须实现
virtual double getResult() = 0;
// 虚析构函数,确保派生类能够被正确析构
virtual ~Operation() {}
};
// 2. 具体产品类:加法类
class OperationAdd : public Operation {
public:
double getResult() override {
return numberA + numberB;
}
};
// 具体产品类:减法类
class OperationSub : public Operation {
public:
double getResult() override {
return numberA - numberB;
}
};
// 具体产品类:乘法类
class OperationMul : public Operation {
public:
double getResult() override {
return numberA * numberB;
}
};
// 具体产品类:除法类
class OperationDiv : public Operation {
public:
double getResult() override {
if (numberB == 0) {
throw invalid_argument("除数不能为0!");
}
return numberA / numberB;
}
};
// 3. 工厂类:运算工厂 (OperationFactory)
class OperationFactory {
public:
// 静态工厂方法,根据操作符返回相应的实例化对象
static Operation* createOperate(char operate) {
Operation* oper = nullptr;
switch (operate) {
case '+':
oper = new OperationAdd();
break;
case '-':
oper = new OperationSub();
break;
case '*':
oper = new OperationMul();
break;
case '/':
oper = new OperationDiv();
break;
}
return oper;
}
};
- (论述题)请为一校服制造厂编写一个校服生产子系统。该工厂可为多家学校生产校服,包括秋季校服和夏季校服。一套秋季校服含一件长袖上衣和一条秋季长裤,一套夏季校服含一件短袖衬衣、一件短袖T恤、一条夏季长裤和一条短裤。不同学校校服款式不同。请设计一个子系统为三所学校(一中、二中、三中)分别生产秋季校服和夏季校服(均码)。例如,当用户输入"一中+夏季"时,系统就会生产出一套一中夏季校服;当用户输入"一中+秋季"时,系统生产出一套一中秋季校服。请写出你所选择的设计模式,画出类图,并给出核心代码。
抽象工厂模式(Abstract Factory Pattern)。
由于存在两个维度的变化:学校(一中、二中、三中)和 校服季节/类型(秋季校服、夏季校服),并且同一个学校的秋季校服和夏季校服构成了一个产品族。因此,最适合的设计模式是抽象工厂模式 (Abstract Factory Pattern)。

cpp
// ==================== 1. 抽象产品类 ====================
// 抽象产品:秋季校服
class AutumnUniform {
};
// 抽象产品:夏季校服
class SummerUniform {
};
// ==================== 2. 具体产品类 ====================
// 一中校服
class FirstSchoolAutumn : public AutumnUniform {
};
class FirstSchoolSummer : public SummerUniform {
};
// 二中校服
class SecondSchoolAutumn : public AutumnUniform {
};
class SecondSchoolSummer : public SummerUniform {
};
// 三中校服
class ThirdSchoolAutumn : public AutumnUniform {
};
class ThirdSchoolSummer : public SummerUniform {
};
// ==================== 3. 抽象工厂类 ====================
class UniformFactory {
public:
virtual AutumnUniform* createAutumnUniform() = 0;
virtual SummerUniform* createSummerUniform() = 0;
virtual ~UniformFactory() {}
};
// ==================== 4. 具体工厂类 ====================
class FirstSchoolFactory : public UniformFactory {
public:
AutumnUniform* createAutumnUniform() override { return new FirstSchoolAutumn(); }
SummerUniform* createSummerUniform() override { return new FirstSchoolSummer(); }
};
class SecondSchoolFactory : public UniformFactory {
public:
AutumnUniform* createAutumnUniform() override { return new SecondSchoolAutumn(); }
SummerUniform* createSummerUniform() override { return new SecondSchoolSummer(); }
};
class ThirdSchoolFactory : public UniformFactory {
public:
AutumnUniform* createAutumnUniform() override { return new ThirdSchoolAutumn(); }
SummerUniform* createSummerUniform() override { return new ThirdSchoolSummer(); }
};
- (论述题)小王正在设计一个导出数据的应用框架。客户要求:导出数据可能存储成不同的文件格式,例如:文本格式、数据库备份形式、Excel格式、Xml格式等等,并且,不管什么格式,导出数据文件都分成三个部分,分别是文件头、文件体和文件尾,在文件头部分,需要描述如下信息:分公司或门市点编号、导出数据的日期,对于文本格式,中间用逗号分隔,在文件体部分,需要描述如下信息:表名称、然后分条描述数据,对于文本格式,表名称单独占一行,数据描述一行算一条数据,字段间用逗号分隔,在文件尾部分,需要描述如下信息:输出人。请写出你所选择的设计模式,画出类图,并给出核心代码。
建造者模式 (Builder Pattern)。
建造者模式的核心思想是:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 在本题中:
- 相同的构建过程:无论什么格式,导出数据都需要经过"组装文件头"、"组装文件体"、"组装文件尾"这三个固定步骤。这就是指导者(Director)需要控制的构建流程。
- 不同的表示:文本格式、XML格式、Excel格式在具体组装时,其拼装逻辑和最终生成的字符串/文件结构是完全不同的。这就是具体建造者(ConcreteBuilder)需要实现的细节。

cpp
// 文件头数据模型
class ExportHeaderModel {
private:
std::string depId; // 分公司或门市点编号
std::string exportDate; // 导出数据的日期
public:
ExportHeaderModel(std::string depId, std::string exportDate)
: depId(std::move(depId)), exportDate(std::move(exportDate)) {}
std::string getDepId() const { return depId; }
std::string getExportDate() const { return exportDate; }
};
// 文件尾数据模型
class ExportFooterModel {
private:
std::string exportUser; // 输出人
public:
ExportFooterModel(std::string exportUser) : exportUser(std::move(exportUser)) {}
std::string getExportUser() const { return exportUser; }
};
// 抽象建造者
class ExportBuilder {
public:
virtual ~ExportBuilder() = default;
// 1. 构建文件头
virtual void buildHeader(const ExportHeaderModel& ehm) = 0;
// 2. 构建文件体 (Key为表名,Value为对应的数据记录集合)
virtual void buildBody(const std::map<std::string, std::vector<std::string>>& mapData) = 0;
// 3. 构建文件尾
virtual void buildFooter(const ExportFooterModel& efm) = 0;
// 获取最终生成的导出结果
virtual std::string getResult() = 0;
};
// 具体建造者
class TxtBuilder : public ExportBuilder {
private:
std::stringstream buffer; // 用于高效拼接字符串
public:
void buildHeader(const ExportHeaderModel& ehm) override {
// 要求:文本格式中间用逗号分隔
buffer << ehm.getDepId() << "," << ehm.getExportDate() << "\n";
}
void buildBody(const std::map<std::string, std::vector<std::string>>& mapData) override {
// 要求:表名称单独占一行,数据描述一行算一条数据,字段间用逗号分隔
for (const auto& pair : mapData) {
// 表名单独一行 (pair.first 是 Key)
buffer << pair.first << "\n";
// 遍历每一条数据 (pair.second 是 Value)
for (const auto& record : pair.second) {
buffer << record << "\n";
}
}
}
void buildFooter(const ExportFooterModel& efm) override {
// 要求:描述输出人
buffer << efm.getExportUser() << "\n";
}
std::string getResult() override {
return buffer.str();
}
};
// 指导者
class Director {
public:
/**
* 控制导出步骤的执行顺序
* 这里传入 ExportBuilder 的引用,利用 C++ 的多态性调用具体实现
*/
void construct(ExportBuilder& builder,
const ExportHeaderModel& header,
const std::map<std::string, std::vector<std::string>>& bodyData,
const ExportFooterModel& footer) {
// 第一步:构建文件头
builder.buildHeader(header);
// 第二步:构建文件体
builder.buildBody(bodyData);
// 第三步:构建文件尾
builder.buildFooter(footer);
}
};
- (论述题)在下面的TicketMaker类(见下表)中,每次调用getNextTicketNumber方法都会返回1000,1001,1002...的数列。我们可以用它生成票的编号或是其他序列号。在现在该类的实现方式下,我们可以生成多个该类的实例。请修改代码,确保只能生成一个该类的实例。


单例模式 (Singleton Pattern)。
cpp
class TicketMaker {
private:
int ticket;
// 1. 将构造函数私有化,禁止外部通过 new 或直接声明来创建对象
TicketMaker() {
ticket = 1000;
}
// 2. 禁用拷贝构造函数和赋值操作符,彻底切断克隆实例的途径
TicketMaker(const TicketMaker&) = delete;
TicketMaker& operator=(const TicketMaker&) = delete;
public:
// 3. 提供一个公有的静态方法,返回唯一实例的引用
static TicketMaker& getInstance() {
// 局部静态变量。在 C++11 及以后的标准中,局部静态变量的初始化是线程安全的
static TicketMaker instance;
return instance;
}
// 获取下一个票号
int getNextTicketNumber() {
// 返回当前票号,然后自增
return ticket++;
}
};
-
(论述题)请你按照单例模式的思想,重新实现下面给出的DBConnections类,确保系统中该类的实例对象最多只能存在三个。
class DBConnections {
public:
DBConnections ( ) { ... }
~ DBConnections ( ) { ... }
void ConnectionInfo ( ) { ... }
};

cpp
class DBConnections {
private:
// 静态指针数组,用于保存最多 3 个实例
static DBConnections* instances[3];
int connectionId; // 用于标识当前是哪个连接实例
// 1. 构造函数私有化
DBConnections(int id) {
connectionId = id;
}
public:
~DBConnections() {
}
// 3. 静态公有方法:通过索引控制只返回3个实例中的某一个
static DBConnections* getInstance(int index) {
// 范围检查,确保最多只能存在3个实例
if (index < 0 || index > 2) {
throw out_of_range("错误:连接池已满或索引非法,最多只能创建3个实例 (索引0-2)!");
}
// 懒加载:如果该槽位还没有实例,则创建一个新实例
if (instances[index] == nullptr) {
instances[index] = new DBConnections(index);
}
// 返回对应的实例
return instances[index];
}
// 具体的业务方法
void ConnectionInfo() {
cout << "正在使用数据库连接 [Connection-" << connectionId << "] 执行查询操作..." << endl;
}
};
// 静态成员变量必须在类外进行初始化
DBConnections* DBConnections::instances[3] = {nullptr, nullptr, nullptr};
- (论述题)某软件系统需要支持多种格式的日志记录,如文件日志、数据库日志、网络日志等。日志的创建过程较为复杂,需要初始化不同的配置参数(文件路径、数据库连接串、网络地址等)。同时,未来可能增加新的日志类型。请选择合适的设计模式实现该日志系统的创建模块。要求:
- 客户端无需知道具体日志类的名字;
- 增加新日志类型时不需要修改已有代码。
请写出所选设计模式的名称,画出类图,并给出核心代码。
工厂方法模式 (Factory Method Pattern)。
题目中提到两个关键限制:
- 客户端无需知道具体日志类的名字(需要将对象的实例化过程封装起来)。
- 增加新日志类型时不需要修改已有代码 (直接排除了简单工厂模式,因为简单工厂在新增类型时必须修改核心的 switch-case 逻辑,违背了开闭原则)。
同时,"日志的创建过程较为复杂",这意味着我们需要专门的类来负责特定日志的初始化工作。因此,最合适的选择是工厂方法模式 (Factory Method Pattern) 。

cpp
// ==================== 1. 抽象产品与具体产品 ====================
// 抽象日志类 (Product)
class Log {
public:
virtual void writeLog(const string& message) = 0;
virtual ~Log() {}
};
// 具体产品:文件日志
class FileLog : public Log {
public:
void writeLog(const string& message) override {
cout << "[文件日志] 写入记录: " << message << endl;
}
};
// 具体产品:数据库日志
class DatabaseLog : public Log {
public:
void writeLog(const string& message) override {
cout << "[数据库日志] 写入记录: " << message << endl;
}
};
// 具体产品:网络日志
class NetworkLog : public Log {
public:
void writeLog(const string& message) override {
cout << "[网络日志] 写入记录: " << message << endl;
}
};
// ==================== 2. 抽象工厂与具体工厂 ====================
// 抽象工厂类 (Creator)
class LogFactory {
public:
// 工厂方法
virtual Log* createLog() = 0;
virtual ~LogFactory() {}
};
// 具体工厂:文件日志工厂
class FileLogFactory : public LogFactory {
public:
Log* createLog() override {
cout << "-> 正在初始化文件路径、权限等复杂参数..." << endl;
return new FileLog();
}
};
// 具体工厂:数据库日志工厂
class DatabaseLogFactory : public LogFactory {
public:
Log* createLog() override {
cout << "-> 正在建立数据库连接、加载驱动等复杂参数..." << endl;
return new DatabaseLog();
}
};
// 具体工厂:网络日志工厂
class NetworkLogFactory : public LogFactory {
public:
Log* createLog() override {
cout << "-> 正在配置网络地址、端口和协议等复杂参数..." << endl;
return new NetworkLog();
}
};
- (论述题)某游戏开发团队需要设计一个角色创建系统。游戏中有多种职业(战士、法师、弓箭手),每种职业又有多种皮肤(默认皮肤、节日皮肤、限定皮肤)。系统要求能够方便地增加新职业或新皮肤,并且客户端可以根据用户选择动态创建具有特定职业和皮肤的角色对象。请选择合适的设计模式,要求避免类爆炸(不使用多重继承),并满足开闭原则。请写出模式名称,画出类图,并给出核心代码。
抽象工厂模式(Abstract Factory Pattern)。
在抽象工厂模式中,我们将"皮肤"视为产品族(Product Family) ,将"职业"视为产品等级结构(Product Type) 。即:一个具体的工厂负责生产某个特定皮肤系列下的所有职业。通过这种方式,我们避免了多重继承(不需要让具体角色同时继承"职业类"和"皮肤类"),满足了题目要求。

cpp
// --- 抽象产品族:战士 ---
class Warrior {
public:
virtual ~Warrior() = default;
virtual void display() const = 0;
};
// 具体产品:默认皮肤战士
class DefaultWarrior : public Warrior {
public:
void display() const override { std::cout << "创建角色:【战士】 - 穿戴【默认皮肤】\n"; }
};
// 具体产品:节日皮肤战士
class FestivalWarrior : public Warrior {
public:
void display() const override { std::cout << "创建角色:【战士】 - 穿戴【节日皮肤】\n"; }
};
// --- 抽象产品族:法师 ---
class Mage {
public:
virtual ~Mage() = default;
virtual void display() const = 0;
};
// 具体产品:默认皮肤法师
class DefaultMage : public Mage {
public:
void display() const override { std::cout << "创建角色:【法师】 - 穿戴【默认皮肤】\n"; }
};
// 具体产品:节日皮肤法师
class FestivalMage : public Mage {
public:
void display() const override { std::cout << "创建角色:【法师】 - 穿戴【节日皮肤】\n"; }
};
- (论述题)某在线考试系统需要生成大量相似的试卷对象。每份试卷包含相同的试题结构(题型、分值分布),但不同考生的试卷中试题的具体内容(题目文本、选项)可能随机生成,且试卷的页眉、页脚包含考生个人信息。如果每次都重新构造一份试卷,性能开销大。请设计一个系统,能够基于一份原型试卷快速创建新试卷,同时能够修改其中的可变部分(如考生信息、随机题目)。请写出模式名称,画出类图,并给出核心代码,简要说明如何确保考生信息独立。
原型模式 (Prototype Pattern)。
基于现有对象(原型)复制生成新的对象,并在复制后对部分属性进行微调,以降低每次重新构造对象(特别是包含复杂结构时)的性能开销。
为了体现"如何确保考生信息独立",我们在设计时需要特别注意深拷贝 (Deep Copy) 的问题。假设考生信息封装在一个独立的 CandidateInfo 类中,试卷类持有该对象的指针。为了确保独立,我们在 ExamPaper 的拷贝构造函数中,对指针类型的成员变量进行了深拷贝 处理:
简要说明:如何确保考生信息独立?
确保护考生信息独立的核心在于实现深拷贝(Deep Copy) 。在 C++ 等面向对象语言中,如果我们不显式编写拷贝逻辑,默认的复制往往是浅拷贝(Shallow Copy) 。如果试卷类中包含指向"考生信息对象"的指针或引用 ,浅拷贝只会复制指针的地址。这会导致克隆出来的多份试卷共享同一个"考生信息"对象在内存中的地址。一旦修改了考生 A 的名字,考生 B 试卷上的名字也会跟着变成 A 的名字。

cpp
// 1. 考生信息类
class CandidateInfo {
public:
string name;
string studentId;
CandidateInfo(string n, string id) : name(n), studentId(id) {}
// 拷贝构造函数
CandidateInfo(const CandidateInfo& other) {
name = other.name;
studentId = other.studentId;
}
};
// 2. 抽象原型类 (可选,但在标准设计模式中通常会定义接口)
class Prototype {
public:
virtual Prototype* clone() = 0;
virtual ~Prototype() {}
};
// 3. 具体原型类:试卷类
class ExamPaper : public Prototype {
private:
string headerTitle; // 页眉:试卷标题(共享不可变部分)
vector<string> questions; // 试题内容(可变部分:可以打乱顺序或替换)
CandidateInfo* candidate; // 考生信息指针(必须深拷贝)
public:
// 基础构造函数(用于创建原型对象)
ExamPaper(string title, const vector<string>& qList) : headerTitle(title), questions(qList) {
candidate = nullptr; // 原型试卷最初没有考生信息
}
// ★ 关键:自定义拷贝构造函数,实现深拷贝 ★
ExamPaper(const ExamPaper& other) {
this->headerTitle = other.headerTitle;
this->questions = other.questions; // vector 的赋值是深拷贝
// 【深拷贝处理】为新试卷重新开辟一块内存来存放考生信息
if (other.candidate != nullptr) {
this->candidate = new CandidateInfo(*(other.candidate));
} else {
this->candidate = new CandidateInfo("未命名", "000000");
}
}
// 实现 clone 方法
Prototype* clone() override {
// 调用拷贝构造函数创建新对象
return new ExamPaper(*this);
}
// 设置考生信息
void setCandidateInfo(string name, string id) {
if (candidate != nullptr) {
candidate->name = name;
candidate->studentId = id;
} else {
candidate = new CandidateInfo(name, id);
}
}
// 模拟随机打乱题目顺序
void shuffleQuestions() {
random_device rd;
mt19937 g(rd());
shuffle(questions.begin(), questions.end(), g);
}
// 打印试卷信息
void display() const {
}
};
- (论述题)某系统需要一个全局的配置管理器,该管理器负责加载配置文件(XML/Properties),并提供配置项的访问。配置管理器在整个应用中只能有一个实例,同时,配置项的创建过程较为复杂(需解析不同格式的文件)。请设计一个系统,综合使用两种创建型模式,确保配置管理器唯一,且能够灵活扩展对不同格式配置文件的解析。请写出所使用的两种设计模式的名称,画出类图,并给出核心代码,简要说明它们如何协作。
单例模式 (Singleton Pattern) 与 工厂方法模式 (Factory Method Pattern)。
**单例模式 (Singleton Pattern):**用于确保 ConfigManager(配置管理器)在全局只有一个实例,保证配置数据的一致性。
**工厂方法模式 (Factory Method Pattern):**用于抽象配置解析器(Parser)的创建过程。将具体的解析逻辑(如解析 XML 还是 Properties)推迟到具体的工厂子类中,满足开闭原则。

cpp
// ==================== [模式2] 工厂方法模式 ====================
// 1. 抽象产品:配置解析器
class IConfigParser {
public:
// 解析文件并返回键值对映射
virtual map<string, string> parse(const string& filePath) = 0;
virtual ~IConfigParser() {}
};
// 2. 具体产品:XML解析器
class XmlParser : public IConfigParser {
public:
map<string, string> parse(const string& filePath) override {
return data;
}
};
// 具体产品:Properties解析器
class PropertiesParser : public IConfigParser {
public:
map<string, string> parse(const string& filePath) override {
return data;
}
};
// 3. 抽象工厂
class IParserFactory {
public:
virtual IConfigParser* createParser() = 0;
virtual ~IParserFactory() {}
};
// 4. 具体工厂
class XmlParserFactory : public IParserFactory {
public:
IConfigParser* createParser() override {
// 可以在这里处理XML解析器需要的复杂初始化(如加载底层XML库等)
return new XmlParser();
}
};
class PropertiesParserFactory : public IParserFactory {
public:
IConfigParser* createParser() override {
return new PropertiesParser();
}
};
// ==================== [模式1] 单例模式 ====================
// 配置管理器 (Singleton)
class ConfigManager {
private:
map<string, string> settings; // 存储全局配置项
// 私有化构造函数和拷贝机制
ConfigManager() {}
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
public:
// 获取全局唯一实例
static ConfigManager& getInstance() {
static ConfigManager instance;
return instance;
}
// 加载配置:依赖注入具体的工厂来生成解析器
void loadConfig(const string& filePath, IParserFactory* factory) {
}
// 访问配置项
string getConfig(const string& key) {
}
};
- (论述题)某在线文档编辑系统需要支持多种文档模板(如简历模板、报告模板、信件模板),每种模板包含固定的样式和结构。用户在创建新文档时,可以选择一种模板,然后根据模板快速生成新文档,并允许用户修改部分内容(如标题、正文)。系统需要能够方便地增加新模板,且创建文档时无需知道具体模板类。同时,为了提高性能,避免重复加载模板资源(如图片、样式表),应该复用模板对象。请综合使用两种创建型模式实现该文档创建模块。请写出所使用的两种设计模式的名称,画出类图,并给出核心代码,简要说明它们如何协作。
工厂方法模式 (Factory Method Pattern): 负责定义创建文档的接口,将实际创建工作推迟到子类中,使得客户端无需知道具体的模板类。
原型模式 (Prototype Pattern): 负责处理对象的高效创建。通过克隆(Clone)已有的、已经加载好重负载资源(如图片、样式表)的原型实例来返回新的对象,避免每次 new 对象时产生的高昂性能开销。
它们是如何协作的?
原型模式: 将文档模板设计为原型类,在对象内部存储已经加载好的重型资源(如 CSS 样式、背景图数据)。通过 clone() 虚函数实现对象的自我复制,避免了重复初始化带来的性能损耗。
工厂方法模式: 工厂类不再通过 new 直接实例化具体的模板类,而是持有一个"原型对象"。当客户端请求创建文档时,工厂调用原型对象的 clone() 方法。这样,工厂与具体产品类进一步解耦,且能动态地切换工厂产出的模板类型。

cpp
// --- 抽象原型 (Abstract Prototype) ---
class Document {
public:
virtual ~Document() = default;
// 原型模式的核心:克隆方法
virtual std::unique_ptr<Document> clone() const = 0;
virtual void setContent(const std::string& title, const std::string& body) = 0;
virtual void display() const = 0;
};
// --- 具体原型:简历模板 (Concrete Prototype) ---
class ResumeDocument : public Document {
private:
std::string styleData; // 模拟重型资源
std::string title;
std::string body;
public:
ResumeDocument() {
// 模拟耗时的资源加载过程
std::cout << ">>> [资源加载] 正在解析简历模板的复杂样式和背景图...\n";
styleData = "【简历专属:精美排版样式表V1.0】";
}
// 实现克隆
std::unique_ptr<Document> clone() const override {
// 使用拷贝构造函数实现克隆
return std::make_unique<ResumeDocument>(*this);
}
void setContent(const std::string& t, const std::string& b) override {
title = t;
body = b;
}
void display() const override {
std::cout << "--- 简历预览 ---\n样式: " << styleData
<< "\n标题: " << title << "\n正文: " << body << "\n\n";
}
};
// --- 具体原型:报告模板 ---
class ReportDocument : public Document {
private:
std::string reportTheme;
std::string title;
std::string body;
public:
ReportDocument() {
std::cout << ">>> [资源加载] 正在下载报告模板的图表插件和页眉页脚...\n";
reportTheme = "【报告专属:商务严谨风格包】";
}
std::unique_ptr<Document> clone() const override {
return std::make_unique<ReportDocument>(*this);
}
void setContent(const std::string& t, const std::string& b) override {
title = t;
body = b;
}
void display() const override {
std::cout << "--- 报告预览 ---\n风格: " << reportTheme
<< "\n标题: " << title << "\n正文: " << body << "\n\n";
}
};
// --- 抽象工厂 (Abstract Factory/Creator) ---
class DocumentFactory {
public:
virtual ~DocumentFactory() = default;
virtual std::unique_ptr<Document> createDocument() = 0;
};
// --- 基于原型的具体工厂 (Concrete Creator) ---
// 该工厂通过克隆现有的原型来创建对象
class PrototypeDocumentFactory : public DocumentFactory {
private:
std::unique_ptr<Document> prototype;
public:
PrototypeDocumentFactory(std::unique_ptr<Document> proto)
: prototype(std::move(proto)) {}
std::unique_ptr<Document> createDocument() override {
// 协作点:不再 new,而是 clone
return prototype->clone();
}
};
编程作业03
- (论述题)小王正在开发一套视频监控系统,考虑到Windows Media Player和Real Player是两种常用的媒体播放器,尽管它们的API结构和调用方法存在区别,但这套应用程序必须支持这两种播放器API,而且在将来可能还需要支持新的媒体播放器。请你针对上面的描述,帮助小王进行设计,给出设计思路及所采用的设计模式,画出类图,并写出关键代码。
适配器模式(Adapter Pattern)。
针对视频监控系统需要调用不同且互不兼容的播放器API的问题,系统不应该直接与具体的播放器API(如 Windows Media Player 或 Real Player)耦合。 我们需要定义一个统一的目标接口(Target)供监控系统客户端调用。然后,为每种具体的播放器编写一个适配器(Adapter)类,该类实现目标接口,并在内部持有一个适配者(Adaptee) (即具体的播放器)的实例。当客户端调用统一接口时,适配器负责将请求转换为具体播放器能够识别的 API 调用。 未来如果有新的播放器加入,只需新增一个实现目标接口的适配器类即可,无需修改客户端及原有代码,完美符合"开闭原则" 。

cpp
// Target:目标接口(客户端期望的统一播放接口)
class VideoPlayer {
public:
virtual ~VideoPlayer() = default;
virtual void play() = 0;
};
// Adaptee 1:适配者(Windows Media Player)
class WindowsMediaPlayer {
public:
void playWMP() {
// Windows Media Player 特有的 API 调用
}
};
// Adaptee 2:适配者(Real Player)
class RealPlayer {
public:
void playReal() {
// Real Player 特有的 API 调用
}
};
// Adapter 1:WMP 的对象适配器
class WMPAdapter : public VideoPlayer {
private:
WindowsMediaPlayer* wmp; // 维持一个对适配者对象的引用
public:
WMPAdapter(WindowsMediaPlayer* player) : wmp(player) {}
void play() override {
if (wmp) {
wmp->playWMP(); // 将客户端请求转发给具体的 API
}
}
};
// Adapter 2:RealPlayer 的对象适配器
class RealPlayerAdapter : public VideoPlayer {
private:
RealPlayer* rp; // 维持一个对适配者对象的引用
public:
RealPlayerAdapter(RealPlayer* player) : rp(player) {}
void play() override {
if (rp) {
rp->playReal(); // 将客户端请求转发给具体的 API
}
}
};
- (论述题)开发一个计算机操作系统的线程调度程序,要求实现时间片调度和抢占调度这2种调度算法,支持Windows、Unix和Linux这3个操作系统,并且将来有可能会增加新的调度算法和支持新的操作系统,请选择恰当的设计模式解决该问题,画出类图,写出关键代码。(不考虑线程调度算法的具体代码实现)
桥接模式(Bridge Pattern)。
系统中存在两个独立变化的维度:
- 调度算法维度(时间片调度、抢占调度等)
- 操作系统维度(Windows、Unix、Linux等)
如果采用传统的多层继承方案,会导致类爆炸(如 Windows时间片调度类、Linux抢占调度类等),且以后任意维度的扩展都会牵一发而动全身。 因此,我们需要将"调度算法(抽象部分)"与"操作系统(实现部分)"分离开来。调度算法类中持有一个操作系统接口的指针(关联关系代替继承关系)。当需要增加新的调度算法或新的操作系统时,两个维度可以独立扩展,互不影响,完美符合"开闭原则"。

cpp
// Implementor (实现类接口):操作系统底层操作
class OS {
public:
virtual ~OS() = default;
virtual void runThread() = 0;
};
// ConcreteImplementor (具体实现类)
class WindowsOS : public OS {
public:
void runThread() override { /* Windows 底层线程运行逻辑 */ }
};
class UnixOS : public OS {
public:
void runThread() override { /* Unix 底层线程运行逻辑 */ }
};
class LinuxOS : public OS {
public:
void runThread() override { /* Linux 底层线程运行逻辑 */ }
};
// Abstraction (抽象类):线程调度器
class ThreadScheduler {
protected:
OS* os; // 桥接核心:持有实现接口的指针
public:
ThreadScheduler(OS* os_impl) : os(os_impl) {}
virtual ~ThreadScheduler() = default;
virtual void schedule() = 0; // 声明抽象业务逻辑
};
// RefinedAbstraction (扩充抽象类):具体调度算法
class TimeSliceScheduler : public ThreadScheduler {
public:
TimeSliceScheduler(OS* os_impl) : ThreadScheduler(os_impl) {}
void schedule() override {
// 时间片调度算法的具体控制逻辑
if (os) {
os->runThread(); // 委派给具体操作系统的实现
}
}
};
class PreemptiveScheduler : public ThreadScheduler {
public:
PreemptiveScheduler(OS* os_impl) : ThreadScheduler(os_impl) {}
void schedule() override {
// 抢占式调度算法的具体控制逻辑
if (os) {
os->runThread(); // 委派给具体操作系统的实现
}
}
};
- (论述题)某饮料店卖饮料时,可以根据顾客的要求在饮料中加入各种配料,饮料店会根据顾客所选的饮料种类和所加入的配料来计算价格。饮料店所供应的部分饮料及配料的种类和价格如下表所示。请你选择恰当的设计模式,为该饮料店开发一个计算饮料价格的程序,要求给出设计思路,画出类图,并写出关键代码。

装饰模式(Decorator Pattern)。
饮料店的计费系统面临的核心问题是:基础饮料和各种配料的组合是指数级的 。如果使用继承(例如创建"草莓布丁奶茶类"、"芝士咖啡类"等),会导致子类数量爆炸,系统极难维护。
为了解决这个问题,我们可以将"基础饮料"作为核心组件(ConcreteComponent),将"配料"作为装饰器(ConcreteDecorator)。
无论是基础饮料还是配料,它们都实现同一个"饮料接口"(Component)。配料装饰器内部持有一个饮料对象的指针。当顾客要求加配料时,就用配料装饰器将被装饰的饮料"包裹"起来。计算价格时,外层的配料会自动调用内层饮料的计费方法,再加上自己的价格,一层层递归返回,从而实现价格的动态累加。这就完美遵守了"组合优于继承"的原则。

cpp
// Component(抽象组件):饮料基类
class Beverage {
public:
virtual ~Beverage() = default;
virtual float cost() = 0; // 核心业务方法:计算价格
};
// ConcreteComponent(具体组件):基础饮料
class MilkTea : public Beverage {
public:
float cost() override { return 8.0f; }
};
class MilkShake : public Beverage {
public:
float cost() override { return 9.0f; }
};
class Coffee : public Beverage {
public:
float cost() override { return 10.0f; }
};
// Decorator(抽象装饰类):配料装饰器基类
class CondimentDecorator : public Beverage {
protected:
Beverage* beverage; // 维持一个对抽象组件对象的引用
public:
CondimentDecorator(Beverage* b) : beverage(b) {}
float cost() override {
if (beverage) {
return beverage->cost();
}
return 0.0f;
}
};
// ConcreteDecorator(具体装饰类):具体配料
class Strawberry : public CondimentDecorator {
public:
Strawberry(Beverage* b) : CondimentDecorator(b) {}
float cost() override {
// 先调用原有功能(内部饮料的价格),再加上附加功能(草莓的价格)
return CondimentDecorator::cost() + 2.0f;
}
};
class Pudding : public CondimentDecorator {
public:
Pudding(Beverage* b) : CondimentDecorator(b) {}
float cost() override {
return CondimentDecorator::cost() + 3.0f;
}
};
class Cheese : public CondimentDecorator {
public:
Cheese(Beverage* b) : CondimentDecorator(b) {}
float cost() override {
return CondimentDecorator::cost() + 4.0f;
}
};
- (论述题)小王为某五星级酒店开发点餐系统。该酒店为满足客户需要,会在不同的时段提供多种不同的餐饮,其菜单的结构图如图所示。请你采用面向对象方法通过恰当的设计模式帮助小王对上述菜单进行设计。

组合模式(Composite Pattern)。通常为了让客户端完全透明地对待所有节点,可以采用透明组合模式 。
从图中可以看出,酒店的菜单呈现典型的树形结构:"所有菜单"包含具体的子菜单(如"餐厅菜单"),而"餐厅菜单"不仅包含具体的"菜式"(叶子节点),还包含更下一级的"甜点菜单"(容器节点)。
如果采用传统方式,客户端在遍历或操作菜单时,必须时刻判断当前对象是具体的"菜式"还是包含其他项的"菜单",这将导致大量繁琐的条件分支代码 。
为了解决这个问题,我们需要提取一个抽象组件 (Component),让具体的"菜式"和包含子项的"菜单"都继承自它。这样,客户端就可以通过统一的接口一致地对待单个菜式和整个菜单集合。当调用一个菜单的操作(如打印或计算价格)时,它会自动递归调用其包含的所有子项的对应操作。

cpp
#include <vector>
#include <stdexcept>
// Component(抽象组件):菜单组件基类
class MenuComponent {
public:
virtual ~MenuComponent() = default;
// 管理子组件的方法(透明组合模式,默认抛出异常)
virtual void add(MenuComponent* component) {
throw std::logic_error("Unsupported Operation");
}
virtual void remove(MenuComponent* component) {
throw std::logic_error("Unsupported Operation");
}
virtual MenuComponent* getChild(int index) {
throw std::logic_error("Unsupported Operation");
}
// 核心业务方法:例如打印菜单信息
virtual void print() = 0;
};
// Leaf(叶子组件):具体菜式
class MenuItem : public MenuComponent {
public:
// 叶子节点没有子项,不需要重写 add/remove/getChild
void print() override {
// 打印该菜式的具体信息
}
};
// Composite(容器组件):包含其他菜单或菜式的菜单集合
class Menu : public MenuComponent {
private:
std::vector<MenuComponent*> children; // 维持对抽象组件的集合引用
public:
void add(MenuComponent* component) override {
children.push_back(component);
}
void remove(MenuComponent* component) override {
// 从 children 集合中移除指定组件的逻辑
}
MenuComponent* getChild(int index) override {
if (index >= 0 && index < children.size()) {
return children[index];
}
return nullptr;
}
void print() override {
// 1. 打印当前菜单本身的描述信息
// 2. 递归调用所有子组件的 print() 方法
for (MenuComponent* child : children) {
if (child) {
child->print();
}
}
}
};
- (论述题)开发一个系统帮助业务部门实现灵活的奖金计算。对于普通员工,主要有个人当月业务奖金、个人当月回款奖金等,对于部门经理,除了有普通员工的奖金外,还有团队当月业务奖金等。目前各奖金类别的计算规则如下:
个人当月业务奖金 = 个人当月销售额 * 3%
个人当月回款奖金 = 个人当月回款额 * 0.1%
团队当月业务奖金 = 团队当月销售额 * 1%
考虑到业务部门要通过调整奖金计算方式来激励士气,系统应灵活地适应各种需求变化,例如,将来可能会增加个人业务增长奖金、团队当月回款奖金、团队业务增长奖金、团队盈利奖金等奖金类别,也可能增加新的员工岗位,或变更奖金计算规则等。请写出你所选择的设计模式,画出类图,并给出核心代码。
装饰模式(Decorator Pattern)。
如果使用继承为每个岗位(普通员工、经理等)硬编码计算公式,一旦岗位增加或奖金组合变化,就会导致类爆炸。
为了实现高度的灵活性,我们可以将"奖金计算"抽象为核心组件(Component)。不同的奖金类别(个人业务奖金、回款奖金、团队业务奖金等)设计为装饰器(Decorator) 。
这样,无论是普通员工还是部门经理,都可以从一个基础的"零奖金"或"底薪"组件开始,根据其岗位职责,动态地将需要的奖金装饰器一层层套上去。计算总奖金时,最外层的装饰器会递归调用内层的方法,实现奖金的层层累加。未来增加新奖金类别,只需增加新的具体装饰器即可;增加新岗位,只需改变装饰的组合方式,完全符合"开闭原则"。

cpp
// 辅助类:用于传递员工的各项业务数据上下文
class EmployeeData {
public:
double personalSales;
double personalCollection;
double teamSales;
// ... 未来可扩展更多数据字段
};
// Component(抽象组件):奖金计算接口
class BonusComponent {
public:
virtual ~BonusComponent() = default;
virtual double calcBonus(EmployeeData* data) = 0;
};
// ConcreteComponent(具体组件):基础奖金(通常作为装饰的起点)
class BaseBonus : public BonusComponent {
public:
double calcBonus(EmployeeData* data) override {
return 0.0; // 基础奖金起步为0
}
};
// Decorator(抽象装饰类):奖金装饰器基类
class BonusDecorator : public BonusComponent {
protected:
BonusComponent* component; // 维持一个对抽象组件的引用
public:
BonusDecorator(BonusComponent* comp) : component(comp) {}
double calcBonus(EmployeeData* data) override {
if (component) {
return component->calcBonus(data);
}
return 0.0;
}
};
// ConcreteDecorator A:个人当月业务奖金
class PersonalBusinessBonus : public BonusDecorator {
public:
PersonalBusinessBonus(BonusComponent* comp) : BonusDecorator(comp) {}
double calcBonus(EmployeeData* data) override {
// 递归计算内层奖金,并累加当前装饰器的特有奖金
return BonusDecorator::calcBonus(data) + (data->personalSales * 0.03);
}
};
// ConcreteDecorator B:个人当月回款奖金
class PersonalCollectionBonus : public BonusDecorator {
public:
PersonalCollectionBonus(BonusComponent* comp) : BonusDecorator(comp) {}
double calcBonus(EmployeeData* data) override {
return BonusDecorator::calcBonus(data) + (data->personalCollection * 0.001);
}
};
// ConcreteDecorator C:团队当月业务奖金
class TeamBusinessBonus : public BonusDecorator {
public:
TeamBusinessBonus(BonusComponent* comp) : BonusDecorator(comp) {}
double calcBonus(EmployeeData* data) override {
return BonusDecorator::calcBonus(data) + (data->teamSales * 0.01);
}
};
- (论述题)请设计一个电子相册自动生成程序,要求该程序能够将一组图片自动生成到一个以树形结构保存的电子相册中,相册的目录采用树形结构,一级目录为年,二级目录为月,三级目录为日。每张照片可以添加音效和花边等特效。请写出你所选择的设计模式,画出类图,并给出核心代码。
组合模式(Composite Pattern) + 装饰模式(Decorator Pattern)。
这个电子相册生成程序包含两个核心需求,我们需要结合使用两种设计模式来解决:
- 相册目录的树形结构 :相册的结构是"年 -> 月 -> 日 -> 照片",属于典型的"整体-部分"层次结构。我们需要将目录(年、月、日)视为容器组件(Composite) ,将照片视为叶子组件(Leaf) 。它们都继承自同一个抽象组件(Component),这样客户端(生成程序)就能用统一的接口去遍历和操作整个相册树。
- 为照片动态添加特效:每张照片都可以添加音效、花边等,且这些特效可以随意组合(比如既有音效又有花边)。如果用继承来实现,会导致类爆炸(如带音效和花边的照片类)。因此,我们需要用装饰器(Decorator)去包装具体的照片对象,在不改变照片原有类的基础上,动态地给它增加新职责(特效)。

cpp
#include <vector>
#include <string>
#include <stdexcept>
// --- 组合模式部分 ---
// Component(抽象组件):相册组件基类
class AlbumComponent {
public:
virtual ~AlbumComponent() = default;
// 目录管理方法(透明组合模式,默认抛出异常以防误用)
virtual void add(AlbumComponent* component) {
throw std::logic_error("Unsupported Operation");
}
virtual void remove(AlbumComponent* component) {
throw std::logic_error("Unsupported Operation");
}
virtual AlbumComponent* getChild(int index) {
throw std::logic_error("Unsupported Operation");
}
// 核心业务方法:展示相册内容
virtual void display() = 0;
};
// Leaf(叶子组件):照片
class Photo : public AlbumComponent {
public:
void display() override {
// 展示基本照片的逻辑
}
};
// Composite(容器组件):相册目录(可代表年、月、日文件夹)
class AlbumDirectory : public AlbumComponent {
private:
std::string dirName;
std::vector<AlbumComponent*> children;
public:
AlbumDirectory(std::string name) : dirName(name) {}
void add(AlbumComponent* component) override {
children.push_back(component);
}
void remove(AlbumComponent* component) override {
// 从集合中移除子组件
}
AlbumComponent* getChild(int index) override {
if (index >= 0 && index < children.size()) return children[index];
return nullptr;
}
void display() override {
// 1. 显示当前目录名称 (年/月/日)
// 2. 递归调用子组件的 display()
for (auto* child : children) {
if (child) child->display();
}
}
};
// --- 装饰模式部分 ---
// Decorator(抽象装饰类):特效装饰器基类
class PhotoDecorator : public AlbumComponent {
protected:
AlbumComponent* component; // 维持对被装饰组件的引用
public:
PhotoDecorator(AlbumComponent* comp) : component(comp) {}
void display() override {
if (component) {
component->display();
}
}
};
// ConcreteDecorator A:音效特效
class SoundDecorator : public PhotoDecorator {
private:
void playSound() { /* 播放音效的内部逻辑 */ }
public:
SoundDecorator(AlbumComponent* comp) : PhotoDecorator(comp) {}
void display() override {
PhotoDecorator::display(); // 先展示照片本身
playSound(); // 附加音效功能
}
};
// ConcreteDecorator B:花边特效
class BorderDecorator : public PhotoDecorator {
private:
void drawBorder() { /* 绘制花边的内部逻辑 */ }
public:
BorderDecorator(AlbumComponent* comp) : PhotoDecorator(comp) {}
void display() override {
PhotoDecorator::display(); // 先展示照片本身
drawBorder(); // 附加花边功能
}
};
- (论述题)某编译器系统包含词法分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成等多个复杂子系统。客户端程序需要调用这些子系统才能完成编译过程,导致客户端代码复杂且与子系统紧密耦合。请设计一个系统,为客户端提供统一、简单的编译接口,同时允许未来增加新的优化算法或目标平台。请写出所选择的设计模式,画出类图,并给出核心代码。
外观模式(Facade Pattern) ,具体采用其扩展形式:抽象外观模式(Abstract Facade) 。
编译器的各个阶段(词法分析、语法分析等)构成了极其复杂的子系统。如果客户端直接调用这些模块,不仅需要了解编译的底层全过程,还会与具体的底层类产生严重的高耦合。 为了解决这个问题,我们需要引入一个外观角色(Facade) ,将这些子系统的调用顺序和交互逻辑封装在一个统一的 compile() 方法中,客户端只需调用这个方法即可完成编译。 此外,题目要求"允许未来增加新的优化算法或目标平台"。为了符合"开闭原则",避免未来增加新平台时修改现有的外观类代码,我们可以引入抽象外观类(Abstract Facade) 。客户端针对抽象外观类编程,而具体的平台编译流(装配了特定的优化器和代码生成器子系统)由具体的具体外观类(Concrete Facade)来实现。

cpp
#include <iostream>
#include <string>
// --- 子系统角色 (SubSystems) ---
class LexicalAnalyzer {
public:
void analyze(const std::string& source) { /* 词法分析逻辑 */ }
};
class SyntaxAnalyzer {
public:
void parse() { /* 语法分析逻辑 */ }
};
class SemanticAnalyzer {
public:
void check() { /* 语义分析逻辑 */ }
};
class IntermediateCodeGenerator {
public:
void generate() { /* 中间代码生成逻辑 */ }
};
// 具体的优化器和代码生成器(未来可扩展为接口和具体实现类)
class Optimizer {
public:
void optimize() { /* 代码优化逻辑 */ }
};
class TargetCodeGenerator {
public:
void generateTarget() { /* 目标代码生成逻辑 */ }
};
// --- 外观角色 ---
// 抽象外观类 (Abstract Facade)
class CompilerFacade {
public:
virtual ~CompilerFacade() = default;
virtual void compile(const std::string& sourceCode) = 0;
};
// 具体外观类 (Concrete Facade):标准编译器流
class StandardCompilerFacade : public CompilerFacade {
private:
LexicalAnalyzer* lexer;
SyntaxAnalyzer* parser;
SemanticAnalyzer* semantic;
IntermediateCodeGenerator* icg;
Optimizer* optimizer;
TargetCodeGenerator* targetGen;
public:
StandardCompilerFacade() {
lexer = new LexicalAnalyzer();
parser = new SyntaxAnalyzer();
semantic = new SemanticAnalyzer();
icg = new IntermediateCodeGenerator();
optimizer = new Optimizer();
targetGen = new TargetCodeGenerator();
}
~StandardCompilerFacade() {
delete lexer; delete parser; delete semantic;
delete icg; delete optimizer; delete targetGen;
}
// 将复杂的子系统调用封装在一个简单接口中
void compile(const std::string& sourceCode) override {
lexer->analyze(sourceCode);
parser->parse();
semantic->check();
icg->generate();
optimizer->optimize(); // 如果有新优化器,只需创建新的Facade类或传入新指针
targetGen->generateTarget(); // 如果有新目标平台,同理
}
};
// 客户端只需针对 CompilerFacade 编程,完全不知道背后的6个子系统
- (论述题)某电商系统需要集成多个第三方支付渠道(支付宝、微信支付、银联支付)。每个支付渠道的API接口(参数、方法名、返回值格式)各不相同。系统内部已定义了一个统一的支付接口(pay(double amount))。请设计一个系统,使得在不修改现有支付接口和客户端代码的前提下,能够灵活接入新的支付渠道。请写出所选择的设计模式,画出类图,并给出核心代码。
适配器模式(Adapter Pattern) ,具体采用更加灵活的对象适配器模式(基于组合) 。
电商系统已经定义了一个标准的、客户端期望的目标接口(即 pay(double amount))。然而,引入的支付宝、微信、银联等第三方支付 API,其方法名称和参数列表与我们的标准接口完全不一致。 如果我们直接在客户端代码中写 if-else 去分别调用这些第三方 API,不仅会导致客户端与大量第三方库严重耦合,而且未来增加新支付渠道时还必须修改原有的业务逻辑,严重违反了"开闭原则"。 正确的做法是:提供一个统一的目标接口(Target) ,并为每一个第三方支付通道创建一个适配器类(Adapter) 。适配器类实现标准的目标接口,并在内部持有一个具体的第三方支付对象(Adaptee)的实例。当客户端调用统一的 pay 方法时,适配器会在内部将其转换为第三方 API 期望的调用格式和方法。

cpp
#include <string>
// Target(目标接口):系统内部统一的支付接口
class PaymentInterface {
public:
virtual ~PaymentInterface() = default;
virtual void pay(double amount) = 0;
};
// --- Adaptee(适配者):第三方支付 API ---
class AlipayAPI {
public:
// 假设支付宝 API 需要货币类型参数
void doAlipay(double amount, std::string currency) {
// 调用支付宝底层接口
}
};
class WeChatPayAPI {
public:
// 假设微信支付 API 参数单位是分(整型)
void doWeChatPay(int total_fee) {
// 调用微信支付底层接口
}
};
class UnionPayAPI {
public:
// 假设银联接口方法名完全不同
void executeUnionPay(double money) {
// 调用银联底层接口
}
};
// --- Adapter(适配器):接口转换层 ---
// 支付宝适配器
class AlipayAdapter : public PaymentInterface {
private:
AlipayAPI* alipay; // 持有适配者引用
public:
AlipayAdapter(AlipayAPI* api) : alipay(api) {}
void pay(double amount) override {
if (alipay) {
// 将统一的 pay 转换为具体的 doAlipay,并处理参数差异
alipay->doAlipay(amount, "CNY");
}
}
};
// 微信支付适配器
class WeChatPayAdapter : public PaymentInterface {
private:
WeChatPayAPI* wechatPay;
public:
WeChatPayAdapter(WeChatPayAPI* api) : wechatPay(api) {}
void pay(double amount) override {
if (wechatPay) {
// 元转分的数据转换逻辑
int total_fee = static_cast<int>(amount * 100);
wechatPay->doWeChatPay(total_fee);
}
}
};
// 银联支付适配器
class UnionPayAdapter : public PaymentInterface {
private:
UnionPayAPI* unionPay;
public:
UnionPayAdapter(UnionPayAPI* api) : unionPay(api) {}
void pay(double amount) override {
if (unionPay) {
unionPay->executeUnionPay(amount);
}
}
};
- (论述题)某企业报表系统需要生成大型销售报表(数据量大,加载耗时)。为了提高系统响应速度,要求采用懒加载策略:只有当用户真正查看报表时才从数据库加载数据并生成报表。此外,用户可以为报表添加多种装饰,如页眉(显示公司名称)、页脚(显示页码)、水印(机密字样)、公司Logo等,装饰可以动态组合,且未来可能增加新装饰。请设计一个系统,满足以下要求:
- 报表数据采用懒加载;
- 报表的装饰可以动态添加和组合;
- 两种模式应协同工作,使得客户端可以一致地处理带装饰的报表,且加载逻辑与装饰逻辑相互独立。
请写出所选择的设计模式(至少两种),画出类图,并给出核心代码。
代理模式(虚拟代理 Proxy Pattern) 联合 装饰模式(Decorator Pattern) 。
针对题目的需求,我们需要将"懒加载"和"动态装饰"解耦,让它们各司其职又相互配合:
- 报表数据懒加载 :生成大型报表非常耗时,所以我们可以引入代理模式(虚拟代理) 。代理对象与真实报表实现相同的接口。当客户端请求生成报表时,先返回一个轻量级的代理对象。只有当客户端真正调用代理对象的
display()方法时,代理才会去实例化并从数据库加载真正的报表对象。 - 报表装饰动态组合 :为了动态添加页眉、水印等,我们引入装饰模式。将页眉、水印等设计为具体的装饰器,它们同样实现报表接口,并持有一个报表对象的引用。
- 协同工作 :由于虚拟代理 和装饰器 都实现了同一个"报表接口(Component)",客户端可以将虚拟代理对象传入装饰器中。当调用最外层装饰器的显示方法时,请求会层层传递,最终到达代理对象,触发真实的懒加载逻辑。加载逻辑与装饰逻辑完全解耦。

cpp
// Component(抽象组件):报表统一接口
class Report {
public:
virtual ~Report() = default;
virtual void display() = 0;
};
// --- 代理模式部分 ---
// RealSubject(真实主题):真正的大型报表
class RealReport : public Report {
private:
std::string reportName;
void loadFromDB() {
// 模拟极其耗时的数据库查询与数据加载操作
}
public:
RealReport(const std::string& name) : reportName(name) {
loadFromDB(); // 实例化时触发高开销操作
}
void display() override {
// 显示核心报表数据
}
};
// Proxy(虚拟代理):控制对大型报表的延迟访问
class ReportProxy : public Report {
private:
RealReport* realReport; // 持有对真实报表的引用
std::string reportName;
public:
ReportProxy(const std::string& name) : realReport(nullptr), reportName(name) {}
~ReportProxy() { delete realReport; }
void display() override {
// 只有在真正需要显示时,才创建真实报表(懒加载)
if (realReport == nullptr) {
realReport = new RealReport(reportName);
}
realReport->display();
}
};
// --- 装饰模式部分 ---
// Decorator(抽象装饰类):报表装饰器基类
class ReportDecorator : public Report {
protected:
Report* report; // 维持一个对抽象组件对象的引用
public:
ReportDecorator(Report* r) : report(r) {}
void display() override {
if (report) {
report->display();
}
}
};
// ConcreteDecorator A:页眉装饰
class HeaderDecorator : public ReportDecorator {
private:
void drawHeader() { /* 绘制公司页眉的内部逻辑 */ }
public:
HeaderDecorator(Report* r) : ReportDecorator(r) {}
void display() override {
drawHeader(); // 增加前置功能:画页眉
ReportDecorator::display(); // 转发请求
}
};
// ConcreteDecorator B:水印装饰
class WatermarkDecorator : public ReportDecorator {
private:
void drawWatermark() { /* 绘制机密水印的内部逻辑 */ }
public:
WatermarkDecorator(Report* r) : ReportDecorator(r) {}
void display() override {
ReportDecorator::display(); // 转发请求
drawWatermark(); // 增加后置功能:画水印
}
};
// 客户端使用示例思路(伪代码):
// Report* lazyReport = new ReportProxy("2026_Sales_Report");
// Report* decoratedReport = new WatermarkDecorator(new HeaderDecorator(lazyReport));
// decoratedReport->display(); // 此时才真正触发底层代理去查数据库
编程作业04
- (论述题)某学校的差旅费报销制度规定,要根据不同的报销金额,由不同的领导审批,1万元以下科长审批,1万元至5万元之间处长审批,5万元至10万元之间副校长审批,10万元以上校长审批。最常用的编程思想是采用条件语句进行判断,但是随着差旅费报销制度的逐渐完善,可能需要判断的条件会越来越多,可能处理的逻辑也更加复杂,代码将变得难以维护。请选择恰当的设计模式解决该问题,画出类图,写出关键代码。
职责链模式 (Chain of Responsibility Pattern)。
在日常审批流程中,一个请求往往需要经过多个层级的处理。传统的 if-else 或 switch 语句会将请求发送者与所有的处理逻辑(科长、处长、副校长、校长)强耦合在一起,导致代码臃肿、难以维护,且违反了开闭原则。
职责链模式 的核心思想是将请求的发送者和接收者解耦,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。 通过职责链模式,我们可以将各级领导封装为具体的处理者对象。每个处理者只需关注自己权限范围内的报销金额,如果超权,则将请求转发给后继者(上级领导)。这样,当报销制度发生改变(例如增加新的审批级别)时,

cpp
// 1. 请求封装类 (仅保留决策所需的核心属性)
class ReimbursementRequest {
public:
double amount; // 报销金额
};
// 2. 抽象处理者
class Approver {
protected:
Approver* successor = nullptr; // 维持对后继者的引用 [cite: 3035]
public:
virtual ~Approver() {}
void setSuccessor(Approver* successor) {
this->successor = successor; // [cite: 3036-3038]
}
// 抽象处理方法 [cite: 3040]
virtual void processRequest(ReimbursementRequest* request) = 0;
};
// 3. 具体处理者:科长
class SectionChief : public Approver {
public:
void processRequest(ReimbursementRequest* request) override {
if (request->amount < 10000) {
// 本层级处理请求逻辑... [cite: 3045-3046]
} else if (successor != nullptr) {
successor->processRequest(request); // 无法处理,转发给后继者 [cite: 3047-3048]
}
}
};
// 4. 具体处理者:处长
class Director : public Approver {
public:
void processRequest(ReimbursementRequest* request) override {
if (request->amount >= 10000 && request->amount < 50000) {
// 本层级处理请求逻辑...
} else if (successor != nullptr) {
successor->processRequest(request); // 无法处理,转发给后继者
}
}
};
// (副校长、校长等其他具体处理者类的逻辑与上述完全一致,仅判断条件不同,此处省略...)
- (论述题)小王准备使用面向对象的方法设计一个快餐店的简单游戏,游戏中有顾客、服务员、菜品和厨师。每个顾客都有一个服务员帮助点菜,并且可以点多个菜;每道菜都由指定厨师制作,不同的菜可能由不同的厨师制作;顾客跟服务员点完菜后,服务员通知后厨做菜。请你针对上面的描述,帮助小王选择合适的设计模式进行设计。
1)简要说明你的设计思路和选择的模式。
2)给出你设计的UML类图和实现代码。
命令模式 (Command Pattern)。
快餐店点餐是一个典型的"请求发送者与接收者解耦"的场景。在游戏中:
- 服务员 (Invoker - 调用者):负责接收顾客的点菜请求,但自己并不真正制作菜品。
- 厨师 (Receiver - 接收者):真正在后厨执行做菜业务逻辑的对象。
- 菜品订单 (Command - 命令):将顾客的"点菜"请求封装成一个对象。由于不同的菜由不同的厨师制作,具体的菜品命令内部会绑定指定的厨师(接收者)。
- 顾客点多个菜 (Command Queue - 命令队列) :服务员对象内部维护一个命令队列(如
std::vector<Command*>),当顾客点完菜后,服务员通过遍历队列,依次调用命令的执行方法(
execute()),进而通知后厨做菜。
通过命令模式,服务员只需要知道如何触发订单,而完全不需要关心这道菜是谁做的、怎么做的,实现了调用者和接收者的完全解耦。

cpp
#include <vector>
// 1. 接收者接口:厨师
class Chef {
public:
virtual ~Chef() = default;
virtual void cook() = 0; // 真正的业务逻辑
};
// 具体接收者:烤肉厨师、面点厨师
class RoastChef : public Chef {
public:
void cook() override { /* 烤肉具体实现 */ }
};
class BakeChef : public Chef {
public:
void cook() override { /* 烘焙具体实现 */ }
};
// 2. 抽象命令类
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
};
// 3. 具体命令类:烤肉订单 (绑定指定厨师)
class RoastDishCommand : public Command {
private:
Chef* receiver; // 绑定接收者
public:
RoastDishCommand(Chef* chef) : receiver(chef) {}
void execute() override {
receiver->cook(); // 调用接收者的业务方法
}
};
// 具体命令类:烘焙订单
class BakeDishCommand : public Command {
private:
Chef* receiver;
public:
BakeDishCommand(Chef* chef) : receiver(chef) {}
void execute() override {
receiver->cook();
}
};
// 4. 调用者:服务员 (引入命令队列,支持点多个菜)
class Waiter {
private:
std::vector<Command*> orders; // 命令队列
public:
// 顾客点菜,将命令加入队列
void addOrder(Command* cmd) {
orders.push_back(cmd);
}
// 顾客取消点菜
void cancelOrder(Command* cmd) {
// 遍历 orders 移除 cmd 逻辑...
}
// 点完菜后,通知后厨做菜
void notifyKitchen() {
for (Command* cmd : orders) {
cmd->execute(); // 依次执行命令队列中的请求
}
}
};
- (论述题)小明正在开发一个病房呼叫应答仿真系统。系统中每个病房配备一个呼叫按钮和一个呼叫显示应答器,疗区大厅配备一个显示应答器和一个语音播报器。假设,按下001号病房的呼叫按钮后,那么,所有的呼叫显示应答器都会显示发出呼叫的病房号001,大厅同时语音播报该病房号001。当医护人员按下任意一处的呼叫显示应答器的应答按钮后,所有呼叫显示应答器停止显示该房间号,大厅也停止语音播报。请用恰当的设计模式实现该系统,画出类图,给出核心代码。
饮料店的计费系统面临的核心问题是:基础饮料和各种配料的组合是指数级的 。如果使用继承(例如创建"草莓布丁奶茶类"、"芝士咖啡类"等),会导致子类数量爆炸,系统极难维护。
为了解决这个问题,我们可以将"基础饮料"作为核心组件(ConcreteComponent),将"配料"作为装饰器(ConcreteDecorator)。
无论是基础饮料还是配料,它们都实现同一个"饮料接口"(Component)。配料装饰器内部持有一个饮料对象的指针。当顾客要求加配料时,就用配料装饰器将被装饰的饮料"包裹"起来。计算价格时,外层的配料会自动调用内层饮料的计费方法,再加上自己的价格,一层层递归返回,从而实现价格的动态累加。这就完美遵守了"组合优于继承"的原则。
装饰模式(Decorator Pattern)。
实现
实现
实现
实现
聚合
继承
继承
继承
<<abstract>>
Beverage
+cost() : float
MilkTea
+cost() : float
MilkShake
+cost() : float
Coffee
+cost() : float
<<abstract>>
CondimentDecorator
#beverage: Beverage
+CondimentDecorator(Beverage*)
+cost() : float
Strawberry
+CondimentDecorator(Beverage*)
+cost() : float
Pudding
+CondimentDecorator(Beverage*)
+cost() : float
Cheese
+CondimentDecorator(Beverage*)
+cost() : float
cpp
// Component(抽象组件):饮料基类
class Beverage {
public:
virtual ~Beverage() = default;
virtual float cost() = 0; // 核心业务方法:计算价格
};
// ConcreteComponent(具体组件):基础饮料
class MilkTea : public Beverage {
public:
float cost() override { return 8.0f; }
};
class MilkShake : public Beverage {
public:
float cost() override { return 9.0f; }
};
class Coffee : public Beverage {
public:
float cost() override { return 10.0f; }
};
// Decorator(抽象装饰类):配料装饰器基类
class CondimentDecorator : public Beverage {
protected:
Beverage* beverage; // 维持一个对抽象组件对象的引用
public:
CondimentDecorator(Beverage* b) : beverage(b) {}
float cost() override {
if (beverage) {
return beverage->cost();
}
return 0.0f;
}
};
// ConcreteDecorator(具体装饰类):具体配料
class Strawberry : public CondimentDecorator {
public:
Strawberry(Beverage* b) : CondimentDecorator(b) {}
float cost() override {
// 先调用原有功能(内部饮料的价格),再加上附加功能(草莓的价格)
return CondimentDecorator::cost() + 2.0f;
}
};
class Pudding : public CondimentDecorator {
public:
Pudding(Beverage* b) : CondimentDecorator(b) {}
float cost() override {
return CondimentDecorator::cost() + 3.0f;
}
};
class Cheese : public CondimentDecorator {
public:
Cheese(Beverage* b) : CondimentDecorator(b) {}
float cost() override {
return CondimentDecorator::cost() + 4.0f;
}
};
- (论述题)小王正在为某公司设计开发一套物业租赁管理系统,该公司有多种类型的物业,如公寓、商铺等,并且在将来可能会增加新的物业类型,如别墅、车库等;公司的经纪每租出一个物业,主管就会收到相应的租赁信息。请你针对上面的描述,帮助小王选择合适的设计模式进行设计。
1)简要说明你的设计思路和选择的模式。
2)给出你设计的UML类图和实现代码。
工厂方法模式 (Factory Method Pattern) + 观察者模式 (Observer Pattern)。
设计思路与模式分析:
- 对象创建的扩展性问题(工厂方法模式) : 系统中目前有公寓、商铺等物业,且明确指出"将来可能会增加新的物业类型(如别墅、车库等)"。如果使用简单的实例化或简单工厂,每次增加新物业都需要修改原有的创建逻辑,违背了"开闭原则"。 引入工厂方法模式 ,将物业的实例化操作延迟到具体的子类工厂中完成。抽象的
PropertyFactory只提供创建接口,当未来增加"别墅"时,只需增加一个Villa产品类和对应的VillaFactory工厂类即可,系统核心骨架无需做任何修改。 - 状态变化的联动通知问题(观察者模式) : "经纪每租出一个物业,主管就会收到相应的租赁信息",这是一个典型的"触发联动"场景。经纪人是状态的改变者,主管是需要被通知的对象。如果让经纪人直接调用主管的代码,会导致两者严重耦合。 引入观察者模式 ,建立一种一对多的依赖关系。将经纪人作为具体目标(Subject) ,主管作为具体观察者(Observer) 。经纪人只需维护一个观察者列表,当"租出物业"这一行为发生时,自动遍历列表并触发通知(
notify)。这样设计不仅解耦了发送者与接收者,未来如果需要增加其他接收者(如财务部记账),只需新增观察者即可。

cpp
// ================= 工厂方法模式模块 =================
// 1. 抽象产品:物业基类
class Property {
public:
virtual ~Property() = default;
virtual std::string getPropName() = 0;
};
// 具体产品
class Apartment : public Property {
public:
std::string getPropName() override { return "公寓"; }
};
class Shop : public Property {
public:
std::string getPropName() override { return "商铺"; }
};
// 2. 抽象工厂:定义创建产品的接口
class PropertyFactory {
public:
virtual ~PropertyFactory() = default;
virtual Property* createProperty() = 0;
};
// 具体工厂:将实例化逻辑分散到子类,符合开闭原则
class ApartmentFactory : public PropertyFactory {
public:
Property* createProperty() override { return new Apartment(); }
};
class ShopFactory : public PropertyFactory {
public:
Property* createProperty() override { return new Shop(); }
};
// ================= 观察者模式模块 =================
// 3. 抽象观察者
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string& message) = 0; // 接收通知的方法
};
// 具体观察者:主管
class Supervisor : public Observer {
public:
void update(const std::string& message) override {
// 主管接收到租赁信息的处理逻辑...
}
};
// 4. 抽象目标 (Subject)
class Subject {
protected:
std::vector<Observer*> observers; // 维护对观察者的引用
public:
virtual ~Subject() = default;
void attach(Observer* observer) { observers.push_back(observer); }
void detach(Observer* observer) { /* 从集合中移除的逻辑... */ }
// 通知所有注册的观察者
virtual void notify(const std::string& message) {
for (Observer* obs : observers) {
obs->update(message);
}
}
};
// 5. 具体目标:经纪人
class Broker : public Subject {
public:
// 经纪人执行租出动作
void rentOut(Property* property) {
// 1. 执行具体的租赁业务逻辑...
// 2. 核心联动:状态发生改变,自动触发通知逻辑
std::string msg = "已成功租出物业:" + property->getPropName();
notify(msg);
}
};
- (论述题)开发一个消防应急响应系统,火灾探测器(FireDetector)发现火灾异常后将自动传递信号给各种响应设备,例如警示灯(WarningLight)将闪烁(flicker())、报警器(Alarm)将发出警报(alarm())、安全逃生门(SecurityDoor)将自动开启(open())、隔离门(InsulatedDoor)将自动关闭(close())等,每一种响应设备的行为由专门的程序来控制。请写出你所选择的设计模式,画出类图,并给出核心代码。
观察者模式 (Observer Pattern)。
设计思路与模式分析: 在消防应急系统中,存在着典型的一对多依赖关系:一个核心对象(火灾探测器)的状态发生改变(发现火灾)时,需要引起一系列其他对象(警示灯、报警器、逃生门、隔离门等)的联动行为。
如果让探测器直接去调用各种响应设备的方法,会导致探测器与所有外围设备深度耦合。未来如果需要增加新的响应设备(如"自动喷淋系统"),就必须修改探测器的源代码,严重违背开闭原则。
引入观察者模式 ,可以将系统划分为目标(Subject)和观察者(Observer):
- 具体目标(FireDetector) :只需维护一个观察者列表,并在发现火灾时触发广播通知(
notify)。它完全不需要知道具体有哪些设备在监听,也不关心设备是如何响应的。 - 具体观察者(各响应设备) :实现统一的响应接口(如
update)。当接收到目标的通知时,各自执行专属的业务逻辑(闪烁、鸣响、开门等)。
通过这种设计,系统实现了发送者与接收者的完全解耦,随时可以动态地增加或移除响应设备。

cpp
#include <vector>
// 1. 抽象观察者:响应设备统一接口
class Observer {
public:
virtual ~Observer() = default;
virtual void update() = 0; // 接收到通知后的更新动作
};
// 2. 具体观察者:封装各自的专属控制逻辑
class WarningLight : public Observer {
private:
void flicker() { /* 警示灯闪烁硬件控制逻辑... */ }
public:
void update() override { flicker(); }
};
class Alarm : public Observer {
private:
void alarm() { /* 报警器鸣响硬件控制逻辑... */ }
public:
void update() override { alarm(); }
};
class SecurityDoor : public Observer {
private:
void open() { /* 逃生门开启硬件控制逻辑... */ }
public:
void update() override { open(); }
};
class InsulatedDoor : public Observer {
private:
void close() { /* 隔离门关闭硬件控制逻辑... */ }
public:
void update() override { close(); }
};
// 3. 抽象目标:维护观察者集合并定义通知规范
class Subject {
protected:
std::vector<Observer*> observers; // 观察者集合
public:
virtual ~Subject() = default;
// 注册观察者
void attach(Observer* observer) { observers.push_back(observer); }
// 注销观察者
void detach(Observer* observer) { /* 遍历 vector 移除 observer 的逻辑... */ }
// 核心联动:遍历通知所有已注册的观察者
virtual void notify() {
for (Observer* obs : observers) {
obs->update();
}
}
};
// 4. 具体目标:火灾探测器
class FireDetector : public Subject {
public:
// 发现火灾异常的业务入口
void detectFire() {
// ...执行物理探测逻辑...
// 状态发生改变,自动触发对所有设备的通知
notify();
}
};
- (论述题)小明为某银行开发一个报警系统,该系统功能描述如下,请你帮助小明完成设计,给出设计模式的名称,画出类图,写出关键代码。

状态模式 (State Pattern)。
设计思路与模式分析: 通过分析系统需求可以发现,金库系统的核心特征是:系统的行为高度依赖于当前的时间范围(白天或晚上) 。在白天和晚上这两种不同的"状态"下,即使触发了相同的动作(如"使用金库"、"使用电话"),系统作出的响应也是完全截然不同的(白天记录日志/正常呼叫;晚上触发紧急通知/留言)。
如果使用传统的面向过程编程,我们需要在每一个动作(useSafe, usePhone)中加入大量的 if-else 条件判断来检测当前时间。随着状态规则的增加,代码将变得极为臃肿且难以维护,严重违反开闭原则。
引入状态模式,我们可以将"白天"和"晚上"抽象为独立的状态类:
- 环境类 (Context - 金库系统):维护当前的时间状态,并将所有的具体动作委托给当前的状态对象去处理。它提供改变状态的接口(如时间流逝引发状态切换)。
- 抽象状态类 (State) :定义在特定状态下系统可以响应的动作接口(如
doUse,doPhone,doAlarm,以及检测时间变化的doClock)。 - 具体状态类 (DayState, NightState) :封装了金库在白天和晚上各自专属的行为逻辑。
通过这种"状态-行为"的封装映射,系统消除了复杂的条件判断分支,

cpp
#include <iostream>
#include <string>
class SafeContext;
// 1. 抽象状态类:封装与时间状态相关的行为接口
class State {
public:
virtual ~State() = default;
virtual void doClock(SafeContext* context, int hour) = 0; // 时间流逝,可能触发状态切换
virtual void doUse(SafeContext* context) = 0; // 使用金库
virtual void doPhone(SafeContext* context) = 0; // 使用电话
virtual void doAlarm(SafeContext* context) = 0; // 使用警铃
};
// 2. 环境类:金库系统上下文
class SafeContext {
private:
State* state; // 维持一个对当前抽象状态的引用
public:
void changeState(State* newState) {
this->state = newState;
}
// 供状态对象调用的底层业务方法
void callAlarmCenter(const std::string& msg) { /* 呼叫警报中心或发送紧急通知... */ }
void recordLog(const std::string& msg) { /* 在警报中心留下记录... */ }
// 外部触发的动作,全部委托给当前的状态对象处理
void setClock(int hour) { state->doClock(this, hour); }
void useSafe() { state->doUse(this); }
void usePhone() { state->doPhone(this); }
void pressAlarm() { state->doAlarm(this); }
};
// 3. 具体状态类:白天状态 (09:00 ~ 16:59)
class DayState : public State {
public:
void doClock(SafeContext* context, int hour) override; // 在此处由于要切换到 NightState,通常在外部统一定义或使用单例
void doUse(SafeContext* context) override {
// 白天使用金库:留存记录
context->recordLog("记录:白天使用金库");
}
void doPhone(SafeContext* context) override {
// 白天使用电话:正常呼叫
context->callAlarmCenter("呼叫:白天正常通话");
}
void doAlarm(SafeContext* context) override {
// 任何时候警铃都发送紧急通知
context->callAlarmCenter("紧急:白天按下警铃");
}
};
// 4. 具体状态类:夜晚状态 (17:00 ~ 08:59)
class NightState : public State {
public:
void doClock(SafeContext* context, int hour) override {
// 状态切换逻辑:若时间到达白天范围内,则切换为白天状态
if (hour >= 9 && hour < 17) {
context->changeState(new DayState());
}
}
void doUse(SafeContext* context) override {
// 晚上使用金库:发送紧急事态通知
context->callAlarmCenter("紧急:晚上异常使用金库!");
}
void doPhone(SafeContext* context) override {
// 晚上使用电话:呼叫留言电话
context->callAlarmCenter("呼叫:晚上转接留言机");
}
void doAlarm(SafeContext* context) override {
// 任何时候警铃都发送紧急通知
context->callAlarmCenter("紧急:晚上按下警铃");
}
};
// 补充 DayState 的状态切换逻辑实现
void DayState::doClock(SafeContext* context, int hour) {
if (hour < 9 || hour >= 17) {
context->changeState(new NightState()); // 切换到夜晚状态
}
}
- (论述题)小明正在开发一个温度自动调节系统,该系统能够根据用户设置的温度值自动调节温度。当系统开启时,它会读取用户设置的期望温度值T,若当前温度低于T,它将温度提升到T,若当前温度高于T,它将温度降为T。当系统关闭时,它将停止调节温度。当系统开启时,它可以接受温度设置及关闭系统的指令,当系统关闭时,它只接受开启系统的指令。请用恰当的设计模式设计该系统,画出类图,给出核心代码。
状态模式 (State Pattern)。
设计思路与模式分析:
该系统的核心特征是:系统的行为高度依赖于其当前所处的状态(开启或关闭)。
- 在"开启状态"下,系统能够响应"关闭指令"和"温度设置指令"(并执行具体的升温/降温业务逻辑)。
- 在"关闭状态"下,系统只能响应"开启指令",并且会拒绝或忽略"温度设置指令"和"关闭指令"。
如果使用传统的编程方式,我们需要在setTemperature、turnOn、turnOff等方法中大量使用if (isOn)的条件判断。随着系统复杂度的增加,这些条件分支会变得难以维护。
引入状态模式,我们可以将"开启"和"关闭"抽象为独立的状态类: - 环境类 (Context - 温度控制器):维护当前的系统状态,并将所有外部的请求(开机、关机、设温)委托给当前的状态对象去处理。
- 抽象状态类 (State) :定义在特定状态下系统可以响应的动作接口(
turnOn,turnOff,setTemperature)。 - 具体状态类 (OnState, OffState ):封装了系统在开启和关闭状态下各自专属的行为逻辑,并在适当时机调用环境类的方法实现状态的自动转换。
通过这种设计,提取了与状态相关的行为,消除了冗长的条件分支,符合开闭原则。

cpp
#include <iostream>
class TemperatureController;
// 1. 抽象状态类
class State {
public:
virtual ~State() = default;
virtual void turnOn(TemperatureController* context) = 0;
virtual void turnOff(TemperatureController* context) = 0;
virtual void setTemperature(TemperatureController* context, int t) = 0;
};
// 2. 环境类:温度控制器上下文
class TemperatureController {
private:
State* state; // 维持对当前状态的引用
int currentTemp; // 当前温度
public:
TemperatureController(State* initialState, int temp) : state(initialState), currentTemp(temp) {}
void setState(State* newState) { this->state = newState; }
int getCurrentTemp() const { return currentTemp; }
void setCurrentTemp(int temp) { this->currentTemp = temp; }
// 将外部请求统一委托给当前状态对象处理
void turnOn() { state->turnOn(this); }
void turnOff() { state->turnOff(this); }
void setTemperature(int t) { state->setTemperature(this, t); }
};
// 3. 具体状态类:关闭状态
class OffState : public State {
public:
void turnOn(TemperatureController* context) override; // 声明:开启系统并切换状态
void turnOff(TemperatureController* context) override {
// 已关闭,不响应
}
void setTemperature(TemperatureController* context, int t) override {
// 关闭状态下不接受调温指令
}
};
// 4. 具体状态类:开启状态
class OnState : public State {
public:
void turnOn(TemperatureController* context) override {
// 已开启,不响应
}
void turnOff(TemperatureController* context) override {
// 接受关机指令,切换为关闭状态
context->setState(new OffState());
}
void setTemperature(TemperatureController* context, int targetTemp) override {
int current = context->getCurrentTemp();
if (current < targetTemp) {
// 执行升温逻辑...
context->setCurrentTemp(targetTemp);
} else if (current > targetTemp) {
// 执行降温逻辑...
context->setCurrentTemp(targetTemp);
}
// 若相等则无需调节
}
};
// 补充 OffState 的状态切换逻辑实现
void OffState::turnOn(TemperatureController* context) {
// 接受开机指令,切换为开启状态
context->setState(new OnState());
}
- (论述题)小王正在开发一套电影院售票系统,在该系统中需要为不同类型的用户提供不同的电影票打折方式,具体打折方案如下:
(1) 学生凭学生证可享受票价7折优惠;
(2) 年龄在10周岁及以下的儿童可享受每张票减免15元的优惠(原始票价需大于等于30元);
(3) 影院会员卡用户除享受票价半价优惠外还可进行积分,积分累计到一定额度可换取电影院赠送的奖品。
该系统在将来可能还要根据需要引入新的打折方式。请你针对上面的描述,帮助小王进行设计,给出设计思路及所采用的设计模式,画出类图,并写出关键代码。
策略模式 (Strategy Pattern)。
设计思路与模式分析: 在售票系统中,计算最终票价存在多种不同的算法(学生7折、儿童减15元、VIP半价且积分)。如果将这些所有的计费逻辑全部写在"电影票(MovieTicket)"类中,通过冗长的 if-else 语句来判断用户类型并计算价格,会导致 MovieTicket类极其臃肿,且严重违反了开闭原则(未来如果要增加"军人优惠"或"节假日特惠",就必须修改 MovieTicket 类的源代码)。
引入策略模式,我们可以将"算法的定义"与"算法的使用"完全分离:
- 抽象策略类 (DiscountStrategy):定义一个统一的计费接口(如 calculate)。
- 具体策略类 (StudentDiscount, ChildrenDiscount, VIPDiscount):分别封装学生、儿童、会员的具体打折算法。
- 环境类 (Context - 电影票 MovieTicket) :内部维护一个对抽象策略接口的引用。它不关心具体的打折逻辑,只需在计算价格时,将计算工作委托给当前绑定的具体策略对象即可。
通过这种设计,系统可以在运行时动态地为电影票设置不同的打折策略。当未来需要引入新的打折方式时,只需新增一个实现了DiscountStrategy接口的具体策略类即可,原有代码完全无需修改,极大地提高了系统的灵活性和可扩展性。

cpp
#include <iostream>
// 1. 抽象策略类:打折策略接口
class DiscountStrategy {
public:
virtual ~DiscountStrategy() = default;
// 抽象计算价格方法
virtual double calculate(double originalPrice) = 0;
};
// 2. 具体策略类:学生票打折
class StudentDiscount : public DiscountStrategy {
public:
double calculate(double originalPrice) override {
// 学生凭证享受7折优惠
return originalPrice * 0.7;
}
};
// 具体策略类:儿童票打折
class ChildrenDiscount : public DiscountStrategy {
public:
double calculate(double originalPrice) override {
// 10周岁及以下儿童,原始票价大于等于30元时减免15元
if (originalPrice >= 30.0) {
return originalPrice - 15.0;
}
return originalPrice; // 不满足条件则不减免
}
};
// 具体策略类:VIP会员打折
class VIPDiscount : public DiscountStrategy {
public:
double calculate(double originalPrice) override {
// VIP半价,并处理积分逻辑
// std::cout << "增加会员积分..." << std::endl;
return originalPrice * 0.5;
}
};
// 3. 环境类:电影票
class MovieTicket {
private:
double originalPrice; // 电影票原价
DiscountStrategy* strategy; // 维持对抽象策略类的引用
public:
MovieTicket(double price) : originalPrice(price), strategy(nullptr) {}
// 动态注入具体的打折策略
void setDiscountStrategy(DiscountStrategy* strategy) {
this->strategy = strategy;
}
// 获取最终折后价格
double getPrice() {
if (strategy != nullptr) {
// 将计算行为委托给具体的策略对象
return strategy->calculate(originalPrice);
}
return originalPrice; // 若未设置任何策略,则原价售出
}
};
- (论述题)小明在使用策略模式进行设计时,发现策略所对应的多个算法在实现上有很多公共功能,请你给出建议帮助小明能更好地实现复用?小明再进一步设计时,又发现这些算法的实现步骤都是一样的,只是在某些局部步骤的实现上有所不同,那么请你再帮帮小明,如何能更好地实现复用?
策略模式(Strategy Pattern)+ 模板方法模式(Template Method Pattern)
- 针对"算法存在较多公共功能"的建议
建议:将策略接口(Interface)重构为抽象类(Abstract Class)。 在标准的策略模式中,抽象策略通常是一个纯接口。如果多个具体策略包含相同的公共功能,会导致代码在子类中大量重复。通过引入一个抽象基类作为抽象策略,把这些公共的成员变量和公共方法提取到抽象基类中实现,具体的策略类只需继承该抽象类,即可直接复用这些公共功能。 - 针对"算法步骤相同,局部不同"的建议
建议:在抽象策略类中引入"模板方法模式"。 既然算法的整体执行步骤是一致的,只是某些局部细节不同,这正是模板方法模式的典型应用场景。
- 在抽象策略类中定义一个模板方法(Template Method),用来固定算法的执行骨架(按先后顺序调用基本步骤)。
- 将那些"局部不同的实现"定义为受保护的抽象方法(Primitive Operations / 钩子方法) ,延迟到具体的子类策略中去实现。
通过这种结合,既保留了策略模式能在运行时动态切换算法的灵活性,又利用模板方法模式消除了骨架代码的重复。
cpp
#include <iostream>
// 1. 抽象策略类 (结合了模板方法模式)
class AbstractStrategy {
public:
virtual ~AbstractStrategy() = default;
// 【解决问题1】:提取的公共功能,所有子类直接复用
void commonFunction() {
std::cout << "执行所有策略共有的前置/后置逻辑或数据处理..." << std::endl;
}
// 【解决问题2】:模板方法,固定算法的骨架和执行步骤
void algorithmTemplate() {
commonFunction(); // 调用公共功能
stepOne(); // 调用局部步骤1 (具体实现留给子类)
stepTwo(); // 调用局部步骤2 (具体实现留给子类)
}
protected:
// 抽象基本方法:延迟到具体策略子类中实现的可变局部步骤
virtual void stepOne() = 0;
virtual void stepTwo() = 0;
};
// 2. 具体策略类 A
class ConcreteStrategyA : public AbstractStrategy {
protected:
void stepOne() override {
std::cout << "策略A:执行专属的步骤 1" << std::endl;
}
void stepTwo() override {
std::cout << "策略A:执行专属的步骤 2" << std::endl;
}
};
// 3. 具体策略类 B
class ConcreteStrategyB : public AbstractStrategy {
protected:
void stepOne() override {
std::cout << "策略B:执行专属的步骤 1" << std::endl;
}
void stepTwo() override {
std::cout << "策略B:执行专属的步骤 2" << std::endl;
}
};
// 4. 环境类 (Context)
class Context {
private:
AbstractStrategy* strategy; // 维持对抽象基类的引用
public:
Context() : strategy(nullptr) {}
// 动态设置/切换策略
void setStrategy(AbstractStrategy* newStrategy) {
this->strategy = newStrategy;
}
// 执行策略
void execute() {
if (strategy) {
// 客户端只需调用模板方法,骨架和具体细节自动结合运行
strategy->algorithmTemplate();
}
}
};
- (论述题)当前开发一个新语言、新中间件或程序框架时,基于充分利用多核CPU能力等方面的考虑,多采用事件循环处理模型+回调函数的设计方式,使用该模型的示例代码和预期输出详见下表。针对该模型,请写出你所选择的设计模式,画出类图,并给出Trigger类及相关类的设计和实现,使得输出能够符合预期结果。(为简便起见,假设所有事件处理函数的原型均为void xxxx(void);)

观察者模式 (Observer Pattern)。
设计思路:
- 目标 (Subject) / 发布者 :
Trigger类。它负责维护一个"事件字典",用来存储所有注册的事件及其对应的回调函数列表。 - 观察者 (Observer) / 订阅者 :各个
func01、func02等回调函数。在这里,我们利用 C++ 的函数指针或std::function来代替传统的抽象观察者类,这样可以实现极简的回调解耦。 - 注册与注销 :调用
on()方法时,将事件名称与回调函数绑定。当传入nullptr时,视为清空(注销)该事件下的所有监听者。 - 触发通知:调用
raiseEvent()时,Trigger会根据事件名称查找到所有注册的回调函数,并依次遍历执行它们,同时在执行前后包装输出Process xxx start/end的预期格式日志。

cpp
// 触发器类 (充当观察者模式中的 Subject / 发布者)
class Trigger {
private:
// 维护一个映射表:事件名称 -> 对应的回调函数列表 (一对多的依赖关系)
map<string, vector<function<void()>>> eventHandlers;
public:
// 注册事件 或 注销事件
void on(const string& eventName, function<void()> callback) {
if (callback == nullptr) {
// 如果传入 nullptr,则清空该事件的所有注册回调(注销)
eventHandlers[eventName].clear();
} else {
// 否则,将回调函数追加到对应事件的列表中
eventHandlers[eventName].push_back(callback);
}
}
// 触发事件 (广播通知所有订阅了该事件的观察者)
void raiseEvent(const string& eventName) {
// 检查该事件是否被注册过
if (eventHandlers.find(eventName) != eventHandlers.end()) {
// 遍历并执行所有绑定的回调函数
for (auto& callback : eventHandlers[eventName]) {
// 预期输出:执行前的包裹日志
cout << "Process " << eventName << " start" << endl;
// 核心业务:执行回调
callback();
// 预期输出:执行后的包裹日志
cout << "Process " << eventName << " end" << endl;
}
}
}
};
// ==========================================
// 客户端测试代码 (对应你题目中的示例代码)
// ==========================================
void func01() { cout << "func01()" << endl; }
void func02() { cout << "func02()" << endl; }
void func03() { cout << "func03()" << endl; }
/*
int main() {
Trigger trigger; // 触发器
// 注册事件
trigger.on("event01", func01);
trigger.on("event01", func02);
trigger.on("event02", func02);
trigger.on("event03", func03);
// 触发事件
trigger.raiseEvent("event01");
trigger.raiseEvent("event02");
trigger.raiseEvent("event03");
// 注销 event01
trigger.on("event01", nullptr);
// 再次触发
trigger.raiseEvent("event01"); // 已被清空,不会产生输出
trigger.raiseEvent("event02");
return 0;
}
*/