一文搞懂三种工厂模式

上一篇文章详细学习了单例模式的多种写法,今天来学习一下如下三种模式:简单工厂、工厂方法、抽象工厂模式,其实简单工厂模式不属于 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 的实现也不太熟悉,这里就不多解释了,有兴趣的小伙伴可以自己研究一下。

参考资料

转载于:https://juejin.im/post/5cd4e64c6fb9a031f61d922e

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值