news 2026/2/17 4:34:55

基于C++的《Head First设计模式》笔记——抽象工厂模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于C++的《Head First设计模式》笔记——抽象工厂模式

目录

一.专栏简介

二.依赖很强的代码

三.对象依赖

四.依赖倒置原则

五.应用原则

六.依赖倒置原则中,“倒置”在哪?

七.帮助我们遵循该原则的几条指南

八.原料工厂

九.重做披萨

十.我们做了什么

十一.定义抽象工厂

十二.比较工厂方法和抽象工厂


一.专栏简介

本专栏是我学习《head first》设计模式的笔记。这本书中是用Java语言为基础的,我将用C++语言重写一遍,并且详细讲述其中的设计模式,涉及是什么,为什么,怎么做,自己的心得等等。希望阅读者在读完我的这个专题后,也能在开发中灵活且正确的使用,或者在面对面试官时,能够自信地说自己熟悉常用设计模式。

在本专栏上一篇工厂模式的学习之后,本章将开始抽象工厂模式的学习。

二.依赖很强的代码

暂时忘掉上一篇的内容,假装我们从未听说过OO工厂。下面是一个“依赖性很强”、没有使用工厂的PizzaStore版本。代码如下:

class DependentPizzaStore { public: Pizza* createPizza(const string& style, const string& type) { Pizza* pizza = nullptr; if (style == "NY") { if (type == "cheese") pizza = new NYStyleCheesePizza(); else if (type == "greek") pizza = new NYStyleGreekPizza(); else if (type == "pepperoni") pizza = new NYStylePepperoniPizza(); else return nullptr; } else if (style == "Chicago") { if (type == "cheese") pizza = new ChicagoStyleCheesePizza(); else if (type == "greek") pizza = new ChicagoStyleGreekPizza(); else if (type == "pepperoni") pizza = new ChicagoStylePepperoniPizza(); else return nullptr; } else return nullptr; pizza->prepare(); pizza->bake(); pizza->cut(); pizza->box(); return pizza; } };

这个类依赖了8个具体类,如果添加一个加州风味的Pizza,将会依赖12个类。

三.对象依赖

当直接实例化一个对象时,我们在依赖于其具体类。上面“非常依赖”的PizzaStore。它就在PizzaStore类中创建所有披萨对象,而不是委托给工厂。

如果我们画图表达该PizzaStore版本以及它依赖的所有对象,看起来像这样:

四.依赖倒置原则

很显然,在我们的代码中减少对具体类的依赖是一件“好事”。事实上,有一个OO设计原则正式阐明了这一点;这个原则甚至还有一个响亮又正式的名称:依赖倒置原则(Dependency Inversion Principle)。

通用原则如下:

首先,这个原则听起来很像“针对接口编程,不针对实现编程”,对吧?是很像,但是,依赖倒置原则更强调抽象。该原则说明,高层组件不应该依赖于低层组件,而且,它们都应该依赖于抽象

“高层”组件是一个类,其行为以其他“低层”组件的形式定义。

例如,PizzaStore是一个高层组件,因为它的行为以披萨的形式定义:PizzaStore创建所有不同的披萨对象、准备、烘焙、切片、装盒;而所有的披萨是低层组件。

现在,这个原则告诉我们,应该重新编写我们的代码,以便依赖于抽象,而不是具体类。这对高层模块和低层模块都适用。

我们来考虑把这个原则应用到我们“非常依赖”的PizzaStore实现......

五.应用原则

现在,“非常依赖”版PizzaStore的主要问题是,它依赖于每个披萨类型,因为实际上它是在自己的orderPizza()方法中,实例化具体类型。

虽然已经创建了一个抽象,即Pizza,不过我们是在代码中创建具体的Pizza,因此,这个抽象没有带来太多好处。

怎样将这些实例化从orderPizza()方法拿出来?正如我们知道的,工厂方法模式正好允许我们做到这一点。

因此,在我们应用工厂方法模式之后,图形看起来如下:

应用工厂方法之后,你会注意到,高层组件PizzaStore以及低层组件(也就是这些披萨)都依赖于抽象,Pizza。工厂方法不是唯一遵循依赖倒置原则的技巧,但它是最有威力的一个。

六.依赖倒置原则中,“倒置”在哪?

依赖倒置原则名称中的“倒置”,是因为它倒转了通常考虑OO设计的方式。看看上面的图,低层组件现在依赖于更高层的抽象。同样,高层组件也绑定到同一抽象。因此,之前自上而下的依赖图自己倒转过来了,高层和低层模块现在都依赖于抽象

