跟我学设计模式之工厂模式

工厂模式应用很是之广,在JDK底层源码以及各大主流框架中随处可见,通常以Factory结尾命名的类,好比Mybatis中的SqlSessionFactorySpring中的BeanFactory等,都是工厂模式的典型表明。java

1、简单工厂模式

1.1 概念

简单工厂模式又称为静态工厂模式,属于设计模式中的建立型模式。简单工厂模式经过对外提供一个静态方法来统一为类建立实例,目的是实现类与类之间解耦:客户端不须要知道这个对象是如何被穿建立出来的,只须要调用简单工厂模式的方法来统一建立就能够了,从而明确了各个类的职责。git

1.2 示例

简单工厂模式,以生产汽车轮胎为例。github

1.2.1 实体类

  • 轮胎通用属性
public class Tire {
    /**
     * 通用属性
     */
    private String common;
}
  • 奔驰车轮胎
包含通用属性外还有本身的特有属性
public class TireForBenz extends Tire{


    Tire tire;
    /**
     * 特有属性
     */
    private String benz;

    public TireForBenz() {
        this.benz = "获得 Benz 轮胎";
    }


    @Override
    public String toString() {
        return "["+this.benz +"]";
    }
}
  • 宝马车轮胎
包含通用属性外还有本身的特有属性
public class TireForBwm extends Tire{

    Tire tire;

    /**
     * 特有属性
     */
    private String bwm;

    public TireForBwm() {
        this.bwm = "获得 Bwm 轮胎";
    }

    @Override
    public String toString() {
        return "["+this.bwm +"]";
    }
}

1.2.2 生产工艺

  • 生产轮胎的抽象方法,各个产线有本身的方式生产
public interface TireFactory {

    Tire produceTire();
}
  • 奔驰汽车轮胎产线
重写生产轮胎的方法返回奔驰型轮胎。
public class BenzTireFactory implements TireFactory {

    /**
     * 生产奔驰轮胎
     */
    @Override
    public Tire produceTire() {
        System.out.println("奔驰轮胎生产中。。。");
        return new TireForBenz();
    }
}
  • 宝马汽车轮胎产线
重写生产轮胎的方法返回宝马型轮胎。
public class BwmTireFactory implements TireFactory {

    /**
     * 生产宝马轮胎
     */
    @Override
    public TireForBwm produceTire() {
        System.out.println("宝马轮胎生产中。。。");
        return new TireForBwm();
    }
}

1.2.3 轮胎工厂类

经过传入的品牌名称调用相应产线生产相应品牌的轮胎
public class SimpleFactoryMode {

    public static TireFactory produceCar(String name) {
        if ("BenzTireFactory".equals(name)) {
            return new BenzTireFactory();
        }
        if ("BwmTireFactory".equals(name)) {
            return new BwmTireFactory();
        }
        return null;
    }
}

1.2.4 测试

客户端经过工厂类获取实例对象。
  • 测试方法
@Test
public void simpleFactoryModeTest() {
    // 造奔驰轮胎
    TireFactory benz = SimpleFactoryMode.produceCar("BenzTireFactory");
    if (null != benz) {
        benz.produceTire();
    }else {
        System.out.println("工厂暂时没法生产奔驰轮胎");
    }
    // 造宝马轮胎
    TireFactory bwm = SimpleFactoryMode.produceCar("BwmTireFactory");
    if (null != bwm) {
        bwm.produceTire();
    }else {
        System.out.println("工厂暂时没法生产宝马轮胎");
    }
    // 造本田汽轮胎(工厂无该方法)
    TireFactory honda = SimpleFactoryMode.produceCar("Honda");
    if (null != honda) {
        honda.produceTire();
    }else {
        System.out.println("工厂暂时没法生产本田轮胎");
    }
}
  • 结果
奔驰轮胎生产中。。。
宝马轮胎生产中。。。
工厂暂时没法生产本田轮胎

该方式确实能完成不一样品牌的轮胎生产,可是,有个问题:方法参数是字符串,可控性有待提高。设计模式

1.3 简单工厂模式优化

