基于C++的《Head First设计模式》笔记——抽象工厂模式
本文通过披萨店的案例详细介绍了抽象工厂模式的设计与实现。首先分析了依赖性强的问题代码,引出依赖倒置原则(DIP),强调高层组件和低层组件都应依赖于抽象。然后展示了如何通过抽象工厂模式重构披萨店系统:定义PizzaIngredientFactory接口创建原料家族,实现NYPizzaIngredientFactory和ChicagoPizzaIngredientFactory两个具体工厂。在披萨类中
目录
一.专栏简介
本专栏是我学习《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;
};
有了这个接口,我们打算这样做:
- 为每个区域建造一个工厂。你需要创建一个PizzaIngredientFactory的子类来实现每个创建方法。
- 实现一组要和工厂一起使用的原料类,像ReggianoCheese、RedPeppers和ThickCrustDough。这些类可以在合适的区域之间共享。
- 然后,我们依然需要把所有这些连接起来,把新的原料工厂整合进老的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;
}
运行结果:

我们在点一个披萨时,先准备了原料,这些原料都是从抽象工厂获得的,然后再烘焙,切割,装盒。
十.我们做了什么
一连串的代码变化,我们到底做了什么?通过引入一个称为抽象工厂的新工厂类型,我们提供了为披萨创建原料家族的方法。
抽象工厂给我们一个创建产品家族的接口。通过使用这个接口编写代码,我们把代码从实际创建产品的工厂解耦。这让我们在为不同上下文(例如不同区域、不同操作系统或者不同视感)生产产品时,能够实现工厂的变化。
因为代码从实际产品解耦,我们可以替换不同的工厂来获得不同的行为(例如获得意式番茄酱,而不是李子番茄酱)。

十一.定义抽象工厂
我们又给模式家族添加了另一个工厂模式,这个模式可以创建产品的家族。我们来看看这个模式的官方定义:
抽象工厂模式提供一个接口来创建相关或依赖对象的家族,而不需要指定具体类。
无疑,我们已经看到,抽象工厂允许客户使用一个抽象接口来创建一组相关的产品,而不需要懂得(或关心)实际生产的具体产品是什么。通过这样的方法,客户就从所有的特定产品解耦。我们来看看类图,了解其中的关系。


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


更多推荐



所有评论(0)