七.帮助我们遵循该原则的几条指南

  • 变量不应该持有到具体类的引用(如果使用new,就会持有到具体类的引用。通过使用工厂来绕开!)
  • 类不应该派生自具体类(如果派生自具体类,就会依赖具体类。派生自一个抽象类)
  • 方法不应该覆盖其任何基类的已实现方法(如果覆盖已实现的方法,那么基类就不是一个真正适合被继承的抽象。基类中这些已实现的方法,应该由所有子类共享)

但是,如果我们完全遵循这几条指南,我们连一个简单的程序都写不出来。任何程序都有违反这些指南的地方!!!

我们应该尽量遵循,而不是当成任何时候都应该遵循的铁律。

但是,如果我们把这些指南融会贯通,在设计时藏在大脑深处,当违反原则时,我们会知道在违反原则,并且会有一个好的理由。例如,如果我们知道有一个类不可能变化,那么,在我们的代码中实例化一个具体类也不是世界末日。想一想,我们一直不假思索地实例化std::string对象,违反这个原则了吗?有。可以这么做吗?可以!为什么,因为std::string不可能改变。

另一方面,如果一个类有可能变化,可以用一些良好的技巧,像工厂方法,来封装变化。

八.原料工厂

为了确保加盟店使用高质量的原料,我们打算建造一家生产原料的工厂,并将原料配送到各家加盟店!

对于这个计划,现在只有一个问题:加盟店坐落在不同的区域,纽约的红酱料和芝加哥的红酱料是不一样的。因此,你有一组需要配送到纽约的原料,另一组不同的原料配送到芝加哥。我们来看得更仔细一点:

现在,我们打算建造一个工厂来创建原料。该工厂将负责创建原料家族中的每一个原料。换句话说,工厂需要创建面团、酱、芝士等。

我们首先为工厂定义一个接口,该接口创建所有原料:

class PizzaIngredientFactory { public: virtual Dough* createDough() = 0; virtual Sauce* createSauce() = 0; virtual Cheese* createCheese() = 0; virtual vector<Veggies*> createVeggies() = 0; virtual Pepperoni* createPepperoni() = 0; virtual Clams* createClam() = 0; };

有了这个接口,我们打算这样做:

  1. 为每个区域建造一个工厂。你需要创建一个PizzaIngredientFactory的子类来实现每个创建方法。
  2. 实现一组要和工厂一起使用的原料类,像ReggianoCheese、RedPeppers和ThickCrustDough。这些类可以在合适的区域之间共享。
  3. 然后,我们依然需要把所有这些连接起来,把新的原料工厂整合进老的PizzaStore代码。

纽约和芝加哥原料工厂代码如下:

Ingredient.h:

class NYPizzaIngredientFactory : public PizzaIngredientFactory { public: Dough* createDough() override; Sauce* createSauce() override; Cheese* createCheese() override; vector<Veggies*> createVeggies() override; Pepperoni* createPepperoni() override; Clams* createClam() override; }; class ChicagoPizzaIngredientFactory : public PizzaIngredientFactory { public: Dough* createDough() override; Sauce* createSauce() override; Cheese* createCheese() override; vector<Veggies*> createVeggies() override; Pepperoni* createPepperoni() override; Clams* createClam() override; };

Ingredient.cpp:

#include "Ingredient.h" Dough* NYPizzaIngredientFactory::createDough() { return new ThinCrustDough(); } Sauce* NYPizzaIngredientFactory::createSauce() { return new MarinaraSauce(); } Cheese* NYPizzaIngredientFactory::createCheese() { return new ReggianoChess(); } vector<Veggies*> NYPizzaIngredientFactory::createVeggies() { return { new Garlic(), new Onion(), new Mushroom(), new RedPepper() }; } Pepperoni* NYPizzaIngredientFactory::createPepperoni() { return new SlicedPepperoni(); } Clams* NYPizzaIngredientFactory::createClam() { return new FreshClams(); } Dough* ChicagoPizzaIngredientFactory::createDough() { return new ThickCrustDough(); } Sauce* ChicagoPizzaIngredientFactory::createSauce() { return new PlumTomatoSauce(); } Cheese* ChicagoPizzaIngredientFactory::createCheese() { return new MozzarellaCheese(); } vector<Veggies*> ChicagoPizzaIngredientFactory::createVeggies() { return { new BalckOlives(), new Spinach(), new EggPlant() }; } Pepperoni* ChicagoPizzaIngredientFactory::createPepperoni() { return new SlicedPepperoni(); } Clams* ChicagoPizzaIngredientFactory::createClam() { return new FrozenClams(); }

九.重做披萨

工厂已经一切就绪,要准备生产高质量原料了。现在,我们只需要重做我们的披萨,让它只使用工厂生产的原料。先从抽象Pizza类开始:

Pizza.h:

class Pizza { public: virtual void prepare() = 0; virtual void bake(); virtual void cut(); virtual void box(); const string& getName(); protected: string name; Dough* dough; Sauce* sauce; vector<Veggies*> veggies; Cheese* cheese; Pepperoni* pepperoni; Clams* clam; };

