前言

本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址

1. 抽象工厂模式概述

工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中工厂类职责太重的问题,

但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销.

此时,我们可以考虑将一些相关的产品组成一个"产品族",由同一个工厂来统一生产.这就是抽象工厂模式的基本思想.

2. 模拟业务

开发一套界面皮肤库,用户在使用时可以通过菜单来选择皮肤,不同的皮肤将提供视觉效果不同的按钮、文本框、组合框等界面元素.

Java设计模式学习笔记(四) 抽象工厂模式-LMLPHP

2.1 结构图

Java设计模式学习笔记(四) 抽象工厂模式-LMLPHP

2.2 问题

使用大量工厂来创建具体的界面组件,可以通过配置文件更换具体界面组件从而改变界面风格.

但是此设计方案存在如下问题:

  • 增加系统的维护成本和运行开销
  • 客户端代码和配置文件较为复杂

2.2.1 增加系统的维护成本和运行开销

当需要增加新的皮肤时,虽然不要修改现有代码,但是需要增加大量类,针对每一个新增具体组件都需要增加一个具体工厂,类的个数成对增加,这无疑会导致系统越来越庞大,增加系统的维护成本和运行开销.

2.2.2 客户端代码和配置文件较为复杂

由于同一种风格的具体界面组件通常要一起显示,因此需要为每个组件都选择一个具体工厂,用户在使用时必须逐个进行设置,如果某个具体工厂选择失误将会导致界面显示混乱,虽然我们可以适当增加一些约束语句,但客户端代码和配置文件都较为复杂。

3. 产品等级结构与产品族

在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法具有唯一性,一般情况下,一个具体工厂中只有一个或者一组重载的工厂方法

但是有时候我们希望一个工厂可以提供多个产品对象,而不是单一的产品对象,如一个电器工厂,它可以生产电视机、电冰箱、空调等多种电器,而不是只生产某一种电器。

为了更好地理解抽象工厂模式,我们先引入两个概念:

  • 产品等级结构
  • 产品族

3.1 产品等级结构

产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。

3.2 产品族

在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品.

如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中,海尔电视机、海尔电冰箱构成了一个产品族。

4. 抽象工厂模式与工厂方法模式的区别

当系统所提供的工厂生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构、属于不同类型的具体产品时就可以使用抽象工厂模式。

抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形式。

抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、更有效率.

需要创建一个产品族的对象到时候,相较于工厂方法模式,抽象工厂模式可以极大的减少系统中类的个数

5. 抽象工厂模式

抽象工厂模式为创建一组对象提供了一种解决方案。与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,它负责创建一族产品

5.1 定义

抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,它是一种对象创建型模式。

5.2 结构图

在抽象工厂模式中,每一个具体工厂都提供了多个工厂方法用于产生多种不同类型的产品,

这些产品构成了一个产品族,抽象工厂模式结构图:

Java设计模式学习笔记(四) 抽象工厂模式-LMLPHP

5.3 角色

