java设计模式之抽象工厂模式

转载请注明出处:http://blog.csdn.net/h_zhang/article/details/51245465

前面我写了一篇关于简单工厂模式的文章,对于简单工厂模式还不清楚的同学建议先去阅读:java设计模式之简单工厂模式 ;本篇文章将讲解抽象工厂模式,并和简单工厂模式的异同点做了对比。

先回顾一下简单工厂模式: 首先,组件的之间的调用应该是面向接口的,这样做可以隔离变化。其次,虽然高层组件通过底层组件的接口来调用底层组件的功能,但是底层组件的接口如何实例化呢?为了避免组件之间耦合度太高,高层组件一般不直接实例化底层组件的接口对象,而是通过简单工厂来获得底层组件的接口对象。这样一来,如果底层组件的具体实现发生变化,就不用变动高层组件的代码,因为这个变化会被简单工厂吸收和屏蔽掉,从而实现组件之间的解耦。

下面就进入本文的主题:抽象工厂模式。


场景需求

举个生活中常见的场景:组装电脑。装机工程师组装一台电脑需要CPU,主板,内存,硬盘,显卡等等。为了演示方便就以CPU、主板为例,其他就不考虑了。现在CPU可能有多种品牌,比如Intel的CPU,AMD的CPU;主板也可能有多种,比如技嘉的主板,华硕的主板。对于装机工程师来说,他只要拿到CPU和主板进行组装即可,他不需要知道是CPU是哪家的CPU,主板是哪家的主板。而CPU和主板的选择是由客户端来决定。

对上面问题进行抽象:装机工程师就是高层组件;CPU和主板就是底层组件。装机工程师需要底层组件的支持才能完成装机工作。

对于装机工程师而言,只知道CPU和主板接口,而不知道具体实现,很明显可以用简单工厂实现啊。客户端告诉装机工程师自己的选择,然后通过相应的工厂获取实例对象。


使用简单工厂实现

首先,我们准备好底层组件。
面向接口编程是前提,先来看看CPU和主板的接口。

CPU的接口定义:

/**
 * CPU接口
 */
public interface CPU
{
    //示意方法, cpu具有运算功能
    public void calculate();
}

主板接口定义:

/**
 * 主板接口
 */
public interface Mainboard
{
    //示意方法,主板具有安装CPU功能
    public void installCPU();
}

再来看看CPU和主板的实现。
底层组件负责实现这些接口,下面来看看具体CPU的实现代码。

IntelCPU的具体代码实现:

/**
 * Intel CPU的实现
 */
public class IntelCPU implements CPU
{
    //CPU的针脚数目
    private int pins;

    //构造方法,传入针脚数目
    public IntelCPU(int pins)
    {
        this.pins = pins;
    }

    @Override
    public void calculate()
    {
        System.out.println("Intel CPU, pins="+pins);
    }
}

AMD CPU的具体代码实现:

/**
 * AMD CPU的实现
 */
public class AmdCPU implements CPU
{
    //CPU的针脚数目
    private int pins;

    public AmdCPU(int pins)
    {
        this.pins = pins;
    }

    @Override
    public void calculate()
    {
        System.out.println("AMD CPU, pins="+pins);
    }
}

下面来看看具体主板的实现。
技嘉主板的代码实现:

/**
 * 技嘉的主板
 */
public class GAMainboard implements Mainboard
{
    //CPU插槽的孔数
    private int cpuHoles;

    public GAMainboard(int cpuHoles)
    {
        this.cpuHoles = cpuHoles;
    }

    @Override
    public void installCPU()
    {
        System.out.println("GA Mainboard, cpuHoles="+cpuHoles);
    }
}

华硕主板的代码实现:

/**
 * 华硕主板
 */
public class ASUSMainboard implements Mainboard
{
    //CPU插槽的孔数
    private int cpuHoles;

    public ASUSMainboard(int cpuHoles)
    {
        this.cpuHoles = cpuHoles;
    }

    @Override
    public void installCPU()
    {
        System.out.println("ASUS Mainboard, cpuHoles="+cpuHoles);
    }
}

OK,底层组件准备完毕。下面我们搞出两个简单工厂,分别负责创建CPU和主板的接口对象。
创建CPU工厂的示例代码:

/**
 * 创建CPU的简单工厂
 */
public class CPUFactory
{
    /**
     * 创建CPU接口对象的方法
     * @param type 选择CPU类型参数
     * @return CPU接口对象方法
     */
    public static CPU createCPU(int type)
    {
        CPU cpu = null;
        if(type == 1)
        {
            cpu = new IntelCPU(1156);
        }else if(type == 2)
        {
            cpu = new AmdCPU(939);
        }
        return cpu;
    }
}

