原型模式:当复制比重新创建更高效时

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:具体原型

也就是具体支持克隆的对象。

比如:

  • VehicleConfig
  • TestScenario
  • DiagnosticRequest
  • SimulationScenario

它们负责实现自己的复制逻辑。


3. Client:调用方

调用方不直接 new 一堆复杂对象。

而是拿一个原型对象,调用 Clone(),再在副本上做必要修改。


4. Prototype Registry:原型注册表,可选

如果系统里有很多标准模板,可以把它们放进一个注册表里。

比如:

  • standard_brake_test
  • winter_brake_test
  • eu_vehicle_config
  • bms_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++ 里最容易出问题的地方。

如果对象内部只有值类型字段,比如:

  • int
  • double
  • std::string
  • std::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_v2
  • standard_city_scene_new
  • battery_diag_default_final

到底该用哪个。


十八、原型模式的优缺点

优点

原型模式的优点很明确:

  • 可以通过复制快速生成新对象
  • 减少重复初始化代码
  • 适合模板化、变体化场景
  • 可以复用已经验证过的对象状态
  • 某些场景下复制比重建更高效
  • 有利于集中管理标准模板
  • 非常适合测试、仿真、配置派生场景

缺点

它的缺点也很明显:

  • 复制语义设计不好容易出错
  • 深浅拷贝问题复杂
  • 包含资源句柄的对象实现成本高
  • 并不是所有对象都适合 Clone
  • 原型注册表容易失控
  • 如果副本修改过多,收益会下降
  • 有时复制不一定比重建更清晰

所以,原型模式不是"看到复制就上"。

它真正适合的是:

现成对象本身有价值,而且复制后只需要少量变化。


十九、使用原型模式前,先问这 6 个问题

1. 这个对象是否经常作为模板被复用?

如果不会复用,就没必要引入原型。

2. 复制后是否只需要少量修改?

如果复制后要改一大半字段,可能不适合。

3. 对象的复制语义是否清晰?

如果深拷贝、共享、重建边界不清楚,先别急着做。

4. 对象内部是否持有不可复制资源?

如果有线程、socket、独占句柄,要特别谨慎。

5. 复制是否真的比重建更自然或更高效?

不是所有对象复制都更好。

6. 模板对象是否值得集中管理?

如果有多个标准模板,注册表会很有价值。


二十、总结

原型模式解决的核心问题是:

当已有对象已经足够接近目标对象时,用复制往往比重新创建更自然、更高效。

它不是为了让代码显得高级。

它真正想解决的是:

  • 重复初始化太多
  • 对象模板本身有价值
  • 变体对象很多
  • 复制比重建更符合业务语义
  • 某些复杂对象初始化成本较高
  • 标准模板需要复用和集中管理

一句话概括:

原型模式的重点不是"new 一个对象",而是"从一个现成对象复制出新对象"。

在车企软件里,它很适合这些场景:

  • 参数模板复制
  • 仿真场景复制
  • 自动化测试工况复制
  • 复杂诊断请求模板复制
  • 配置对象、任务对象、规则对象的变体生成

但它也不适合所有场景。

如果对象很简单,或者复制语义很混乱,或者内部绑定了大量不可复制资源,就不要为了"像设计模式"而强行上原型模式。

设计模式真正有价值的地方,不是让代码显得高级,而是让复杂性有地方安放。

如果上一篇建造者模式提醒我们:

不要把"复杂对象"硬塞进一个巨大构造函数里。

那么这一篇原型模式提醒我们:

不要把"明明只是模板变体"也每次都从头创建一遍。

如果这篇对你有帮助,欢迎点赞、转发、关注。

我们下一篇继续拆设计模式。

相关推荐
用户356302904876 小时前
【设计模式】观察者模式——事件通知机制
设计模式
追烽少年x7 小时前
STL中的设计模式(二)
c++·设计模式
悟05157 小时前
设计模式-模板模式
设计模式
BLSxiaopanlaile7 小时前
有关创建型的几个设计模式总结
设计模式
蜡笔小马8 小时前
14.C++设计模式-状态模式
c++·设计模式·状态模式
加油20199 小时前
嵌入式软件技术栈和学习路线详解
linux·arm开发·数据结构·mqtt·设计模式·嵌入式
likerhood9 小时前
设计模式 · 代理模式(Proxy Pattern)java
java·设计模式·代理模式
刀法如飞1 天前
Palantir Ontology 存储结构与读写机制原理深入剖析
大数据·设计模式·系统架构
KobeSacre1 天前
设计模式——七大设计原则
设计模式