设计模式学习笔记之工厂模式

工厂模式是实际工作中用得比较多的设计模式,本笔记把简单工厂模式、工厂模式和抽象工厂模式放在一起,主要因为它们关系有点类似孙子、父亲和爷爷,简单工厂是工厂模式的一种特例,工厂模式又是抽象工厂的一种特例,这种关系很有意思,感觉就是我能变成你,你亦能变成我。

简单工厂模式

简单工厂并不算是23种设计模式的一种,但实际开发中,我们会常常用到这种工厂模式的迷你版,而且也有助于理解工厂模式。

都知道面向接口编程是Java编程的一个重要原则,那何谓接口?接口的重要思想便是:封装隔离。而简单工厂模式便是很好的体现了这个思想。

何谓简单工厂?其定义是:提供一个创建对象实例的功能,而无需关心其具体实现。简单来说,类似现实生活中的,我要买一个罐头,你把罐头给我就对了,罐头是一个抽象的概念,对应Java里的接口,而拿到的沙丁鱼罐头就是对应的具体实现。

简单工厂的本质:选择实现

提供这么一个编程场景:通过简单工厂得到接口的具体实现,至于是哪个子类实现的,客户端无需关心。

就以罐头为例,来看代码:

package com.dgd.factory.demo1;

/**
 * @Author DGD
 * @date 2017/10/31.
 * 罐头接口
 */
public interface Can {
    void show();
}

作为购买者,我不需要知道罐头怎么生产的,我说要罐头,就有一个具体的沙丁鱼罐头了,那罐头怎么来的,就是在简单工厂里生产出来的,所以需要一个生产罐头的工厂类。

package com.dgd.factory.demo1;

/**
 * @Author DGD
 * @date 2017/10/31.
 */
public class CanFactory {
    public static Can createCan() {
        //默认只提供一种实现
        Can can = null;
        try {
            can = (Can) Class.forName("com.dgd.factory.demo1.CannedSardines").newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return can;
    }
}

可以看到,工厂里创建罐头实例是通过反射的机制来创建的,需要定义具体的罐头类,如下:

package com.dgd.factory.demo1;

/**
 * @Author DGD
 * @date 2017/10/31.
 */
public class CannedSardines implements Can {
    @Override
    public void show() {
        System.out.println("我是沙丁鱼罐头");
    }
}

现在来看看客户端是怎么调用的。

package com.dgd.factory.demo1;

/**
 * @Author DGD
 * @date 2017/10/31.
 * 测试罐头工厂
 */
public class CanClient {
    public static void main(String[] args) {
        Can can = CanFactory.createCan();
        can.show();
    }
}

运行代码,控制台就会打印出“我是沙丁鱼罐头”的字样,看客户端也能感受到简单工厂的实用性,我想要罐头,只要调用的工厂类的生产罐头方法就行了CanFactory.createCan(),使用是不是很方便?

那如果我还要水果罐头呢?因为上面的工厂方法只能生产一种罐头,现在需要根据客户需要再生产水果罐头,那么我们需要一个标识来告诉工厂,如果我要水果罐头,你就能给我生产水果罐头,下面我们来模拟一下,首先在罐头接口里添加水果罐头的类型定义:

package com.dgd.factory.demo1;

/**
 * @Author DGD
 * @date 2017/10/31.
 * 罐头接口
 */
public interface Can {
    //添加了水果罐头的标识
    Integer CANNED_FRUITS=1;

