简单工厂模式、工厂方法模式、抽象工厂模式,都是属于创建型设计模式。严格上来说,简单工厂模式不属于23设计模式之一,因为它违背了开闭原则。这三种设计模式,名字都包含“工厂”二字,如果没有认真地对它们的设计思想、代码进行认真比较,还真的很难区分出究竟是哪一种模式,很多开发者很容易混淆。接下来,我就将这三种设计模式放在一篇文章,用Java来实现它们并放在一起进行比较。
天气很炎热,相信很多人都有着去买跟冰激凌吃的冲动,今天,我们就以冰激凌为例子,来学习这三种设计模式。不过,为了弄清这三种设计模式,可能有些描述和类比不是很贴切,也请大家见谅!
一、简单工厂模式
想象一下,现在商场有很多卖不同口味的冰激凌摊位,每个摊位的位置都不尽相同,每个商家只卖一种口味,商家提供了制作冰激凌的所有原料,需要顾客自己去制作。这时,同伴三个人,你说你去买冰激凌,让大家说说都想买什么口味的冰激凌,最后三个人刚好喜欢的口味都不同,分别是苹果味、香蕉味、橘子味。大家委托你去买,你就跑到了A摊位,自己调制起了一根(一个?)苹果味的冰激凌,然后,你又跑到几十米外另一摊位去制作香蕉味冰激凌,最后又跑到另一摊位去制作橘子味的冰激凌。终于买好了三根冰激凌,拿回去后,发现自己那杯苹果味的已经融化了…如果,你们一行有十几个人,每个人需要的口味都不同呢?这样是不是很麻烦?
商场管理人员意识到了这个问题,想了一个方法。他将所有的口味的冰激凌摊位,全部集中到了商场入口处来卖,并且,订购了一台卖冰激凌的机器,机器上有一排按钮,每个按钮代表一种口味的冰激凌,你想买哪一种口味就点击哪个按钮,机器内部就会自己去制作一根该口味的冰激凌,然后吐出该冰激凌给顾客。可以发现,顾客只需按一个按钮,根本不需要像之前那样自己去制作冰激凌,机器制作冰激凌的过程对于顾客来说也是透明的。这种模式,就是我们的 简单工厂模式 ,而生产冰激凌的机器,就是我们所说的 工厂 。
首先看下这个模式的UML图:
接下来看看具体的代码实现:
1. 首先定义一个冰激凌接口,里边只有一个方法,就是显示是哪种口味
public
interface
IceCream {
public
void
taste();
}
定义三个类,AppleIceCream、BananaIceCream、OrangeIceCream,并继承上面的接口,实现其中的方法。显示自己独特的口味
public
class
AppleIceCreamFactory
implements
IceCreamFactory {
public
IceCream
createIceCream() {
return
new
AppleIceCream();
}
}
public
class
BananaIceCreamFactory
implements
IceCreamFactory {
public
IceCream
createIceCream() {
return
new
BananaIceCream();
}
}
public
class
OrangeIceCreamFactory
implements
IceCreamFactory{
public
IceCream
createIceCream() {
return
new
OrangeIceCream();
}
}
定义一个工厂类,用来制作不同口味的冰激凌
public
class
IceCreamFactory {
public
static
IceCream
creamIceCream(
String
taste){
IceCream
iceCream =
null;
// 这里我们通过switch来判断,具体制作哪一种口味的冰激凌
switch(taste){
case
"Apple"
:
iceCream =
new
AppleIceCream();
break;
case
"Orange"
:
iceCream =
new
OrangeIceCream();
break;
case
"Banana"
:
iceCream =
new
BananaIceCream();
break;
default:
break;
}
return iceCream;
}
}
最后看一下客户端的代码
// 通过统一的工厂,传入不同参数调用生产冰激凌的方法去生产不同口味的冰激凌
public
class
Client {
public
static
void
main(
String[]
args) {
IceCream
appleIceCream =
IceCreamFactory.
creamIceCream(
"Apple");
appleIceCream.
taste();
IceCream
bananaIceCream =
IceCreamFactory.
creamIceCream(
"Banana");
bananaIceCream.
taste();
IceCream
orangeIceCream =
IceCreamFactory.
creamIceCream(
"Orange");
orangeIceCream.
taste();
}
}
可以看到,简单工厂模式确实很“简单”。一般,简单工厂方法模式中,工厂类中有一个方法,通过switch中不同的值或者if else语句来创建不同的对象并返回,通常这个方法是一个静态方法,(顺便一提:简单工厂模式也被称作“静态工厂模式”)在客户端直接调用工厂类的该方法就可以。整个冰激凌的生产(创建不同口味冰激凌的过程)被这个工厂类封装了,客户端不用去关注这些细节。
二、工厂方法模式
用了简单方法模式后,顾客们方便多了,想要哪种口味的冰激凌就直接点一下按钮即可直接购买。现在,该机器只能卖三种口味的冰激凌,并没有满足很多顾客的需求,管理员打算再添加一种草莓味的冰激凌来满足更多顾客。问题来了,整个机器已经做成了,按钮数目为3也已经固定了,如果想要再添加一种口味,那么就要打开机器内部,往里边添加制作草莓味冰激凌的原料以及制作工艺,还要在机器外部再增加一个按钮。这可麻烦了,毕竟整个机器的布局什么的都固定下来了。
其实,这正是简单工厂模式的缺点,其违背了开闭原则,扩展性差。观察简单工厂模式的工厂类代码,我们可以发现,其内部做了很多逻辑的处理,通过switch值的不同来创建不同的对象。现在,如果要新增草莓味的冰激凌,首先要新增StrawberryIceCream,并且,还要在switch里边,新增一个case分支,来判断是否生产StrawberryIceCream,所以不是很合理。
通过 工厂方法模式 ,我们就能解决这一问题。仔细研究,之所以会产生上面的问题,是因为我们只有一个“工厂”,无论何种口味的冰激凌生产,都交给这个工厂去处理导致的。那么,现在,我们可以,设计多个“工厂”,每种工厂只负责生产一种口味的冰激凌。回到商场来,还是在商场入场处,我们购置多台生产冰激凌的机器,每台机器只能生产一种口味的冰激凌,每台机器有一个按钮,点击下去就会吐出该口味冰激凌,然后将这些机器一个挨着一个排列起来就行。现在,如果要添加一种新的口味,那么只要再购置一台机器,然后挨着放进去即可。
现在,我们来看看 工厂方法模式 的UML图:
接下来,看看具体的代码实现:
1. 和简单工厂模式一样,先定义一个接口。再定义AppleIceCream、BananaIceCream和OrangeIceCream去实现这个接口
public
interface
IceCream {
public
void
taste();
}
定义工厂接口
public
interface
IceCreamFactory {
public
IceCream
createIceCream();
}
再分别定义AppleIceCreamFactory、BananaIceCreamFactory、OrangeIceCreamFactory,继承刚刚定义的工厂接口
public
class
AppleIceCreamFactory
implements
IceCreamFactory {
public
IceCream
createIceCream() {
return
new
AppleIceCream();
}
}
public
class
BananaIceCreamFactory
implements
IceCreamFactory {
public
IceCream
createIceCream() {
return
new
BananaIceCream();
}
}
public
class
OrangeIceCreamFactory
implements
IceCreamFactory{
public
IceCream
createIceCream() {
return
new
OrangeIceCream();
}
}
客户端代码
// 客户端代码
public
class
Client {
/**
*
@param
args
*/
public
static
void
main(
String[]
args) {
//生产苹果味冰激凌
IceCreamFactory
appleFactory =
new
AppleIceCreamFactory();
IceCream
appleIceCream =
appleFactory.
createIceCream();
appleIceCream.
taste();
//生产香蕉口味冰激凌
IceCreamFactory
bananaFactory =
new
BananaIceCreamFactory();
IceCream
bananaIceCream =
bananaFactory.
createIceCream();
bananaIceCream.
taste();
//生产橘子口味冰激凌
IceCream
orangeIceCream =
new
OrangeIceCreamFactory().
createIceCream();
orangeIceCream.
taste();
}
}
可以看到,每个工厂只生产一种产品,客户端通过不同的工厂去生产不同的产品,而生产哪一产品的逻辑交给客户端这边去处理了。
三、抽象工厂模式
可以想象,不同人的需求是不同的,有的人吃冰激凌是当做饭后甜点,有的人直接把它当饭吃。因此,商场为了让顾客有多种选择,想要对每种口味冰激凌的量进行分类,分为大份和小份的。比如我们之前生产的是大份的冰激凌,那么要完成生产多种口味小份的冰激凌,能怎么做呢?当然,商场可以再购置苹果味冰激凌(小份)、香蕉味冰激凌(小份)、橘子味冰激凌(小份)的机器,然后挨着放在之前几台旁边。这种思路就是我们上边的工厂方法模式。可是,这样就需要添加多了一倍的机器数量。想象一下,如果商场在一开始规划的时候就已经打算卖两种分量多种口味的冰激凌,那能怎么做呢?
由于每种口味的冰激凌,材料和制作工艺都是相同的,所以,我们完全可以,每台机器负责生产特定口味的冰激凌,并配备两个按钮,用来区分生产大份的或者是小份的,这样是比较符合现实的。这就是我们接下来要说的 抽象工厂模式。
让我们先来看看抽象工厂模式的UML图:
再来看看具体的代码实现:
1. 由于要生产不同大小的冰激凌,所以,现在冰激凌的接口有两个,分别是 BigIceCream 和 SmallIceCream
public
interface
BigIceCream {
public
void
taste();
}
public
interface
SmallIceCream {
public
void
taste();
}
定义各个口味的冰激凌,继承刚刚定义的接口,实现其方法
public
class
BigAppleIceCream
implements
BigIceCream {
public
void
taste() {
System.
out.
println(
"这是苹果味冰激凌(大份)");
}
}
public
class
SmallAppleIceCream
implements
SmallIceCream {
public
void
taste() {
System.
out.
println(
"这是苹果味冰激凌(小份)");
}
}
//后边还有四个类BigBananaIceCream、SmallBananaIceCream、BigOrangeIceCream、SmallOrangeIceCream
定义工厂接口,可以看到,这里有两个方法,分别生产小份大的和大份的
public
interface
IceCreamFactory {
public
BigIceCream
createBigIceCream();
public
SmallIceCream
createSmallIceCream();
}
定义三个工厂类继承上边的接口并实现其方法
public
class
AppleIceCreamFactory
implements
IceCreamFactory {
public
BigIceCream
createBigIceCream() {
return
new
BigAppleIceCream();
}
public
SmallIceCream
createSmallIceCream() {
return
new
SmallAppleIceCream();
}
}
public
class
BananaIceCreamFactory
implements
IceCreamFactory {
public
BigIceCream
createBigIceCream() {
return
new
BigBananaIceCream();
}
public
SmallIceCream
createSmallIceCream() {
return
new
SmallBananaIceCream();
}
}
public
class
OrangeIceCreamFactory
implements
IceCreamFactory {
public
BigIceCream
createBigIceCream() {
return
new
BigOrangeIceCream();
}
public
SmallIceCream
createSmallIceCream() {
return
new
SmallOrangeIceCream();
}
}
客户端代码
public
class
Client {
public
static
void
main(
String[]
args) {
//生产苹果味冰激凌
IceCreamFactory
appleIceCreamFactory =
new
AppleIceCreamFactory();
BigIceCream
appleBigIceCream =
appleIceCreamFactory.
createBigIceCream();
SmallIceCream
appleSmallIceCream =
appleIceCreamFactory.
createSmallIceCream();
appleBigIceCream.
taste();
appleSmallIceCream.
taste();
//生产香蕉味冰激凌
IceCreamFactory
bananaIceCreamFactory =
new
BananaIceCreamFactory();
BigIceCream
bananaBigIceCream =
bananaIceCreamFactory.
createBigIceCream();
SmallIceCream
bananaSmallIceCream =
bananaIceCreamFactory.
createSmallIceCream();
bananaBigIceCream.
taste();
bananaSmallIceCream.
taste();
//生产橘子味冰激凌
IceCreamFactory
orangeIceCreamFactory =
new
OrangeIceCreamFactory();
BigIceCream
orangeBigIceCream =
orangeIceCreamFactory.
createBigIceCream();
SmallIceCream
orangeSmallIceCream =
orangeIceCreamFactory.
createSmallIceCream();
orangeBigIceCream.
taste();
orangeSmallIceCream.
taste();
}
}
可以看到,之所以叫抽象工厂,是因为和工厂方法相比,这里有多个抽象产品类存在(即大份的冰激凌和小份的冰激凌),每个抽象产品类可以派生出多个具体的产品,生产的是系列产品,其工厂接口相对于工厂方法模式而言,是有多个方法的,用来生产不同的抽象产品。
不过,我们也很容易看出,抽象工厂模式的弊端。比如现在,商场想要提供的是大、中、小三种系列产品,那么现在,需要改动的代码就有点多了。首先需要创建一个接口,用来生产中份的冰激凌,然后还要实现具体的类,还需要修改工厂接口,并修改具体的工厂类。