0%

Java设计模式:工厂模式

工厂模式简介

所谓工厂模式,简单的来说,就是自己不再主动创建对象,而是让工厂来帮我们创建对象。通常工厂提供一个获取对象的方法,通过调用这个方法来获取自己实际想要的对象。那有的同学很可能就会问:创建对象我自己new一个对象出来不就好了,简单快捷。用得着你这个工厂模式吗?搞一个工厂出来还要写一堆的代码呢。我们可以从使用场景来考虑这个问题。
想想我们将创建对象的操作进行了一层封装会有什么好处?好处是我们可以很灵活地控制对象的生成过程,比如可以通过一些参数来控制创建的权限,创建过程可以加一些装饰,可以加入一些日志来说明创建的过程(如果对象的创建有很多环节)。其实我们可以看到工厂模式是对创建对象和实现对象过程的解耦。用了工厂模式可能还会使创建对象的代码变得简洁。

工厂模式有何用处?

  • 可以隐藏具体类名,提供一个更容易理解的名字。
  • 避免你辛苦的准备构造方法的参数,可以简化创建对象。(如:Executors.newCachedThreadPool())
  • 可以通过配置类来动态选择需要创建的对象,提供灵活性。也就是简单工厂模式。
  • 静态的工厂方法可以在每次调用时控制不创建一个新的对象。(其实单例模式就是静态工厂方法)
  • 对象的实现和创建进行解偶,修改了具体的实现类,对客户端(调用方)而言是完全不用修改的。

工厂模式的分类

所谓的工厂模式,一般有三种方式来封装:

  • 简单工厂:把对象的创建放到一个工厂类中,通过参数来创建不同的对象。这个缺点是每添一个对象,就需要对简单工厂进行修改(尽管不是删代码,仅仅是添一个switch case,但仍然违背了“不改代码”的原则)
  • 工厂方法:每种产品由一种工厂来创建,一个工厂保存一个对象new遵循 “不改代码”的原则。可以很好的对产品进行扩展
  • 抽象工厂:仅仅是工厂方法的复杂化,保存了多个对象new,可以定义一系列产品族。

具体实现方式

简单/静态工厂

该模式对对象创建管理方式最为简单,对不同类对象的创建进行了一层简单的封装。该模式通过向工厂传递类型来指定要创建的对象。
我们拿手机生产来举例:每种品牌的手机都要遵循同一种手机生产标准,可以定义一个手机的标准规范接口:

1
2
3
public interface Phone {
void call();
}

接下来我们就可以定义各种品牌的手机产品了,他们都要实现同一个Phone接口。

1
2
3
4
5
6
7
8
9
10
11
12
// 苹果手机
public class Iphone implements Phone {
public void call() {
System.out.println("Iphone call...");
}
}
// 联想手机
public class Lenovo implements Phone {
public void call() {
System.out.println("Lenovo call...");
}
}

我们可以创建一个工厂来生产我们需要的品牌手机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class PhoneFactory {
// 静态工厂(单例模式的实现方式)
public static Iphone makeIphone() {
return new Iphone();
}

public static Lenovo makeLenovo() {
return new Lenovo();
}

public Phone makePhone(String phoneType) {
if(phoneType.equalsIgnoreCase("iphone")){
return new Iphone();
}
else if(phoneType.equalsIgnoreCase("lenovo")) {
return new Lenovo();
}
return null;
}
}

使用起来也很简单:

1
2
3
4
PhoneFactory phoneFactory = new PhoneFactory();

phoneFactory.makePhone("iphone").call();
phoneFactory.makeLenovo().call();

简单工厂类的优点很明显:我就一个具体的工厂来创建对象,简单维护,代码量少。缺点也很明显:当需求改变了(新增一种产品),我就要改代码。

工厂方法

在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
还是拿生产手机为例,此时的工厂不具备所有品牌手机的生产能力,而是具体品牌的手机拥有专门生产的工厂。我们抽象一个生产手机的工厂接口,拥有生产手机的能力:

1
2
3
public interface AbstractFactory {
Phone makePhone();
}

每种品牌手机都拥有自己的专属工厂,如苹果工厂、联想工厂等:

1
2
3
4
5
6
7
8
9
10
11
public class AppleFactory implements AbstractFactory {
public Phone makePhone() {
return new Iphone();
}
}
public class LenovoFactory implements AbstractFactory {

public Phone makePhone() {
return new Lenovo();
}
}

我们可以从各自的工厂中生产各自品牌的手机:

1
2
new AppleFactory().makePhone().call();
new LenovoFactory().makePhone().call();

使用工厂方法模式带来的优势是:

  • 客户端不再需要负责对象的创建,明确了各个类的职责
  • 如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,不会影响已有的代码,后期维护容易,增强系统的扩展性

缺点就是带来额外的代码编写,增加了工作量。
使用场景主要有:

  • 一个类希望其子类指定其创建的对象
  • 一个类无法预期它必须创建的对象的类

JDK中实际参考的例子有:

  • java.text.NumberFormat
  • java.nio.charset.Charset
  • java.util.EnumSet

抽象工厂

现在思考这个问题,如果我是个苹果粉,不但想拥有iphone还想买一台MacBook,我希望通过一个入口方便地拥有这两种产品或者说苹果这一系列的产品怎么办?我们不可能说再建一个造PC的工厂然后模仿手机的模式写一套同样的代码。那我们可以定义一个工厂来生产这一系列的产品。
简单来说:工厂方法模式的工厂是创建出一种产品,而抽象工厂是创建出一系列产品。抽象工厂仅仅是工厂方法的复杂化。
我们使用抽象工厂,在其中增加创建产品的接口,并在具体子工厂中实现新加产品的创建。

1
2
3
4
public interface AbstractFactory {
Phone makePhone();
PC makePC();
}

类似我们需要PC接口和具体实现:

1
2
3
4
5
6
7
8
9
10
public interface PC {
void play();
}

public class MacBook implements PC {

public void play() {
System.out.println("play Macbook...");
}
}

这样我们通过苹果工厂就能创建一系列苹果的产品:

1
2
3
4
5
6
7
8
9
public class AppleFactory implements AbstractFactory {
public Phone makePhone() {
return new Iphone();
}

public PC makePC() {
return new MacBook();
}
}

使用起来也很简单:

1
2
3
AppleFactory appleFactory = new AppleFactory();
appleFactory.makePC().play();
appleFactory.makePhone().call();

抽象工厂模式说到底就是多了一层抽象,减少了工厂的数量。和工厂方法类似,只不过是不同概念的叫法而已。实际编程哪种合适就怎么来,不必纠结这些概念。
使用场景主要有:

  • 系统配置需要多个产品系列之一
  • 一个相关产品对象系列旨在一起使用,因此您需要强制执行此约束
  • 您想要提供产品的类库,并且只想显示它们的接口,而不是它们的实现
  • 您需要一个运行时值来构建特定的依赖关系
  • 当您需要产品之间的一致性时

JDK中实际参考的例子有:

  • javax.xml.parsers.DocumentBuilderFactory
  • javax.xml.transform.TransformerFactory
  • javax.xml.xpath.XPathFactory

总结

总的来说三种工厂模式有各自的应用场景,实际应用时能解决问题满足需求即可,可灵活变通,无所谓高级与低级,也不需要纠结这些概念。对我们来说用简单工厂模式比较多,工厂方式模式的话代码量会比较大,抽象工厂模式的话需要业务比较大的情况下才会用到。
多说一句,工厂模式配合反射来使用也是极好的。

源码地址:https://github.com/bebee4java/java-design-patterns/tree/master/factory