设计模式:工厂模式
场景再现
今天要讲的这个设计模式应该算是非常常用的,用好了可以大大提高代码的可维护性。我们还是从一个最为原始的场景出发,来一步步优化。
假设有一个工厂,这个工厂生产各种各样的产品,每个产品都是通过类似的一套流程进行生产,基于此,我们就有下面这个方法:
1
2
3
4
5
6
7
8
9
10
11
12
class client {
Product produceProduct() {
Product product = new Product();
product.process1();
product.process2();
product.process3();
product.process4();
return product;
}
}
如果仅仅是生产一种产品,那么上面的这个方法没啥毛病,但是问题是这个工厂需要在其生产线上生产不同的产品,所以基于上面的方法进行改造的话可以得到下面的方法:
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
50
51
52
class Product {}
class Product1 extends Product {
public void process1(){ /*...*/ };
public void process2(){ /*...*/ };
public void process3(){ /*...*/ };
public void process4(){ /*...*/ };
}
class Product2 extends Product {
public void process1(){ /*...*/ };
public void process2(){ /*...*/ };
public void process3(){ /*...*/ };
public void process4(){ /*...*/ };
}
class Product3 extends Product {
public void process1(){ /*...*/ };
public void process2(){ /*...*/ };
public void process3(){ /*...*/ };
public void process4(){ /*...*/ };
}
class Product4 extends Product {
public void process1(){ /*...*/ };
public void process2(){ /*...*/ };
public void process3(){ /*...*/ };
public void process4(){ /*...*/ };
}
class Client {
Product produceProduct(String type) {
Product product;
if (type.equals("product1")) {
product = new Product1();
} else if (type.equals("product2")) {
product = new Product2();
} else if (type.equals("product3")) {
product = new Product3();
} else if (type.equals("product4")) {
product = new Product4();
}
product.process1();
product.process2();
product.process3();
product.process4();
return product;
}
}
那么这就出现了问题,方法基于输入的类型来选择需要构建的对象,那么如果有新的类型加入,我们势必需要修改方法,这样方法就不再是封闭修改(close for modification)的了。上面仅仅是一个例子,现实中的场景和业务逻辑肯定更加复杂,并且随着越来越多的业务逻辑的加入,produceProduct
方法讲会变得难以维护。
由此可见,我们就必须对其进行重构,如何重构?思路又是什么?
我们重构的目的是封闭修改,而方法之所以会被修改是因为里面存在易变的逻辑,比如上面的例子中的基于类型来构建对象这一逻辑。那么一个直接的想法就是将易变的逻辑和不易变的逻辑分开来,于是我们可以得到下面的代码:
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
// ...
public class SimpleFactory {
public Product createProduct(String type) {
Product product = null;
if (type.equals("product1")) {
product = new Product1();
} else if (type.equals("product2")) {
product = new Product2();
} else if (type.equals("product3")) {
product = new Product3();
} else if (type.equals("product4")) {
product = new Product4();
}
return product;
}
}
class Client {
SimpleFactory factory;
Client(SimpleFactory factory) {
this.factory = factory;
}
Product produceProduct(String type) {
Product product = factory.createProduct(type);
product.process1();
product.process2();
product.process3();
product.process4();
return product;
}
}
试着将条件判断部分和其他逻辑分离确实带来了一些提升,首先是把创建对象的逻辑提取到一个单独的类 SimpleFactory
中,这样任何需要创建 Product
相关的变量,都可以通过 SimpleFactory
类来构建,极大地减少了代码重复。另外,produceProduct
方法也达到了封闭修改。
但是这里仅仅是把易变的部分换了个地方,换到了 SimpleFactory
类中的 createProduct
方法,虽然代码看上去比之前更有组织,但是久而久之随着业务逻辑的增多,createProduct
依然会成为问题。
需要强调的是,上面这个优化并不是我们今天要讲的工厂模式,这仅仅算是一个代码重组。
想要进一步优化,那么就需要利用一些设计原则来辅助我们思考。
工厂模式(Factory Method Pattern)
SOLID 设计原则中的 D 指的是 Dependency Inversion Principle,也就是 依赖反转原则 ———— 不应该依赖于实体类,而是应该依赖于抽象。上面的 SimpleFactory
其实就依赖于实体的 Product
类。依赖于实体类会带来什么问题呢?
很明显,每个实体都是不一样的,比如例子中的 Product1
,Product2
等等,因而和它们所关联的条件也是不一样的,如果依赖于这些实体类,也就意味着依赖于它们的类需要弄清楚这些逻辑关系。此外,实体类通常都是会被频繁地增减地,对应到我们的例子就是增加一种产品或是减少一种产品,这都会对原有的逻辑进行删改。
而依赖于抽象就不存在上述的问题,所有产品我都认为是 Product
这个抽象,都做一样的处理。这样一来,上层类就不需要处理无关的细节,代码职责变得更加清晰了。
所以我们需要对之前的逻辑进行一些改造:
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
// ...
abstract class Factory {
Product produceProduct(String type) {
Product product = createProduct(type);
product.process1();
product.process2();
product.process3();
product.process4();
return product;
}
protected abstract Product createProduct(String type);
}
class ProductFactory extends Factory {
public Product createProduct(String type) {
Product product = null;
if (type.equals("product1")) {
product = new Product1();
} else if (type.equals("product2")) {
product = new Product2();
} else if (type.equals("product3")) {
product = new Product3();
} else if (type.equals("product4")) {
product = new Product4();
}
return product;
}
}
经过改造,Factory
就只依赖于 Product
这个抽象,而不依赖于具体类。这里很多人可能会说,不对呀,Factory
的子类不还是依赖于一堆具体的 Product
类吗?还是没有依赖于抽象呀?
因为这里的逻辑很简单,所以我们把之前的所有逻辑直接移到了子类中,当然,如果每个产品都有很复杂的逻辑,我们也可以进行拆分:
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
// ...
abstract class Factory {
public Product produceProduct(String param) {
Product product = createProduct(param);
product.process1();
product.process2();
product.process3();
product.process4();
return product;
}
protected abstract Product createProduct(String param);
}
class Product1Factory extends Factory {
protected Product createProduct(String param) {
// business logic for create product1...
return new product1();
}
}
class Product2Factory extends Factory {
protected Product createProduct(String param) {
// business logic for create product2...
return new product2();
}
}
class Product3Factory extends Factory {
protected Product createProduct(String param) {
// business logic for create product3...
return new product3();
}
}
class Product4Factory extends Factory {
protected Product createProduct(String param) {
// business logic for create product4...
return new product4();
}
}
比如这里,我们可以把 Product1Factory
和 Product1
当作一个整体,它们仅仅依赖于 Factory
和 Product
这两个抽象,也就是说Factory
和 Product
下面其他的子类逻辑如何变都跟它们没关系。
较真的人会说,你说的 Product1Factory
和 Product1
都是具体类,它们之间相互依赖了,这确实是,但是这两个类的目的都是为了一个产品。并且从用户端来看,只需要创建 Product1Factory
,调用 produceProduct
方法,传入相关参数就可以得到 Product1
,不需要考虑它们之间的关系。它们本就是一个整体,至少从外部来看。
再回过头来看之前的那些问题,有新的产品加进来,我们只需要创建一个 Product
子类,然后创建一个 Factory
子类,填充对应的业务逻辑,其他产品和产品的构建逻辑均不受影响。删除一个产品也只需要删除对应的 Product
子类和 Factory
子类即可。这完全实现不同产品之间构建的解耦。
整个逻辑就如下图所示:
这里的 createProduct
就是工厂方法,是整个模式的核心,因此这个模式也被称为工厂方法模式。Factory
并不包含任何有关产品的细节与实现逻辑,而是提供了一个抽象接口 factoryMethod
,将其交由子类实现并构建具体的产品。这实现了产品与产品之间的解耦,也降低了代码重复,让业务逻辑变得清晰。