创建主板的工厂示例代码:

/**
 * 创建主板的简单工厂
 */
public class MainboardFactory
{
    /**
     * 创建主板接口对象方法
     * @param type 选择主板类型参数
     * @return 主板接口对象
     */
    public static Mainboard createMainboard(int type)
    {
        Mainboard mainboard = null;
        if(type == 1){
            mainboard = new GAMainboard(1156);
        }else if(type == 2){
            mainboard = new ASUSMainboard(939);
        }
        return mainboard;
    }
}

代码相当简单,没什么好说的。

下面我们看看装机工程师的示例代码:

/**
 * 装机工程师类
 */
public class ComputerEngineer
{
    //装机需要的CPU
    private CPU cpu;

    //装机需要的主板
    private Mainboard mainboard;

    /**
     * 装机过程
     * @param cpuType 客户所选CPU类型
     * @param mainboardType 客户所选主板类型
     */
    public void makeComputer(int cpuType, int mainboardType)
    {
        //1.准备好装机所需配件
        this.cpu = CPUFactory.createCPU(cpuType);
        this.mainboard = MainboardFactory.createMainboard(mainboardType);
        //测试配件是否好用
        this.cpu.calculate();
        this.mainboard.installCPU();

        //2.有了配件之后,组装机器

        //3.组装完毕,测试,交付用户
    }

}

OK, 类ComputerEngineer就是高层组件,其内部所维持的都是底层组件的接口(cpu, mainboard),也就是说装机工程师并不知道cpu和mainboard到底谁来实现以及怎么实现的。装机工程师向客户端提供makeComputer()方法来完成装机过程。装机第1步通过简单工厂获得底层配件实例对象。

下面我们来看客户端代码:

public class Client
{
    public static void main(String[] args)
    {
        //创建装机工程师实例
        ComputerEngineer computerEngineer = new ComputerEngineer();

        //告诉装机工程师自己的配件,让装机工程师组装电脑
        computerEngineer.makeComputer(1, 1);
    }
}

运行结果如下:

Intel CPU, pins=1156
GA Mainboard, cpuHoles=1156

OK,利用简单工厂就实现好了,通过简单工厂获取CPU和主板对象,然后就可以组装电脑了,貌似非常完美。然而有一个问题没有解决。。。

上面的实现,利用简单工厂解决了装机工程师和CPU、主板之间的解耦问题:对于装机工程师而言只知道CPU和主板的接口,而不知道具体实现。CPU和主板的改变不会影响到装机工程师的代码,因为CPU和主板的改变会被简单工厂吸收和屏蔽。但是还有一个问题没有解决,那就是CPU对象和主板对象之间是有关系的,是需要相互匹配的。而上面的实现中并没有维护这种依赖关系。CPU和主板的型号,客户端可以随意选择,这当然是有问题的。

比如,如果客户端在调用makeComputer()方法时传入的参数是(1,2),运行结果如下:

Intel CPU, pins=1156
GA Mainboard, cpuHoles=939

这样就出现问题:因为1156个针脚的CPU是无法安装到939个针孔插槽的主板上。要解决这个问题,可以利用抽象工厂模式。

抽象工厂模式

先看一下抽象工厂模式的定义:

提供创建一系列互相依赖对象的接口,而无需指定他们具体的类。

分析上面的问题,其实有两个问题点。一个是:只知道一系列对象的接口而不知道其具体实现,或者不知道具体使用哪一个实现。另一个是:这一系列对象是相互依赖的,要约束他们之间的关系。不能孤立的创建它们。

我们定义一个抽象工厂,在里面抽象的定义出创建CPU和主板的方法。抽象工厂对外提供了一个统一的外观,由抽象工厂的子类创建出来的一系列对象肯定是能够相互匹配的。

也就是说,抽象工厂不再单独创建某一个产品对象,而是创建一系列产品对象(产品簇)。这也是抽象工厂模式和简单工厂模式最大的区别。

还有一点需要说明:大家不要被抽象工厂的名称所误导,以为是抽象类。实际上抽象工厂大多都是以接口的形式实现的。

还是通过代码说明比较直观。下面我们利用抽象工厂重写上面示例。
需要注意一下两点:
1. 底层组件对象(CPU, Mainboard)还是上面的代码,不用改变;
2. 创建CPU的简单工厂和主板的简单工厂不再需要,直接删除即可;

