在了解工厂方法模式前,我们先了解一下什么是工厂模式。
定义:工厂模式,是一种创建型设计模式,它提供了在超类中创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类中。
我们把被创建的(目标)对象称为“产品”,把创建产品的对象称为“工厂”。
我们为什么需要工厂模式呢?归根结底就是为了解耦。通俗的说,工厂就是用来封装创建对象的代码,负责处理对象细节的类被称为工厂,工厂模式实现了创建者和调用者的分离。
我们在学习依赖倒置原则的时候其实就提到过简单工厂模式。按照实际业务场景划分,工厂模式有三种不同的实现方式,分别是:简单工厂模式、工厂方法模式和抽象工厂模式。不过,在 GoF 的《设计模式》一书中,它将简单工厂模式看作是工厂方法模式的一种特例,所以工厂模式只被分成了工厂方法和抽象工厂两类。工厂模式在实际项目中还是比较常用的。
要分清楚这三者的关系,首先我们先了解两个概念,什么是产品等级?什么是产品族?
产品等级:即有继承结构的同种类的产品,我们称之为产品等级。比如:苹果手机、小米手机。
产品族:一个具体工厂生产的不同等级的一组产品称为一个产品族。比如:小米手机、小米扫地机等。
如果分别用一句话来概括他们,那就是:
1)简单工厂:生产同一产品等级的任意指定的产品。(不支持增加新产品,增加新产品需要修改代码)
2)工厂方法:生产同一产品等级的固定工厂产品。(支持增加新产品)
3)抽象工厂:生产不同产品族的全部产品。(支持增加产品族,但不支持增加新产品)
既然简单工厂,是工厂方法的一种特例。那我们就来聊聊什么是简单工厂。
定义:简单工厂模式也叫静态工厂模式,就是工厂类(一般使用静态方法)通过接收的参数来区分并返回不同的对象实例。
我们先来看一个案例:比亚迪和特斯拉都是能在路上跑的车。
interface Car{
void run();
}
class Byd implements Car{
@Override
public void run() {
System.out.println("比亚迪在路上跑");
}
}
class Tsl implements Car{
@Override
public void run() {
System.out.println("特斯拉在路上跑");
}
}
这样写我们现在的UML图关系是:
在没有工厂的情况下,我们要让比亚迪或者特斯拉出来跑的话。我们会这样写:
public class CarUse {
public static void main(String[] args) {
Car c1 = new Byd();
Car c2 = new Tsl();
c1.run();
c2.run();
}
}
此时的UML关系图:
此时的调用者(CarUse类)和创建者Byd和Tsl都是有关联的,调用者什么都是亲力亲为。
我们知道,工厂模式就是要分离调用者和创建者。现在我们创建一个简单工厂,由简单工厂类去统一管理创建者。
//写法一
public class CarFactory {
static final int CAR_BYD = 0;
static final int CAR_TSL = 1;
public static Car createCar(int type){
switch (type) {
case 0:
return new Byd();
case 1:
return new Tsl();
default:
System.out.println("无法识别的品牌");
return null;
}
}
}
//写法二
public class CarFactory {
private static final Map createCar = new HashMap();
static{
createCar.put("byd", new Byd());
createCar.put("tsl", new Tsl());
}
public static Car createCar(String type){
if(type==null){
return null;
}
return createCar.get(type);
}
}
此次的UML关系图:
虽然我们多写了一个CarFactory工厂类,整体上的代码变多了一点点。但是我们的调用者不必依赖我们的创建者,仅仅需要告诉工厂你要什么,无需知道工厂去哪里帮你实现你的需求,这样调用者就省去了很多的事情。
后来生意做红火了,来了个有钱的客户,这次要买玛莎拉蒂,也就是新产品了。我们的工厂就不适用了,我们的工厂需要找到玛莎拉蒂的实体,然后在我们的工厂上加入新的品牌,不修改代码是无法扩展了。
简单工厂的弊端:每增加一个产品就要增加一个具体产品类和修改工厂类,这增加了系统的复杂度,违背了“开闭原则”。
虽然简单工厂模式解决了调用者和创建者之间的耦合,但是工厂和创建者之间依然存在着耦合。这样的设计违背了我们的“开闭原则”,未来新产品的扩展并不灵活。而“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码(比如工厂类)的情况下引进新的产品,即满足开闭原则。我们的工厂方法模式就是做这个的。
//声明一个抽象工厂
interface CarFactory {
Car createCar();
}
//比亚迪工厂
public class BydFactory implements CarFactory{
@Override
public Car createCar() {
return new Byd();
}
}
//特斯拉工厂
public class TslFactory implements CarFactory{
@Override
public Car createCar() {
return new Tsl();
}
}
//调用者
public class CarUse {
public static void main(String[] args) {
Car c1 = new BydFactory().createCar();
Car c2 = new TslFactory().createCar();
c1.run();
c2.run();
}
}
//工厂方法模式和简单工厂模式最大的不同在于,简单工厂模式(对于一个项目或者一个独立的模块而言)只有一个工厂类,而工厂方法模式有一组实现了相同接口的工厂类。调用者不是什么都找一个工厂,而是需要什么就找什么工厂提供,这样可选择的范围更大。
此时的UML关系图:
现在,我们的新客户要玛莎拉蒂,就可以直接找我们的玛莎拉蒂工厂。我们只需要有新建的玛莎拉蒂工厂即可满足客户的要求。
//新建玛莎拉蒂工厂
public class MsldFactory implements CarFactory{
@Override
public Car createCar() {
return new Msld();
}
}
class Msld implements Car{
@Override
public void run(){
System.out.println("玛莎拉蒂在路上跑");
}
}
我们的客户,只需要找到这家玛莎拉蒂工厂即可。
public class CarUse {
public static void main(String[] args) {
Car c1 = new BydFactory().createCar();
Car c2 = new TslFactory().createCar();
c1.run();
c2.run();
Car c3 = new MsldFactory().createCar();
c3.run();
}
}
我们增加新的产品,不需要去修改我们的工厂类,工厂方法模式不违背开闭原则,扩展性非常良好。
优点
1)用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
2)对于新产品的创建,只需多写一个相应的工厂类,不会影响已有的代码,后期维护容易,增强系统的扩展性。
缺点
类的个数容易过多,增加系统的复杂度和理解难度。
注:从结构的复杂度、代码的复杂度以及客户端调用者的编程
简单工厂模式和工厂方法模式的比较:
比较维度 | 简单工厂模式 | 工厂方法模式 | 说明 |
---|---|---|---|
结构、代码复杂度 | 优 | 良 | 简单工厂模式占优,只需要一个工厂类,而工厂方法模式会随着工厂的数量增加而增加 |
调用者使用难度 | 优 | 良 | 简单工厂模式的工厂类是静态类,调用者无需实例化 |
扩展性 | 良 | 优 | 工厂方法模式满足OCP原则,具有非常良好的扩展性。简单工厂的扩展性稍弱,但是只要改动不大产品不是特别复杂,简单工厂模式无疑是更优的选择。 |
注:事实上我们有时并不需要像一些框架一样做到那么灵活,反而一般都用简单工厂模式,这样代码简洁逻辑也清晰,可以使用
应用场景:
1)对于产品种类相对较少的情况,考虑使用简单工厂模式;
2)当您想为您的库或框架的用户提供一种扩展其内部组件的方法时,请使用工厂方法模式。
DJK中工厂模式的使用:
1)java.util.Calendar中的getInstance()
方法使用了工厂模式。
我们之前的工厂方法中,针对的是一种产品等级——汽车。我们都知道,现实生活中的场景比我们想象的要更复杂?为了产品细分和精准的客户定位,比亚迪工厂要给汽车装配不同的配饰,来做产品细分。比如:在高配版的比亚迪中装配高级座椅、高级轮胎和高级发动机;在低配版本的比亚迪中装配低级座椅、低级轮胎和低级发动机;这就形成了产品族的概念,装配有座椅、轮胎和发动机,并不是一种产品了。
针对不同的产品族,如果还想沿用继承思想的话,那会使得整体的结构变得非常复杂。且工厂个数非常庞大。
这种玩法是不是有点熟悉,我们在讲合成复用原则的时候就遇到过这种问题。这里的抽象工厂模式,就是用来解决这个问题的。
定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
简单的说:就是用来生产不同产品族的全部产品的,抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。
优点
抽象工厂模式是“工厂的工厂”,可以轻松扩展以容纳更多产品。
缺点
代码会变得比应有的更复杂,因为随着模式引入了许多新的接口和类。
首先分析问题,产品族不是同一类型的产品我们使用工厂方法模式的继承思想并不能很好的解决问题。前面我们也学到了合成复用原则。对于产品族而言,更适合使用组合去实现对工厂的进一步抽象。
//----首先写我们的产品族,轮胎、座椅、发动机
interface Tyre{//轮胎接口
void rotate(); //抓地摩擦力
}
class HighTyre implements Tyre{//高端轮胎
@Override
public void rotate() {
System.out.println("高端轮胎抓地摩擦力好");
}
}
class LowTyre implements Tyre{//低端轮胎
@Override
public void rotate() {
System.out.println("低端轮胎抓地摩擦力一般");
}
}
interface Seat{//座椅接口
void material(); //原材料
}
class HighSeat implements Seat{//高端座椅
@Override
public void material() {
System.out.println("高端座椅真皮柔软");
}
}
class LowSeat implements Seat{//低端座椅
@Override
public void material() {
System.out.println("低端座椅假皮不柔软");
}
}
interface Engine{//发动机
void speed(); //转速
}
class HighEngine implements Engine{//高端发动机
@Override
public void speed() {
System.out.println("高端发动机转速快");
}
}
class LowEngine implements Engine{//低端座椅
@Override
public void speed() {
System.out.println("低端发动机转速一般");
}
}
//----在构造组合这些产品的工厂
interface BydFactory{ //比亚迪工厂
Tyre addTyre(); //装配轮胎
Seat addSeat(); //装配座椅
Engine addEngine(); //装配发动机
}
//高端比亚迪工厂
class HighBydFactory implements BydFactory{
@Override
public Engine addEngine() {
return new HighEngine();
}
@Override
public Seat addSeat() {
return new HighSeat();
}
@Override
public Tyre addTyre() {
return new HighTyre();
}
}
//低端比亚迪工厂
class LowBydFactory implements BydFactory{
@Override
public Engine addEngine() {
return new LowEngine();
}
@Override
public Seat addSeat() {
return new LowSeat();
}
@Override
public Tyre addTyre() {
return new LowTyre();
}
}
测试
//--测试
public class BydAbstractFactoryDemo {
public static void main(String[] args) {
System.out.println("高端比亚迪工厂----------------");
BydFactory high = new HighBydFactory();
Engine e = high.addEngine();
e.speed();
Seat s = high.addSeat();
s.material();
Tyre t = high.addTyre();
t.rotate();
System.out.println("低端比亚迪工厂----------------");
BydFactory low = new LowBydFactory();
low.addEngine().speed();
low.addSeat().material();
low.addTyre().rotate();
}
}
案例效果
此时的UML关系图:
抽象工厂模式,适合比较复杂的情形,一般我们用的比较少。
应用场景:
抽象工厂模式是“工厂的工厂”,抽象工厂设计模式为接口而不是实现提供了代码方法,抽象工厂模式是健壮的,避免了简单工厂模式的条件逻辑;
当您的代码需要与各种相关产品系列一起使用时,请使用抽象工厂;
DJK中抽象工厂模式的使用:
javax.xml.parsers.DocumentBuilderFactory#newInstance()
javax.xml.transform.TransformerFactory#newInstance()
javax.xml.xpath.XPathFactory#newInstance()