将prepare()变成纯虚函数。

Pizza.cpp:

void Pizza::bake() { cout << "350°烘焙25分钟" << endl; } void Pizza::cut() { cout << "将披萨按照对角线切割" << endl; } void Pizza::box() { cout << "将披萨装盒" << endl; } const string& Pizza::getName() { return name; }

其他的方法保持不变,除了prerpare方法。

NYCheesPizza和ChicagoCheesePizza代码

Pizza.h:

class NYStyleCheesePizza : public Pizza { PizzaIngredientFactory* ingerdientFactory; public: NYStyleCheesePizza(PizzaIngredientFactory* factory); void prepare() override; }; class ChicagoStyleCheesePizza : public Pizza { PizzaIngredientFactory* ingerdientFactory; public: ChicagoStyleCheesePizza(PizzaIngredientFactory* factory); void prepare() override; void cut() override; };

Pizza.cpp:

NYStyleCheesePizza::NYStyleCheesePizza(PizzaIngredientFactory* factory) { ingerdientFactory = factory; } void NYStyleCheesePizza::prepare() { cout << "prepare " << name << endl; dough = ingerdientFactory->createDough(); sauce = ingerdientFactory->createSauce(); veggies = ingerdientFactory->createVeggies(); cheese = ingerdientFactory->createCheese(); pepperoni = ingerdientFactory->createPepperoni(); clam = ingerdientFactory->createClam(); } ChicagoStyleCheesePizza::ChicagoStyleCheesePizza(PizzaIngredientFactory* factory) { ingerdientFactory = factory; } void ChicagoStyleCheesePizza::prepare() { cout << "prepare " << name << endl; dough = ingerdientFactory->createDough(); sauce = ingerdientFactory->createSauce(); veggies = ingerdientFactory->createVeggies(); cheese = ingerdientFactory->createCheese(); pepperoni = ingerdientFactory->createPepperoni(); clam = ingerdientFactory->createClam(); } void ChicagoStyleCheesePizza::cut() { cout << "将披萨按照正方形线切割" << endl; }

prepare()方法一步一步地创建芝士披萨,每次需要原料时,就请工厂生产。

Pizza的代码用所组合的工厂生产披萨所用的原料。所生产的原料依赖所用的工厂。Pizza类不关心,它知道如何制作披萨。现在,Pizza从区域原料差异解耦,无论工厂是在奥斯汀、纳什维尔还是其他地方,Pizza类可以轻易地复用。

披萨店代码:

Pizza.h:

class PizzaStore { public: Pizza* orderPizza(string type); protected: virtual Pizza* createPizza(string type) = 0; }; class NYStylePizzaStore : public PizzaStore { private: Pizza* createPizza(string type) override; }; class ChicagoStylePizzaStore : public PizzaStore { private: Pizza* createPizza(string type) override; };

Pizza.cpp:

