10|原型模式:当复制比重新创建更高效时
上一篇讲了建造者模式。
它解决的问题是:
一个复杂对象,不应该靠巨大构造函数或一堆随意 setter 拼出来。
比如一个复杂诊断请求、一个自动化测试工况、一个整车配置对象,都可以通过 Builder 一步步构建,最后得到一个完整、合法、稳定的对象。
但真实工程里,还有另一类创建问题。
这类问题不是"对象不好构建",而是:
对象能构建,但每次从头创建都太重、太慢、太麻烦。
比如车企软件里很常见的场景:
- 一个仿真场景已经配好了大部分参数,只想改其中两三个字段
- 一个测试工况已经写好了基础模板,只想复制一份做变体测试
- 一个诊断请求对象已经初始化完成,只想换一个 DID 或目标 ECU
- 一个复杂配置对象已经加载了大量默认参数,只想为某个车型做少量覆盖
- 一个运行时对象初始化成本很高,重新创建比复制更贵
这时问题就变了。
我们不再关心:
这个对象怎么一步步构建?
而是关心:
已经有一个现成对象时,能不能直接复制一份,再在副本上做小修改?
这就是原型模式要解决的问题。
一、先从一个车企场景说起
假设我们现在要做自动化测试。
系统里有很多测试工况对象 TestScenario,每个工况都包含:
- 初始车速
- SOC
- 驾驶模式
- 环境温度
- 道路附着系数
- 故障注入配置
- 采样信号列表
- 期望结果
- 日志采集开关
- 回放数据源
- 仿真时间参数
很多工况其实只有少量差异。
比如同一组制动测试,可能有一个基础模板:
- 初始车速 60 km/h
- SOC 80%
- 环境温度 25℃
- 路面附着系数 0.8
- 驾驶模式 Normal
- 默认采集 ABS、ESP、WheelSpeed 信号
- 默认期望制动距离阈值
然后你要基于它扩展出:
- 雨天工况
- 低温工况
- 低附着路面工况
- 轮速传感器故障工况
- 高 SOC 工况
- 低 SOC 工况
如果每次都从头构建:
cpp
TestScenario scenario;
scenario.SetInitialSpeed(60);
scenario.SetSoc(80);
scenario.SetTemperature(25);
scenario.SetRoadFriction(0.8);
scenario.SetDriveMode(DriveMode::Normal);
scenario.EnableSignal("ABS");
scenario.EnableSignal("ESP");
scenario.EnableSignal("WheelSpeed");
scenario.SetExpectedBrakeDistance(36.5);
...
代码当然能写。
但问题也很明显。
第一,重复代码很多。
第二,公共部分很容易写漏。
第三,如果模板默认值改了,所有场景都要一起改。
第四,有些对象初始化很重,反复重新构建并不划算。
所以这里真正的问题不是"不会创建对象"。
而是:
当大多数内容都一样时,每次都从头创建,既低效,又不利于维护。
原型模式,就是为了解决这个问题。
二、为什么"重新创建"有时不是最优解?
很多人一开始会觉得:
重新 new 一个对象,再把参数填进去,不就行了吗?
简单对象当然没问题。
比如:
cpp
Point p2 = Point(10, 20);
重新创建几乎没有成本。
但复杂对象不一样。
1. 对象初始化成本可能很高
有些对象构建时会做很多事:
- 加载默认配置
- 分配大块内存
- 初始化内部缓存
- 构造嵌套子对象
- 解析模板文件
- 建立索引结构
- 预计算某些派生字段
比如一个仿真场景对象,可能在创建时就要:
- 读取场景模板
- 加载道路拓扑
- 初始化参与者列表
- 构造传感器配置
- 建立事件触发规则
如果你每次都从头创建,只为了改一个温度参数,就会显得很重。
2. 重复创建会带来大量模板式代码
比如有一个基础诊断请求模板,默认包含:
- 会话类型
- 超时时间
- 重试策略
- 响应校验模式
- trace 配置
- 安全访问配置
如果每次都从头写,调用方会不断重复同样的初始化代码。
这不仅啰嗦,还容易不一致。
3. 复杂对象的"默认状态"本身就有价值
很多复杂对象不是空对象。
它们往往有一个"已经调好"的基础状态。
比如:
- 一个标准城市道路仿真场景模板
- 一个 BMS 诊断测试模板
- 一个 OTA 升级任务模板
- 一个欧规车型配置模板
- 一个供应商 A 的传感器配置模板
这些对象的价值不只是"能被构造出来"。
更重要的是:
它们已经代表了一套经过验证的基础配置。
这时复制模板对象,比重新从零拼装更自然。
4. 从现成对象演化副本,往往更符合业务语义
比如你不是在说:
我要创建一个全新的测试工况。
而是在说:
我要基于"标准制动测试模板"复制一份,再改成低温版本。
这个业务语义本身就更接近"复制"。
原型模式就是把这种意图表达出来。
三、原型模式到底是什么?
原型模式可以这样理解:
通过复制一个已有对象来创建新对象,而不是每次都从头实例化并初始化。
再说得直白一点:
先准备一个"原型对象",需要新对象时,不是重新造,而是把这个原型克隆一份。
这个新对象通常和原型对象初始状态一致。
然后调用方可以根据需要,在副本上做少量修改。
所以原型模式关注的不是:
- 创建哪个类
- 创建哪一族对象
- 一个复杂对象如何一步步构建
它关注的是:
当已有对象足够接近目标对象时,能不能通过复制来获得新对象。
四、原型模式解决的核心问题
原型模式最核心的价值有三个。
1. 减少重复初始化成本
如果对象构建过程很重,复制可能比重新创建更划算。
这不一定只是性能问题。
也可能是开发成本问题。
比如你已经有一套完整测试模板,再复制一份去改,远比从头再配一遍轻松。
2. 复用已经验证过的对象状态
很多工程对象的难点不在"创建类实例"。
而在"把对象配对、配全、配对劲"。
一个已经验证过的模板对象,本身就是一种知识沉淀。
复制模板对象,本质上是在复用这套配置经验。
3. 让"以模板为基础生成变体"更自然
很多业务场景天然就是"基础版 + 少量差异"。
比如:
- 标准工况 + 低温变体
- 标准配置 + 欧规差异
- 默认报文 + 某个字段变化
- 标准仿真场景 + 故障注入版本
原型模式很适合表达这种"从基线演化副本"的结构。
五、原型模式的核心角色
原型模式通常有几个角色。
1. Prototype:抽象原型
定义复制接口。
比如:
cpp
Clone()
调用方并不关心具体类怎么复制。
它只关心:
我能不能从这个对象得到一个副本。
2. ConcretePrototype:具体原型
也就是具体支持克隆的对象。
比如:
VehicleConfigTestScenarioDiagnosticRequestSimulationScenario
它们负责实现自己的复制逻辑。
3. Client:调用方
调用方不直接 new 一堆复杂对象。
而是拿一个原型对象,调用 Clone(),再在副本上做必要修改。
4. Prototype Registry:原型注册表,可选
如果系统里有很多标准模板,可以把它们放进一个注册表里。
比如:
standard_brake_testwinter_brake_testeu_vehicle_configbms_diag_template
调用方先从注册表取出原型,再复制。
这在工程里非常常见。
六、原型模式的结构
可以先用一个简化结构理解:
text
调用方
↓
Prototype
↓ Clone()
新对象
如果有原型注册表,则变成:
text
调用方
↓ 获取模板
PrototypeRegistry
↓ 返回原型
Prototype
↓ Clone()
新对象
用 UML 简化表示:
clone
Prototype
+Clone()
TestScenario
+Clone()
SimulationScenario
+Clone()
Client
最关键的点不是 UML 长什么样。
而是:
新对象不是"从零创建",而是"从已有对象复制"。
七、一个 C++ 示例:复制测试工况对象
下面用一个简化的测试工况对象来说明。
先定义抽象原型接口。
cpp
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class IScenarioPrototype {
public:
virtual ~IScenarioPrototype() = default;
virtual std::unique_ptr<IScenarioPrototype> Clone() const = 0;
};
然后定义具体产品。
cpp
class TestScenario : public IScenarioPrototype {
public:
TestScenario(
int initialSpeedKph,
int socPercent,
int temperatureC,
double roadFriction,
const std::string& driveMode,
const std::vector<std::string>& signals)
: m_initialSpeedKph(initialSpeedKph),
m_socPercent(socPercent),
m_temperatureC(temperatureC),
m_roadFriction(roadFriction),
m_driveMode(driveMode),
m_signals(signals) {}
std::unique_ptr<IScenarioPrototype> Clone() const override {
return std::make_unique<TestScenario>(*this);
}
void SetTemperatureC(int temperatureC) {
m_temperatureC = temperatureC;
}
void SetRoadFriction(double roadFriction) {
m_roadFriction = roadFriction;
}
void AddFaultInjection(const std::string& fault) {
m_faults.push_back(fault);
}
void Print() const {
std::cout << "speed=" << m_initialSpeedKph
<< ", soc=" << m_socPercent
<< ", temp=" << m_temperatureC
<< ", friction=" << m_roadFriction
<< ", mode=" << m_driveMode
<< ", faults=" << m_faults.size()
<< "\n";
}
private:
int m_initialSpeedKph;
int m_socPercent;
int m_temperatureC;
double m_roadFriction;
std::string m_driveMode;
std::vector<std::string> m_signals;
std::vector<std::string> m_faults;
};
使用时可以这样写:
cpp
int main() {
TestScenario baseScenario(
60,
80,
25,
0.8,
"Normal",
{"ABS", "ESP", "WheelSpeed"});
auto winterScenario =
std::unique_ptr<TestScenario>(
static_cast<TestScenario*>(baseScenario.Clone().release()));
winterScenario->SetTemperatureC(-15);
auto lowGripScenario =
std::unique_ptr<TestScenario>(
static_cast<TestScenario*>(baseScenario.Clone().release()));
lowGripScenario->SetRoadFriction(0.3);
auto faultScenario =
std::unique_ptr<TestScenario>(
static_cast<TestScenario*>(baseScenario.Clone().release()));
faultScenario->AddFaultInjection("WheelSpeedSensorLost");
baseScenario.Print();
winterScenario->Print();
lowGripScenario->Print();
faultScenario->Print();
return 0;
}
这里的重点不是 Clone() 这一行代码有多神奇。
而是:
baseScenario代表一个已经配置好的基础模板- 新场景不是从头拼出来的
- 而是从模板复制出来,再做局部调整
这就很符合测试工程里的真实使用方式。
八、如果不想暴露抽象基类,也可以直接在具体类里 Clone
很多 C++ 工程里,原型模式不一定非要写抽象接口。
如果复制逻辑主要发生在具体类内部,也可以直接这样设计:
cpp
class VehicleConfig {
public:
VehicleConfig Clone() const {
return *this;
}
void EnableFeature(const std::string& feature) {
m_features.push_back(feature);
}
private:
std::vector<std::string> m_features;
};
使用时:
cpp
VehicleConfig euConfig = baseConfig.Clone();
euConfig.EnableFeature("AEB");
这种写法更轻。
所以不要把原型模式理解成:
一定要有一套很重的继承体系。
原型模式的本质不是类图复杂。
而是:
通过复制已有对象来创建新对象。
只要这个意图表达清楚,形式可以灵活一些。
九、一个更贴近工程的例子:原型注册表
如果系统里有很多标准模板,可以加一个注册表。
比如测试平台里维护一组基础测试工况模板。
cpp
#include <memory>
#include <string>
#include <unordered_map>
class ScenarioRegistry {
public:
void Register(
const std::string& name,
std::unique_ptr<IScenarioPrototype> prototype) {
m_prototypes[name] = std::move(prototype);
}
std::unique_ptr<IScenarioPrototype> CreateFromPrototype(
const std::string& name) const {
auto it = m_prototypes.find(name);
if (it == m_prototypes.end()) {
return nullptr;
}
return it->second->Clone();
}
private:
std::unordered_map<std::string, std::unique_ptr<IScenarioPrototype>> m_prototypes;
};
使用时:
cpp
ScenarioRegistry registry;
registry.Register(
"standard_brake_test",
std::make_unique<TestScenario>(
60, 80, 25, 0.8, "Normal",
std::vector<std::string>{"ABS", "ESP", "WheelSpeed"}));
auto scenario = registry.CreateFromPrototype("standard_brake_test");
这样做的价值是:
- 标准模板集中管理
- 调用方不需要知道模板怎么初始化
- 要做变体时直接复制现有模板
- 模板更新后,所有新副本都能继承最新基础配置
这在测试平台、仿真平台、配置中心里都很常见。
十、这个例子到底好在哪里?
1. 重复代码明显减少
如果没有原型模式,每个场景都要把公共字段重写一遍。
有了原型模式,公共部分只在模板里维护一次。
调用方只关心差异项。
这会让变体场景更清楚。
2. 模板对象更容易统一演进
比如基础制动测试模板新增了一个默认采集信号:
- BrakePressure
如果你是从头构建多个场景,就要到处补。
如果你是通过原型复制,新创建的副本天然继承这项更新。
这比"复制粘贴式初始化"更可维护。
3. 更符合"基线 + 变体"的业务思维
很多工程配置不是一坨平铺参数。
它有一个清晰基线。
比如:
- 标准车型配置
- 标准仿真环境
- 标准诊断会话配置
- 标准测试模板
原型模式正好适合表达这种基于基线派生新对象的思路。
4. 某些场景下性能也更好
如果对象内部已经完成了较重初始化,复制可能比重新构建更便宜。
当然,这要具体分析。
不是所有复制都一定更快。
但在某些包含大量默认结构、缓存、模板字段的对象上,复制确实可能更划算。
十一、车企项目里,哪些地方适合原型模式?
1. 参数模板复制
这是最典型的场景。
比如一组整车标定参数对象已经配置好了:
- 市场区域
- 动力类型
- 电池容量
- 热管理参数
- 默认控制策略
- 安全阈值
现在只想基于它复制出:
- 欧规版本
- 高寒版本
- 高温版本
- 低配版本
- 高配版本
这时原型模式非常自然。
2. 仿真场景复制
仿真场景常常是"一个模板 + 一点变化"。
比如基础城市道路场景:
- 白天
- 晴天
- 双车道
- 正常交通流
- 默认传感器噪声
然后派生出:
- 夜晚版本
- 雨天版本
- GPS 漂移版本
- 前车急刹版本
- 路口横穿行人版本
如果每次都从头搭场景,会很重。
复制模板再改局部参数,通常更顺手。
3. 自动化测试工况复制
测试工况是最适合原型模式的对象之一。
因为测试工程里最常见的动作就是:
基于已有用例,复制一份,改一点点。
比如:
- 基础充电测试 → 低温充电测试
- 基础制动测试 → 故障注入测试
- 基础能量回收测试 → 高 SOC 限制测试
原型模式可以把这种演化关系表达清楚。
4. 复杂诊断请求模板复制
有些诊断请求除了服务号和 DID 外,还有一堆公共配置:
- 目标 ECU
- 会话类型
- 安全等级
- 超时配置
- trace 配置
- 重试策略
如果一组请求的公共部分一样,就可以先准备一个请求模板,再复制并修改少数字段。
5. 配置对象、任务对象、规则对象复制
只要一个对象满足这几个特点,就可以考虑原型模式:
- 构建成本不低
- 公共部分很多
- 常常需要做变体
- "复制再改"比"重建"更自然
比如:
- OTA 升级任务模板
- 数据采集任务模板
- 规则引擎规则模板
- HMI 页面组件模板
十二、原型模式和工厂方法有什么区别?
可以先用一句话区分:
工厂方法关注"创建哪种对象"。
原型模式关注"从哪个现有对象复制新对象"。
比如工厂方法解决的是:
text
我要创建 CANTransport,还是 DoIPTransport?
原型模式解决的是:
text
我已经有一个标准诊断请求模板,能不能复制它,再改 DID?
再对比一下:
| 对比项 | 工厂方法 | 原型模式 |
|---|---|---|
| 关注点 | 选择对象类型 | 复制已有对象 |
| 典型问题 | 创建哪种具体实现 | 基于现成对象快速生成变体 |
| 创建方式 | new 某个具体类 | clone 一个已有对象 |
| 适合场景 | 类型切换 | 模板复制、变体生成 |
| 核心价值 | 隔离类型选择 | 复用对象状态 |
所以不要把原型模式理解成"另一种工厂"。
它更像是:
用复制替代重建。
十三、原型模式和建造者模式有什么区别?
这两个也很容易混。
可以先用一句话区分:
建造者模式关注"复杂对象怎么一步步构建完整"。
原型模式关注"已有对象足够接近时,怎么复制出新对象"。
比如建造者模式适合:
text
我还没有对象,我要把一个复杂诊断请求一步步构建出来。
原型模式适合:
text
我已经有一个标准诊断请求模板,我要复制一份再做小修改。
再对比一下:
| 对比项 | 建造者模式 | 原型模式 |
|---|---|---|
| 起点 | 从空白开始构建 | 从已有对象开始复制 |
| 关注点 | 构建过程 | 对象复制 |
| 适用问题 | 参数多、校验多、顺序多 | 模板复用、变体生成 |
| 典型对象 | 复杂报文、配置对象 | 场景模板、参数模板、测试用例模板 |
两者甚至可以一起用。
比如先用 Builder 构建一个标准模板对象,再把它作为 Prototype 去复制。
这在工程里非常常见。
十四、原型模式和拷贝构造有什么关系?
很多人会问:
C++ 本来就有拷贝构造,为什么还需要原型模式?
这两个有关,但不完全一样。
拷贝构造是语言机制。
原型模式是设计思路。
比如:
cpp
TestScenario s2 = s1;
这本质上就是复制。
但原型模式强调的是:
- 系统有意识地把"复制现有对象"当成一种创建方式
- 复制逻辑是对象职责的一部分
- 调用方通过统一接口或约定来拿副本
- 可以配合原型注册表管理模板对象
也就是说:
拷贝构造是"怎么复制"的技术手段之一。
原型模式是"为什么用复制来创建对象"的设计选择。
十五、实现原型模式时,最重要的问题:浅拷贝还是深拷贝?
这是原型模式在 C++ 里最容易出问题的地方。
如果对象内部只有值类型字段,比如:
intdoublestd::stringstd::vector
很多时候默认复制就够了。
但如果对象内部持有资源,比如:
- 原始指针
- 文件句柄
- socket
- 互斥锁
- 线程对象
- 外部句柄
- GPU 资源
- 数据库连接
那复制就没那么简单了。
1. 浅拷贝可能导致共享同一份底层资源
比如:
cpp
class BadScenario {
private:
int* m_buffer;
};
如果 Clone() 只是简单按位复制,那么两个对象可能指向同一块内存。
一个对象释放后,另一个对象就悬空了。
这就是经典问题。
2. 深拷贝要明确复制边界
复杂对象复制时,要想清楚:
- 哪些状态应该独立复制
- 哪些资源应该共享
- 哪些句柄不允许复制
- 哪些字段复制后必须重建
比如一个仿真场景对象可能包含:
- 配置数据:应该复制
- 日志句柄:通常不应该直接复制
- 运行时线程:通常不能复制
- 缓存索引:也许可以重建,也许可以共享只读部分
所以原型模式不是一句 return *this; 就完了。
真正难的是:
明确对象的复制语义。
3. 有些对象根本不适合做原型
如果一个对象强依赖不可复制资源,比如:
- 活跃线程
- 打开的 socket
- 独占文件句柄
- 硬件设备上下文
- 锁状态
那它通常不适合直接做 Prototype。
这时更合理的做法可能是:
- 把可复制的配置对象和不可复制的运行时对象拆开
- 只让配置部分支持原型复制
- 运行时部分重新初始化
这一点非常关键。
十六、原型模式最容易被滥用在哪里?
1. 只是为了少写几行初始化代码
如果对象本身很简单,复制并不会比重建更清晰。
比如:
cpp
Point p2 = p1;
这当然可以,但没必要上升到模式层面。
原型模式适合的是:
- 复杂对象
- 模板对象
- 高复用对象
- 复制比重建更自然的对象
2. 没想清楚复制语义就盲目 Clone
这是最危险的。
很多对象表面上能复制,实际上内部资源复制后会出事。
比如:
- 两个对象误共享同一块缓冲区
- 两个对象持有同一个句柄
- 复制后状态语义失真
- 复制后的对象看似独立,实际还互相影响
所以原型模式最怕"复制动作很简单,但语义并不简单"。
3. 把所有对象都做成可克隆
并不是所有对象都应该支持 Clone()。
如果一个类没有明确的模板复用需求,就不要机械加上原型接口。
否则接口会变得很泛,但真正能安全复制的类并不多。
4. 复制出来后还要大改一堆字段
如果调用方每次 Clone 之后,还要改十几个字段,那说明这个原型并不接近目标对象。
这时原型模式的收益就很弱。
更好的方式可能是:
- 重新划分模板对象
- 用 Builder 重建
- 用工厂生成不同基础版本
原型模式适合"少量差异",不适合"复制完几乎全改"。
5. 原型注册表失控
注册表很好用,但也容易失控。
如果你把各种模板都往里塞:
- 测试模板
- 场景模板
- 配置模板
- UI 模板
- 升级任务模板
- 规则模板
最后没有边界、没有命名规范、没有版本管理,注册表就会变成另一个杂物间。
所以模板注册表也要有清晰边界。
十七、工程中更推荐的用法
1. 先确认业务是不是"模板复制"问题
使用原型模式前,先问:
我现在是在创建一个全新对象,还是在基于一个基线对象做变体?
如果明显是后者,原型模式更有价值。
2. 只给真正适合复制的对象实现 Clone
适合的对象通常具备这些特征:
- 主要由配置数据组成
- 可复制状态边界清晰
- 复制后应该独立存在
- 常常作为模板复用
比如:
- 配置对象
- 测试工况
- 仿真场景描述
- 请求模板
- 规则模板
不适合的往往是重运行时资源的对象。
3. 明确深拷贝和共享策略
实现 Clone() 时一定要回答清楚:
- 哪些字段复制值
- 哪些资源重新分配
- 哪些部分共享只读数据
- 哪些运行时状态不进入原型
不要让复制语义模糊不清。
4. 原型对象更适合作为"模板",不是"活跃实例"
原型对象最好是模板对象,而不是正在运行中的对象。
比如:
- 测试模板对象
- 配置模板对象
- 场景模板对象
不要轻易把一个正在运行、状态不断变化、绑定外部资源的对象直接拿来做原型。
5. 可以和 Builder、工厂一起用
真实项目里,模式往往不是单独出现的。
比如:
- 用 Builder 构建标准模板对象
- 把标准模板注册到 Prototype Registry
- 调用方按名字取模板并 Clone
- 再通过工厂把模板关联到具体业务流程
这种组合在工程里很自然。
不要把模式理解成互斥题。
6. 给模板命名和版本管理
如果用了原型注册表,最好明确:
- 模板名称
- 模板适用范围
- 模板版本
- 模板变更责任人
否则过一段时间,没人知道:
winter_case_v2standard_city_scene_newbattery_diag_default_final
到底该用哪个。
十八、原型模式的优缺点
优点
原型模式的优点很明确:
- 可以通过复制快速生成新对象
- 减少重复初始化代码
- 适合模板化、变体化场景
- 可以复用已经验证过的对象状态
- 某些场景下复制比重建更高效
- 有利于集中管理标准模板
- 非常适合测试、仿真、配置派生场景
缺点
它的缺点也很明显:
- 复制语义设计不好容易出错
- 深浅拷贝问题复杂
- 包含资源句柄的对象实现成本高
- 并不是所有对象都适合 Clone
- 原型注册表容易失控
- 如果副本修改过多,收益会下降
- 有时复制不一定比重建更清晰
所以,原型模式不是"看到复制就上"。
它真正适合的是:
现成对象本身有价值,而且复制后只需要少量变化。
十九、使用原型模式前,先问这 6 个问题
1. 这个对象是否经常作为模板被复用?
如果不会复用,就没必要引入原型。
2. 复制后是否只需要少量修改?
如果复制后要改一大半字段,可能不适合。
3. 对象的复制语义是否清晰?
如果深拷贝、共享、重建边界不清楚,先别急着做。
4. 对象内部是否持有不可复制资源?
如果有线程、socket、独占句柄,要特别谨慎。
5. 复制是否真的比重建更自然或更高效?
不是所有对象复制都更好。
6. 模板对象是否值得集中管理?
如果有多个标准模板,注册表会很有价值。
二十、总结
原型模式解决的核心问题是:
当已有对象已经足够接近目标对象时,用复制往往比重新创建更自然、更高效。
它不是为了让代码显得高级。
它真正想解决的是:
- 重复初始化太多
- 对象模板本身有价值
- 变体对象很多
- 复制比重建更符合业务语义
- 某些复杂对象初始化成本较高
- 标准模板需要复用和集中管理
一句话概括:
原型模式的重点不是"new 一个对象",而是"从一个现成对象复制出新对象"。
在车企软件里,它很适合这些场景:
- 参数模板复制
- 仿真场景复制
- 自动化测试工况复制
- 复杂诊断请求模板复制
- 配置对象、任务对象、规则对象的变体生成
但它也不适合所有场景。
如果对象很简单,或者复制语义很混乱,或者内部绑定了大量不可复制资源,就不要为了"像设计模式"而强行上原型模式。
设计模式真正有价值的地方,不是让代码显得高级,而是让复杂性有地方安放。
如果上一篇建造者模式提醒我们:
不要把"复杂对象"硬塞进一个巨大构造函数里。
那么这一篇原型模式提醒我们:
不要把"明明只是模板变体"也每次都从头创建一遍。
如果这篇对你有帮助,欢迎点赞、转发、关注。
我们下一篇继续拆设计模式。