前言
我们在开发过程中,总是要编写许许多多的类,如果直接new出来的话,new操作分布在代码的不同地方,管理起来很麻烦,后期这个类更名了、移除了、功能增删了,你就得一个个地方去修改这些new对象的地方才能再次让你的代码正常运行(烦死你)(这就是一种耦合度高的体现)。
工厂模式就是为了解决这种问题而出现的,它的特点就是复用性强、可扩展、好维护。它提供一个类,我们叫它“工厂”,它把这些创建对象的代码放到工厂里统一管理。既减少了重复代码,也方便以后的维护和修改。如果后期这某个类改名、换掉了,只需要在工厂里修改、换掉即可,而不需要一个个文件去修改,这样就有效的降低了模块之间的耦合。
工厂模式有3种,分别是“简单工厂模式、工厂方法模式、抽象工厂模式”,接下来我们一个个的讲解。
简单工厂模式
模式描述:
提供一个工厂类,在工厂类中做判断,根据传入的类型创造相应的产品。当增加新的产品时,就需要修改工厂类。比如说有个工厂能生产两种车,奔驰和吉普,当客户要买车的时候,就要告诉工厂,要那种类型的车。怎么生产由工厂完成,用户不需要知道其中细节。
角色组成:
产品接口: 定义了产品的共用资源例如方法,提供给子类继承使用。
产品类: 继承产品接口,实现接口的方法,从而产生各种各类的产品。
工厂类: 定义了创建产品的方法,负责创建产品对象。
实现方案
有一个汽车(Car)接口,包含一个方法start() 输出自己的信息,然后现在有两种车辆,奔驰、吉普。
示例:
创建一个产品接口:汽车接口
public interface Car {
// 开车
void start();
}
创建两个产品类,奔驰车类和吉普车类
奔驰车类
public class BenzCar implements Car {
@Override
public void start() {
System.out.println("启动了奔驰车!");
}
}
吉普车类
public class JeepCar implements Car {
@Override
public void start() {
System.out.println("启动了吉普车!");
}
}
创建一个工厂类:汽车工厂
public class CarFactory {
public static final String JEEP = "jeep"; // 吉普
public static final String BENZ = "benz"; // 奔驰
public static Car createCar(String type){
// 吉普车
if("jeep".equals(type)){
return new JeepCar();
}
// 奔驰车
else if("benz".equals(type)){
return new BenzCar();
}else {
System.out.println("爱上一匹野马,可我的家里没有草原,你走吧!");
return null;
}
}
}
测试案例:
public class test {
public static void main(String[] args) {
// 如果不使用简单工厂模式,我们这样使用,每种车辆直接用new生成。
// JeepCar jeepCar = new JeepCar();
// jeepCar.start();
// BenzCar benzCar = new BenzCar();
// benzCar.start();
// 如果使用简单工厂模式呢?
Car car = CarFactory.createCar(CarFactory.JEEP);
car.start();
car = CarFactory.createCar(CarFactory.BENZ);
car.start();
}
}
从客户端的代码上可以看到,我们不用到处写new了,所有的创建都由工厂完成。如果后期这某个类改名、换掉了,去工厂里改就行了,不应到处改,增加产品时,只需要在简单工厂类上增加一个分支判断就行了。
所以我们首先应该依赖的是一个抽象的接口或父类,而不是依赖具体的子类,这也是依赖倒转原则强调的。
简单工厂优点:
实现了对象的创建和对象的使用分离,将对象的创建交给专门的工厂类负责。它可以根据传入的类型去判断该创建哪个类的的对象。不用直接创建具体类了,也不用管它们是怎么实现的。明确了各自的职责和权利,有利于整个软件体系结构的优化。
简单工厂缺点:
扩展性差,违反了开闭原则(因为所有的判断都写在工厂类里,每增加一种车辆,都要修改工厂类代码)。
应用场景:
1、在程序中,需要创建的对象很多,导致对象的new操作多且杂时,需要使用简单工厂模式(要求结构稳定,没有太多的改变时使用)。
2、客户端只知道传入工厂类的参数,对于如何创建对象不关心。
工厂方法模式
如果每次增加子类或者删除子类,那么都需要打开简单工厂类来进行修改,这不就很明显的会导致这个简单工厂类很庞大臃肿、耦合性高吗,而且也违反了开-闭原则,如果这是客户端代码还好说,但简单工厂类也算是一个功能类,所以不能随便就去修改它。
所以为了解决以上所提到的问题,就需要使用到工厂方法模式了,工厂方法模式是对简单工厂模式进一步的解耦,因为在工厂方法模式中是一个产品类对应一个工厂类(就如同奔驰车类有奔驰工厂类生产,吉普车类有吉普工厂类生产),而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为扩展和业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了,怎么增加都不会修改原来的代码。
相比于简单工厂模式,工厂方法模式的扩展性更好,拿上面的例子来说,无论怎么增加其它类型的车辆,都不会去修改原有的代码
模式描述:
定义了一个用来创建对象的接口,让它的子类决定实例化哪一个产品类,并且由这个实现类创建对应产品类的实例,让系统同时具备了弹性和扩展性。
角色组成:
产品接口: 定义了产品的共用资源例如方法,提供给子类继承使用,强制子类实现。
产 品 类: 继承产品接口,实现接口的方法,也可以覆盖接口的方法,从而产生各种各类的产品。
工厂接口: 定义了创建产品的方法,具体创建者必须继承该类,实现工厂方法。
工厂实现类: 继承工厂接口,实现工厂方法,负责创建产品对象。
实现方案
如图,我们提供了一个工厂接口,创建产品由他的子类完成,拿车子来说,就是每一种汽车都有专门的工厂生产,而工厂继承于工厂接口。
示例:
抽象产品接口:
public interface Car {
void start();
}
工厂接口,相比“简单工厂模式”,将核心的工厂类修改为抽象接口,这样它不再担任生产工作,将生产工作交给专门的工厂(子类)去做。
public interface CarFactory {
Car createCar();
}
奔驰车类
public class BenzCar implements Car {
@Override
public void start() {
System.out.println("启动了奔驰车!");
}
}
奔驰车工厂(每一种汽车都有专门的工厂来生产,只需要声明一个工厂类继承于汽车工厂接口)
public class BenzCarFactory implements CarFactory {
@Override
public Car createCar() {
return new BenzCar();
}
}
扩展性
如果这个时候还要添加一种新的车辆,只需添加对应的车辆和工厂类。例如我想添加个吉普车,我只需要声明一个“吉普车类”继承Car车辆接口,再声明一个“吉普车工厂类”继承于CarFactory车辆工厂接口,不用再去修改原有的类,如图:
示例:
吉普车类
public class JeepCar implements Car {
@Override
public void start() {
System.out.println("启动了吉普车!");
}
}
吉普车工厂
public class JeepCarFactory implements CarFactory {
@Override
public Car createCar() {
return new JeepCar();
}
}
不管以后怎么增加车辆,原来的代码都不用修改了,体现了“对扩展开放,对修改关闭”的设计原则。
也不用再依赖具体类,这就是我们要讲的依赖倒置原则:要依赖抽象,不要依赖具体类。
优点和缺点
优点
1、具有很强的的扩展性、弹性和可维护性。扩展时只要添加一个工厂类继承工厂接口,而无须修改原有的工厂代码,因此维护性也好。解决了简单工厂对修改开放的问题。
2、使用了依赖倒置原则,依赖抽象而不是具体,使用(客户)和实现(具体类)松耦合。
3、客户只需要知道所需产品的具体工厂,而无须知道具体工厂的创建产品的过程,甚至不需要知道具体产品的类名。
缺点
1、一个具体产品对应一个类,当具体产品过多时会使系统类的数目过多,增加系统复杂度。
2、每增加一个产品时,都需要一个产品类和一个产品工厂类,使得类的个数成倍增加,导致系统类数目过多,复杂性增加。
使用场合
1、在设计的初期,就考虑到产品在后期会进行扩展的情况下,可以使用工厂方法模式。
2、产品结构较复杂的情况下,可以使用工厂方法模式。
从以上的编写的代码可以看到,工厂方法模式看起来要比起简单工厂要麻烦不少,一个产品类就要对应一个工厂类,要增加产品类时也要相应地增加工厂类,客户端的代码也增加了不少,这也是很多初学者不明白的地方。的确,简单工厂是要简便些,所以它不适合应用在比较大的项目里,而且大部分情况下也是简单工厂要常用些。但是,你得先事先考虑好简单工厂是否无法承受你的项目,如果不能承受,就应该考虑使用工厂方法模式。
所以,有些设计模式只有在接触到大型项目时才能体会到它们好在哪里。以上的代码也比较粗糙和简单,但是我觉得对于讲解设计模式来说,不需要用太复杂的代码去演示它,不然会让初学者的注意力集中在代码上,而不是设计模式的思想上。所以说代码是死的而思想是活的,代码是用于承载设计者的思想的,我们应该在写代码的过程中去活用这些设计模式。但也不要啥玩意都去用设计模式去写,不然就会导致代码设计过度。
抽象工厂模式
虽然工厂方法模式解决了简单工厂模式关于扩展、维护的问题,但是问题还没完,你想想,汽车工厂,除了生产汽车,他是不是还要生产汽车相关的配件,例如发动机,以及轮胎等等。像是奔驰工厂如果要生产这些东西,不可能每一个配件都创建一个工厂吧。
拿上面的例子来说,由于工厂方法模式创建的对象都是继承于Car的,所以工厂方法模式中,每个工厂只能创建单一种类的产品,每当增加一种产品时,都要增加对应的工厂,如果要增加的产品多了,代码就会变得庞大而复杂。
例如奔驰工厂想要生产发动机,如果使用工厂方法模式,又得增加一个工厂。这样做,对象越多,就越难进行管理和维护,如果工厂数量过多,那么管理和维护的成本将大大增加。如果能在原有的工厂里面增加一条流水线生产发动机,这样就不会耗费那么大成本了。
抽象工厂就是为了解决这些问题的出现,他和工厂方法模式的区别在于:抽象工厂是可以生产多个产品的,例如 BenzCarFactoty 奔驰工厂里可以生产 BenzTyre(轮胎) 以及 BenzEngine(发动机)两个产品,而这两个产品又是属于一个系列的,因为它们都是属于奔驰工厂的产品。而工厂方法模式则只能生产一个产品,例如之前的 BenzCarFactoty 里就只可以生产一个 BencCar 产品。
模式描述:
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类;具体的工厂负责实现具体的产品实例。
角色组成:
-
产品族接口:产品家族的父类,由此可以衍生很多子产品。
-
具体产品:继承抽象产品接口,由工厂方法直接创建。
-
工厂接口:客户端直接引用,由未实现的工厂方法组成,子类必须实现其工厂方法创建产品家族。
-
具体工厂:实现工厂接口,负责实现工厂方法,一个具体工厂可以创建一组产品。
如何理解抽象产品族、抽象产品和具体产品的区别如图:
实现方案:
示例:
抽象产品接口
/**
* 抽象产品接口: 汽车
*/
public interface Car {
void start();
}
/**
* 抽象产品接口: 轮胎
*/
public interface Tyre {
void show();
}
具体产品
/**
* 奔驰车
* @author Administrator
*/
public class BenzCar implements Car {
@Override
public void start() {
System.out.println("启动了奔驰车!");
}
}
/**
* 奔驰轮胎
* @author Administrator
*/
public class BenzTyre implements Tyre {
@Override
public void show() {
System.out.println("使用奔驰轮胎!");
}
}
抽象工厂接口
/**
* 抽象工厂接口(提供生产车辆和轮胎的接口)
* 至于生产什么车辆,什么轮胎的,就交给子类
* @author Administrator
*/
public interface CarFactory {
Car createCar(); // 车辆
Tyre createTyre(); // 轮胎
}
具体工厂
/**
* 专门生产奔驰车和其他配件
* @author Administrator
*/
public class BenzCarFactory implements CarFactory {
@Override
public Car createCar() {
return new BenzCar();
}
@Override
public Tyre createTyre() {
return new BenzTyre();
}
}
/**
* 专门生产吉普车和其他配件
* @author Administrator
*/
public class JeepCarFactory implements CarFactory {
@Override
public Car createCar() {
return new JeepCar();
}
@Override
public Tyre createTyre() {
return new JeepTyre();
}
}
测试案例:
public class test {
public static void main(String[] args) {
Car car = null;
Tyre tyre = null;
// 客户需要奔驰车时
BenzCarFactory benzCarFactory = new BenzCarFactory();
car = benzCarFactory.createCar();
tyre = benzCarFactory.createTyre();
car.start();
tyre.show();
// 客户需要吉普车时
JeepCarFactory jeepCarFactory = new JeepCarFactory();
car = jeepCarFactory.createCar();
tyre = jeepCarFactory.createTyre();
car.start();
tyre.show();
}
}
优点:
1、一个具体工厂可以创建多个产品,与工厂方法模式相比,可以少产生具体工厂的类数量。
2、允许客户使用抽象的接口创建一组相关产品,而不需要知道(或者关心)产出的具体产品是什么,这样客户就可以从具体的产品中解耦出来。
3、易于交换产品系列,只要更换具体工厂,就可以改变这个产品系列。(从奔驰到吉普,我换一个工厂类就行了)
缺点:
1、抽象工厂是使用组合的方式把工厂方法集合到一个类中,当新增一个产品家族成员时就要修改抽象工厂类及其下面的具体工厂类,所以它的扩展性比较差。
2、产品族的扩展将是一件十分费力的事情,假如产品族中需要增加一个新的产品,则几乎所有的工厂类都需要进行修改。所以使用抽象工厂模式时,对产品等级结构的划分是非常重要的。
适用场景:
抽象工厂方法适用于产品种类结构多的场合,主要用于创建一组相关的产品,为它们提供创建的接口,就是当具有多个抽象角色时,抽象工厂便可以派上用场。
工厂模式总结
无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了。而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式。
所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了。