抽象工厂(AbstractFactory)模式

摘要:
抽象工厂模式也称为工具箱模式。这也是抽象工厂模式的意图。UnixFactory对象创建Unix产品系列产品,WinFactory负责Windows产品系列产品。抽象工厂模式涉及以下角色:抽象工厂角色:工厂方法模式的核心扮演这个角色,它独立于应用程序系统的业务逻辑。

  抽象工厂模式又称工具箱模式。其实抽象工厂模式可以简单的理解为一个工厂生成一个产品族的产品。

  抽象工厂模式可以向客户端提供一个接口,使得客户端在不指定产品的具体类型的情况下,创建多个产品族中的产品对象。这也是抽象工厂模式的用意。

  抽象工厂模式面对的是一个产品等级结构的系统设计。

  抽象工厂模式和工厂模式最大的区别就是:工厂模式针对的是一个产品等级结构,而抽象工厂针对的是多个产品等级结构。

产品族:

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

    抽象工厂(AbstractFactory)模式第1张

抽象工厂模式:

  抽象工厂模式最早的应用是用于创建分属于不同操作系统的视窗构建。比如:命令按键(Button)与文字框(Text)都是视窗构件,在Unix和Windows操作系统的视窗环境中,这两个构件有不同的本地实现,它们的细节也有所不同。

其产品如下:

抽象工厂(AbstractFactory)模式第2张

  可以发现在上面图中有两个产品的等级结构,分别是Button等级结构和Text等级结构。同时有两个产品族,也就是Unix产品族和Windows产品族。Unix产品族由UnixButton和UnixText构成,Windows产品族由WinButton和WinText构成,相图描述如下:

抽象工厂(AbstractFactory)模式第3张

  系统对产品对象的创建需求由一个工厂的等级满足,其中有两个具体工厂角色,UnixFactory和WinFactory。UnixFactory对象创建Unix产品族的产品,WinFactory负责Windows产品族的产品。这就是抽象工厂模式的应用。对应图如下:

抽象工厂(AbstractFactory)模式第4张 

用工厂模式解决上面问题:

  试想如果我们用工厂模式,就需要写多个具体的工厂(UnixButtonFactory、UnixTextFactory、WinButtonFactory、WinTextFactory,如果将来产品等级增多就需要更多的具体工厂),每个工厂生产对应的产品,如果需要切换产品族需要切换整个产品族对应的工厂。比如原来生产的是Unix产品,使用的是UnixButtonFactory、UnixTextFactory,如果现在想切换Windows系列的产品就需要改成WinButtonFactory、WinTextFactory(如果产品等级增加需要切换更多的工厂)。而且增加新的产品族需要编写更多的工厂。

抽象工厂模式涉及到以下角色:

抽象工厂(Abstract Factory)角色:担任这个角色的是工厂方法模式的核心,它是与应用系统的商业逻辑无关的。通常使用Java接口或者类来实现,所有的具体工厂必须实现这个接口或继承这个抽象类。

具体工厂(Concrete Factory)类:这个角色直接在客户端的调用下创建产品的实例。这个角色含有选择合适产品的逻辑,而这个逻辑是与应用系统的商业逻辑紧密相关的。

抽象产品(Abstract Product):担任这个角色的类是工厂方法模式所创建的对象的父类或它们共同拥有的接口。

具体产品(Concrete Product):由具体工厂创建的具体产品。这是客户端需要的东西,与与应用系统的商业逻辑紧密相关的。

UML类图如下:

抽象工厂(AbstractFactory)模式第5张

源码如下:

package cn.qlq.absfactory;

public interface Button {

    void clickedCutton();
}
package cn.qlq.absfactory;

public class UnixButton implements Button {

    @Override
    public void clickedCutton() {
        System.out.println("点击 UnixButton ");
    }

}
package cn.qlq.absfactory;

public class WinButton implements Button {

    @Override
    public void clickedCutton() {
        System.out.println("点击 WinButton ");
    }

}
package cn.qlq.absfactory;

public interface Text {

    void clickedText();
}
package cn.qlq.absfactory;

public class UnixText implements Text {

    @Override
    public void clickedText() {
        System.out.println("点击 UnixText ");
    }

}
package cn.qlq.absfactory;

public class WinText implements Text {

    @Override
    public void clickedText() {
        System.out.println("点击 WinText ");
    }

}
package cn.qlq.absfactory;

public interface AbstractFactory {

    Button createButton();

    Text createText();
}
package cn.qlq.absfactory;

public class UnixFactory implements AbstractFactory {

    @Override
    public Button createButton() {
        return new UnixButton();
    }

    @Override
    public Text createText() {
        return new UnixText();
    }

}
package cn.qlq.absfactory;

public class WinFactory implements AbstractFactory {

    @Override
    public Button createButton() {
        return new WinButton();
    }

    @Override
    public Text createText() {
        return new WinText();
    }

}

测试代码:

package cn.qlq.absfactory;

public class MainClass {

    public static void main(String[] args) {
        AbstractFactory unixFactory = new WinFactory();
        
        Button button = unixFactory.createButton();
        Text text = unixFactory.createText();
        
        button.clickedCutton();
        text.clickedText();
    }

}

 补充:如果有必要可以增加一个组合器进行组装生产工厂生产的产品,如下:

package cn.qlq.absfactory;

public class Combiner {

    public Combiner(AbstractFactory factory) {
        // 生产产品
        factory.createButton();
        factory.createText();

        // 用产品做后续处理
    }
}

适用场景:

1.一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。

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

3.同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。

4.系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。

优缺点:

 优点:

分离接口和实现:

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

