转载请注明出处
作者:@ZJXin
说明:本文借助冰激凌的例子,通过Java来实现三大工厂模式,代码运行无误。由于本人水平有限,如有不正确的地方,欢迎留言指出。谢谢!
简单工厂模式、工厂方法模式、抽象工厂模式,都是属于创建型设计模式。严格上来说,简单工厂模式不属于23设计模式之一,因为它违背了开闭原则。这三种设计模式,名字都包含“工厂”二字,如果没有认真地对它们的设计思想、代码进行认真比较,还真的很难区分出究竟是哪一种模式,很多开发者很容易混淆。接下来,我就将这三种设计模式放在一篇文章,用Java来实现它们并放在一起进行比较。
天气很炎热,相信很多人都有着去买跟冰激凌吃的冲动,今天,我们就以冰激凌为例子,来学习这三种设计模式。不过,为了弄清这三种设计模式,可能有些描述和类比不是很贴切,也请大家见谅!
一、简单工厂模式
想象一下,现在商场有很多卖不同口味的冰激凌摊位,每个摊位的位置都不尽相同,每个商家只卖一种口味,商家提供了制作冰激凌的所有原料,需要顾客自己去制作。这时,同伴三个人,你说你去买冰激凌,让大家说说都想买什么口味的冰激凌,最后三个人刚好喜欢的口味都不同,分别是苹果味、香蕉味、橘子味。大家委托你去买,你就跑到了A摊位,自己调制起了一根(一个?)苹果味的冰激凌,然后,你又跑到几十米外另一摊位去制作香蕉味冰激凌,最后又跑到另一摊位去制作橘子味的冰激凌。终于买好了三根冰激凌,拿回去后,发现自己那杯苹果味的已经融化了...如果,你们一行有十几个人,每个人需要的口味都不同呢?这样是不是很麻烦?
商场管理人员意识到了这个问题,想了一个方法。他将所有的口味的冰激凌摊位,全部集中到了商场入口处来卖,并且,订购了一台卖冰激凌的机器,机器上有一排按钮,每个按钮代表一种口味的冰激凌,你想买哪一种口味就点击哪个按钮,机器内部就会自己去制作一根该口味的冰激凌,然后吐出该冰激凌给顾客。可以发现,顾客只需按一个按钮,根本不需要像之前那样自己去制作冰激凌,机器制作冰激凌的过程对于顾客来说也是透明的。这种模式,就是我们的 简单工厂模式 ,而生产冰激凌的机器,就是我们所说的 工厂 。
首先看下这个模式的UML图:
接下来看看具体的代码实现:
- 首先定义一个冰激凌接口,里边只有一个方法,就是显示是哪种口味
public interface IceCream {
public void taste();
}
- 定义三个类,AppleIceCream、BananaIceCream、OrangeIceCream,并继承上面的接口,实现其中的方法。显示自己独特的口味
public class AppleIceCream implements IceCream {
public void taste(){
System.out.println("这是苹果口味的冰激凌");
}
}
public class BananaIceCream implements IceCream {
public void taste() {
System.out.println("这是香蕉口味的冰激凌");
}
}
public class OrangeIceCream implements IceCream{
public void taste(){
System.out.println("这是橘子口味的冰激凌");
}
}
- 定义一个工厂类,用来制作不同口味的冰激凌
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图:
接下来,看看具体的代码实现:
- 和简单工厂模式一样,先定义一个接口。再定义AppleIceCream、BananaIceCream和OrangeIceCream去实现这个接口
public interface IceCream {
public void taste();
}
AppleIceCream、BananaIceCream、OrangeIceCream
- 定义工厂接口
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图:
再来看看具体的代码实现:
- 由于要生产不同大小的冰激凌,所以,现在冰激凌的接口有两个,分别是 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();
}
}
可以看到,之所以叫抽象工厂,是因为和工厂方法相比,这里有多个抽象产品类存在(即大份的冰激凌和小份的冰激凌),每个抽象产品类可以派生出多个具体的产品,生产的是系列产品,其工厂接口相对于工厂方法模式而言,是有多个方法的,用来生产不同的抽象产品。
不过,我们也很容易看出,抽象工厂模式的弊端。比如现在,商场想要提供的是大、中、小三种系列产品,那么现在,需要改动的代码就有点多了。首先需要创建一个接口,用来生产中份的冰激凌,然后还要实现具体的类,还需要修改工厂接口,并修改具体的工厂类。
四、总结
通过UML图以及代码不难对三种工厂模式进行一个区分。简单工厂模式,工厂类是整个模式的关键所在,包含了必要的逻辑判断,能够外界给定的信息, 决定究竟创建哪个具体类的对象。工厂方法模式 是对简单工厂方法模式的一个抽象,抽离出了一个Factory类(或者接口),这个接口不负责具体产品的生产,而只是指定一些规范,具体的生产工作由其子类去完成。这个模式中,工厂类和产品类往往是一一对应的,完全解决了简单工厂模式中违背“开闭原则”的问题,实现了可扩展;抽象工厂模式 的特点是存在多个抽象产品类,每个抽象产品类可以派生出多个具体产品类,工厂提供多种方法,去生产“系列”产品。
简单工厂模式适用于工厂类需要创建的对象比较少的情况,客户只需要传入具体的参数,就可以忽略工厂的生产细节,去获取想要的对象;工厂方法模式,主要是针对单一产品结构的情景;抽象工厂模式则是针对多级产品结构(系列产品)的一种工厂模式。最后在说一下,每种模式都有自己的优点和弊端,没有最好的模式,只有最适合的模式,只要符合实际开发需求就是最好的。