(五)抽象工厂模式详解

2年前 (2022) 程序员胖胖胖虎阿
333 0 0

            作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可。

            前两章我们已经讨论了两种有关工厂的模式,今天我们来看最后一种与工厂相关的模式,抽象工厂模式。

            抽象工厂模式算是工厂相关模式的终极形态,如果各位完全理解了上一章的工厂方法模式,那么抽象工厂模式就很好理解了。它与工厂方法唯一的区别就是工厂的接口里是一系列创造抽象产品的方法,而不再是一个,而相应的,抽象产品也不再是一个了,而是一系列相关的产品。这其实是工厂方法模式的一种扩展不是吗?

            通常意义来我们谈到扩展,通常有两种方式可以扩展一个接口或者类,就是继承和组合。

            通常情况下,我们推荐使用组合扩展一个现有的类或接口,但这并非绝对,如果你扩展的子类或子接口与现有的类或接口明显是“是一个(is a)”的关系,也就是继承的关系,那么使用继承可以获得更多的好处。

            下面我们就首先来看一下抽象工厂模式的定义以及类图,全部引自百度百科。

            定义:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。

            定义中说了,我们是要创建一个接口, 而这个接口是干嘛的呢,前面说了,是为了创建一组相关或者相互依赖的对象,而且还有一点就是,我们创建的对象不是具体的类,也就是说我们创建的是一个接口或者一个抽象类。

            下面我们来看看抽象工厂模式的类图。

(五)抽象工厂模式详解

            我们对比下刚才的定义,LZ给各位分析下上面的类图,首先刚才说了,我们要创建一个接口,这个接口就是指的Creator,而一组相关或者相互依赖的对象,就是指的ProductA和ProductB以及它们具体的实现类,而上面又提到说不是返回的具体的类,所以我们返回的应该是接口或者抽象类,那么在上述类图当中,则是指的ProductA和ProductB接口。

            下面LZ将上述类图诠释成容易理解的JAVA代码,供各位参考。

            首先给出我们的产品族,也就是类图中右半部分。

package net;

interface ProductA {

    void methodA();
}

interface ProductB {
    
    void methodB();
}

class ProductA1 implements ProductA{

    public void methodA() {
        System.out.println("产品A系列中1型号产品的方法");
    }
    
}

class ProductA2 implements ProductA{

    public void methodA() {
        System.out.println("产品A系列中2型号产品的方法");
    }
    
}

class ProductB1 implements ProductB{

    public void methodB() {
        System.out.println("产品B系列中1型号产品的方法");
    }
    
}

class ProductB2 implements ProductB{

    public void methodB() {
        System.out.println("产品B系列中2型号产品的方法");
    }
    
}

            结构比较清晰,下面是类图中左半部分,首先给出工厂接口。

package net;

public interface Creator {

    ProductA createProductA();
    
    ProductB createProductB();
    
}

            下面是两个具体的工厂实现类。

package net;

public class ConcreteCreator1 implements Creator{

    public ProductA createProductA() {
        return new ProductA1();
    }

    public ProductB createProductB() {
        return new ProductB1();
    }

}
package net;

public class ConcreteCreator2 implements Creator{

    public ProductA createProductA() {
        return new ProductA2();
    }

    public ProductB createProductB() {
        return new ProductB2();
    }

}

            这样我们的类图代码就实现完毕,下面我们写一个测试类,去调用一下,感受一下抽象工厂模式的客户端调用方式。

package net;


public class Client {

    public static void main(String[] args) throws Exception {
        Creator creator = new ConcreteCreator1();
        ProductA productA = creator.createProductA();
        ProductB productB = creator.createProductB();
        productA.methodA();
        productB.methodB();
        
        creator = new ConcreteCreator2();
        productA = creator.createProductA();
        productB = creator.createProductB();
        productA.methodA();
        productB.methodB();
    }
}

            在过程当中,我们切换过一次工厂实现类,而下面的代码是一模一样的,但是我们使用的就是另一套产品实现体系了,我们看运行结果。

