建立产品族的方式——抽象工厂模式

前言

继续接 建立多个“产品”的方式——工厂方法模式总结html

如今又有了新的需求:果厂里新进了一批进口水果:进口香蕉,进口苹果,进口梨,一样的是须要采集水果,以前的程序只是对工厂进行了抽象,使得不一样的产品对应各自的工厂,而产品仅是国内水果,如今涉及到了进口水果,如今有了两大类的产品,每一个产品又分为不一样的等级,咱们叫它产品族。java

产品族概念

所谓产品族,是指位于不一样产品等级结构中,功能相关联的产品组成的家族。apache

一、每个产品族中含有产品的数目与产品等级结构的数目是相等的编程

二、产品的等级结构与产品族将产品按照不一样方向划分,造成一个二维的坐标系。设计模式

横轴表示产品的等级结构,纵轴表示产品族。app

三、只要指明一个产品所处的产品族以及它所属的等级结构,就能够惟一的肯定这个产品。ide

一、国产水果和进口水果就是产品族(纵坐标)post

二、苹果,香蕉,鸭梨等,就是产品的等级结构,具体每一个产品族的等级ui

这个业务就相对复杂了,因此以前的模式就变得很差用。若是硬要使用,那就是在具体的苹果工厂类里再增长一个新的方法——返回进口苹果类的实例,同时也为进口苹果增长对应的进口苹果类,同理对于香蕉,鸭梨也是同样的,好像也没什么问题。url

引入抽象工厂模式

如今需求又变了,果园厂增长了温室种植技术,有了一种新的产品——温室种植水果。如何写代码?

按照工厂方法模式的写法,天然就要在具体的苹果工厂类里再增长一个新的方法——返回温室苹果类的实例,同时也要增长温室苹果这个新的产品……其余水果工厂类是同样的作法。这明显违背了 OCP 原则,并且苹果工厂类既能生成进口苹果也能生产国产苹果,违背了单一职责原则。此时抽象工厂模式就派上了用场。

一、抽象工厂模式是全部形态的工厂模式中最为抽象和通常性的。

二、抽象工厂模式能够向客户端提供一个接口,使得客户端在没必要指定产品的具体类型的状况下,可以建立多个产品族的产品对象。

抽象工厂模式实现

水果厂有进口水果,国产水果两个产品族,而具体得到哪一个产品族是客户端调用决定的。好比,进口苹果,进口橘子,国产苹果,国产橘子等……苹果,橘子是产品族(y轴)拥有的产品等级(x轴),客户端能够调用某个产品族的某个产品,以前的工厂方法模式,就对应一个产品族的设计模式,只有一个水果工厂去维持各水果实体类(产品等级),只有x轴,没有y轴,客户端采集进口苹果,调用的是原先国产苹果工厂里新增长的进口苹果生产方法……很别扭

采用抽象工厂模式,就须要维持一个 y 轴,解耦各个产品族,提供一个接口给客户端,让客户端能在不指定具体类型的前提下,建立多个产品族……

代码以下:

一个水果的接口,维持一个得到水果的规则,全部产品等级对象的父类(接口),它负责描述全部实例所共有的公共接口

public interface Fruit {
    void get();
}

对应产品等级的结构:苹果和香蕉组成一个产品族的产品等级,这里升华为水果的抽象类,是具体产品的父类

public abstract class AppleA implements Fruit {
    // 由于横向x轴的产品等级,有苹果,香蕉,可是多了纵向的其余产品族的苹果,香蕉,那么产品的抽象要进一步体现出来,苹果类变为
    // 抽象基类,分别去维持多个和苹果相关的产品族对应的产品等级
    public abstract void get();
}

public abstract class BananaA implements Fruit {
    public abstract void get();
}

具体的产品

public class ForeignApple extends AppleA {
    @Override
    public void get() {
        System.out.println("进口苹果");
    }
}

public class ForeignBanana extends BananaA {
    @Override
    public void get() {
        System.out.println("进口香蕉");
    }
}

public class HomeApple extends AppleA {
    @Override
    public void get() {
        System.out.println("国产苹果");
    }
}

public class HomeBanana extends BananaA {
    @Override
    public void get() {
        System.out.println("国产香蕉");
    }
}

抽象工厂类——抽象工厂模式的核心,包含对多个产品等级结构的声明,任何具体工厂类都必须实现这个接口

// 一个抽象的工厂类(这里是接口)去维持产品族——y轴
public interface FruitFactory {
    // 每个工厂子类(产品族)都有对应的得到产品等级的方法——x轴
    Fruit getApple();
    Fruit getBanana();
}

具体工厂类是抽象工厂的一个实现,负责实例化某个产品族中的产品等级的对象

public class ForeignFruitFactory implements FruitFactory {
    @Override
    public Fruit getApple() {
        return new ForeignApple();
    }

    @Override
    public Fruit getBanana() {
        return new ForeignBanana();
    }
}

public class HomeFruitFactory implements FruitFactory {
    @Override
    public Fruit getApple() {
        return new HomeApple();
    }

    @Override
    public Fruit getBanana() {
        return new HomeBanana();
    }
}

