1.简述
这里描述的设计模式,来源于head first系列,这里的工厂模式是基于书中披萨店的例子。
2.简单工厂
简单工厂的设计就是说把所有需要创建的东西放在一个普通的函数中,以书中披萨的例子来讲,假设有一百种披萨,就再函数中全部处理完。当然简单工厂内部的实现也可以有技巧,常见的就是if或者switch逐个处理,但是如果太多了,这里就麻烦了。科技设计map,把函数和对应的key存储下来。直接判断的方式,基本只适用于分支比较少的情况。
Pizza* create_pizza(string type)
{
if(type == 'a')
{
}
else if(type == 'b')
{
}
.....
else
{
}
return nullptr;
}
3.工厂方法
工厂方法的处理,其实就是上面的处理做抽象,做封装。因为这里披萨只是一个产品,工厂用于创建产品,而产品最终怎么用取决于业务,也就是所谓的高层,这里就是披萨商店。每个商店既然要卖披萨,肯定要制造披萨,所以这里就得到一个共通的地方,创建方法。
- 抽象商店出来,把披萨的制作方式作为虚接口,c++中就实现为虚函数,或者纯虚,应该不影响
- 每个商店都需要实现自己的制作方法
这就是所谓工厂方法,把具体的实现方式推迟到实例中,由实例或者说业务自己决定怎么处理这个接口。这里说叫做设计模式,就感觉有点扯虚了,其实就是很简单的OO,稍微抽象一下,然后每个自己实现自己的处理方式而已。
class pizzaStore{
private:
Pizza *create_pizza(string type) = 0;
}
然后具体的商店就继承自虚基类,然后实现自己的方法。这里注意一下,Pizza也是抽象过的,不是某种具体的pizza。这里函数的实现也不能放在h里面,需要放在源文件中。首先Pizza的抽象,是需要遵循设计原则中的,依赖于接口,而不依赖于实现。另外把实现放在源中,而不是放在头文件中,是为了减少编译依赖(effective c++),如果放在了头文件中,上层稍微有改动的头文件,就会导致下次编译时所有依赖头文件的重新编译,这里也算是需要遵循依赖接口的,因为在设计中,没有使用具体类的实例,而是使用的是抽象接口,所以接口就只需要前置声明,而不需要包含对应的头文件。前面这点算是相互依存吧。
class MyPizzaStore : public pizzaStore
{
private:
Pizza *create_pizza(string type);
}
4.抽象工厂
抽象工厂有点像是工厂方法和策略模式的组合,再包装一次,这里不以书中原料工厂的例子展开。
class Pizza;
class PizzaFactory;
class PizzaStroe
{
public:
PizzaStroe(string name,PizzaFactory *fac = nullptr);
virtual ~PizzaStroe();
private:
virtual Pizza *createPizza(int type);
virtual int afterCreatePizza(Pizza *pizza);
private:
PizzaFactory *useFac;
string name;
}
这里简单的处理一下上面店铺的模式,把工厂抽象出来,比如A工厂和B工厂有不同的实现,但是都是继承自PizzaFactory。然后在创建商店的时候,指定自己的商店名字,当类型使用,这个类型可以默认找到一个对应工厂,用于初始化useFac,或者说直接在构造函数中传递工厂。然后在创建披萨的时候,通过useFac的工厂接口去创建一个,创建完之后调用afterCreatePizza,这里局看具体的pizza怎么实现了,书中提到了原料工厂,原料工厂是用在Pizza中的,这里不具体提及Pizza的实现,因为设计pizza和设计店铺的原理是一样的。关于抽象工厂有一个重要概念,依赖倒置,即高层不能依赖于底层实现,两者都需要尽量去依赖于抽象,这里说尽量,是因为没有办法完全做到不依赖于实例,总有地方会依赖于具体的处理,但是需要尽量在业务上依赖于接口。比如这里商店是业务,是高层,要实现的东西是披萨,如果直接像上面的工厂方法一样,就导致每个商店都要依赖于具体的商品类型,需要知道有哪些商品,如果后面有商品的变化,且这个商品可能在多个店铺中处理,那就会导致多个商铺都需要修改,但是这里把它封装到工厂中,这里依赖于抽象工厂接口,就实现了高层对底层的解耦,不再依赖于具体的处理。
但是这里依赖倒置又无法完全实现,比如对于制造来讲,工厂就是高层,披萨就是底层,工厂总还是需要知道具体的披萨类型才能处理,只是它经过自己处理之后,为上层业务提供了抽象接口,这里的工程内部实现,其实就是上面的工厂方法实现。
抽象工厂的设计原则
- 尽量不持有具体的实例,比如这里商铺不会持有具体的披萨,但是工厂又没有办法不处理这种
- 不依赖于具体类,就是说要尽量抽象出接口来
- 不覆盖继承过来的接口,这里在C++中,应该是不影响的,虚函数和纯虚函数,可覆盖,也可不覆盖,基类可以是虚基类,也可以是普通拥有虚函数的类。
这些东西都是说尽量去满足,没有办法完全满足的,因为只要需要使用,就总需要实例化。
5.总结
其实工厂模式,更像是中间件,封装一下具体处理,为上层提供抽象,总的就是为了解耦,于拓展。比如这里如果用了抽象工厂模式,后面如果需要添加了一种商品,就只需要在工厂中修改,而上层基本无感,只需要知道有这种产品(因为需求就是从这里来的),而不关心具体实现,也完全不需要修改自己的业务处理。