(五)抽象工厂模式详解

            上面的代码比较简单,结构很清晰但不太容易理解,因为它全部是抽象的表示,与实际联系不上,所以也会对各位的理解造成阻碍,下面我们就一起讨论一个现有的例子,去加深去抽象工厂模式的理解。

            上一章我们介绍了iterable接口,它可以制作iterator,iterator方法是一个工厂方法,用于让子类制作一系列的iterator,不过java集合框架一般都将iterator的实现作为内部类出现,所以我们从未见过LZ上章提到的ListIterator和KeyIterator的实现类,但它们确实存在于JAVA的集合框架,并且它们的实现类被封装在相应的抽象类或者具体的容器实现类中。

            oracle公司为何不让我们看到这些iterator的实现类呢?其实原因很简单,一是怕我们在写程序的时候依赖于这些iterator的实现类,二是这些迭代器的实现都要依赖于当前的容器实现,我们假设有一天JDK中的集合框架要升级,要替换掉某个iterator的实现,换做一种更快的迭代方式(假设存在这种方式),那么以前使用特定迭代器的程序可能就无法正常运行了。当然大部分的情况下,oracle不会将现有的类剔除,但是会加上@Deprecated注解,来标识这是一个过时的东西,不再推荐你使用。但就算是这样,还是有缺点,就是JDK升级以后,你享受不到JDK集合框架速度上的提升,除非你将所有你使用过具体的Iterator的地方全部手动替换掉。

            上述大致描述了下集合框架设计时对iterator处理方式的初衷,从中可以看出抽象工厂模式就是为了解决抽象产品不再是一个的时候的问题。因为不管是简单工厂,还是工厂方法,都有一个缺陷,那就是整个模式当中只能有一个抽象产品,所以直观的,你在工厂方法模式中再添加一个创造抽象产品的方法就是抽象工厂模式了,相应的当然还有添加一个抽象产品,还有一系列具体的该抽象产品的实现。

            在集合框架里,有一个不太明显的抽象工厂模式,就是List接口,它在iterable的基础上,扩展了一个创建产品的方法,本次以List接口为例,我们来看看List接口的源码。

package java.util;

public interface List<E> extends Collection<E> {
    
    Iterator<E> iterator();//一种产品

    Object[] toArray();

    <T> T[] toArray(T[] a);

    ListIterator<E> listIterator();//另外一种产品

    ListIterator<E> listIterator(int index);

}

               LZ去掉了List接口中的很多方法,一是为了节省版面,另外是为了更清晰,我们主要关注iterator和listIterator方法,LZ在上面加了标注。

               其中ListIterator是Iterator的子接口,但归根到底,它其实属于另外一种产品,为什么这么说呢,ListIterator不是Iterator的子接口吗,怎么能算是另外一种产品呢?这是因为我们listIterator方法的返回类型是ListIterator,而不是Iterator,所以两者的功能是不同的,比如ListIterator还可以向前移动。

               我们可以认为这两个方法产生的一个是只能向后移动的迭代器,一个是可以前后移动的迭代器,这算是两种产品,相当于上面的ProductA和ProductB。

               这个设计可以看做是一个抽象工厂模式,List接口定义了两种生产不同产品的方法,这属于两个系列的产品,不过由于产品接口本身的继承关系,两者的实现类也会被做成继承的关系。下面给出上面提到的接口的UML图。

