上一篇文章详细学习了单例模式的多种写法,今天来学习一下如下三种模式:简单工厂、工厂方法、抽象工厂模式,其实简单工厂模式不属于 GOF 23 种设计模式,不过它实现简单,在有些场景下它也比较适用,所以就首先来看一下它。
简单工厂模式
通常我们使用 new 关键字就可以创建对象,为什么还要使用工厂模式呢?我们以下面这个例子来看一下。
如果有一个手机店,出售 IPhone、Samsung、Huawei 品牌的手机。
public class Phone {
public void pay() {}
public void box() {}
}
class IPhone extends Phone {
}
class Samsung extends Phone {
}
class Huawei extends Phone {
}
复制代码
顾客在购买手机的代码可以这样写:
public class PhoneStore {
public Phone buyPhone(String type) {
Phone phone = null;
if ("Iphone".equals(type)) {
phone = new IPhone();
} else if ("Samsung".equals(type)) {
phone = new Samsung();
} else if ("Huawei ".equals(type)) {
phone = new Huawei();
}
phone.pay();
phone.box();
return phone;
}
}
复制代码
如果店铺想要增加竞争力,又添加了几种手机品牌,就需要去修改 buyPhone 方法,在其中继续添加 if-else 语句。
也就是说,如果代码有变化或扩展,就必须重新修改该方法,这就违反了对扩展开放、对修改关闭的原则。而且这样修改对于系统来说,将难以维护和更新。
其实,我们可以将创建对象的代码移到另一个对象,封装成一个工厂类,在添加或改变手机的品牌时,只需要修改该工厂类即可:
public class SimplePhoneFactory {
public static Phone createPhone(String type) {
Phone phone = null;
if ("Iphone".equals(type)) {
phone = new IPhone();
} else if ("Samsung".equals(type)) {
phone = new Samsung();
} else if ("Huawei ".equals(type)) {
phone = new Huawei();
}
return phone;
}
}
复制代码
而 PhoneStore 的代码就可以修改为:
public class PhoneStore {
public Phone buyPhone(String type) {
Phone phone = SimplePhoneFactory.createPhone(type);
phone.pay();
phone.box();
return phone;
}
}
复制代码
上述模式就是简单工厂模式,也可以利用静态方法来定义工厂,这称为静态工厂。
我们来看一下它的 UML 图:
下面来总结一下,简单工厂模式有哪些优缺点。
优点:
- 如果一个调用者想创建一个对象,只要知道其名称就可以了。
- 如果想增加一个产品,只要实现一个新的扩展自工厂类的子类就可以了。
- 它屏蔽了产品的具体实现,调用者只需要关心产品的接口。
但它也有一些缺点:
- 每次增加产品时,都需要增加一个产品类,使得系统中的类太多,增加了系统的复杂度。
简单工厂模式具体实践
Calendar#createCalendar
该方法部分源码如下:
private static Calendar createCalendar(TimeZone zone,
Locale aLocale)
···
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
cal = new GregorianCalendar(zone, aLocale);
}
}
复制代码
这里会根据传入的语言和国家来决定生成什么 Calendar。
工厂方法模式
如果手机店规模比较大,希望开设 IPhone、Samsung、Huawei 专卖分店(假设分店自己制作手机),这应该如何扩展呢?
由于购买手机的过程类似,都需要付款、打包;而分店则需要生产其相应品牌的手机。我们可以将 PhoneStore 修改为抽象类 ,将 SimplePhoneFactory 的 createPhone 方法改为抽象方法,放置到 AbstractPhoneStore 中。
public abstract class PhoneStore {
public Phone buyPhone() {
Phone phone = createPhone();
phone.pay();
phone.box();
return phone;
}
protected abstract Phone createPhone();
}
复制代码
IPhone、Samsung、Huawei 三种产品分别如下:
public class Phone {
public void pay() { }
public void box() { }
}
class IPhone extends Phone {
}
class Samsung extends Phone {
}
class Huawei extends Phone {
}
复制代码
三种产品对应的 IPhone、Samsung、Huawei 三家分店,它们的具体实现如下:
public class IPhoneStore extends PhoneStore {
@Override
protected Phone createPhone() {
return new IPhone();
}
}
public class SamsungStore extends PhoneStore {
@Override
protected Phone createPhone() {
return new Samsung();
}
}
public class HuaweiStore extends PhoneStore {
@Override
protected Phone createPhone() {
return new Huawei();
}
}
复制代码
如果我们要 IPhone 手机,代码可以如下:
public class Client {
public static void main(String[] args) {
PhoneStore phoneStore = new IPhoneStore();
Phone phone = phoneStore.buyPhone();
// phone 为 IPhone
}
}
复制代码
上述这种模式就是工厂方法模式,它会定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。例如这里创建了一个 PhoneStore 抽象类,但实际上由 IPhoneStore 来决定实例化哪个 Phone 的实现类。
我们可以看到工厂方法模式包括了四个角色:
- Product:抽象产品,对应上面的 Phone;
- ConcreteProduct:具体产品,对应 IPhone、Samsung、Huawei 等;
- Factory:抽象工厂,对应 PhoneStore;
- ConcreteFactory:具体工厂,对应 IPhoneStore、SamsungStore、HuaweiStore。
它的 UML 图如下:
下面总结一下工厂方法模式的优点:
- 用户只需要关心所需产品对应的工厂,而无需关心创建产品的细节;
- 在系统中加入新产品时,无需修改抽象工厂和抽象产品的接口,也无需修改其他的具体工厂和具体产品,只需要添加一个具体的工厂和具体产品就可以了。
缺点:
- 每添加一个新产品就要添加对应的工厂和产品,造成系统中类的个数太多,增加了系统的复杂度。
- 考虑系统的扩展性,需要引入抽象层,增加了系统的抽象性,系统实现的难度也加大。
简单工厂模式与工厂方法模式之间的区别如下:
- 在简单工厂中,是将对象的创建封装在另一个类中;
- 而在工厂方法中,它创建了一个框架,由子类来决定创建哪个对象。
工厂方法模式具体实践
Java 集合的 iterator 方法就是一个工厂方法。部分集合的 UML 图如下:
抽象工厂
该实例中抽象工厂就是 Iterable 接口:
public interface Iterable<T> {
Iterator<T> iterator();
}
复制代码
具体工厂
具体工厂在 Java 集合中非常多,这里举两个例子,例如在 ArrayList 中的实现:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public Iterator<E> iterator() {
return new Itr();
}
}
复制代码
在 HashMap 中的实现中,entrySet 方法返回一个 EntrySet 对象:
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
}
复制代码
抽象产品
抽象产品就是 Iterator 接口
public interface Iterator<E> {
boolean hasNext();
E next();
}
复制代码
具体产品
这里的具体产品,以上面说的 ArrayList 中的 Itr 和 EntrySet 中的 EntryIterator 为例。
Itr 对象如下:
private class Itr implements Iterator<E> {
public boolean hasNext() { ··· }
public E next() { ··· }
}
复制代码
EntryIterator 对象如下:
abstract class HashIterator {
public final boolean hasNext() { ··· }
final Node<K,V> nextNode() { ··· }
}
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
复制代码
抽象工厂模式
如果想要改变商业模式,三家专卖店内不仅可以卖 IPhone、Sansung、Huawei 三种品牌的手机,也可以卖相应品牌的电脑(假设分店自己生产手机、电脑),这应该如何设计呢?(这个例子不太符合实际情况,不过能说明抽象工厂模式的含义,凑合看吧)
店铺可以如下设计:
public abstract class PhoneStore {
public Phone buyPhone() {
Phone phone = createPhone();
phone.pay();
phone.box();
return phone;
}
public Computer buyComputer() {
Computer computer = createComputer();
computer.pay();
computer.pack();
return computer;
}
protected abstract Phone createPhone();
protected abstract Computer createComputer();
}
复制代码
三种品牌的手机类如下:
public class Phone {
public void pay() { }
public void box() { }
}
class IPhone extends Phone {
}
class SamsungPhone extends Phone {
}
class HuaweiPhone extends Phone {
}
复制代码
三种品牌的电脑类如下:
public class Computer {
public void pay() { }
public void pack() { }
}
class MacComputer extends Computer {
}
class SamsungComputer extends Computer {
}
class HuaweiComputer extends Computer {
}
复制代码
对于三家相应品牌的专卖店,它们的具体实现如下:
public class IPhoneStore extends PhoneStore {
@Override
protected Phone createPhone() {
return new IPhone();
}
@Override
protected Computer createComputer() {
return new MacComputer();
}
}
public class SamsungStore extends PhoneStore {
@Override
protected Phone createPhone() {
return new SamsungPhone();
}
@Override
protected Computer createComputer() {
return new SamsungComputer();
}
}
public class HuaweiStore extends PhoneStore {
@Override
protected Phone createPhone() {
return new HuaweiPhone();
}
@Override
protected Computer createComputer() {
return new HuaweiComputer();
}
}
复制代码
如果我们要在 IPhone 专卖店购买手机和电脑,代码可以如下:
public class Test {
public static void main(String[] args) {
PhoneStore phoneStore = new IPhoneStore();
Phone phone = phoneStore.buyPhone();
Computer computer = phoneStore.buyComputer();
// phone 为 IPhone
// computer 为 Mac
}
}
复制代码
上述的模式就是抽象工工厂模式,它提供了一个接口,用于创建一个产品的家族,而不需要指定具体类。每个具体工厂会创建某个产品家族。
在上述例子,IPhoneStore、SamsungStore、HuaweiStore 就是一个个具体的工厂,它们可以生产对应品牌的手机和电脑。其中 IPhoneStore 这个工厂就是创建 IPhone、MacComputer 这个产品家族。
它的 UML 图如下:
下面总结一下抽象工厂模式的优缺点。
优点:
- 同样地,将用户代码和实际的具体产品解耦,使其无需关心产品的创建细节;
- 使用某个工厂,可以创建一系列相关的产品。如果想要增加一条个产品线,例如上面想要增加一个新的品牌店和其相应的产品,只需要扩展 PhoneStore 工厂,并创建相应的 Phone、Computer 类即可,非常简单。
缺点:
- 限制了所能创建的产品集合,例如上面的 Phone 和 Computer,如果想要增加新的产品,增加 camera,就会比较困难,需要修改抽象工厂的接口,会增加很大的工作量;
- 另外,工厂类和产品类较多,增加了系统的抽象性和复杂度。
抽象工厂模式与工厂方法模式很类似,它们之间的区别如下:
- 在工厂方法模式中,每个具体工厂负责创建一个具体产品。所以,在增加一个具体产品时,也要增加其相应的工厂,需要创建一个继承自抽象工厂的类,并覆盖它的工厂方法。也就是所说的工厂方法使用继承创建对象。
- 而在抽象工厂模式中,每个具体工厂负责创建一个系列的具体产品。所以,只有在新增加一个类型的具体产品时,才需要增加相应的工厂。它可以用来创建一系列具体产品,将这些相关的产品组合起来,也就是所说的使用组合创建对象。
它们之间也有一些关联,就是抽象工厂的方法以工厂方法的方法来实现。在抽象工厂的接口中,每个方法都负责创建一个具体产品,而具体工厂来提供具体的实现。
例如,PhoneStore 中的 createPhone、createComputer 方法由子类实现,这两个方法单独来看都是在创建一个对象,其实也就是一个工厂方法。
抽象工厂模式的实践
JDBC 中的 Connection 就是一个抽象工厂模式,在不同的连接池中有不同的实现,例如 druid 和 dbcp:
由于本人对于 druid 和 dbcp 的实现也不太熟悉,这里就不多解释了,有兴趣的小伙伴可以自己研究一下。
参考资料
- 《Head First 设计模式》
- CyC2018/CS-Notes:设计模式
- 越努力越幸运-致自己:java设计模式精讲 Debug 方式+内存分析 第4章 简单工厂模式