创建型-简单工厂(Simple Factory)

3/12/2022 设计模式

摘要

JDK:1.8.0_202

# 一:问题

看看下面基本的接口和实现

接口:(Api.java)

public interface Api {
    void print(String s);
}
1
2
3

实现类:(ApiImpl.java)

public class ApiImpl implements Api {
    public void print(String s) {
        System.out.println("Now In Impl. The input s==" + s);
    }
}
1
2
3
4
5

客户端:(Client1.java)

public class Client1 {
    public static void main(String[] args) {
        Api api = new ApiImpl();
        api.print("测试");    // Now In Impl. The input s==测试
    }
}
1
2
3
4
5
6

问题:

仔细看位于客户端的这句话:Api api = new ApiImpl(); 你就会发现在客户端调用时,客户端不但知道了接口,同时还知道了具体的实现就是ApiImpl。而接口的思想是 "封装隔离",而Impl这个实现类,应该是被接口Api封装并同客户端隔离开的。也就是说,客户端根本就不应该知道具体的实现类是ApiImpl。

有人说,那好,就把Impl从客户端拿掉,让Api真正的对实现进行 "封装隔离",然后我们还是面向接口来编程。可是,新的问题出现了,当他把 "new ApiImpl()" 去掉过后,发现他无法得到Api接口对象了,怎么办呢?

把这个问题描述一下:

在Java编程中,出现只知接口而不知实现,该怎么办?
就像现在的Client,它知道要使用Api接口,但是不知由谁实现,也不知道如何实现,从而得不到接口对象,就无法使用接口,该怎么办呢?

# 二:解决方案

解决上述问题的一个合理的解决方案就是简单工厂

简单工厂:提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以使接口、抽象类,也可以是具体的类

# 2.1 解决思路

分析上面的问题,虽然不能让模块外部知道模块内的具体实现,但是模块内部是可以知道实现类的,而且创建接口是需要具体实现类的

那么干脆在模块内部新建一个类,在这个类里面来创建接口,然后把创建好的接口返回给客户端,这样外部应用就只需要根据这个类来获取相应的接口对象,然后就可以操作接口定义的方法了。把这样的对象称为简单工厂,就叫Factory吧

这样一来,客户端就可以通过这个Factory来获取需要的接口对象,然后调用接口的方法来实现需要的功能,而且客户端也不用再关心具体实现了

# 2.2 结构和说明

简单工厂的结构如图所示:

classDiagram Api <.. Client Api <.. Factory Api <|.. ImplA Api <|.. ImplB Factory <.. Client Factory ..> ImplA Factory ..> ImplB class Api{ <<interface>> +operation(String)* void } class ImplA{ +operation(String) void } class ImplB{ +operation(String) void } class Factory{ +operation(String) void } class Client{ }
  • NewApi:定义客户所需要的功能接口
  • Impl*:具体实现Api的实现类,可能会有多个
  • Factory:工厂,选择合适的实现类来创建Api接口对象
  • Client:客户端,通过Factory去获取Api接口对象,然后面向Api接口编程

# 2.3 代码示例

接口:(NewApi.java)

public interface NewApi {
    void operation(String s);
}
1
2
3

实现类:(ImplA.java 和 ImplB.java)

public class ImplA implements NewApi {
    @Override
    public void operation(String s) {
        System.out.println("ImplA s==" + s);
    }
}
1
2
3
4
5
6
public class ImplB implements NewApi{
    @Override
    public void operation(String s) {
        System.out.println("ImplB s==" + s);
    }
}
1
2
3
4
5
6

客户端:(Client2.java)

public class Client2 {
    public static void main(String[] args) {
        NewApi api = Factory.createApi(1);
        api.operation("正在使用简单工厂");  // ImplA s==正在使用简单工厂
    }
}
1
2
3
4
5
6

# 2.4 小结

事实上,简单工厂能帮助我们真正开始面向接口编程,像以前的做法,其实只是用到了接口的多态那部分的功能,最重要的 "封装隔离性" 并没有体现出来

# 三:模式讲解

# 3.1 典型疑问

首先来解决一个常见的疑问:可能有朋友会认为,上面示例中的简单工厂看起来不就是把客户端里面的 "new ApiImpl()" 移动到简单工厂里面吗?不还是一样通过new一个实现类来得到接口吗?把 "new Impl()" 这句话放到客户端和放到简单工厂里面有什么不同吗?

理解这个问题的重点就在于理解简单工厂所处的位置

