Java常用的设计模式:抽象工厂

一、场景问题

    举个生活中常见的例子-组装电脑,这里说的范围更小一些,组装CPU到主板上,CPU有各种各样的型号,主板也有各种各样的型号,CPU不同的型号有不同的针脚数目,如果针脚数目与主板的插口不兼容,那么将无法进行组装。对于装机工程师来说,他只负责配件的组装,而具体的选择哪种类型的CPU和主板,还是由客户来进行选择的。现在需要设计一个程序来把装机的过程,尤其是选择组装电脑配件的过程实现出来。

    这个场景问题在不用模式的情况下,考虑到客户需要自己选择CPU和主板,然后由工程师来负责装机,那么对工程师来说,只是知道CPU和主板的接口,而不知道他们的具体实现。(这里由于场景需要,排除掉主观原因,工程师仅仅负责装配,不具备知道主板接口是否符合条件的能力),由前边的设计模式可以知道,这里使用简单工厂模式可以实现。客户告诉工程师想要的配件,工程师根据指定的要求从工厂里拿相应的配件。

CPU的接口定义

package com.chenxyt.java.practice;

/**
 * CPU接口
 * @author Crayon
 *
 */
public interface CPUApi {
	/**
	 * CPU的模拟方法
	 */
	public void calculate();
}

主板的接口定义

package com.chenxyt.java.practice;

/**
 * 主板接口
 * @author Crayon
 *
 */
public interface MainBoardApi {
	/**
	 * 主板模拟方法
	 */
	public void installCpu();
}

具体的CPU实现 

package com.chenxyt.java.practice;
/**
 * IntelCPU的实现
 * @author Crayon
 *
 */
public class IntelCpu implements CPUApi {
	/**
	 * CPU针脚数
	 */
	private int pins = 0;
	public IntelCpu(int pins) {
		// TODO Auto-generated constructor stub
		this.pins = pins;
	}
	@Override
	public void calculate() {
		// TODO Auto-generated method stub
		System.out.println("INTEL CPU 针脚数:" + pins);
	}
}
package com.chenxyt.java.practice;

/**
 * AMDCpu实现
 * @author Crayon
 *
 */
public class AMDCpu implements CPUApi{
	/**
	 * 针脚数
	 */
	private int pins = 0;
	public AMDCpu(int pins) {
		// TODO Auto-generated constructor stub
		this.pins = pins;
	}
	@Override
	public void calculate() {
		// TODO Auto-generated method stub
		System.out.println("AMD CPU针脚数:" + pins);
	}
}

主板的具体实现

package com.chenxyt.java.practice;

/**
 * GA主板实现
 * @author Crayon
 *
 */
public class GAMainBoard implements MainBoardApi{
	private int cpuHoles = 0;
	public GAMainBoard(int cpuHoles) {
		// TODO Auto-generated constructor stub
		this.cpuHoles = cpuHoles;
	}
	@Override
	public void installCpu() {
		// TODO Auto-generated method stub
		System.out.println("GA MainBoard 插孔数:" + cpuHoles);
	}

}
package com.chenxyt.java.practice;
/**
 * MSI主板实现
 * @author Crayon
 *
 */
public class MSIMainBoard implements MainBoardApi{
	private int cpuHoles = 0;
	public MSIMainBoard(int cpuHoles) {
		// TODO Auto-generated constructor stub
		this.cpuHoles = cpuHoles;
	}
	@Override
	public void installCpu() {
		// TODO Auto-generated method stub
		System.out.println("MSI MainBord 插孔数:" + cpuHoles);
	}
	
}

创建CPU的简单工厂

package com.chenxyt.java.practice;
public class CPUFactory {
	public static CPUApi createCpu(int type){
		CPUApi api = null;
		if(type == 1){
			api = new IntelCpu(1156);
		}else if(type == 2){
			api = new AMDCpu(939);
		}
		return api;
	}
}

创建主板简单工厂

