再看简单工厂和工厂方法

几个工厂模式的意义都在于将用户从创建依赖(也就是 new)的过程解脱出来,让创建对象和使用对象这两个职责进行解耦。同时能够让用户面向抽象层编程,实现依赖倒置原则。

产品类如下——

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 在这里是 Fruit,在业务中可能就是 XXService 了
interface Fruit {
void whoAmI();
}
class Apple implements Fruit {
@Override
public void whoAmI() {
System.out.println("Apple!");
}
}
class Banana implements Fruit {
@Override
public void whoAmI() {
System.out.println("Banana!");
}
}
class Peach implements Fruit {
@Override
public void whoAmI() {
System.out.println("Peach!");
}
}

下面的代码实现了通过字符串匹配和类匹配的简单工厂模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class FruitFactory {
public static Fruit getFruit(String name) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException("需给定有效字符串!");
}
Fruit fruit = null;
// 可以使用反射,通过输入的字符串获取相应类,这能够不违反开闭原则——添加新的实现类不需要做任何改动,但会导致只能调用统一的构造器,这可能无法抽象多种多样的构造过程
// 可以发现工厂方法模式相较于简单工厂模式的不同——使用反射构造每个 fruit 的工厂而非 fruit,在工厂中处理特定的初始化过程
switch (name) {
case "apple":
// 一堆个性的初始化过程……
fruit = new Apple();
break;
case "banana":
// 另一堆个性的初始化过程……
fruit = new Banana();
break;
case "peach":
// 再一堆个性的初始化过程……
fruit = new Peach();
break;
// 添加新的 Fruit 就需要修改源代码,违反开闭原则
default:
break;
}
if (fruit == null)
throw new IllegalArgumentException("字符串不匹配!");
return fruit;
}
// 按类匹配,优点是能够在编译期能够判断类是否合法,缺点是暴露了底层的具体类用户,且无法自定义初始化过程,其它的和反射法没有区别
public static Fruit getFruit(Class<? extends Fruit> clazz) {
try {
return (Fruit) clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
FruitFactory.getFruit("banana").whoAmI(); // 可以用字符串,也就意味着可以用配置文件
FruitFactory.getFruit(Peach.class).whoAmI();
}
}

工厂方法模式和简单工厂模式的形式比较相似,除了有这些不同——

  • 工厂方法模式中一个工厂只负责生产一种产品(需要和抽象工厂模式中的一族进行区分,一族指代一系列类似的多种产品,如某 ui 框架针对各个系统编写了窗口,按钮,文字等控件,Linux 中的窗口,按钮,文字等就是一族控件(产品),windows 中的这些则属于另一族控件……),简单工厂模式的工厂负责生成所有产品。

  • 工厂方法模式的静态方法获取生成产品的工厂,简单工厂模式的静态方法直接获取产品。

可以认为,工厂方法模式就是将简单工厂中各个产品的初始化(生产)过程划分开来。生成工厂的工厂仍旧是类似简单工厂的模式,这是没有问题的——工厂的初始化应当是简单且一致的,可以进行抽象。提供给外部的接口也可以与简单工厂一致。其中一个实现如下——

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
interface FruitFactory {
// 工厂中获取水果实例的方法
Fruit getFruit();
// 这两个静态方法将获取对应工厂并通过其来获取 Fruit,这里使用了模板模式
// 实际使用中不应当让用户输入全类名,可以像 spring 中的 xml 配置那样给类名绑定一个 id
static Fruit getFruit(String factoryName) {
if (factoryName == null || factoryName.length() == 0) {
throw new IllegalArgumentException("需给定有效字符串!");
}
try {
Class<?> factoryClass = Class.forName(factoryName);
// 获取工厂时可以使用单例模式!
// 这里没有加入错误检查
return getFruit((Class<? extends FruitFactory>) factoryClass);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
static Fruit getFruit(Class<? extends FruitFactory> factoryClass) {
try {
return factoryClass.getDeclaredConstructor().newInstance().getFruit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
}
class AppleFactory implements FruitFactory {
@Override
public Fruit getFruit() {
// 一些处理。..
return new Apple();
}
}
class BananaFactory implements FruitFactory {
@Override
public Fruit getFruit() {
// 一些处理。..
return new Banana();
}
}
// . . .
public class Main {
public static void main(String[] args) throws InterruptedException, ClassNotFoundException {
FruitFactory.getFruit(AppleFactory.class).whoAmI();
}
}

工厂方法模式可以与各种设计模式组合使用,最有价值的几个组合是和单例模式,原型模式,代理模式……技术上对工厂方法模式的发展和改进是持续进行的,目前最经典和最有实践价值的实现是 Spring 的核心容器,它不仅负责管理各个产品(称为 bean),也能管理各个产品之间的相互依赖,用户能够完全不用关心代码的具体实现。这种抽象性虽然让系统更加难以理解,但对工程实践是极为方便的。


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 协议 ,转载请注明出处!