使切换产品族和增加产品组变得容易:

  因为一个具体的工厂实现代表的是一个产品族,比如上面例子的从windows系列产品到unix系列产品只需要切换一下具体工厂。增加一系列产品也容易,比如增加一个mac系列产品则需要增加对应的mac产品与对应的macfactory即可。

 缺点:

 增加和减少产品等级结构不容易:

  如果需要给整个产品族添加一个新的产品,那么就需要修改抽象工厂,这样就会导致修改所有的工厂实现类。比如所有的产品都需要增加一个label,则需要增加一个label产品等级并修改工厂接口与修改所有的具体工厂。这也违反了开闭原则。

  综合起来,抽象工厂模式以一种倾斜的方式支持增加新的产品,它为新产品族的增加提供便利,而不能为产品等级结构的增加提供便利。

利用简单工厂改造上面的抽象工厂:

  我们尝试利用简单工厂改造上面的抽象工厂。UML类图如下:

抽象工厂(AbstractFactory)模式第6张

 产品类代码同上,工厂类代码如下:

package cn.qlq.simplefactory;

public class ProductFactory {

    private String productName;

    public ProductFactory(String productName) {
        this.productName = productName;
    }

    public Button createButton() {
        Button button = null;
        switch (productName) {
        case "Win":
            button = new WinButton();
            break;
        case "Unix":
            button = new UnixButton();
            break;
        default:
            break;
        }
        return button;
    }

    public Text createText() {
        Text text = null;
        switch (productName) {
        case "Win":
            text = new WinText();
            break;
        case "Unix":
            text = new UnixText();
            break;
        default:
            break;
        }
        return text;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

}

测试代码:

package cn.qlq.simplefactory;

public class MainClass {

    public static void main(String[] args) {
        String productName = "Win";
        ProductFactory productFactory = new ProductFactory(productName);

        Button button = productFactory.createButton();
        Text text = productFactory.createText();

        button.clickedCutton();
        text.clickedText();
    }

} 

  上面有个缺点就是增加产品族的时候需要修改工厂

 利用反射+简单工厂改造:

 修改工厂类采用反射创建对象:

  这里需要遵循一个约定:产品族的名称 + 抽象产品类名称构成具体类名称,比如产品族为Win,抽象产品为Button,则具体类为WinButton。

package cn.qlq.simplefactory;

public class ProductFactory {

    private String productName;

    public ProductFactory(String productName) {
        this.productName = productName;
    }

    public Button createButton() {
        String packageName = "cn.qlq.simplefactory";
        Button button = null;
        try {
            Class clazz = Class.forName(packageName + "." + productName + "Button");
            button = (Button) clazz.newInstance();
        } catch (Exception e) {
            // 记录日志
            throw new RuntimeException("非法参数异常");
        }

        return button;
    }

    public Text createText() {
        String packageName = "cn.qlq.simplefactory";
        Text text = null;
        try {
            Class clazz = Class.forName(packageName + "." + productName + "Text");
            text = (Text) clazz.newInstance();
        } catch (Exception e) {
            // 记录日志
            throw new RuntimeException("非法参数异常");
        }

        return text;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

}

  这种方式就比较灵活了,将来扩展新的产品族的时候也比较方便,无需工厂类。

  一般最常用的就是反射 + 简单工厂。 

免责声明:文章转载自《抽象工厂(AbstractFactory)模式》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Mysql 主从复制.net core 3.0 路由及区域路由与默认首页的配置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

PHP设计模式(三)抽象工厂模式(Abstract Factory For PHP)

一、什么是抽象工厂模式   抽象工厂模式的用意为:给客户端提供一个接口,可以创建多个产品族中的产品对象 ,而且使用抽象工厂模式还要满足以下条件: 系统中有多个产品族,而系统一次只可能消费其中一族产品。  同属于同一个产品族的产品可以使用。    产品族:位于不同产品等级结构中,功能相关联的产品组成的家族。下面例子的 汽车和空调就是两个产品树, 奔驰C2...

Android 自动生成表格

Layout.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layou...

安卓开发学习日记 DAY5——监听事件onClick的实现方法

今天主要学习了监听事件的是实现方法,就是说,做了某些动作后,怎么监听这个动作并作出相应反应。 方法主要有三种: 1.匿名内部类的方法 2.独立类的方法 3.类似实现接口的方法 以下分别分析: 1.匿名内部类的方法 就是使用innerClass的方式创建监听事件 步骤如下: 1)创建一个button,在xml中拖入一个button即可 2)在源程序中对but...

Vue 获取自定义属性的值

在jquery中,如果要获取 data-***的值可以通过$('目标元素').data('属性名')来获取。 在Vue中如何获取该值呢? 1.换个思路,作为参数传递。 如下代码: <button @click="say('Hi')">say hi</button> methods: { say(message){...

测试用例设计-电梯的测试用例

两部电梯的测试用例 界面测试: 外观(里面、外面)美观性 电梯空间尺寸是否和设计尺寸一致 按钮是否清晰和易懂 显示楼层的显示屏是否安装 是否联系外界的电话、紧急电话 设备检测说明书 安全规范说明书 灯 标识的承重和人数 扶手 镜子 仅提供可到达楼层的按钮 电梯制作的材料 空调 摄像头 功能测试: 测试电梯能否实现正常...

利用H5缓存机制实现点击按钮第一次与之后再点击分别跳转不同页面

昨天碰到这样一个需求,要求点击按钮第一次跳转到a页面,之后再点击它就跳转到b页面。这个问题我首先就想到了利用H5的缓存sessionstorage来实现,SessionStorage用于本地存储一个会话中的数据,窗口关闭后,数据就会消失。是一种会话级别的存储。 sessionStorage: sessionStorage.setItem("key","1"...