(五)抽象工厂模式详解

                 这个图看起来有点复杂,各位可以和上面标准的抽象工厂模式类图对比一下,下面LZ来解释一下在抽象工厂模式当中,上述几个类都代表的什么角色。


                 1.List,是抽象工厂的角色,它有两个制造产品的方法,iterator和listIterator,相当于Creator。


                 2.ListIterator和Iterator都是抽象产品,相当于ProductA和ProductB。其中ListIterator有两个实现类,分别是AbstractList.ListItr和LinkedList.ListItr,相当于ProductA1和ProductA2。Iterator的实现类为AbstractList.Itr,相当于ProductB1,但是没有B2。


                 3.LinkedList是其中一个具体的工厂类,相当于ConcreteCreator1,实现抽象工厂List,它制造的两个具体产品分别是LinkedList.ListItr和AbstractList.Itr。


                 4.同样的,ArrayList也是一个具体的工厂类,相当于ConcreteCreator2,实现抽象工厂List,它制造的两个具体产品分别是AbstractList.ListItr和AbstractList.Itr。


                结合上一章工厂方法模式,我们来分析一下工厂方法模式和抽象工厂模式二者的关系。


                Iterable接口是List的父接口,所以它只负责一个产品Iterator的制造,所以是工厂方法模式,而List接口扩展了Iterable接口,又添加了一个制造产品的方法,即又添加了一个系列的产品,所以就成为了抽象工厂模式。


              LZ下面给出上述两个类图的对应关系,会让各位看的更加清晰:


              1.Creator=List


              2.ConcreteCreator1=ArrayList


              3.ConcreteCreator2=LinkedList


              4.ProductA=Iterator


              5.ProductB=ListIterator


              6.ProductA1=AbstractList.Itr


              7.ProductA2=无(具体的A产品2在第一个类图中是没有的,但这并不影响整个体系)


              8.ProductB1=AbstractList.ListItr


              9.ProductB2=LinkedList.ListItr


              ArrayList和LinkedList分别是List接口的两种实现,前者是基于数组操作,后者是基于链表。两者都可以产生Iterator和ListIterator,而Iterator的实现都是在AbstractList中实现的,是一样的处理方式,而对于ListIterator的实现却不相同,AbstractList.ListItr是基于数组的操作,LinkedList.ListItr是基于链表的操作方式。


              所以抽象工厂模式一般是为了处理抽象产品多于一个的问题,而且这些产品多数情况下是有关系的,像上述JAVA集合框架的例子当中,Iterator和ListIterator就是继承的关系,大部分情况下,很少会使用抽象工厂模式去创造一批毫无关系的产品。


              基于抽象工厂一旦定义,抽象产品的个数就已经固定,所以最好在抽象产品的个数不太会变化的情况下使用抽象工厂模式,当然,我们可以使用继承去弥补抽象工厂模式的这一不足,创造另外一个继承体系去扩展现有的框架。


              下面LZ给出简单工厂模式,工厂方法模式一直到抽象工厂模式的演变过程,三者是由简到繁的关系。由于三者都已经详细的解释过,所以此处不再多做解释,留给各位读者自己思考它们的进化过程,首先LZ给出简单工厂的具体代码。

//抽象产品
interface Product{}

//具体产品
class ProductA implements Product{}
class ProductB implements Product{}

//产品工厂(下一步就是它的进化,就变成了工厂方法模式)
public class ProductFactory {

    private ProductFactory(){}
    
    public static Product getProduct(String productName){
        if (productName.equals("A")) {
            return new ProductA();
        }else if (productName.equals("B")) {
            return new ProductB();
        }else {
            return null;
        }
    }
}

               LZ在上面加了简单的注释,下面LZ给出工厂方法模式的代码,注意,前面有关产品的类和接口是不变的。

//抽象产品
interface Product{}

//具体产品
class ProductA implements Product{}
class ProductB implements Product{}

//将简单工厂中的工厂给抽象成接口
interface Factory{
    Product getProduct();
}
//具体的工厂A,创造产品A
class FactoryA implements Factory{

    public Product getProduct() {
        return new ProductA();
    }
    
}
//具体的工厂B,创造产品B
class FactoryB implements Factory{

    public Product getProduct() {
        return new ProductB();
    }
    
}

                  可以看到,产品部分并没有变化,只是将简单工厂中的工厂类抽象成接口,并给相应产品添加相应的工厂类,就进化成了工厂方法模式。下面我们再看工厂方法如何进化成抽象工厂模式。

//抽象产品
interface Product{}

//具体产品
class ProductA implements Product{}
class ProductB implements Product{}

//多了一个抽象产品1
interface Product1{}

//具体产品1
class Product1A implements Product1{}
class Product1B implements Product1{}