Pizza* PizzaStore::orderPizza(string type) { Pizza* pizza = createPizza(type); pizza->prepare(); pizza->bake(); pizza->cut(); pizza->box(); return pizza; } Pizza* NYStylePizzaStore::createPizza(string type) { Pizza* pizza = nullptr; PizzaIngredientFactory* ingredientFactory = new NYPizzaIngredientFactory(); if (type == "cheese") { //cout << "NYStyleCheesePizza" << endl; pizza = new NYStyleCheesePizza(ingredientFactory); pizza->setName("NYStyleCheesePizza"); } else if (type == "pepperoni") { cout << "NYStylePepperoniPizza" << endl; //pizza = new NYStylePepperoniPizza(); } else if (type == "greek") { cout << "NYStyleGreekPizza" << endl; //pizza = new NYStyleGreekPizza(); } return pizza; } Pizza* ChicagoStylePizzaStore::createPizza(string type) { Pizza* pizza = nullptr; PizzaIngredientFactory* ingredientFactory = new ChicagoPizzaIngredientFactory(); if (type == "cheese") { //cout << "ChicagoStyleCheesePizza" << endl; pizza = new ChicagoStyleCheesePizza(ingredientFactory); pizza->setName("ChicagoStyleCheesePizza"); } else if (type == "pepperoni") { cout << "ChicagoStylePepperoniPizza" << endl; //pizza = new ChicagoStylePepperoniPizza(); } else if (type == "greek") { cout << "ChicagoStyleGreekPizza" << endl; //pizza = new ChicagoStyleGreekPizza(); } return pizza; } NYStyleCheesePizza::NYStyleCheesePizza(PizzaIngredientFactory* factory) { ingerdientFactory = factory; } void NYStyleCheesePizza::prepare() { cout << "prepare " << name << endl; dough = ingerdientFactory->createDough(); sauce = ingerdientFactory->createSauce(); veggies = ingerdientFactory->createVeggies(); cheese = ingerdientFactory->createCheese(); pepperoni = ingerdientFactory->createPepperoni(); clam = ingerdientFactory->createClam(); } ChicagoStyleCheesePizza::ChicagoStyleCheesePizza(PizzaIngredientFactory* factory) { ingerdientFactory = factory; } void ChicagoStyleCheesePizza::prepare() { cout << "prepare " << name << endl; dough = ingerdientFactory->createDough(); sauce = ingerdientFactory->createSauce(); veggies = ingerdientFactory->createVeggies(); cheese = ingerdientFactory->createCheese(); pepperoni = ingerdientFactory->createPepperoni(); clam = ingerdientFactory->createClam(); } void ChicagoStyleCheesePizza::cut() { cout << "将披萨按照正方形线切割" << endl; }

运行结果:

我们在点一个披萨时,先准备了原料,这些原料都是从抽象工厂获得的,然后再烘焙,切割,装盒。

十.我们做了什么

一连串的代码变化,我们到底做了什么?通过引入一个称为抽象工厂的新工厂类型,我们提供了为披萨创建原料家族的方法。

抽象工厂给我们一个创建产品家族的接口。通过使用这个接口编写代码,我们把代码从实际创建产品的工厂解耦。这让我们在为不同上下文(例如不同区域、不同操作系统或者不同视感)生产产品时,能够实现工厂的变化。

因为代码从实际产品解耦,我们可以替换不同的工厂来获得不同的行为(例如获得意式番茄酱,而不是李子番茄酱)。

十一.定义抽象工厂

我们又给模式家族添加了另一个工厂模式,这个模式可以创建产品的家族。我们来看看这个模式的官方定义:

抽象工厂模式提供一个接口来创建相关或依赖对象的家族,而不需要指定具体类。

无疑,我们已经看到,抽象工厂允许客户使用一个抽象接口来创建一组相关的产品,而不需要懂得(或关心)实际生产的具体产品是什么。通过这样的方法,客户就从所有的特定产品解耦。我们来看看类图,了解其中的关系。

工厂方法其实潜伏在抽象工厂里面。抽象工厂的方法经常实现为工厂方法。抽象工厂的定义是定义一个接口,这个接口创建一组产品。这个接口的每个方法负责创建一个具体产品,我们实现抽象工厂的子类,以提供这些实现。因此,在抽象工厂中,用工厂方法来实现生产方法,是相当自然的方式。

十二.比较工厂方法和抽象工厂

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/13 21:46:07

GDAL 实现矢量裁剪

前言 ❝ 矢量数据作为数据处理的半壁江山&#xff0c;在日常工作中涉及到多种操作&#xff0c;矢量数据裁剪尤其具有代表性和重要性&#xff0c;是常用操作&#xff0c;核心原理为从指定数据中提取出目标范围。在之前的文章中讲了如何使用GDAL或者ogr2ogr工具将txt以及csv文本数…

作者头像 李华
网站建设 2026/2/8 10:22:18

华为研究团队突破代码修复瓶颈,8B模型击败32B巨型对手!

这项由华为技术有限公司、南洋理工大学、香港大学和香港中文大学联合完成的突破性研究发表于2026年1月&#xff0c;论文编号为arXiv:2601.01426v1。研究团队通过一种名为SWE-Lego的创新训练方法&#xff0c;让相对较小的8B参数模型在软件代码自动修复任务上的表现超越了许多32B…

作者头像 李华
网站建设 2026/2/7 11:20:34

git创建远程分支、分支合并、删除分支

# git创建远程分支 #查看分支 git branch -a#创建git分支 git checkout -b branch_name#push到远程仓库 git push -u origin branch_name# git 合并分支 # 标准合并&#xff08;保留分支历史&#xff09; git checkout master # 先切到要「接受」变更的分支 git pull …

作者头像 李华
网站建设 2026/2/9 7:19:29

3.2 FileStream

1.FileStream1.FileStream FileStream操作的是字节数组, 读写文本需配合Encoding(如: UTF8)转换1).构造函数这是使用FileStream的第一步, 用于打开/创建文件并指定操作模式// 参数&#xff1a;文件路径、文件模式、文件访问权限、文件共享方式 FileStream fs new FileStream(&…

作者头像 李华
网站建设 2026/2/16 12:59:18

实战案例:汽车数字孪生车间的提示工程应用

从物理车间到数字孪生&#xff1a;提示工程如何让汽车制造“会思考”&#xff1f; 关键词 数字孪生车间、提示工程、AI智能制造、故障预测、生产优化、自然语言交互、数据闭环 摘要 当汽车制造车间从“物理实体”进化为“数字孪生”&#xff0c;如何让这个“虚拟双胞胎”不仅能…

作者头像 李华