    void show();
}

水果罐头的实现:

package com.dgd.factory.demo1;

/**
 * @Author DGD
 * @date 2017/10/31.
 */
public class CannedFruits implements Can {
    @Override
    public void show() {
        System.out.println("我是水果罐头");
    }
}

简单工厂类里添加选择提供罐头的方法:

public static Can createCan(int type) {
        //根据罐头类型来提供相应的罐头:如果标识是1,生产水果罐头,其它情况生产沙丁鱼罐头
        Can can = null;
        if (Can.CANNED_FRUITS == type) {
            can = new CannedFruits();
        } else {
            can = new CannedSardines();
        }
        return can;
    }

客户端调用:

package com.dgd.factory.demo1;

/**
 * @Author DGD
 * @date 2017/10/31.
 * 测试罐头工厂
 */
public class CanClient {
    public static void main(String[] args) {
        //默认的罐头实现
        Can can = CanFactory.createCan();
        can.show();

        //选择性的生产水果罐头
        Can can1 = CanFactory.createCan(Can.CANNED_FRUITS);
        can1.show();
    }
}

通过以上代码可以感受到简单工厂为我们创建实例提供的方便,其优点也是很明显的,帮助封装和解耦,实际上,简单工厂常常配合单例模式,即私有化简单工厂的构造方法,因为工厂类我们只需要一个实例即可。简单工厂的一个缺点就是:可能会增加客户端的复杂度,如果通过参数来选择具体实例的话,客户端需要理解各个参数的含义,这样也会暴露部分内部实现。

以上具体代码可以参考Github

选择实现是简单工厂的本质,下面我们来继续工厂模式,按照我个人理解,它就是简单工厂的爸爸。

工厂模式

工厂模式的定义:定义一个创建对象的接口,让子类决定实例化哪一个类,并用工厂方法将类的实例化延迟到其子类

定义读起来有些拗口,简单来说,就是工厂模式定义了一个工厂接口,工厂方法生产出来的产品还是抽象的,产品对象的实例化工作被延迟到子工厂里去了。

即工厂模式的本质:延迟实现到子类

还是利用现实问题来讲吧,提供这么一个场景:想要实现一个文件导出功能,由客户来选择导出的方式,比如说:直接导出文本文件,或者是导出到数据库中,那么如何用工厂模式来解决呢?

由于客户不需要知道文件是如何导出的,你只要告诉他调用这个方法就能导出文件就可以了,所以我们需要先把导出文件的工厂类定义好,如下:

package com.dgd.factory.demo2;

/**
 * @Author DGD
 * @date 2017/10/26.
 * 该类定义成抽象类,因为接口不能有方法的实现
 */
public abstract class ExportFileAbstractFactory {
    public boolean export(String data){
        return createExportFileApi().export(data);
    }

    //这个时候并不知道文件导出功能的具体实现
    protected abstract ExportFileApi createExportFileApi();
}

导出文件的工厂需要知道导出文件功能的接口,因为工厂就是生产产品的,导出文件功能可以理解成该工厂要生产的一个产品,定义如下:

package com.dgd.factory.demo2;

/**
 * @Author DGD
 * @date 2017/10/26.
 */
public interface ExportFileApi {
    /**
     * 导出内容成为文件
     * @param data 文件内容
     * @return 是否导出成功
     */
    boolean export(String data);
}

工厂类被定义成抽象类,是因为我们不确定产品的具体实现方式,所以我们需要通过子工厂来体现出来,这里分别是导出文本文件的工厂和导出数据到数据库的工厂,或者理解成,上面定义的抽象工厂类是一个大工厂,负责具体功能产出的是旗下的子工厂。

将数据导出成文件文件的工厂:

package com.dgd.factory.demo2;

/**
 * @Author DGD
 * @date 2017/10/26.
 * 将数据导出成文本文件
 */
public class ExportTextFileFactory extends ExportFileAbstractFactory {
    @Override
    protected ExportFileApi createExportFileApi() {
        return new ExportTextFile();
    }
}

将数据导出到数据库中的工厂:

package com.dgd.factory.demo2;

/**
 * @Author DGD
 * @date 2017/10/26.
 * 将数据导出到数据库中
 */
public class ExportDbFileFactory extends ExportFileAbstractFactory {
    @Override
    protected ExportFileApi createExportFileApi() {
        return new ExportDbFile();
    }
}

可以看到,文件导出功能的执行者都是在子工厂里面创建的,它们的定义如下:

package com.dgd.factory.demo2;

/**
 * @Author DGD
 * @date 2017/10/26.
 */
public class ExportTextFile implements ExportFileApi {
    @Override
    public boolean export(String data) {
        System.out.println("导出数据"+data+"到文本文件");
        return true;
    }
}
package com.dgd.factory.demo2;

/**
 * @Author DGD
 * @date 2017/10/26.
 */
public class ExportDbFile implements ExportFileApi {
    @Override
    public boolean export(String data) {
        System.out.println("导出数据"+data+"到数据库备份文件");
        return true;
    }
}

用户怎么使用导出文件的功能?他需要知道这些实现细节吗?不需要。他只要知道,如果数据要导出成文本文件,跟导出文本文件的子工厂通知一声就行,如果要将数据导出到数据库,那就跟对应的子工厂通知一声就可以了,代码体现如下:

package com.dgd.factory.demo2;

/**
 * @Author DGD
 * @date 2017/10/26.
 */
public class Client {
    public static void main(String[] args) {
        //将数据导出成文本文件
        ExportFileAbstractFactory factory = new ExportTextFileFactory();
        factory.export("123");

        //将数据导出到数据库中
        factory = new ExportDbFileFactory();
        factory.export("DB");
    }
}

运行代码,可以看到控制台分别打印出“导出数据123到文本文件“,“导出数据DB到数据库备份文件”。

其实工厂模式的核心思想就是,在不知道产品如何生产出来的时候,也要先给用户出来产品方案,对应代码里ExportFileAbstractFactory 中的这段:

    //这个时候并不知道对象的具体实现
    protected abstract ExportFileApi createExportFileApi();

也就是说,先跟客户谈好我们能生产这个产品,把合同签下来了,钱到手了,到时具体功能的实现再找下面的子工厂,至于这个子工厂是自家的还是外包的,其实并不重要,重要是能给我生产出这个产品出来就行,在本例中是用子工厂,也就是继承的方式。

即工厂模式的本质:延迟实现到子类

如果把工厂模式中的功能代码去掉,只保留提供接口实现的部分,比如:

package com.dgd.factory.demo2;

/**
 * @Author DGD
 * @date 2017/10/31.
 */
public class ExportFatory {
    //提供文本文件功能导出的实例
    public static ExportFileApi createExportFileApi() {
        return new ExportTextFile();
    }
}

这样子是不是就是简单工厂模式了?所以说工厂模式退化一下,不把产品的功能实现揽在自己身上,只负责提供产品的话,那么它就变成简单工厂了,是不是感觉工厂模式就是简单工厂的爸爸,因为它比简单工厂懂得更多一点,担子重一些。

工厂模式的相关代码可以参考Github

抽象工厂模式

之所以说抽象工厂模式在三者之中地位最高,自然是因为它的能力最大,简单工厂和工厂模式都是生产一种产品,比如罐头就是罐头,导出文件就是导出文件(功能也可以理解成是一种产品),而抽象工厂呢?它就是生产一系列产品的。

比如提供这么一种场景:去电脑城组装电脑,有这么两种装机方案(简单起见,只讨论CPU和主板),一是:Intel的CPU+技嘉的主板,二是:AMD的CPU+微星的主板,那么如何用抽象工厂模式来实现这个功能呢?

既然抽象工厂是生产一系列产品,自然要在接口定义里要体现出来,如下:

package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 * 定义抽象工厂的接口
 */
public interface AbstractFactory {
    //提供创建CPU的方法
    CPUApi createCPUApi();

    //提供创建主板的方法
    MainboardApi createMainboardApi();
}

上面用到的CPU和主板的接口定义如下:

package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 * CPU接口
 */
public interface CPUApi {
    void calculate();
}
package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 * 主板接口
 */
public interface MainboardApi {
    void installCPU();
}

CPU提供两种实现,分别是Intel型号的CPU和AMD型号的CPU:

package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 */
public class IntelCPU implements CPUApi {
    //CPU针脚数目
    private int pins;

    public IntelCPU(int pins) {
        this.pins = pins;
    }
    @Override
    public void calculate() {
        System.out.println("now in Intel CPU,pins="+pins);
    }
}
package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 */
public class AMDCpu implements CPUApi {
    private int pins;

    public AMDCpu(int pins) {
        this.pins = pins;
    }
    @Override
    public void calculate() {
        System.out.println("now in AMD CPU,pins="+pins);
    }
}

主板也提供两种实现,分别是技嘉的主板和微星的主板:

package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 * 技嘉的主板
 */
public class GAMainboard implements MainboardApi {
    /**
     * CPU插槽的孔数
     */
    private int cpuHoles;