客户端调用

public class Main {
    public static void main(String[] args) {
        // 得到某一个产品族
        FruitFactory fruitFactory = new ForeignFruitFactory();

        // 得到该产品族下的产品等级
        Fruit apple = fruitFactory.getApple();
        apple.get();
        Fruit banana = fruitFactory.getBanana();
        banana.get();

        // 得到国产水果产品族
        FruitFactory homeFruitFactory = new HomeFruitFactory();
        // 得到该产品族下的产品等级
        Fruit apple1 = homeFruitFactory.getApple();
        apple1.get();
        Fruit banana1 = homeFruitFactory.getBanana();
        banana1.get();
    }
}

当之后引入温室水果的时候,除了必需要创建温室苹果类,温室香蕉类去继承对应的水果抽象类以外,只须要再创建一个温室水果工厂类去实现抽象工厂接口便可,已经存在的代码不须要修改。

类图以下

一、抽象工厂模式中的方法对应产品等级结构,具体子工厂对应不一样的产品族。

二、产品族,简单理解就是不一样的产品类型

三、产品等级结构,简单理解就是一个产品类型里的具体的产品

抽象工厂模式的优缺点

优势

一、分离接口和实现

客户端使用抽象工厂来建立须要的对象,而客户端根本就不知道具体的实现是谁,客户端只是面向产品的接口编程而已。也就是说,客户端从具体的产品实现中解耦

二、使切换产品族变得容易

因一个具体的工厂实现表明的是一个产品族,切换产品族只须要切换一下具体工厂

缺点

不太容易扩展新的产品,如须要给整个产品族添加一个新的产品,那么就须要修改抽象工厂(增长新的接口方法),这样就会致使修改全部的工厂实现类。好比增长橘子这个产品等级……

也就是说,纵向不怕扩展,横向不方便扩展

抽象工厂模式和工厂方法模式对比

抽象工厂模式与工厂方法模式的最大区别就在于,工厂方法模式针对的是一个产品等级结构(苹果,鸭梨,橘子……)

而抽象工厂模式则须要面对多个产品等级结构(进口、国产、温室栽培……的苹果,鸭梨,橘子……)

什么状况下使用抽象工厂模式?

系统的产品有多于一个的产品族,而系统只消费其中某一族的产品

JDK中使用抽象工厂模式的例子

常见有:DocumentBuilderFactory 使用了抽象工厂模式:使程序可以从 XML 文档获取生成 DOM 对象树的解析器

DOM:Document Object Model 的缩写,即文档对象模型。XML将数据组织为一颗树,因此 DOM 就是对这颗树的一个对象描叙。

通俗的说是经过解析 XML 文档,为 XML 文档在逻辑上创建一个树模型,树的节点是一个个对象。经过存取这些对象就可以存取 XML 文档的内容。

咱们来看一个简单的例子,看看 DocumentBuilderFactory 是如何使用的抽象工厂模式来操做一个 XML 文档的。这是一个XML文档:

<?xml version="1.0" encoding="UTF-8"?>
<messages>
  <message>Good-bye serialization, hello Java!</message> 
</messages>

把这个文档的内容解析到 Java 对象,供程序使用。

首先须要 DocumentBuilderFactory 创建一个解析器工厂,利用这个工厂来得到一个具体的解析器对象,获取 DocumentBuilderFactory 的新实例。用下面这个 static 方法建立一个新的工厂实例

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 

newInstance 方法源码以下

public static DocumentBuilderFactory newInstance() {
        return FactoryFinder.find(
                /* The default property name according to the JAXP spec */
                DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
                /* The fallback implementation class name */
                "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
}

在这里使用 DocumentBuilderFacotry 抽象类的目的是为了建立与具体解析器无关的程序,当 DocumentBuilderFactory 类的静态方法 newInstance() 被调用时,它根据一个系统变量来决定具体使用哪个解析器。

当得到一个 DocumentBuilderFactory 工厂对象后,使用它的静态方法 newDocumentBuilder() ,能够得到一个 DocumentBuilder 对象。

DocumentBuilder db = dbf.newDocumentBuilder();  

DocumentBuilder,也就是 db 这个对象,表明了具体的 DOM 解析器(具体的某个产品族),但具体是哪种解析器,好比微软的或者IBM的,对于程序而言并不重要。

获取此类实例以后,将能够利用这个解析器来对XML文档进行解析:

Document doc = db.parse("xxx.xml");  

这个解析器能够从各类输入源解析 XML,这些输入源有 InputStreams、Files、URL 和 SAX InputSources,这些输入源就是产品等级(具体的产品),不一样的解析器(实现)就是产品族,又由于全部的解析器都服从于 JAXP 所定义的接口,因此不管具体使用哪个解析器,调用者的(客户端)代码都是同样的。

当在不一样的解析器之间进行切换时(各个解析器就是不一样的产品族,好比有微软的解析器,有IBM的解析器……),只须要更改系统变量的值,而不用更改任何代码。这就是抽象工厂所带来的好处,很是巧妙。

相关文章
相关标签/搜索