设计模式之工厂模式
一、工厂模式简介
1、使用工厂模式的好处
工厂模式是创建型模式中最典型的模式,主要作用是用来创建对象,使用工厂模式替代new操作有如下几个好处:
1:解耦,把创建对象的工作和使用对象的工作分开,比如我们想使用对象A,我们无需关心对象A是如何创建的,直接去工厂获取就行。
2:对于构造函数很复杂的对象,可以很好地对外层屏蔽代码的复杂性。
3:把初始化实例的工作放到工厂里进行统一管理,使代码更容易维护。
4:修改代码时不会引起太大的变动,如果想改代码的话,只需要改一处即可,良好的扩展性。
5:减少代码量,如果构造方法比较复杂,有很多很多个构造函数,需要一连串的代码创建对象,如果使用new就会造成很多重复的代码。
2、用一个例子说明使用工厂模式相对于new操作的优势
下面我们分别从使用new创建对象和工厂模式两种方面讲解,帮助大家理解工厂模式的优势。
需求1:有三个用户,需要定制相同的蓝莓黑巧动物奶油做的蛋糕。使用new创建对象:
// 蛋糕类
public class Cake {
private String fruit;// 配料:水果
private String chocolate;// 配料:巧克力
private String cream;// 配料:奶油
public Cake(String fruit, String chocolate, String cream){
this.fruit = fruit;
this.chocolate = chocolate;
this.cream = cream;
}
}
public class Test1 {
public static void main(String[] args) {
// 用户A订了一个蓝莓黑巧动物奶油做的蛋糕
Cake cake1 = new Cake("蓝莓", "黑巧克力", "动物奶油");
// 用户B订了一个蓝莓黑巧动物奶油做的蛋糕
Cake cake2 = new Cake("蓝莓", "黑巧克力", "动物奶油");
// 用户C订了一个蓝莓黑巧动物奶油做的蛋糕
Cake cake3 = new Cake("蓝莓", "黑巧克力", "动物奶油");
}
}
工厂模式:
// 蛋糕工厂
public class CakeFactory {
public Cake createCake(){
return new Cake("蓝莓", "黑巧克力", "动物奶油");
}
}
public class Test2 {
public static void main(String[] args) {
CakeFactory cakeFactory = new CakeFactory();
// 用户A订了一个蓝莓黑巧动物奶油做的蛋糕
cakeFactory.createCake();
// 用户B订了一个蓝莓黑巧动物奶油做的蛋糕
cakeFactory.createCake();
// 用户C订了一个蓝莓黑巧动物奶油做的蛋糕
cakeFactory.createCake();
}
}
需求2;这三个用户想要在蛋糕中加入一种坚果,如果使用new创建对象,需要修改三个new操作,而使用工厂模式,只需修改工厂中的new操作即可,改动更少,带来错误的可能性就越少。 使用new创建对象:
// 蛋糕类
public class Cake {
private String fruit;// 配料:水果
private String chocolate;// 配料:巧克力
private String cream;// 配料:奶油
private String nut;// 配料:坚果
public Cake(String fruit, String chocolate, String cream, String nut){
this.fruit = fruit;
this.chocolate = chocolate;
this.cream = cream;
this.nut = nut;
}
}
public class Test1 {
public static void main(String[] args) {
// 用户A订了一个蓝莓黑巧动物奶油榛子做的蛋糕
Cake cake1 = new Cake("蓝莓", "黑巧克力", "动物奶油", "榛子");
// 用户B订了一个蓝莓黑巧动物奶油榛子做的蛋糕
Cake cake2 = new Cake("蓝莓", "黑巧克力", "动物奶油", "榛子");
// 用户C订了一个蓝莓黑巧动物奶油榛子做的蛋糕
Cake cake3 = new Cake("蓝莓", "黑巧克力", "动物奶油", "榛子");
}
}
工厂模式:
// 蛋糕工厂
public class CakeFactory {
public Cake createCake(){
return new Cake("蓝莓", "黑巧克力", "动物奶油", "榛子");
}
}
public class Test2 {
public static void main(String[] args) {
CakeFactory cakeFactory = new CakeFactory();
// 用户A订了一个蓝莓黑巧动物奶油榛子做的蛋糕
cakeFactory.createCake();
// 用户B订了一个蓝莓黑巧动物奶油榛子做的蛋糕
cakeFactory.createCake();
// 用户C订了一个蓝莓黑巧动物奶油榛子做的蛋糕
cakeFactory.createCake();
}
}
3、工厂模式分类
工厂模式,包含三种情况,简单工厂、工厂方法、抽象工厂,下面我们就来一一介绍一下。
我相信大家都有这样的困惑,目前我所在的项目都在程序开发的过程中,还是有很多的new()操作出现在程序中,并没有通过工厂来创建对象,一方面可能是因为我们自身比较懒,不规范项目的编码形式,另外一方面也是由于项目的进度比较紧,没有那么多的时间去完成工厂的统一创建。
二、简单工厂
我们将用几个例子来逐步讲解简单工厂、工厂方法、抽象工厂,下面我们就开始吧。
1、简单工厂示例
小明在老家开了一家饮品店,提供多种饮品,当有顾客来店里,只需要和服务员说他想喝哪种饮料,服务员便可提供哪种饮料,这就是简单工厂模式,服务员就是这个简单工厂。
// 抽象饮料
public abstract class Drink {
public abstract void desc();
}
// 苹果汁
public class AppleDrink extends Drink {
@Override
public void desc() {
System.out.println("这是一杯苹果汁!");
}
}
// 葡萄汁
public class GrapeDrink extends Drink {
@Override
public void desc() {
System.out.println("这是一杯葡萄汁!");
}
}
// 西瓜汁
public class WatermelonDrink extends Drink {
@Override
public void desc() {
System.out.println("这是一杯西瓜汁!");
}
}
// 饮料工厂
public class DrinkFactory {
// name:饮料名字
public Drink getDrink(String name){
if("apple".equals(name)){
return new AppleDrink();
}else if("watermelon".equals(name)){
return new WatermelonDrink();
}else if("grape".equals(name)){
return new GrapeDrink();
}
return null;
}
}
// 客户端
public class Client {
public static void main(String[] args) {
DrinkFactory drinkFactory = new DrinkFactory();
// 顾客想要苹果汁
Drink appleDrink = drinkFactory.getDrink("apple");
appleDrink.desc();
// 顾客想要葡萄汁
Drink grapeDrink = drinkFactory.getDrink("grape");
grapeDrink.desc();
}
}
2、简单工厂的适用场景
(1)、工厂类负责创建的对象比较少。
(2)、客户端(应用层)只知道传入工厂类的参数,对于如何创建对象(逻辑)不关心。
3、简单工厂的优点
只需要传入一个正确的参数,就可以获取你所需要的对象,而无需知道其创建细节,工厂类要有必要的判断逻辑(if else),可以决定在什么时候创建哪一个产品的实例,客户端可以免除直接创建对象的责任。
4、简单工厂的缺点
(1)、工厂类的职责相对较重,增加新的产品,需要修改工厂类的判断逻辑(增加if else分支),修改就意味着引入风险,这违反了开闭原则。
(2)、当产品很多的时候,这个工厂类就会很复杂,不利于维护和扩展,代码无法阅读,很难交接。
(3)、无法形成基于继承的等级结构。在工厂方法和抽象工厂中,会讲到产品族和产品等级结构,再了解什么是基于继承的等级结构。
三、工厂方法
1、工厂方法示例
后来店里饮料品种越来越多,每增加一款饮料都需要让服务员知晓,否则顾客说的时候,服务员就无法提供(这就是简单工厂中所说的对工厂类的修改)。
于是,小明直接买了饮料机,每种饮料都有自己的饮料机(这个饮料机就是工厂方法中的产品工厂,每个产品都有自己的工厂),顾客想喝什么,就自己去饮料机取,饮品店以后想增加饮料,只要增加饮料机即可(工厂方法相对于简单工厂,更加易于扩展)。
// 抽象饮料
public abstract class Drink {
public abstract void desc();
}
// 苹果汁
public class AppleDrink extends Drink {
@Override
public void desc() {
System.out.println("这是一杯苹果汁!");
}
}
// 葡萄汁
public class GrapeDrink extends Drink {
@Override
public void desc() {
System.out.println("这是一杯葡萄汁!");
}
}
// 西瓜汁
public class WatermelonDrink extends Drink {
@Override
public void desc() {
System.out.println("这是一杯西瓜汁!");
}
}
// 抽象饮料工厂
public abstract class DrinkFactory {
public abstract Drink getDrink();
}
// 苹果汁饮料工厂(苹果汁饮料机)
public class AppleDrinkFactory extends DrinkFactory {
@Override
public Drink getDrink() {
return new AppleDrink();
}
}
// 葡萄汁饮料工厂
public class GrapeDrinkFactory extends DrinkFactory {
@Override
public Drink getDrink() {
return new GrapeDrink();
}
}
// 西瓜汁饮料工厂
public class WatermelonDrinkFactory extends DrinkFactory {
@Override
public Drink getDrink() {
return new WatermelonDrink();
}
}
// 客户端
public class Client {
public static void main(String[] args) {
// 顾客想要苹果汁
DrinkFactory appleDrinkFactory = new AppleDrinkFactory();
Drink appleDrink = appDrinkFactory.getDrink();
appleDrink.desc();
// 顾客想要葡萄汁
DrinkFactory grapeDrinkFactory = new GrapeDrinkFactory();
Drink grapeDrink = grapeDrinkFactory.getDrink();
grapeDrink.desc();
}
}
2、工厂方法和简单工厂的区别
简单工厂:所有的产品都在一个工厂中生产,在工厂中使用if else根据传入参数判断生产哪种产品,当增加一种产品的时候,就需要修改工厂的if else逻辑。
工厂方法:每种产品都有自己的工厂,当需要增加一种产品时,无需修改现有的工厂,只需要增加一种工厂即可。遵循了开闭原则,对于扩展是开放的,对于修改是关闭的。
3、工厂方法的优点
(1)、用户只需要关心所需产品对应的工厂,无需关心产品的创建细节。
(2)、加入新产品时,只需要再增加对应的工厂,而不用修改已有的工厂,符合开闭原则,提高扩展性。
4、工厂方法的缺点
类爆炸,因为每个产品都要有一个工厂,如果产品很多,那就会有很多的工厂。用上面的例子来描述,如果后续饮品店出了成百上千种饮品,就要在店里放成百上千台饮料机,店里放不下啊。。。
四、抽象工厂
1、抽象工厂示例
至于抽象工厂模式,可以用套餐来理解。小明在后期,除了提供饮品,还给每种饮品搭配了合适的甜点,向顾客提供下午茶套餐,以此来提高销售量。
所以饮品店制定了一套标准(抽象工厂,具体工厂需要遵循的工厂接口),所有的套餐(具体工厂)必须遵循该标准,比如这个抽象标准可能是:1、需要有一杯果汁。2、需要有一块甜点。于是有了第一个套餐(具体工厂):西瓜汁 + 泡芙,我们称之为套餐A,那顾客点套餐A,即可获得刚才所说产品。当然这里面的西瓜汁、泡芙(具体产品对象),也需要遵守相应的产品规则(产品接口),以保证顾客能吃到放心食品。
产品接口如下:
// 饮料抽象类
public abstract class Drink {
public abstract void desc();
}
// 甜点抽象类
public abstract class Dessert {
public abstract void desc();
}
具体产品对象如下:
// 苹果汁
public class AppleDrink extends Drink {
@Override
public void desc() {
System.out.println("这是一杯苹果汁!");
}
}
// 葡萄汁
public class GrapeDrink extends Drink {
@Override
public void desc() {
System.out.println("这是一杯葡萄汁!");
}
}
// 西瓜汁
public class WatermelonDrink extends Drink {
@Override
public void desc() {
System.out.println("这是一杯西瓜汁!");
}
}
// 泡芙
public class Puff extends Dessert {
@Override
public void desc() {
System.out.println("这是一块泡芙!");
}
}
// 慕斯蛋糕
public class MousseCake extends Dessert {
@Override
public void desc() {
System.out.println("这是一块慕斯蛋糕!");
}
}
// 提拉米苏蛋糕
public class Tiramisu extends Dessert {
@Override
public void desc() {
System.out.println("这是一块提拉米苏蛋糕!");
}
}
抽象工厂如下:
// 下午茶套餐工厂
public interface SetFactory {
Drink getDrink();
Dessert getDessert();
}
具体工厂如下:
/**
* 套餐A的工厂
* 套餐A提供西瓜汁 + 泡芙
*/
public class ASetFactory implements SetFactory {
@Override
public Drink getDrink() {
return new WatermelonDrink();
}
@Override
public Dessert getDessert() {
return new Puff();
}
}
/**
* 套餐B的工厂
* 套餐B提供葡萄汁 + 慕斯蛋糕
*/
public class BSetFactory implements SetFactory {
@Override
public Drink getDrink() {
return new GrapeDrink();
}
@Override
public Dessert getDessert() {
return new MousseCake();
}
}
/**
* 套餐C的工厂
* 套餐C提供苹果汁 + 提拉米苏蛋糕
*/
public class CSetFactory implements SetFactory {
@Override
public Drink getDrink() {
return new AppleDrink();
}
@Override
public Dessert getDessert() {
return new Tiramisu();
}
}
客户端如下:
public class Client {
public static void main(String[] args) {
// 顾客想要一份A套餐
System.out.println("------A套餐-------");
SetFactory aSetFactory = new ASetFactory();
Drink aDrink = aSetFactory.getDrink();
Dessert aDessert = aSetFactory.getDessert();
aDrink.desc();
aDessert.desc();
// 顾客想要一份B套餐
System.out.println("------B套餐-------");
SetFactory bSetFactory = new BSetFactory();
Drink bDrink = bSetFactory.getDrink();
Dessert bDessert = bSetFactory.getDessert();
bDrink.desc();
bDessert.desc();
}
}
在这里我们看到,对于抽象工厂是无需指定具体的果汁和甜点,但是这些果汁、甜点却是相互依赖的,这也是抽象工厂的意图:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
3、抽象工厂的优点
将一系列的产品统一到一起创建。
4、抽象工厂的缺点
(1)、规定了所有可能被创建的产品集合,当产品集合中需要扩展新的产品时,需要修改抽象工厂的接口,违反了开闭原则,比如上面的例子,当我们需要在套餐中加入一份小吃时,就需要修改抽象工厂和所有的具体工厂,改动很大。
(2)、增加了系统的抽象性和理解难度。
5、适用场景
强调一系列相关的产品组成一个产品集合一起使用时,此时使用抽象工厂模式。
五:工厂模式总结
我们结合上面饮品店的例子,来总结一下简单工厂、工厂方法、抽象工厂这三种工厂有什么不同吧,方便大家更好地记忆。
简单工厂,就是所有的产品都在这个工厂中生产,用户需要告诉工厂他需要哪个产品,工厂才能给他对应的产品。
简单工厂
工厂方法,就是每种产品都有自己的工厂,用户想要哪个产品,直接去对应的工厂取即可。
工厂方法
抽象工厂,相当于对所有的产品进行分类(分类的维度根据业务场景来决定),同一种类的产品在同一工厂生产,用户想要哪一种类的产品,直接去对应的工厂取即可。
标注:图中绿色方框表示工厂,橘色方框表示产品。