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

目录

一.专栏简介

二.new操作符创建对象

三.识别变化的方面

四.压力来自披萨类型的增删查改

五.封装对象的创建

六.建立一个简单的披萨工厂和重做PizzaStore类

七.定义简单工厂

八.连锁加盟比萨店

九.为披萨店而做的框架

十.允许子类决定

十一.具体化披萨类

十二.会见工厂方法模式的最后时刻

十三.平行视角看创建者和产品

十四.定义工厂方法模式

十五.总结


一.专栏简介

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

本章将开始工厂模式的学习。

二.new操作符创建对象

俗话说得好,没有对象那就new一个出来,但我们在new的时候违反了一个设计原则,那就是针对接口编程,而不是针对实现编程。当我们看到"new"操作符时,会想到具体。

把我们的代码绑在具体类上,会使代码更脆弱,更缺乏弹性。

cpp 复制代码
Duck* duck = new MallardDuck();

我们要用抽象类型来保持代码弹性,但我们不得不创建一个具体类的实例!

当我们有一整群相关的具体类时,经常会这样编码:

cpp 复制代码
Duck* duck = nullptr;

if(picnic) duck = new MallardDuck();
else if(hunting) duck = new DecoyDuck();
else if(inBathTub) duck = new RubberDuck();

我们有一堆不同的鸭子类直到运行时才会知道需要实例化哪一个。

这里有一些要实例化的具体类,实例化哪一个,要在运行时由一些条件决定。

当你看到这样的代码,你就知道,当有变化或需要扩展时,你将不得不再次打开这份代码,检查需要添加(或删除)的地方。通常,这种代码会分布在系统的各处,使得维护和更新更加困难,而且更容易出错。

那么,怎样把应用中所有实例化具体类的代码拿出来分离,或者封装起来,这样不会影响应用的其他部分?

三.识别变化的方面

在一个披萨店的订单系统中,我们写了这样的代码:

