news 2026/6/16 2:03:57

基于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/6/12 13:06:39

基于SpringBoot的考编论坛网站毕业设计源码

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在构建一个基于SpringBoot框架的考编论坛网站&#xff0c;以实现以下研究目的&#xff1a; 首先&#xff0c;通过设计并实现一个功能完善的考编论坛网站…

作者头像 李华
网站建设 2026/6/15 14:47:47

干货满满!大数据流处理的数据清洗技巧

干货满满&#xff01;大数据流处理的数据清洗技巧&#xff1a;从“流水质检”到“智能提纯” 一、引入与连接&#xff1a;当“流水”变“乱流”&#xff0c;你需要实时“治水” 清晨7点&#xff0c;某电商平台的实时推荐系统突然“抽风”——给用户推送了10条“婴儿奶粉”广告&…

作者头像 李华
网站建设 2026/6/15 14:49:04

基于SpringBoot的仓库管理系统毕设源码

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。一、研究目的本研究旨在设计并实现一个基于SpringBoot框架的仓库管理系统&#xff0c;以满足现代企业对高效、可靠、易用的仓库管理解决方案的需求。具体研究目的如下&#x…

作者头像 李华
网站建设 2026/6/10 14:51:41

HY-MT1.5如何接入现有系统?API接口调用实战教程

HY-MT1.5如何接入现有系统&#xff1f;API接口调用实战教程 1. 引言&#xff1a;为什么选择HY-MT1.5进行翻译集成&#xff1f; 随着全球化业务的不断扩展&#xff0c;多语言实时翻译能力已成为企业出海、内容本地化和跨语言沟通的核心需求。传统商业翻译API&#xff08;如Goog…

作者头像 李华
网站建设 2026/5/30 22:57:37

Fine-tuning十年演进(2015–2025)

Fine-tuning十年演进&#xff08;2015–2025&#xff09; 一句话总论&#xff1a; 2015年Fine-tuning还是“全参数手工微调小样本监督学习”的粗暴时代&#xff0c;2025年已进化成“端到端VLA意图级自适应微调量子鲁棒零样本亿级在线自进化全域具身知识统一”的普惠智能时代&am…

作者头像 李华
网站建设 2026/6/14 22:30:02

Agent十年演进(2015–2025)

Agent十年演进&#xff08;2015–2025&#xff09; 一句话总论&#xff1a; 2015年Agent还是“规则脚本单一任务执行器”的工具时代&#xff0c;2025年已进化成“万亿级多模态VLA具身智能Agent实时意图级自进化量子鲁棒社交协作全域自主决策伙伴”的通用智能物种&#xff0c;中…

作者头像 李华