新谈设计模式 Chapter 02 — 工厂方法模式 Factory Method
Chapter 02 — 工厂方法模式 Factory Method灵魂速记开分店各店自己决定卖什么。总部只定规矩不管细节。秒懂类比你开了一家披萨连锁品牌。总部规定了点单→做披萨→打包→送出的标准流程但具体做什么口味各地分店自己定。北京店做北京烤鸭披萨纽约店做纽约芝士披萨东京店做照烧鸡肉披萨总部不new具体的某种披萨它只说给我一个披萨。至于是什么披萨分店说了算。问题引入// 典型的坏味道一坨 if-else 创建对象Transport*createTransport(conststd::stringtype){if(typetruck)returnnewTruck();elseif(typeship)returnnewShip();elseif(typeplane)returnnewPlane();// 每加一种改这里elseif(typedrone)returnnewDrone();// 又改// ……无穷无尽}每次新增一种运输方式你就得打开这个函数改一次。这违反了开闭原则Open-Closed Principle对扩展开放、对修改关闭。而且调用方只要#include了这个函数所在的头文件就间接依赖了所有具体产品类型——改一个类全部重编。模式结构┌───────────────┐ │ Creator │ ← 抽象创建者 ├───────────────┤ │ createProduct│ ← 工厂方法纯虚函数 │ operate() │ ← 用产品干活模板流程 └───────┬───────┘ │ 继承 ┌────────────┴─────────────┐ │ │ ┌────┴─────┐ ┌──────┴──────┐ │CreatorA │ │ CreatorB │ ├──────────┤ ├─────────────┤ │createProduct→ProdA│ │createProduct→ProdB│ └──────────┘ └─────────────┘核心思路把new具体类的动作推迟到子类去做。C 实现#includeiostream#includememory#includestring// 产品接口 classTransport{public:virtual~Transport()default;// 基类必须有虚析构后面解释virtualvoiddeliver()const0;};classTruck:publicTransport{public:voiddeliver()constoverride{std::cout卡车走公路运输\n;}};classShip:publicTransport{public:voiddeliver()constoverride{std::cout轮船走海路运输\n;}};// 工厂接口 classLogistics{public:virtual~Logistics()default;// 工厂方法 —— 纯虚函数由子类决定造什么virtualstd::unique_ptrTransportcreateTransport()const0;// 业务逻辑 —— 只面向 Transport 接口编程不关心具体是卡车还是轮船voidplanDelivery()const{autotransportcreateTransport();std::cout物流规划完毕开始运输\n;transport-deliver();}};classRoadLogistics:publicLogistics{public:std::unique_ptrTransportcreateTransport()constoverride{returnstd::make_uniqueTruck();}};classSeaLogistics:publicLogistics{public:std::unique_ptrTransportcreateTransport()constoverride{returnstd::make_uniqueShip();}};// 客户端 voidclientCode(constLogisticslogistics){logistics.planDelivery();}intmain(){std::cout 公路物流 \n;RoadLogistics road;clientCode(road);std::cout\n 海运物流 \n;SeaLogistics sea;clientCode(sea);// 要加空运写一个 AirLogistics 就行不改已有任何代码。}输出 公路物流 物流规划完毕开始运输 卡车走公路运输 海运物流 物流规划完毕开始运输 轮船走海路运输几个值得多说一句的 C 细节1. 为什么基类一定要写virtual ~Transport() default;因为我们用std::unique_ptrTransport持有具体子类对象。unique_ptr销毁时通过基类指针delete如果基类析构函数不是virtual的调用的就是基类的析构函数子类的析构函数不会被调用——资源泄漏、未定义行为。规則很简单只要一个类打算被继承并通过基类指针/引用使用就给它一个虚析构函数。2.override关键字不写行不行语法上可以不写程序照样编译。但强烈建议写override让编译器帮你检查签名是否真的匹配了基类的虚函数。常见的翻车场景——你把deliver()手误写成了Deliver()没有override的话编译器不报错你只是多定义了一个新函数运行时调基类版本debug 到吐血。3. 0和 default和 delete分别什么意思这三个长得像初学容易混写法含义 0纯虚函数没有默认实现子类必须重写 default让编译器生成默认实现构造/析构/拷贝/移动 delete禁止使用该函数如禁止拷贝什么时候用✅ 适合❌ 别用不确定将来要创建什么类型的对象对象类型固定不会扩展想让子类决定实例化哪个具体类创建逻辑很简单直接new就完事框架/库设计让用户扩展创建逻辑只有一两种产品没必要搞继承体系防混淆工厂方法 vs 简单工厂简单工厂工厂方法结构一个函数 if-else一个抽象类 多个子类扩展改函数违反开闭原则加子类遵守开闭原则开闭原则违反遵守适用类型少且稳定类型多且会扩展简单工厂在很多项目里够用没必要为了正确性强行用工厂方法。选择取决于你的产品类型是否真的会频繁变化。工厂方法 vs 抽象工厂工厂方法抽象工厂产品数量一种产品一族产品多个相关产品核心手段继承——子类重写一个方法组合——工厂对象有多个创建方法粒度细粗一句话工厂方法造一个东西抽象工厂造一套东西。工厂方法 vs 模板方法它们长得很像因为工厂方法就是模板方法的一种特殊形式工厂方法的子类决定创建什么对象模板方法的子类决定怎么执行某个步骤如果你理解了模板方法Ch21再回头看工厂方法会更通透。现代 C 小贴士如果产品类型不多、也不需要严格的继承体系可以用std::function做轻量注册表式工厂#includefunctional#includeunordered_map#includestdexcept// 工厂注册表string → 创建函数std::unordered_mapstd::string,std::functionstd::unique_ptrTransport()registry;voidregisterAll(){registry[truck][]{returnstd::make_uniqueTruck();};registry[ship][]{returnstd::make_uniqueShip();};// 新增类型这里加一行就行}std::unique_ptrTransportcreate(conststd::stringtype){autoitregistry.find(type);if(itregistry.end()){throwstd::invalid_argument(Unknown transport type: type);}returnit-second();// 调用 lambda 创建对象}这种方式在插件系统和配置文件驱动的对象创建中非常实用。它不遵守正统工厂方法的继承结构但实际工程中用得比正统版本多得多。 上一章 | 目录 | 下一章 抽象工厂