不要经过传入的字符串来判断须要建立对象,而是客户端想要建立什么对象,只须要传入具体的实现类就能够了,而后经过 Java的反射来建立对象。
public static TireFactory produceCar(Class<? extends TireFactory> clazz) {
    try {
        // 经过Java的反射来建立对象
        return clazz.newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return null;
}

每次建立对象都是经过反射来建立的,因此在性能上是有必定的损耗。框架

  • 测试
public void simpleFactoryModeUpgradeTest() {
    // 造奔驰轮胎
    TireFactory benzTire = SimpleFactoryMode.produceCar(BenzTireFactory.class);
    TireForBenz benz = (TireForBenz) benzTire.produceTire();
    System.out.println(benz.toString());
    // 造宝马轮胎
    TireFactory bwmTire = SimpleFactoryMode.produceCar(BwmTireFactory.class);
    TireForBwm bwm = (TireForBwm) bwmTire.produceTire();
    System.out.println(bwm.toString());
}
  • 结果
奔驰轮胎生产中。。。
[获得 Benz 轮胎]
宝马轮胎生产中。。。
[获得 Bwm 轮胎]

1.4 小结

简单工厂模式确实在必定程度上实现代码的解耦,而这种解耦的特色在于,这种模式将对象的建立和使用分离。这种模式的本质在于经过一个传入的参数,作if...else判断,来达到返回不一样类型对象的目的。缺点也很明显,不符合开闭原则(好比新增一个保时捷轮胎的生产,除了须要增长实体和生产方法,还须要修改工厂类SimpleFactoryMode.java)。所以,若是须要增长新的类型,就不得不去修改原来的代码,违反开闭原则。ide

  • 简单工厂模式优势:
  1. 简单优化了软件体系结构,明确了各自功能模块的职责和权利;
  2. 经过工厂类,外界不须要直接建立具体产品对象,只须要负责消费,不须要关心内部如何建立对象。
  • 简单工厂模式缺点:
  1. 改进前的简单工厂模式所有建立逻辑都集中在一个工厂类中,能建立的类只能是考虑到的,若是须要添加新的类,就必须改变工厂类了;
  2. 改进前的简单工厂模式随着具体产品的不断增多,可能会出现共产类根据不一样条件建立不一样实例的需求,这种对条件的判断和对具体产品类型的判断交错在一块儿,很难避免功能模块的蔓延,对系统的维护和扩展不利;
  3. 改进后的简单工厂模式主要是使用反射效率会低一些。

2、工厂方法模式

  • 简单工厂模式之因此违反开闭原则,关键在于什么?

那就是它把全部对象的建立都集中在同一个工厂类里面了,所以,当新增一个新对象时,必然会须要修改这个共享工厂类,违反开闭原则天然不可避免。post

  • 解决方案

既然问题关键在于,全部对象的建立都跟这个惟一的工厂类耦合了,那我每一个对象各自都配置一个单独的工厂类,这个工厂类只建立各自类型的对象,那这样不就解决耦合的问题了吗?性能

2.1 概念

工厂方法模式是指定义一个建立对象的接口,但让实现这个接口的类来决定实例化哪一个类。工厂方法让类的实例化推迟到子类中进行。在工厂方法模式中用户只须要关心所需产品对应的工厂,无须关心建立细节,并且加入新的产品符合开闭原则。测试

2.2 示例

工厂方法模式,以生产发动机为例。优化

2.2.1 实体

  • 发动机的通用属性
public class Engine {

    /**
     * 型号
     */
    private String common;

}
  • 奔驰发动机
包含通用属性外还有本身的特有属性
public class EngineForBenz extends Engine{

    Engine engine;
    /**
     * 特有属性
     */
    private String benz;

    public EngineForBenz() {
        this.benz = "获得 Benz 发动机";
    }

    @Override
    public String toString() {
        return "["+this.benz +"]";
    }
}
  • 宝马发动机
包含通用属性外还有本身的特有属性
public class EngineForBwm extends Engine{

    Engine engine;
    /**
     * 特有属性
     */
    private String bwm;

    public EngineForBwm() {
        this.bwm = "获得 Bwm 发动机";
    }

    @Override
    public String toString() {
        return "["+this.bwm +"]";
    }
}

2.2.2 生产工艺(发动机的工厂类)

  • 抽象工厂类,定义生产发动机的方法,各个产线本身的去实现
public interface EngineFactory<T> {

    Engine produceEngine();

}
  • 建立奔驰子工厂,实现其的工艺
public class BenzEngineFactory implements EngineFactory<EngineForBenz> {

    /**
     * 生产奔驰发动机
     */
    @Override
    public Engine produceEngine() {
        System.out.println("奔驰发动机生产中。。。");
        return new EngineForBenz();
    }
}
  • 建立宝马子工厂,实现其的工艺
public class BwmEngineFactory implements EngineFactory<EngineForBwm> {

    /**
     * 生产宝马发动机
     */
    @Override
    public Engine produceEngine() {
        System.out.println("宝马发动机生产中。。。");
        return new EngineForBwm();
    }
}

2.2.3 测试

@Test
public void factoryModeTest() {
    // 造奔驰发动机
    EngineFactory car = new BenzEngineFactory();
    EngineForBenz benz = (EngineForBenz) car.produceEngine();
    System.out.println(benz.toString());
    // 造宝马发动机
    EngineFactory carFactory = new BwmEngineFactory();
    EngineForBwm bwm = (EngineForBwm) carFactory.produceEngine();
    System.out.println(bwm.toString());
}
  • 结果
奔驰发动机生产中。。。
[获得 Benz 发动机]
宝马发动机生产中。。。
[获得 Bwm 发动机]

2.3 小结

工厂方法模式轻松解决了简单工厂模式的问题,符合开闭原则。在上面例子中,当须要新增一个保时捷汽车,此时只须要提供一个对应的EngineForBSJ.java实现produceEngine()方法便可,对于原先代码再不须要作任何修改。

可是每一个类型的对象都会有一个与之对应的工厂类。若是对象的类型很是多,意味着会须要建立不少的工厂实现类,形成类数量膨胀,对后续维护带来一些麻烦。

  • 缺点
  1. 客户端(应用层)不依赖于产品类实例如何被建立、实现等细节;
  2. 一个类经过其子类来指定建立哪一个对象。
  • 缺点
  1. 类的个数容易过多,增长复杂度;
  2. 增长了系统的抽象性和理解难度。

3、抽象工厂模式

抽象工厂模式出现,就是为了解决上述工厂方法模式存在的问题,能够当作是工厂方法模式的升级。

3.1 背景

  • 类数量膨胀的情景

工厂方法模式建立的对象其实归根到底都是同一类对象。以汽车生产为例,不管是轮胎仍是发动机,都是汽车生产的一部分,都是属于汽车生产的过程。以下图:

风尘博客

由上图咱们能够发现,虽然分为奔驰车和宝马车,可是从工厂方法角度,他们都属于汽车这一类别,这就致使了须要单独为每个零件指定各自的工厂类,从而致使了类数量膨胀的问题。

  • 解决方案

既然这样,咱们能够把每类汽车指定一个工厂,而后再让不一样产线去生产他须要的产品,以下图

风尘博客

这样当每一类物品组件数量特别多,能够把它称为产品族。抽象工厂模式就是为了建立一系列以产品族为单位的对象,这样在须要建立大量系列对象时能够大大提升开发效率,下降维护成本。

3.2 示例

由于奔驰轮胎/宝马轮胎/奔驰发动机/宝马发动机的实体在前面已经建立过,这里就直接用了。

3.2.1 汽车工厂类(顶层抽象工厂类)

该类已包含轮胎/发动机生产,具体实体键一/二中相关实体。
public interface CarFactory {

    /**
     * 准备生产
     */
    void init();

    /**
     * 生产轮胎
     * @return
     */
    Tire produceTire();

    /**
     * 生产发动机
     * @return
     */
    Engine produceEngine();
}

3.2.2 奔驰汽车产品族(奔驰汽车工厂类)

public class BenzCarFactory implements CarFactory{


    @Override
    public void init() {
        System.out.println("----------------------- 奔驰汽车准备生产 -----------------------");
    }

    @Override
    public Tire produceTire() {
        System.out.println("正在生产奔驰轮胎");
        return new TireForBenz();
    }

    @Override
    public Engine produceEngine() {
        System.out.println("正在生产奔驰发动机");
        return new EngineForBenz();
    }
}

3.2.2 宝马汽车产品族(宝马汽车工厂类)

public class BwmCarFactory implements CarFactory{


    @Override
    public void init() {
        System.out.println("----------------------- 宝马汽车准备生产 -----------------------");
    }

    @Override
    public Tire produceTire() {
        System.out.println("正在生产宝马轮胎");
        return new TireForBwm();
    }

    @Override
    public Engine produceEngine() {
        System.out.println("正在生产宝马发动机");
        return new EngineForBwm();
    }
}

3.2.3 测试

@Test
public void abstractFactoryModeTest() {
    // 生产奔驰整车的零部件
    CarFactory benz = new BenzCarFactory();
    benz.init();
    TireForBenz benzTire = (TireForBenz) benz.produceTire();
    System.out.println(benzTire.toString());

    EngineForBenz benzEngine = (EngineForBenz) benz.produceEngine();
    System.out.println(benzEngine.toString());

    // 生成宝马整车的零部件d
    CarFactory bwm = new BwmCarFactory();
    bwm.init();
    TireForBwm bwmTire = (TireForBwm) bwm.produceTire();
    System.out.println(bwmTire.toString());

    EngineForBwm bwmEngine = (EngineForBwm) bwm.produceEngine();
    System.out.println(bwmEngine.toString());
}
  • 结果
----------------------- 奔驰汽车准备生产 -----------------------
正在生产奔驰轮胎
[获得 Benz 轮胎]
正在生产奔驰发动机
[获得 Benz 发动机]
----------------------- 宝马汽车准备生产 -----------------------
正在生产宝马轮胎
[获得 Bwm 轮胎]
正在生产宝马发动机
[获得 Bwm 发动机]

3.3 思考

既然说抽象工厂模式是工厂方法模式的升级,那到底升级了啥?

实际上是由原来的单一产品的生产升级成为了系列产品的生产。设想一下,假设上面汽车的例子中,每一品牌汽车中就只生产一种部件,好比就只生产发动机,不生产轮胎等其余组件了,以下图

发现了什么没有?抽象工厂模式竟然转变为咱们以前讲过的工厂方法模式了!换句话说,当你的产品族中只生产一种产品的时候,你的抽象工厂模式其实已经退化为工厂方法模式了。反过来讲,当生产多种产品时,工厂方法模式就进化为抽象工厂模式。

3.4 小结

抽象工厂模式在建立大量系列对象时能够大大提升开发效率,就是为生产产品族而生的,而对于生产单一产品却无能为力。

若是须要添加一个新的产品族,那就简单了,好比新增一个保时捷汽车,那就只须要添加一个保时捷汽车的工厂实现类就行了,并不会对原有的代码形成任何影响。

可是,若是假设在汽车中,我须要再加一个组件,好比倒车影像,怎么操做?你须要在CarFactory接口中添加返回倒车影像对象的接口。这一加不得了了......全部品牌汽车实现类所有须要修改并追加该方法的实现,违反了开闭原则

  • 抽象工厂模式优势:

建立大量系列对象时能够大大提升开发效率,下降维护成本。

  • 抽象工厂模式缺点:
  1. 规定了全部可能被建立的产品集合,产品族中扩展新的产品困难,须要修改抽象工厂的接口;
  2. 增长了系统的抽象性和理解难度。

4、总结

4.1 如何选择

工厂模式的三种形式都介绍完了,那咱们实际开发中该如何去选择呢?
  1. 从设计原则来讲,简单工厂模式不符合开闭原则。可是很神奇,在实际场景中,简单工厂模式确实用的最多的。
  2. 工厂方法模式是专门用于解决单个对象建立工做,自己模式没问题,也符合开闭原则。可是存在工厂类数量膨胀的问题。若是须要建立的工厂类不是不少,是一种不错的选择。
  3. 抽象工厂模式天生就是为生产产品族而生的。因此若是你须要建立的对象很是之多,可是对象之间存在明显产品族特征,那么这个时候用抽象工厂模式很是合适。

4.2 示例源码

Github 示例代码

4.3 技术交流

  1. 风尘博客:https://www.dustyblog.cn
  2. 风尘博客-掘金
  3. 风尘博客-博客园
  4. Github
  5. 公众号

风尘博客
参考文章