//原有的工厂方法模式的工厂里添加一个方法
interface Factory{
    Product getProduct();
    //添加另外一个产品族的创造方法
    Product1 getProduct1();
}
//具体的工厂A,创造产品A
class FactoryA implements Factory{

    public Product getProduct() {
        return new ProductA();
    }
    //添加相应的实现
    public Product1 getProduct1() {
        return new Product1A();
    }
    
}
//具体的工厂B,创造产品B
class FactoryB implements Factory{

    public Product getProduct() {
        return new ProductB();
    }
    //添加相应的实现
    public Product1 getProduct1() {
        return new Product1B();
    }
    
}

                  与工厂方法对比下就发现,多了一个产品系列叫Product1,工厂接口里多了一个方法,叫getProduct1,所以抽象工厂模式就是工厂方法模式添加了抽象产品所演变而来的。

                  有关工厂的三个模式到这里就全部介绍完了,三者有着很大的关联和明显的关系,要想灵活运用这三种设计模式,还是要彻底理解它们所针对的问题以及三者的关系。下面罗列下这三种设计模式依次进化的原因。


                  1,首先从简单工厂进化到工厂方法,是因为工厂方法弥补了简单工厂对修改开放的弊端,即简单工厂违背了开闭原则。


                  2,从工厂方法进化到抽象工厂,是因为抽象工厂弥补了工厂方法只能创造一个系列的产品的弊端。


                  各位可以思考下,假设我们不使用抽象工厂模式,改用工厂方法去处理抽象工厂中多产品的问题,如何处理呢?其实很简单,就是有几个产品系列,我们就造几个工厂方法模式就可以了,只不过这样处理未免太不优雅,就像下面这样。

//抽象产品
interface Product{}

//具体产品
class ProductA implements Product{}
class ProductB implements Product{}

//工厂接口
interface Factory{
    Product getProduct();
}

//具体的工厂A,创造产品A
class FactoryA implements Factory{

    public Product getProduct() {
        return new ProductA();
    }
    
}
//具体的工厂B,创造产品B
class FactoryB implements Factory{

    public Product getProduct() {
        return new ProductB();
    }
    
}

/*   以上是一个产品的工厂方法  */

//抽象产品1
interface Product1{}

//具体产品1
class Product1A implements Product1{}
class Product1B implements Product1{}

//工厂接口1
interface Factory1{
    Product1 getProduct1();
}

//具体的工厂1A,创造产品1A
class Factory1A implements Factory1{

    public Product1 getProduct1() {
        return new Product1A();
    }
    
}
//具体的工厂1B,创造产品1B
class Factory1B implements Factory1{

    public Product1 getProduct1() {
        return new Product1B();
    }
    
}

                以上用两个工厂方法模式,代替了抽象工厂模式,那么可想而知,假设又多了一个产品Product2,那么我们还需要再建立一套工厂方法模式,这显然会大大增加系统的复杂性,而且也不易于客户端操作。

                不过这也不一定就不可以,假设我们上面Product和Factory接口包括它们的一套实现是现有的,并且我们无法改变,比如是一个第三方的jar包提供的。那么为了扩展这个第三方的jar包,我们可以将jar包中的工厂方法模式扩展成为抽象工厂来达到我们扩展现有类功能的目的,就像下面这样。

//抽象产品
interface Product{}

//具体产品
class ProductA implements Product{}
class ProductB implements Product{}

//工厂接口
interface Factory{
    Product getProduct();
}

//具体的工厂A,创造产品A
class FactoryA implements Factory{

    public Product getProduct() {
        return new ProductA();
    }
    
}
//具体的工厂B,创造产品B
class FactoryB implements Factory{

    public Product getProduct() {
        return new ProductB();
    }
    
}

/*   假设以上是一个第三方jar包中的工厂方法模式,我们无法改动源码   */

//我们自己特有的产品
interface MyProduct{}

//我们自己特有的产品实现
class MyProductA implements MyProduct{}
class MyProductB implements MyProduct{}

//扩展原有的工厂接口
interface MyFactory extends Factory{
    MyProduct getMyProduct();
}

//我们自己特有的工厂A,扩展自原有的工厂A,并且实现获得我们自己特有产品的接口方法
class MyFactoryA extends FactoryA implements MyFactory{