    public GAMainboard(int cpuHoles) {
        this.cpuHoles = cpuHoles;
    }
    @Override
    public void installCPU() {
        System.out.println("now in GAMainboard,cpuHoles="+cpuHoles);
    }
}
package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 * 微星的主板
 */
public class MSIMainboard implements MainboardApi {
    private int cpuHoles;

    public MSIMainboard(int cpuHoles) {
        this.cpuHoles = cpuHoles;
    }
    @Override
    public void installCPU() {
        System.out.println("now in MSIMainboard,cpuholes="+cpuHoles);
    }
}

好了,现在负责生产产品的工厂可以开工了,这里要保证的是CPU型号和主板型号要对应客户给的方案,代码如下:

package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 * 第一种电脑组装实现:Intel的CPU+技嘉的主板
 */
public class Schema1 implements AbstractFactory {
    @Override
    public CPUApi createCPUApi() {
        return new IntelCPU(1156);
    }

    @Override
    public MainboardApi createMainboardApi() {
        return new GAMainboard(1156);
    }
}
package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 * 第二种装机方案:AMD的CPU+微星的主板
 */
public class Schema2 implements AbstractFactory{
    @Override
    public CPUApi createCPUApi() {
        return new AMDCpu(939);
    }

    @Override
    public MainboardApi createMainboardApi() {
        return new MSIMainboard(939);
    }
}

现在客户把方案扔过来,我们要给他提供合适的电脑,按理来说我们还需要一个装机工程师,因为工厂负责把装机需要的一系列产品生产出来,需要装机工程师来组装。装机工程师的定义如下:

package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 * 装机工程师
 */
public class ComputerEngineer {
    private CPUApi cpuApi;
    private MainboardApi mainboardApi;

    public void makeComputer(AbstractFactory schema) {
        //1,准备好电脑配件
        prepareHardwares(schema);
        //2,组装电脑
        //3,测试电脑
        //4,交付用户
    }

    private void prepareHardwares(AbstractFactory schema) {
        //使用抽象工厂来获取相关组件
        this.cpuApi = schema.createCPUApi();
        this.mainboardApi = schema.createMainboardApi();

        //测试组件是否好用
        this.cpuApi.calculate();
        this.mainboardApi.installCPU();
    }
}

也就是说,客户把想要组装的方案交给装机工程师,工程师跟生产产品的工厂说一声,工厂直接把对应的CPU和主板生产好,交给装机工程师,最后装机工程师就把组装好的电脑交付给客户。

对应的客户端代码如下:

package com.dgd.factory.demo3;

/**
 * @Author DGD
 * @date 2017/10/27.
 * 利用抽象工厂模式来实现电脑组装功能,客户可以随意切换装机方案,要保证不同方案下组装的电脑组件一致
 */
public class Client {
    public static void main(String[] args) {
        //方案一:Intel的CPU+技嘉的主板
        AbstractFactory factory = new Schema1();
        ComputerEngineer engineer = new ComputerEngineer();
        engineer.makeComputer(factory);

        //方案二:AMD的CPU+微星的主板
        factory = new Schema2();
        engineer.makeComputer(factory);

    }
}

运行一下,可以看到方案一组装出来的电脑就是Intel+技嘉的主板,方案二组装出来的电脑就是AMD+微星的主板。

我们看看抽象工厂的定义,提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类

由此可以知道,抽象工厂适用于工厂要生产一系列产品的场景,并且产品之间是有关系的比如CPU和主板,都是电脑的一部分,这一系列的产品不能一个是汽车发动机,一个是电脑主板,这样就变成单纯的工厂模式或者简单工厂了。

因而,抽象工厂的本质:选择产品簇的实现

由于工厂模式只专注于一种产品的实现,而抽象工厂是产品簇的实现,因而可以说抽象工厂是工厂模式的父亲,自然就是简单工厂的爷爷了。换句话说,工厂模式是抽象工厂模式的一种特例,而简单工厂模式又是工厂模式的一种特例。

抽象工厂的示例代码可以参考Github

后记:本文是我学习陈臣,王斌著的《研磨设计模式》的读书心得笔记,不当之处,欢迎指教,以便互相学习,共同进步。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值