package com.chenxyt.java.practice;
public class MainBoardFactory {
	public static MainBoardApi creatMainBoard(int type){
		MainBoardApi MainBoard = null;
		if(type == 1){
			MainBoard = new GAMainBoard(1156);
		}else if(type == 2){
			MainBoard = new MSIMainBoard(939);
		}
		return MainBoard;
	}
}

工程师装机类

package com.chenxyt.java.practice;

/**
 * 装机工程师实现类
 * @author Crayon
 *
 */
public class ComputerEngineer {
	
	private CPUApi cpu = null;
	
	private MainBoardApi mainBoard = null;
	
	/**
	 * 装机过程
	 * @param cpuType
	 * @param mainBoardType
	 */
	public void makeComputer(int cpuType,int mainBoardType){
		//准备硬件
		prepareHardWares(cpuType, mainBoardType);
	}
	/**
	 * 准备硬件
	 * @param cpuType
	 * @param mainBoardType
	 */
	public void prepareHardWares(int cpuType,int mainBoardType){
		//直接从工厂获取
		this.cpu = CPUFactory.createCpu(cpuType);
		this.mainBoard = MainBoardFactory.creatMainBoard(mainBoardType);
		//测试一下硬件型号
		this.cpu.calculate();
		this.mainBoard.installCpu();
	}
	
}

用户客户端类

package com.chenxyt.java.practice;
public class Client {
	public static void main(String[] args) {
		ComputerEngineer cn = new ComputerEngineer();
		//告诉工程师我选择的配件
		cn.makeComputer(1, 1);
	}
}

运行结果

    Java常用的设计模式:抽象工厂_第1张图片

上述代码通过简单工厂的模式实现了CPU和主板的选择,但是存在一个问题。假如客户选择了1CPU、2主板呢?

		cn.makeComputer(1, 2);

运行结果

    Java常用的设计模式:抽象工厂_第2张图片

这种情况下,就没有办法完成电脑的组装。

    上面的实现,虽然通过简单工厂设计模式解决了对于装机工程师来说只知道接口不知道具体实现的问题,但是还有一个问题没有解决,就是当需要通过工厂创建的对象,彼此之间是有关联的,是需要相互匹配的,而简单工厂并没有解决对象之间相互依赖匹配的问题,所以问题就出现了。

二、解决方案

2.1使用抽象工厂模式来解决问题

    上述问题的一个合理解决方案就是使用抽象工厂设计模式:提供一个创建一系列相关或者相互依赖的接口,而无需指定他们具体的类。上述问题要解决的有两点,一点是只知道所需要的一系列对象的接口,而不知道具体的实现。另一点是这些需要的对象接口彼此相互关联或者相互依赖。这里要解决的第一点问题可以使用简单工厂或者工厂方法解决,而这两种设计模式只解决了单一的对象创建,没有办法满足对象之间彼此关联或者依赖的功能。

2.2抽象工厂模式的结构和说明

    Java常用的设计模式:抽象工厂_第3张图片

AbstractFactory:抽象工厂,提供统一的接口工厂。

ConcreteFactory:具体工厂,用来创建具体的带有关联关系的实例产品

AbstractProduct:统一的产品接口

ConcreteProduct:具体产品

2.3抽象工厂示例代码

先定义一个抽象工厂,定义两个方法返回两种产品的类型

package com.chenxyt.java.practice;

/**
 * 抽象工厂
 * @author Crayon
 *
 */
public interface AbstractFactory {
 
	/**
	 * 创建产品A
	 * @return
	 */
	public AbstractProductA createProductA();
	/**
	 * 创建产品B
	 * @return
	 */
	public AbstractProductB createProductB();
	
	
}

然后定义公共的产品接口

package com.chenxyt.java.practice;

public interface AbstractProductA {

}
package com.chenxyt.java.practice;

public interface AbstractProductB {

}

然后分别实现两种产品

package com.chenxyt.java.practice;

public class ProductA1 implements AbstractProductA{

}
package com.chenxyt.java.practice;

