👉文章示例代码👈

定义

一个工厂方法,根据传入的不同参数,生成对应的具体实例化对象。

简单工厂属于创建性模式,但是其不属于GOF23种设计模式

场景示例

假设笔者拥有很多品牌的手机(虽然笔者没有),这里以笔者选择品牌手机打电话作为示例。

创建基类(可以是接口或者是抽象类)

创建基类,基类可以是接口或者是抽象类。这里以创建手机接口为例。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author zhh
* @description 手机接口
* @date 2020-02-10 11:04
*/
public interface Phone {

/**
* 拨打电话
*/
void call();
}

创建实现类

创建实现类实现手机接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author zhh
* @description 苹果手机
* @date 2020-02-10 11:07
*/
public class IPhone implements Phone {

public void call() {
System.out.println("使用苹果手机拨打电话");
}
}

/**
* @author zhh
* @description 小米手机
* @date 2020-02-10 11:08
*/
public class MiPhone implements Phone {

public void call() {
System.out.println("使用小米手机拨打电话");
}
}

创建工厂类

创建手机工厂类,实现获取手机的方法,根据方法的入参生成对应实现类的对象。

使得应用层不依赖对应的具体实现类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @author zhh
* @description 手机工厂类
* @date 2020-02-10 11:09
*/
public class PhoneFactory {

/**
* 获取手机
* @param type 手机品牌类型
* @return
*/
public static Phone getPhone(String type) {
if ("iphone".equalsIgnoreCase(type)) {
return new IPhone();
} else if ("miphone".equalsIgnoreCase(type)) {
return new MiPhone();
}
return null;
}
}

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author zhh
* @description 测试类
* @date 2020-02-10 11:11
*/
public class Test {

public static void main(String[] args) {
Phone phone = PhoneFactory.getPhone("iphone");
if (phone != null) {
phone.call();
} else {
System.out.println("无此手机品牌类型");
}
}
}

测试类的输出结果如下:

使用苹果手机拨打电话

类结构图

以上示例类的结构图如下所示

image.png

存在的问题

上述模式的实例其实还是可以继续扩展的,例如我们需要额外增加华为手机类型:

我们只需创建一个新的实现类 HuaweiPhone ,同时实现 Phone 接口,然后再在工厂类获取手机方法中增加一个关于创建 HuaweiPhone 实例对象的判断分支。

代码如下:

1
2
3
else if ("huaweiphone".equalsIgnoreCase(type)) {
return new HuaweiPhone();
}

使用简单工厂模式,我们会发现一个问题: 增加如上的判断分支修改了我们原有的工厂类方法。

即不满足于开闭原则中所规定的:

软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的

这个问题在工厂方法模式中得到了很好的解决。

方案改进

除了工厂方法外,我们还可以利用反射来弥补简单工厂的扩展性。

工厂类改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* @author zhh
* @description 手机工厂类
* @date 2020-02-10 11:09
*/
public class PhoneFactory {

/**
* 获取手机(方案改进,利用反射)
* @param clazz 类对象
* @return
*/
public static Phone getPhone(Class clazz) {
try {
return (Phone) Class.forName(clazz.getName()).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

测试类改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author zhh
* @description 测试类
* @date 2020-02-10 11:11
*/
public class Test {

public static void main(String[] args) {
Phone phone = PhoneFactory.getPhone(IPhone.class);
if (phone != null) {
phone.call();
} else {
System.out.println("无此手机品牌类型");
}
}
}

这时候再去新增华为手机类型 HuaweiPhone ,我们只需要传递 HuaweiPhone 这个类对象进去即可,而工厂类是不需要变动的,这种改进从一定程度上满足了开闭原则。

源码中的用例

  • java.util.Calendar 类( createCalendar() 方法)
  • Class.forName("**") 加载数据库驱动
  • Logback (以具体实现 ch.qos.logback.classic.LoggerContext.getLogger() 方法为例)

总结

适用场景

  • 工厂类负责创建的对象比较少
  • 客户端只关注传入工厂类的参数,不关心实体类对象创建的具体过程

优点

  • 客户端不用去关心实体类对象创建的具体过程,从而达到了解耦的效果
  • 工厂类可以根据客户端输入的条件动态地去实例化对应的类

缺点

工厂类的职责相对过重,新增产品需要修改工厂类的判断逻辑,违背了开闭原则

参考

  • 《Head First 设计模式》
  • 《大话设计模式》