    public MyProduct getMyProduct() {
        return new MyProductA();
    }
    
}
//同A
class MyFactoryB extends FactoryB implements MyFactory{

    public MyProduct getMyProduct() {
        return new MyProductB();
    }
    
}

                  这样我们就可以得到我们自己特有的抽象工厂和使用我们自己特有的产品了,并且我们自己的抽象工厂还兼并了第三方jar包中的产品,例如,我们可以使用MyFactoryA获得jar包中的ProductA产品等。

                  上面的做法相当于我们从现有的体系当中,扩展出一套我们自己的继承体系,这样做的好处是我们可以完整的复用jar包中的各个类功能,缺点是继承会导致系统的复杂性增加,耦合度相对较高。

                  所以我们还可以有另外一种做法,就是创造我们自己的一套独有的工厂方法模式,这套体系与jar包中的类和接口毫无关系,我们再使用一个组合工厂将二者结合起来,就像下面这样。

//抽象产品
interface Product{}

//具体产品
class ProductA implements Product{}
class ProductB implements Product{}

//工厂接口
interface Factory{
    Product getProduct();
}

//具体的工厂A,创造产品A
class FactoryA implements Factory{

    public Product getProduct() {
        return new ProductA();
    }
    
}
//具体的工厂B,创造产品B
class FactoryB implements Factory{

    public Product getProduct() {
        return new ProductB();
    }
    
}

/*   假设以上是一个第三方jar包中的工厂方法模式,我们无法改动源码   */

//我们自己特有的产品
interface MyProduct{}

//我们自己特有的产品实现
class MyProductA implements MyProduct{}
class MyProductB implements MyProduct{}

//我们自己的工厂接口
interface MyFactory{
    MyProduct getMyProduct();
}

//我们自己特有的工厂A,产生产品A
class MyFactoryA implements MyFactory{
    
    public MyProduct getMyProduct() {
        return new MyProductA();
    }
    
}

//我们自己特有的工厂B,产生产品B
class MyFactoryB implements MyFactory{
    
    public MyProduct getMyProduct() {
        return new MyProductB();
    }
    
}

/*  到这里是我们自己的一套工厂方法模式,去创造我们自己的产品,以下我们将以上二者组合   */

//我们使用组合的方式将我们的产品系列和jar包中的产品组合起来
class AssortedFactory implements MyFactory,Factory{
    
    MyFactory myFactory;
    Factory factory;
    
    public AssortedFactory(MyFactory myFactory, Factory factory) {
        super();
        this.myFactory = myFactory;
        this.factory = factory;
    }

    public Product getProduct() {
        return factory.getProduct();
    }

    public MyProduct getMyProduct() {
        return myFactory.getMyProduct();
    }
    
}

                    可以看到,组合的工厂AssortedFactory集成了我们自己的工厂和jar包中的工厂两者的功能。这样做则会非常灵活,因为我们的一套体系不再依赖于jar包中的类或接口而存在,哪怕是jar包中的类改变或者不在了,我们自己的这一套依旧可以独立存在。

                    从上面就可以看出,我们在处理很多问题的时候其实是有很多种方式的,而且每一种方式可能都有各自的好处和坏处,很难去判断说那一种方式是最好的,而且也根本就没有这个说法,所以我们能做的,就是根据实际的情况去掂量各个方式的利弊,从而选择出一种更适合当前情况的处理方式。

                    本期抽象工厂模式就到这里了,希望各位看到这里对三个与工厂相关的模式有一定自己的理解,并且可以灵活使用,达到无模式就是最好的模式的境界,甚至,我们可以把我们自己写的东西起个名字,冒充设计模式,比如最后一种我们叫它组合工厂模式。当然,LZ本人并未达到无模式的境界,正在与各位一起努力。

                    感谢各位的收看。

                    下期预告,目测是观察者模式。


                  


                     

版权声明:程序员胖胖胖虎阿 发表于 2022年9月26日 上午1:08。
转载请注明:(五)抽象工厂模式详解 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...