根据前面的学习,我们知道接口是用来封装隔离具体的实现的,目标就是不要让客户端知道封装体内部的具体实现。简单工厂的位置是位于封装体内的,也就是简单工厂是跟接口和具体的实现在一起的,算是封装体内部的一个类,所以简单工厂知道具体的实现类是没有关系的

# 3.2 命名的建议

类名建议为 "模块名称+Factory",比如:用户模块的工厂就称为:UserFactory

方法名称通常为 "get+接口名称" 或者是 "create+接口名称",比如:有一个接口名称为UserEbi,那么方法名称通常为:getUserEbi 或者是 createUserEbi

当然,也有一些朋友习惯于把方法名称命名为 "new+接口名称",比如:newUserEbi,我们不是很建议。因为new在Java中代表特定的含义,而且通过简单工厂的方法来获取对象实例,并不一定每次都是要new一个新的实例。如果使用newUserEbi,这会给人错觉,好像每次都是new一个新的实例一样

# 3.3 优缺点

  • 帮助封装:简单工厂虽然很简单,但是非常友好的帮助我们实现了组件的封装,然后让组件外部能真正面向接口编程。

  • 解耦:通过简单工厂,实现了客户端和具体实现类的解耦。如同上面的例子,客户端根本就不知道具体是由谁来实现,也不知道具体是如何实现的,客户端只是通过工厂获取它需要的接口对象。

  • 可能增加客户端的复杂度:如果通过客户端的参数来选择具体的实现类,那么就必须让客户端能理解各个参数所代表的具体功能和含义,这会增加客户端使用的难度,也部分暴露了内部实现,这种情况可以选用可配置的方式来实现。

  • 不方便扩展子工厂:私有化简单工厂的构造方法,使用静态方法来创建接口,也就不能通过写简单工厂类的子类来改变创建接口的方法的行为了。不过,通常情况下是不需要为简单工厂创建子类的。

# 3.4 思考简单工厂

1. 简单工厂的本质

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

注意简单工厂的重点在选择,实现是已经做好了的。就算实现再简单,也要由具体的实现类来实现,而不是在简单工厂里面来实现。简单工厂的目的在于为客户端来选择相应的实现,从而使得客户端和实现之间解耦,这样一来,具体实现发生了变化,就不用变动客户端了,这个变化会被简单工厂吸收和屏蔽掉。

实现简单工厂的难点就在于 "如何选择" 实现,前面讲到了几种传递参数的方法,那都是静态的参数,还可以实现成为动态的参数。比如:在运行期间,由工厂去读取某个内存的值,或者是去读取数据库中的值,然后根据这个值来选择具体的实现等等。

2. 何时选用简单工厂

建议在如下情况中,选用简单工厂:

如果想要完全封装隔离具体实现,让外部只能通过接口来操作封装体,那么可以选用简单工厂,让客户端通过工厂来获取相应的接口,而无需关心具体实现;

如果想要把对外创建对象的职责集中管理和控制,可以选用简单工厂,一个简单工厂可以创建很多的、不相关的对象,可以把对外创建对象的职责集中到一个简单工厂来,从而实现集中管理和控制。

# 3.5 相关模式

简单工厂是用来选择实现的,可以选择任意接口的实现,一个简单工厂可以有多个用于选择并创建对象的方法,多个方法创建的对象可以有关系也可以没有关系。

简单工厂的本质是选择实现,所以它可以跟其它任何能够具体的创建对象实例的模式配合使用,比如:单例模式、原型模式、生成器模式等等

# 四:Spring中使用

接口:(Pay.java)

/**
 * 支付接口
 */
public interface Pay {

    /**
     * 支付操作方法
     */
    void pay();
}
1
2
3
4
5
6
7
8
9
10

实现类:(WeiXinPay.java 和 ZhiFuBaoPay.java)

@Component("WX")
public class WeiXinPay implements Pay{
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}
1
2
3
4
5
6
7
@Component("ZFB")
public class ZhiFuBaoPay {
    public void pay() {
        System.out.println("微信支付");
    }
}
1
2
3
4
5
6

工厂:(PayFactory.java)

/**
 * 工厂类,用来创造具体支付业务对象
 */
@Component
public class PayFactory {

    /**
     * 项目启动后,Spring会为实现了Pay接口并添加注解的类穿件对象实例,并放到此map中
     */
    @Autowired
    private Map<String, Pay> payMap;

    public Pay createPay(String type) {
        return payMap.get(type);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

测试:(TestPay.java)

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestPay {

    @Autowired
    private PayFactory payFactory;

    @Test
    public void testPay() {
        Pay pay = payFactory.createPay("WX");
        pay.pay();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

测试结果

# 五:参考文献

最后更新: 3/31/2022, 2:28:58 PM