zoukankan      html  css  js  c++  java
  • 创建产品族的方式——抽象工厂模式

    前言

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

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

    产品族概念

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

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

    2、产品的等级结构与产品族将产品按照不同方向划分,形成一个二维的坐标系。

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

    3、只要指明一个产品所处的产品族以及它所属的等级结构,就可以唯一的确定这个产品。

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

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

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

    引入抽象工厂模式

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

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

    1、抽象工厂模式是所有形态的工厂模式中最为抽象和一般性的。

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

    抽象工厂模式实现

    水果厂有进口水果,国产水果两个产品族,而具体获得哪个产品族是客户端调用决定的。比如,进口苹果,进口橘子,国产苹果,国产橘子等……苹果,橘子是产品族(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();
        }
    }

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

    类图如下

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

    2、产品族,简单理解就是不同的产品类型

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

    抽象工厂模式的优缺点

    优点

    1、分离接口和实现

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

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

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

    缺点

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

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

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

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

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

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

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

    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的解析器……),只需要更改系统变量的值,而不用更改任何代码。这就是抽象工厂所带来的好处,非常巧妙。

    欢迎关注

    dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

  • 相关阅读:
    如何卸载服务(转)
    The Frightening Science of Prediction: How Target & 10 Others Make Money Predicting Your Next Life Event(转摘)
    如果你迷恋厚实的屋顶,就会失去浩瀚的繁星
    李开复:移动互联网创业不要总是“入口思维”(转)
    [微言]增长与幸福 zz
    缔元信
    Tabledriven Approach
    北京医院排名 很有用,留下了
    青春期企业如何突围(转)
    意大利罗马&佛罗伦萨 攻略
  • 原文地址:https://www.cnblogs.com/kubixuesheng/p/10344434.html
Copyright © 2011-2022 走看看