从今天开始,我们就要正式开始学习设计模式了。关于设计模式的重要性,不言而喻,你写的是代码还是诗,一个重要的考察维度就是代码的健壮性,可扩展性,这些都离不开设计模式的支持。
本文,我们就从最简单的工厂方法模式开始,带大家揭开设计模式的神秘面纱。
简单工厂
要学习工厂方法,我们得先来学习下简单工厂模式。工厂方法模式则是在简单工厂的基础上做的进一步优化。
模式定义
简单工厂模式也叫静态工厂方法模式,它是一种创建型模式。创建型模式还包括工厂方法模式、抽象工厂模式、建造者模式、单例模式以及原型模式。
在简单工厂模式中,我们定义一个专门的类用来创建其他的实例,这个专门定义的类会根据不同的参数,返回不同的实例,这些不同的实例一般来说都有一个共同的父类。
但是需要注意的是,简单工厂模式并不属于 GOF 定义的 23 种设计模式。
模式抽象
我们先来通过一个简单的 UML 图来理解下简单工厂模式到底是什么样子的:
图-2
从这个 UML 图中可以看到,在简单工厂模式中,我们会首先定义一个产品的抽象类,然后每一个具体的产品都是实现这个抽象类,再通过工厂方法来创建一个产品的实例。
实例分析
我们通过一个生活中常见的场景来给大家描述一下这个模式。
例如我现在有两台电脑,一台台式机,另一台是笔记本,两台电脑都有一个开机的方法,如下:
public class MacBook {
public void open() {
System.out.println("MacBook open...");
}
}
public class MacPro {
public void open() {
System.out.println("MacPro open...");
}
}
如果今天打算用 MacPro 来 coding 的话,那我就按照如下方式开机:
MacPro macPro = new MacPro();
macPro.open();
如果我今天打算用 MacBook 来 coding 的话,那我就按照如下方式开机:
MacBook macBook = new MacBook();
macBook.open();
上面的代码看着没问题,但是它毕竟是代码,还不是诗,我们要将它变成诗。
首先,既然都是电脑,都有开机的方法,问什么不能统一处理开机问题呢?
于是,我们首先来定义一个 Mac 接口,如下:
public interface Mac {
void open();
}
这个 Mac 就相当于我们上文说的抽象产品,然后让 MacPro 和 MacBook 分别实现这个接口,并且实现接口中的 open 方法,它们是具体的产品,如下:
public class MacPro implements Mac{
public void open() {
System.out.println("MacPro open...");
}
}
public class MacBook implements Mac{
public void open() {
System.out.println("MacBook open...");
}
}
最终的类结构如下图:
图-3
最后我们在通过一个工厂类来提供具体产品的实例:
public class MacFactory {
public static Mac getInstance(String type) throws Exception {
if ("macpro".equals(type)) {
return new MacPro();
} else if ("macbook".equals(type)) {
return new MacBook();
}
throw new Exception("");
}
}
这样,当用户需要获取 MacBook 或者 MacPro 的实例时,只需要通过给工厂方法传入不同的参数,就可以获取到这个实例。如下:
Mac mac = MacFactory.getInstance("macbook");
mac.open();
这就是我们一直心心心念念的简单工厂。那么这么做到底有什么好处呢?
优缺点分析
优点
在简单工厂模式中,有三个重要的元素,就是我们上面 UML 图中的抽象产品、具体产品以及具体工厂,三个元素中,最最核心的当属工厂类了,工厂类根据具体的需要来创建不同的实例,需求方只需要告诉工厂它需要创建什么实例即可,剩下的事情就交给工厂类去完成,需求方不需要去创建产品的实例,实现一个解耦。
缺点
有优点,当然就会有缺点,最大的缺点在于这个工厂类不够灵活,当产品增加的时候,我们就得修改这个工厂类,不够友好;并且由于我们使用了静态方法,导致简单工厂也没法形成继承结构。
对于这些问题,我们在下面的设计模式中将会逐个解决。
实际应用
简单工厂在 Java 中还是有很多非常广泛的应用,例如格式化一个本地日期:
public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,Locale locale);
这就是简单工厂模式,接下来我们将在工厂模式种对此做进一步的优化。
工厂方法
工厂方法模式是对简单工厂的一个升级。我们来具体看下。
模式定义
工厂方法模式也属于创建型模式,又名工厂模式、虚拟构造器模式或者多态工厂模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
在简单工厂模式中,实例的创建都是在工厂类中完成的,如果添加了新产品就得修改工厂方法,现在,我们对工厂类也进行抽象,抽出一个接口,然后创建多个工厂类,不同的工厂类创建不同的产品,这样,如果添加新的产品线,我们只需要提供一个相应的工厂类即可。
模式抽象
我们通过一个简单的 UML 图来看下工厂模式是什么样子:
图-4
从这张图中可以看到,比上面多了一个抽象工厂而已。
实例分析
接下来,我们在上文的基础上,继续来完善。
首先 MacPro 和 MacBook 的定义就不需要变了,我们只需要重新定义 MacFactory,首先我们来定义 MacFactory:
public interface MacFactory {
Mac getMac();
}
然后再来定义 MacFactory 的实现类:
public class MacBookFactory implements MacFactory {
public Mac getMac() {
return new MacBook();
}
}
public class MacProFactory implements MacFactory {
public Mac getMac() {
return new MacPro();
}
}
在这里我们分别定义了两个实现类,不同的实现类用来创建不同的 Mac 对象。
最终,通过如下方式来创建实例:
MacFactory macProFactory = new MacProFactory();
Mac mac = macProFactory.getMac();
mac.open();
MacFactory macBookFactory = new MacBookFactory();
Mac mac2 = macBookFactory.getMac();
mac2.open();
这就是我们说的工厂方法模式,就比上面的多了一个工厂类的抽象,以后如果有新的产品上线,我们只需要提供相关工厂类即可。
优缺点分析
优点
- 工厂类满足单一职责原则,一个工厂类只创建一个类。
- 符合开-闭原则。
- 实例方法,可以形成工厂类的等级结构。
缺点
- 每次添加新产品,都需要提供对应的工厂类。
- 一个具体的工厂只能创建一种产品。
- 如果要换 Mac 系的产品,还是要修改工厂类。
实际应用
工厂方法设计模式应用相当广泛,最典型的莫过于 java.util.Collection
接口中的 iterator()
方法。
图-5
在这张图中,Collection 和 Iterator 分别相当于产品的抽象类和工厂的抽象类。