在抽象工厂模式结构图中包含如下几个角色:

  1. AbstractFactory(抽象工厂): 它声明了一组用于创建一族产品的方法,每一个方法对应一种产品。
  2. ConcreteFactory(具体工厂: 它实现了在抽象工厂中声明的创建产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中。
  3. AbstractProduct(抽象产品): 它为每种产品声明接口,在抽象产品中声明了产品所具有的业务方法。
  4. ConcreteProduct(具体产品: 它定义具体工厂生产的具体产品对象,实现抽象产品接口中声明的业务方法

5.4 代码

AbstractFactory(抽象工厂): 在抽象工厂中声明了多个工厂方法,用于创建不同类型的产品,抽象工厂可以是接口,也可以是抽象类或者具体类


/**

 * @author liuboren

 * @Title: 抽象工厂

 * @Description:

 * @date 2019/7/16 13:30

 */

public abstract class AbstractFactory {

    //工厂方法一

    public abstract AbstractProductA createProductA();

    //工厂方法二

    public abstract AbstractProductB createProductB();



}

ConcreteFactory(具体工厂): 具体工厂实现了抽象工厂,每一个具体的工厂方法可以返回一个特定的产品对象,而同一个具体工厂所创建的产品对象构成了一个产品族


/**

 * @author liuboren

 * @Title: 具体工厂类

 * @Description:

 * @date 2019/7/16 13:32

 */

public class ConcreteFactory1 extends  AbstractFactory {

    @Override

    public AbstractProductA createProductA() {

        return new ConcreteProductA1();

    }



    @Override

    public AbstractProductB createProductB() {

        return new ConcreteProductB1();

    }

}

AbstractProduct(抽象产品):


/**

 * @author liuboren

 * @Title: 抽象产品A

 * @Description:

 * @date 2019/7/16 13:31

 */

public class AbstractProductA {

}


ConcreteProduct(具体产品):


/**

 * @author liuboren

 * @Title: 具体产品A1

 * @Description:

 * @date 2019/7/16 13:32

 */

public class ConcreteProductA1 extends AbstractProductA {

}


6. 使用抽象工厂模式解决业务问题

结构图:

Java设计模式学习笔记(四) 抽象工厂模式-LMLPHP




/**

 * @author liuboren

 * @Title: 客户端类

 * @Description:

 * @date 2019/7/16 13:48

 */

public class Client {

    public static void main(String[] args) {

        SkinFactory skinFactory = new SummerSkinFactory();

        Button button = skinFactory.createButton();

        ComboBox comboBox = skinFactory.createComboBox();

        TextField textField = skinFactory.createTextField();

        button.display();

        comboBox.display();

        textField.display();

    }

}



/*

* 按钮接口

* */

interface Button {

     void display();

}

//Spring按钮类:具体产品

class SpringButton implements Button {

    @Override

    public void display() {

        System.out.println("显示浅绿色按钮。");

    }

}



//Summer按钮类:具体产品

class SummerButton implements Button {

    @Override

    public void display() {

        System.out.println("显示浅蓝色按钮。");

    }

}

//文本框接口:抽象产品

interface TextField {

     void display();

}

//Spring文本框类:具体产品

class SpringTextField implements TextField {

    @Override

    public void display() {

        System.out.println("显示绿色边框文本框。");

    }

}

//Summer文本框类:具体产品

class SummerTextField implements TextField {

    @Override

    public void display() {

        System.out.println("显示蓝色边框文本框。");

    }

}

//组合框接口:抽象产品

interface ComboBox {

    public void display();

}

//Spring组合框类:具体产品

class SpringComboBox implements ComboBox {

    @Override

    public void display() {

        System.out.println("显示绿色边框组合框。");

    }

}

//Summer组合框类:具体产品

class SummerComboBox implements ComboBox {

    @Override

    public void display() {

        System.out.println("显示蓝色边框组合框。");

    }

}

//界面皮肤工厂接口:抽象工厂

interface SkinFactory {

     Button createButton();

     TextField createTextField();

     ComboBox createComboBox();

}

//Spring皮肤工厂:具体工厂

class SpringSkinFactory implements SkinFactory {

    @Override

    public Button createButton() {

        return new SpringButton();

    }

    @Override

    public TextField createTextField() {

        return new SpringTextField();

    }

    @Override

    public ComboBox createComboBox() {

        return new SpringComboBox();

    }

}

//Summer皮肤工厂:具体工厂

class SummerSkinFactory implements SkinFactory {

    @Override

    public Button createButton() {

        return new SummerButton();

    }

    @Override

    public TextField createTextField() {

        return new SummerTextField();

    }

    @Override

    public ComboBox createComboBox() {

        return new SummerComboBox();

    }

}


可以使用反射+xml动态生成工厂类,参见上一篇博客的3.3.3节,地址: 工厂模式

7. "开闭原则"的倾斜性

抽象工厂模式增加新的产品族很方便(通过创建新的工厂实现类和产品实现类),但是增加新的产品等级结构很麻烦(需在抽象工厂模式中添加新的方法,违背了"开闭原则")

抽象工厂模式的这种性质称为“开闭原则”的倾斜性。“开闭原则”要求系统对扩展开放,对修改封闭,通过扩展达到增强其功能的目的,对于涉及到多个产品族与多个产品等级结构的系统,其功能增强包括两方面:

  • 增加产品族
  • 增加新的产品登记结构

7.1 增加产品族

对于增加新的产品族,抽象工厂模式很好地支持了“开闭原则”,只需要增加具体产品并对应增加一个新的具体工厂,对已有代码无须做任何修改。

7.2 增加新的产品等级结构

对于增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法,违背了“开闭原则”。

正因为抽象工厂模式存在“开闭原则”的倾斜性,它以一种倾斜的方式来满足“开闭原则”,为增加新产品族提供方便,但不能为增加新产品结构提供这样的方便,因此要求设计人员在设计之初就能够全面考虑,不会在设计完成之后向系统中增加新的产品等级结构,也不会删除已有的产品等级结构,否则将会导致系统出现较大的修改,为后续维护工作带来诸多麻烦。

8. 总结

8.1优点:

  1. 易于改变软件系统的行为: 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为

  2. 始终只使用同一个产品族中的对象: 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。

8.2 缺点

不易扩展; 增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。

8.3 适用场景

(1) 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是很重要的,用户无须关心对象的创建过程,将对象的创建和使用解耦。

(2) 系统中有多于一个的产品族,而每次只使用其中某一产品族。可以通过配置文件等方式来使得用户可以动态改变产品族,也可以很方便地增加新的产品族。

(3) 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。同一个产品族中的产品可以是没有任何关系的对象,但是它们都具有一些共同的约束,如同一操作

系统下的按钮和文本框,按钮与文本框之间没有直接关系,但它们都是属于某一操作系统的,此时具有一个共同的约束条件:操作系统的类型。

(4) 产品等级结构稳定,设计完成之后,不会向系统中增加新的产品等级结构或者删除已有的产品等级结构。

9. 代码下载

github地址

07-17 17:06