cpp 复制代码
Pizza orderPizza()
{
    Pizza* pizza = new Pizza();
    
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

为了弹性,我们需要Pizza类成为一个抽象类接口,但是我们不能直接实例化抽象类。

但我们需要更多类型的披萨,所以我们添加更多的代码,决定合适的披萨类型,然后再制作披萨:

cpp 复制代码
Pizza* orderPizza(string type)
{
	Pizza* pizza = nullptr;

	if (type == "cheese") pizza = new CheesePizza();
	else if (type == "greek") pizza = new GreekPizza();
	else if (type == "pepperoni") pizza = new PepperoniPizza();
	else return nullptr;

	pizza->prepare();
	pizza->bake();
	pizza->cut();
	pizza->box();
	return pizza;
}

四.压力来自披萨类型的增删查改

当我们要改变披萨的类型时,比如删除一种销量不好的披萨,增加一种大众喜欢的披萨,这时候我们就需要跑到这份代码里修改if语句,也就是说,这份代码的if语句部分没有对修改关闭,但又是经常需要变化的部分。另外,这份代码里的准备,烘焙,切割,装盒操作是固定不变的,这样我们就区分出了代码中变化和不变化的地方。

显然,关于哪个具体类被实例化的代码才是真正搞乱orderPizza()方法的罪魁祸首,它使得代码无法对修改关闭。但现在我们知道哪些会变化,哪些不会变化,可能是时候把它封装起来了。

五.封装对象的创建

我们打算把创建代码移到另一个对象中,该对象只关心创建披萨。

我们称这个对象为:工厂

工厂处理对象创建的细节。一旦我们有了一个SimplePizzaFactory,orderPizza()方法就变成这个对象的客户。任何时候它需要披萨,就叫披萨工厂做一个。那些orderPizza()方法需要知道希腊披萨或者蛤蜊披萨的日子就一去不复返了。现在orderPizza()方法只关心它得到了一个实现Pizza接口的披萨,这样它就可以调用prepare()、bake()、cut()和box()。

六.建立一个简单的披萨工厂和重做PizzaStore类

我们从工厂本身开始。我们打算定义一个类,这个类封装所有披萨的对象创建。如下所示:

Pizza.h:

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
using namespace std;

class Pizza
{
public:
	void prepare();
	void bake();
	void cut();
	void box();
};

class CheesePizza : public Pizza
{

};

class GreekPizza : public Pizza
{

};

class PepperoniPizza : public Pizza
{

};

class SimplePizzaFactory
{
public:
	Pizza* createPizza(string type)
	{
		Pizza* pizza = nullptr;

		if (type == "cheese") pizza = new CheesePizza();
		else if (type == "greek") pizza = new GreekPizza();
		else if (type == "pepperoni") pizza = new PepperoniPizza();
		else return nullptr;

		return pizza;
	}
};

class PizzaStore
{
public:
	PizzaStore(const SimplePizzaFactory& factory);
	Pizza* orderPizza(string type);
private:
	SimplePizzaFactory factory;
};

Pizza.cpp:

cpp 复制代码
#include "Pizza.h"

void Pizza::prepare()
{
	cout << "准备" << endl;
}

void Pizza::bake()
{
	cout << "烘焙" << endl;
}

void Pizza::cut()
{
	cout << "切片" << endl;
}

void Pizza::box()
{
	cout << "装盒" << endl;
}

PizzaStore::PizzaStore(const SimplePizzaFactory& factory):
	factory(factory)
{

}

Pizza* PizzaStore::orderPizza(string type)
{
	SimplePizzaFactory spf;
	Pizza* pizza = spf.createPizza(type);

	pizza->prepare();
	pizza->bake();
	pizza->cut();
	pizza->box();
	return pizza;
}

main.cpp:

cpp 复制代码
#include "Pizza.h"

int main()
{
	SimplePizzaFactory factory;
	PizzaStore store(factory);
	store.orderPizza("cheese");
	store.orderPizza("pepperoni");

	return 0;
}

运行结果:

这里我们PizzaStore组合了工厂,我们可以动态的改变这个工厂,生产不同风味的披萨。

七.定义简单工厂

我们上面地披萨工厂和披萨商店类就使用了简单工厂这一编程习惯。

简单工厂其实不是一个设计模式,更多是一种编程习惯。一些开发人员的确把这个习惯误认为是工厂模式,不过,下一次碰到这种场合,就可以微秒地展示一下你懂得其中区别,但不要过分炫耀。

下面是基披萨工厂的简单工厂类图

八.连锁加盟比萨店

披萨店的生意越做越好,我们作为连锁加盟店的经销商,要确保连锁加盟运营的质量,因此希望加盟店都使用我们那些经过时间考验的代码。

但是区域的差异呢?根据加盟店地点以及该地区披萨美食家的口味,每家加盟店可能要提供不同风味的披萨(比如纽约、芝加哥、加州)。

这里就体现了组合的优势。我们把SimplePizzaFactory拿出来,创建三个不同的工厂:NYPizzaFactory、ChicagoPizzaFactory和CaliforniaPizzaFactory,然后,我们只要把PizzaStore和适合工厂组合起来,加盟店的问题就解决了。

我们来看看代码长什么样子~~~代码如下:(这一段是伪代码,不同工厂应该继承自一个基类,然后传基类指针给PizzaStore的构造函数

cpp 复制代码
NYPizzaFactory* nyFactory = new NYPizzaFactory();
PizzaStore* nyStore = new PizzaStore(nyFactory );
nyStore.orderPizza("Veggie");

ChicagoPizzaFactory* chicagoFactory = new ChicagoPizzaFactory();
PizzaStore* chicagoStore = new PizzaStore(chicagoFactory );
chicagoStore.orderPizza("Veggie");

这里我们创建做纽约风味披萨的工厂。然后我们创建一个PizzaStore,并给它传递一个纽约工厂的引用。当我们制作披萨时,得到了纽约风味的披萨。芝加哥披萨店也类似。

九.为披萨店而做的框架

上面介绍的简单工厂还要一个单独的工厂类,使用起来不是很方便。有个方法可让披萨制作活动局限于PizzaStore类,同时给予加盟店自由来拥有他们自己的地区风味。

我们打算做的是,把createPizza()方法放回PizzaStore,但这一次作为一个抽象方法,然后为每个区域风味创建一个PizzaStore子类。

首先,我们来看看PizzaStore的变化:

cpp 复制代码
class PizzaStore
{
public:
	Pizza* orderPizza(string type)
	{
		Pizza* pizza = createPizza(type);
		pizza->prepare();
		pizza->bake();
		pizza->cut();
		pizza->box();
		return pizza;
	}
private:
	virtual Pizza* createPizza(string type) = 0;
};

现在createPizza从工厂对象移回PizzaStore,并且是纯虚函数,使得PizzaStore基类成为抽象类接口,咱们面向接口编程,而不是实现。

现在我们有了一个等待着子类的店。我们打算让每个区域类型有一个子类(NYPizzaStoreChicagoPizzaStoreCaliforniaPizzaStore),每个子类将决定披萨由什么组成。我们看看怎么运作。

十.允许子类决定

各个区域之间的差异,在于他们制作披萨的风味,我们打算把所有这些变化放进createPizza()方法。因此,会有一些PizzaStore的具体子类,每个有自己的披萨变体,但都适合PizzaStore框架,依然使用调试好的orderPizza()方法。

代码如下:

Pizza.h:

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
using namespace std;

class Pizza
{
public:
	void prepare();
	void bake();
	void cut();
	void box();
};

class CheesePizza : public Pizza
{

};

class GreekPizza : public Pizza
{

};

class PepperoniPizza : public Pizza
{

};

class NYStyleCheesePizza : public CheesePizza
{

};

class NYStyleGreekPizza : public GreekPizza
{

};

class NYStylePepperoniPizza : public PepperoniPizza
{

};

class ChicagoStyleCheesePizza : public CheesePizza
{

};

class ChicagoStyleGreekPizza : public GreekPizza
{

};

class ChicagoStylePepperoniPizza : public PepperoniPizza
{

};

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:

cpp 复制代码
#include "Pizza.h"

void Pizza::prepare()
{
	cout << "准备" << endl;
}

void Pizza::bake()
{
	cout << "烘焙" << endl;
}

void Pizza::cut()
{
	cout << "切片" << endl;
}

void Pizza::box()
{
	cout << "装盒" << endl;
}

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;
	if (type == "cheese")
	{
		cout << "NYStyleCheesePizza" << endl;
		pizza = new 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;
	if (type == "cheese")
	{
		cout << "ChicagoStyleCheesePizza" << endl;
		pizza = new ChicagoStyleCheesePizza();
	}
	else if (type == "pepperoni")
	{
		cout << "ChicagoStylePepperoniPizza" << endl;
		pizza = new ChicagoStylePepperoniPizza();
	}
	else if (type == "greek")
	{
		cout << "ChicagoStyleGreekPizza" << endl;
		pizza = new ChicagoStyleGreekPizza();
	}
	return pizza;
}

main.cpp:

cpp 复制代码
#include "Pizza.h"

int main()
{
	PizzaStore* store = new NYStylePizzaStore();
	store->orderPizza("cheese");
	cout << endl;
	store->orderPizza("pepperoni");

	delete store;
	store = nullptr;
	cout << endl;

	store = new ChicagoStylePizzaStore();
	store->orderPizza("cheese");
	cout << endl;
	store->orderPizza("pepperoni");

	return 0;
}

运行结果:

orderPizza()方法对Pizza对象做了许多事情(例如:准备、烘焙、切片、装盒),但由于Pizza是抽象的,orderPizza()不知道涉及了哪一个实际的具体类。换句话说,这就是被解耦了。

书中有一句很绕的话:子类不是真正"决定"(是你通过选择披萨店决定),但它们确实决定了做哪种披萨

十一.具体化披萨类

实现披萨类之后的代码如下:

Pizza.h:

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Pizza
{
public:
	void prepare();
	void bake();
	void cut();
	void box();
	const string& getName();
protected:
	string name;
	string dough;
	string sauce;
	vector<string> toppings;
};

class NYStyleCheesePizza : public Pizza
{
public:
	NYStyleCheesePizza();
};

class NYStyleGreekPizza : public Pizza
{

};

class NYStylePepperoniPizza : public Pizza
{

};

class ChicagoStyleCheesePizza : public Pizza
{
public:
	ChicagoStyleCheesePizza();
};

class ChicagoStyleGreekPizza : public Pizza
{

};

class ChicagoStylePepperoniPizza : public Pizza
{

};

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:

cpp 复制代码
#include "Pizza.h"

void Pizza::prepare()
{
	cout << "准备 " + name << endl;
	cout << "揉面团......" << endl;
	cout << "添加酱汁......" << endl;
	cout << "添加馅料:";
	for (const auto& e : toppings)
	{
		cout << " " << e;
	}
	cout << endl;
}

void Pizza::bake()
{
	cout << "350°烘焙25分钟" << endl;
}

void Pizza::cut()
{
	cout << "将披萨按照对角线切割" << endl;
}

void Pizza::box()
{
	cout << "将披萨装盒" << endl;
}

const string& Pizza::getName()
{
	return name;
}

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;
	if (type == "cheese")
	{
		cout << "NYStyleCheesePizza" << endl;
		pizza = new 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;
	if (type == "cheese")
	{
		cout << "ChicagoStyleCheesePizza" << endl;
		pizza = new 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()
{
	name = "纽约风味酱料芝士比萨";
	dough = "薄底面团";
	sauce = "意大利番茄酱";
	toppings.push_back("磨碎的雷吉亚诺奶酪");
}

ChicagoStyleCheesePizza::ChicagoStyleCheesePizza()
{
	name = "芝加哥风格深盘奶酪披萨";
	dough = "特厚披萨饼底面团";
	sauce = "李子番茄酱";
	toppings.push_back("碎马苏里拉奶酪");
}

main.cpp:

cpp 复制代码
#include "Pizza.h"

int main()
{
	PizzaStore* store = new NYStylePizzaStore();
	store->orderPizza("cheese");

	delete store;
	store = nullptr;
	cout << endl;

	store = new ChicagoStylePizzaStore();
	store->orderPizza("cheese");

	return 0;
}

运行结果如下:

两个披萨都准备好了,馅料已加,已烘焙、切片、装盒,超类不必知道细节,子类只是通过实例化正确的披萨来处理所有这些。

十二.会见工厂方法模式的最后时刻

所有工厂模式都封装对象的创建。工厂方法模式通过让子类决定创建哪个对象,来封装对象的创建。我们来看看这些类图,了解这个模式中有哪些玩家:

十三.平行视角看创建者和产品

对于每一个具体创建者,通常有一整套产品要创建。芝加哥披萨创建者创建不同类型的芝加哥风味披萨,纽约披萨创建者创建不同类型的纽约风味披萨等。事实上,我们可以看到创建者类及其相应的产品类集平行的层次。

我们来看看两个平行类层次以及它们如何相关:

十四.定义工厂方法模式

工厂方法模式的官方定义:

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法让类把实例化推迟到子类。

我们会经常听到开发人员说,"工厂方法模式让子类决定实例化哪个类"。因为Creator类不知道将被创建的实际产品,我们说"决定",不是因为该模式允许子类本身决定,该决定实际上指用哪个子类创建产品。

十五.总结

本篇文章主要介绍了简单工厂和工厂方法模式,下篇会介绍抽象方法模式,依赖倒置原则等等。

相关推荐
我的golang之路果然有问题10 小时前
Mac 上的 Vue 安装和配置记录
前端·javascript·vue.js·笔记·macos
十五年专注C++开发10 小时前
CMake进阶:核心命令get_filename_component 完全详解
开发语言·c++·cmake·跨平台编译
我的golang之路果然有问题10 小时前
Docker 之常用操作(实习中的)
java·运维·笔记·docker·容器·eureka
charlie11451419110 小时前
从0开始的机器学习(笔记系列)——导数 · 多元函数导数 · 梯度
人工智能·笔记·学习·数学·机器学习·导数
mrcrack10 小时前
洛谷 B3656 【模板】双端队列 1 方案1+离线处理+一维数组+偏移量 方案2+stl list
c++·list
lingzhilab10 小时前
零知IDE——基于STMF103RBT6结合PAJ7620U2手势控制192位WS2812 RGB立方体矩阵
c++·stm32·矩阵
optimistic_chen10 小时前
【Redis系列】事务特性
数据库·redis·笔记·缓存·事务
go_bai10 小时前
生产消费模型-简洁线程池
linux·c++·笔记
Dolphin_Home10 小时前
MyBatis 核心属性详解笔记(由浅入深)
笔记·mybatis