OK,下面先加入抽象工厂的定义:

/**
 * 抽象工厂接口,定义创建一系列抽象产品
 */
public interface AbstractFactory
{
    /**
     * 创建CPU对象
     * @return CPU的对象
     */
    public CPU createCPU();

    /**
     * 创建主板对象
     * @return 主板对象
     */
    public Mainboard createMainboard();
}

下面看看具体的装机方案,也就是为抽象工厂的实现子类。

装机方案一的实现。示例代码如下:

/**
 * 装机方案一:Intel的CPU + 技嘉的主板
 *
 * 这里创建的CPU和主板对象是对应,能匹配上的
 */
public class ComputerSchema1 implements AbstractFactory
{
    @Override
    public CPU createCPU()
    {
        return new IntelCPU(1156);
    }

    @Override
    public Mainboard createMainboard()
    {
        return new GAMainboard(1156);
    }
}

装机方案二的实现。示例代码如下:

/**
 * 装机方案一:AMD的CPU + 华硕的主板
 *
 * 同样,这里创建的CPU和主板对象是对应,能匹配上的
 */
public class ComputerSchema2 implements AbstractFactory
{
    @Override
    public CPU createCPU()
    {
        return new AmdCPU(939);
    }

    @Override
    public Mainboard createMainboard()
    {
        return new ASUSMainboard(939);
    }
}

下面,装机工程师的代码要稍微改变一下。

/**
 * 装机工程师类
 */
public class ComputerEngineer
{
    //装机需要的CPU
    private CPU cpu;

    //装机需要的主板
    private Mainboard mainboard;

    /**
     * 组装电脑过程
     * @param schema 客户端选择装机方案
     */
    public void makeComputer(AbstractFactory schema)
    {
        //1.准备好装机所需配件
        this.cpu = schema.createCPU();
        this.mainboard = schema.createMainboard();
        //测试配件是否好用
        this.cpu.calculate();
        this.mainboard.installCPU();

        //2.有了配件之后,组装机器

        //3.组装完毕,测试,交付用户
    }

}

可以看到,makeComputer()方法不再接收具体的cpuType和mainboardType参数,而是接收一个抽象工厂对象。这个抽象工厂对象负责实例化哪一个CPU和哪一个主板。也就是说,客户端不再通过指定具体cpu和主板来组装电脑,而是选择一个抽象工厂的实现类去组装电脑,每一个抽象工厂的实现类就代表一种装机方案,并且由抽象工厂确定的CPU和主板的型号肯定是相互匹配的。

那么下面就来看看客户端代码:

public class Client
{
    public static void main(String[] args)
    {
        //创建装机工程师实例
        ComputerEngineer computerEngineer = new ComputerEngineer();

        //选择一个装机方法
        AbstractFactory schema = new ComputerSchema1();

        //告诉装机工程师自己的配件,让装机工程师组装电脑
        computerEngineer.makeComputer(schema);
    }
}

OK!回顾上面分析两个问题点,利用抽象工厂是不是都解决了呢?

答案是肯定的。第一、装机工程师是只知道电脑配件的接口,而不知道其具体实现以及由谁实现,从而实现了组件之间的解耦。第二、利用抽象工厂给出的装机方案,每个配件之间肯定是相互匹配的。因为配件的选择由抽象工厂的实现类去控制,每一个抽象工厂对象就会创建一系列产品(产品簇),就代表一种装机方案。用户直接选择使用哪一个抽象工厂的实现类即可。


总结

抽象工厂的优点:
1. 分离接口和实现,使得客户端只面向接口编程即可。当然这个特点,简单工厂也有。
2. 切换产品簇变得容易;比如上面的例子的ComputerSchema1代表装机方案:Intel的CPU+技嘉的主板;如果要切换成ComputerSchema2就变成新的方案:AMD的CPU+华硕的主板。是不是很方便呢。。。

抽象工厂和简单工厂对比:
工厂嘛!造东西的。在java里面就是创造接口对象的。

不同的地方就是:简单工厂主要用来创建一个接口对象(当然可以创建多个接口对象,但是这些对象之间是没有约束关系的,不相互依赖的)。而抽象工厂则是用来创建一系列相互依赖的接口对象(就比如上面装机例子中的CPU和主板)。

简单工厂可以看做是抽象工厂的特例,当抽象工厂中只创建一个接口对象,那么抽象工厂就退化成简单工厂了。

OK,那么本篇文章到这就结束了。关于抽象工厂模式就说这么多,谢谢大家观看~~~

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值