Post

设计模式:工厂模式

场景再现

今天要讲的这个设计模式应该算是非常常用的,用好了可以大大提高代码的可维护性。我们还是从一个最为原始的场景出发,来一步步优化。

假设有一个工厂,这个工厂生产各种各样的产品,每个产品都是通过类似的一套流程进行生产,基于此,我们就有下面这个方法:

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 类。依赖于实体类会带来什么问题呢?

很明显,每个实体都是不一样的,比如例子中的 Product1Product2 等等,因而和它们所关联的条件也是不一样的,如果依赖于这些实体类,也就意味着依赖于它们的类需要弄清楚这些逻辑关系。此外,实体类通常都是会被频繁地增减地,对应到我们的例子就是增加一种产品或是减少一种产品,这都会对原有的逻辑进行删改。

而依赖于抽象就不存在上述的问题,所有产品我都认为是 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();
  }
}

比如这里,我们可以把 Product1FactoryProduct1 当作一个整体,它们仅仅依赖于 FactoryProduct 这两个抽象,也就是说FactoryProduct 下面其他的子类逻辑如何变都跟它们没关系。

较真的人会说,你说的 Product1FactoryProduct1 都是具体类,它们之间相互依赖了,这确实是,但是这两个类的目的都是为了一个产品。并且从用户端来看,只需要创建 Product1Factory,调用 produceProduct 方法,传入相关参数就可以得到 Product1,不需要考虑它们之间的关系。它们本就是一个整体,至少从外部来看。

再回过头来看之前的那些问题,有新的产品加进来,我们只需要创建一个 Product 子类,然后创建一个 Factory 子类,填充对应的业务逻辑,其他产品和产品的构建逻辑均不受影响。删除一个产品也只需要删除对应的 Product 子类和 Factory 子类即可。这完全实现不同产品之间构建的解耦。

整个逻辑就如下图所示:

这里的 createProduct 就是工厂方法,是整个模式的核心,因此这个模式也被称为工厂方法模式。Factory 并不包含任何有关产品的细节与实现逻辑,而是提供了一个抽象接口 factoryMethod,将其交由子类实现并构建具体的产品。这实现了产品与产品之间的解耦,也降低了代码重复,让业务逻辑变得清晰。

This post is licensed under CC BY 4.0 by the author.