public class ProductA2 implements AbstractProductA{

}
package com.chenxyt.java.practice;

public class ProductB1 implements AbstractProductB{

}
package com.chenxyt.java.practice;

public class ProductB2 implements AbstractProductB{

}

最后实现抽象工厂,分别创建两种相关联的产品A1-B1,A2-B2的具体工厂

package com.chenxyt.java.practice;

public class ConcreteFactory1 implements AbstractFactory{

	@Override
	public AbstractProductA createProductA() {
		// TODO Auto-generated method stub
		return new ProductA1();
	}

	@Override
	public AbstractProductB createProductB() {
		// TODO Auto-generated method stub
		return new ProductB1();
	}

}
package com.chenxyt.java.practice;

public class ConcreteFactory2 implements AbstractFactory{

	@Override
	public AbstractProductA createProductA() {
		// TODO Auto-generated method stub
		return new ProductA2();
	}

	@Override
	public AbstractProductB createProductB() {
		// TODO Auto-generated method stub
		return new ProductB2();
	}

}

客户端调用方式:

package com.chenxyt.java.practice;
public class Client {
	public static void main(String[] args) {
		//------------- 创建A1-B1
		AbstractFactory f1 = new ConcreteFactory1();
		f1.createProductA();
		f1.createProductB();
		//--------------创建A2-B2
		AbstractFactory f2 = new ConcreteFactory1();
		f2.createProductA();
		f2.createProductB();
	}
}

这样子就OK啦,相比来说,我感觉抽象工厂的思路还是相对明确一些。

2.4使用抽象工厂重写示例

    接下来使用抽象工厂模式重写前边工程师装配CPU与主板的示例,分析对应关系。修改之后客户从对应的实体工厂获取CPU与主板,这样下来获取由实体工厂生产出来的CPU与主板,必然是符合关联关系的。因为毕竟是一个工厂生产出来的啦。结构图就不画了,与前边的对应。

    代码部分,前面的CPU和主板的接口与实现都不需要变化,同时删除创建的简单工厂,创建新的抽象工厂

package com.chenxyt.java.practice;

/**
 * 抽象工厂
 * @author Crayon
 *
 */
public interface AbstractFactory {
 
	/**
	 * 创建CPU
	 * @return
	 */
	public CPUApi createCpu();
	/**
	 * 创建主板
	 * @return
	 */
	public MainBoardApi createMainBoard();
	
	
}

接下来创建具体的实现工厂

package com.chenxyt.java.practice;

public class ConcreteFactory1 implements AbstractFactory{

	@Override
	public CPUApi createCpu() {
		// TODO Auto-generated method stub
		return new AMDCpu(1156);
	}

	@Override
	public MainBoardApi createMainBoard() {
		// TODO Auto-generated method stub
		return new MSIMainBoard(1156);
	}

}
package com.chenxyt.java.practice;

public class ConcreteFactory2 implements AbstractFactory{

	@Override
	public CPUApi createCpu() {
		// TODO Auto-generated method stub
		return new AMDCpu(939);
	}

	@Override
	public MainBoardApi createMainBoard() {
		// TODO Auto-generated method stub
		return new MSIMainBoard(939);
	}

}

装机工程师类:

package com.chenxyt.java.practice;

/**
 * 装机工程师实现类
 * @author Crayon
 *
 */
public class ComputerEngineer {
	
	private CPUApi cpu = null;
	
	private MainBoardApi mainBoard = null;
	
	/**
	 * 准备CPU
	 * @param factory
	 */
	public void makeComputer(AbstractFactory factory){
		//准备硬件
		prepareHardWares(factory);
	}
	/**
	 * 准备硬件
	 * @param factory
	 */
	public void prepareHardWares(AbstractFactory factory){
		//直接从工厂获取
		this.cpu = factory.createCpu();
		this.mainBoard = factory.createMainBoard();
		//测试一下硬件型号
		this.cpu.calculate();
		this.mainBoard.installCpu();
	}
	
}

都定义好了看下客户端的调用:

package com.chenxyt.java.practice;
public class Client {
	public static void main(String[] args) {
		ComputerEngineer engineer = new ComputerEngineer();
		//使用第一种组件
		engineer.makeComputer(new ConcreteFactory1());
		System.out.println("----------------------");
		//使用第二种组件
		engineer.makeComputer(new ConcreteFactory2());
	}
}

运行结果:

    Java常用的设计模式:抽象工厂_第4张图片

    结果符合预期,同一个工厂生产出了同一种类型的配件,可以愉快的进行组装了。

三、模式讲解

3.1认识抽象工厂模式

    抽象工厂模式为一系列相关或相互依赖的对象创建一个接口,接口内部严格按照相关或相互依赖的对象关系创建,切勿堆积其它没有用的方法或者无关联关系的方法,相关或相互依赖的一类产品被称作是产品族,如上边的示例就有两个产品族,切换产品族的时候只需要切换一下抽象工厂的实现类即可。抽象工厂类与名字无关,虽然这里定义成了AbstractFactory,但是实际上它通常被定义为一个接口,而不是一个抽象类。由前面的代码可以看到,具体创建产品的功能由抽象工厂的实现类完成,这个思想与工厂方法的思想不谋而合,将创建产品的方法延迟到子类完成。

3.2抽象工厂模式的可扩展性分析

    由于抽象工厂模式创建了一系列的产品族,那么假如我们在抽象工厂中新加一个产品的创建,比如前边的示例,想新增一个创建内存的方法,那么就会导致当前的所有产品族,也就是抽象工厂方法的实现类发生变化。这其实是非常不友好的一种扩展方式。书中提供了一种横向的扩展方式,但是不太安全,就是抽象工厂只定义一个方法,然后这个方法传递一个参数,返回Object类,与原来不同的是,每个抽象工厂的实现类是一个种类的产品,比如定义一个Schema1,它只生产CPU,传递参数为1生产Interl CPU,传递参数为2生产AMD CPU,定义Schema2生产主板,如果产品族中新加入了内存,就继续创建一个实现类Schema3,在使用的过程中只需要传递不同的类型参数即可。

3.3抽象工厂模式和DAO

     DAO是数据访问对象,是用来处理业务与数据之间的一种标准模式,底层实现方式采用抽象工厂模式,有的也使用工厂方法模式。它用来解决不同的数据源、不同的存储类型、不同的访问操作、不同数据库等之间的访问一致性问题。也就是对于操作数据库的用户来说,它不需要管那么多,只需要按照同一个标准调用即可。这里不同的数据源、数据类型对应前边示例所说的主板、CPU等,具体实现方式这里就不实现了。

3.4抽象工厂模式的优缺点

优点:

    解耦,实现客户端的面向接口编程。抽象工厂模式创建了一系列的产品族,使得切换产品族变得很容易。

缺点:

    不容易扩展,也就是可扩展性不好,抽象工厂类一改,所有的具体工厂实现类都要变化。抽象工厂模式也会造成类层次过为复杂的情况。

3.5思考抽象工厂模式

    抽象工厂模式的本质是选择产品族的实现,所以当希望创建一系列相关联的产品,切只希望让客户端知道接口而不知道具体实现的时候,那么可以使用抽象工厂模式。

3.6相关模式

    与工厂方法模式:抽象工厂模式创建的是产品族,工厂方法模式创建的是单个产品,二者的实现原理都是将创建类的过程延迟到子类。

    与单例模式:抽象工厂模式里的具体工厂实现通常在整个应用中只有一个实例,因此可以把具体的实现创建为单例。

四、总结

    抽象工厂模式就是定义一个抽象接口,内部创建多个相关或者有依赖关系的方法,然后具体创建产品功能延迟到子类也就是接口的实现类中去实现。每一个实现类相当于一个产品族。这种方式扩展性不是很好。

    哎嘿~又学到新知道了~又到星期五啦~今天过生日~生日快乐啦!!

你可能感兴趣的:(设计模式)