目录
一、概述
1.1 为什么会有工厂模式
如果使用new来创建对象,简单的对象来好。如果是复杂的对象,有很多的细节(一些成员变量的初始化),这个时候我们就需要了解对象的细节。其实大多数时候我们只是将这个对象拿来用就可以了,并不需要关注那么多细节。
使用new 创建对象的缺点:
-
客户端需要了解对象的内部细节。
-
不符合面向接口编程原则,需要知道具体的实现类的细节。
-
耦合在客户端,万一对象需要做扩展。100 个客户端使用该对象,100 个客户端都需要做修改。
二、 简单工厂模式
2.1 简单工厂模式的类图
- Api: 接口定义客户端需要的功能
- ApiImpl1/2: 具体的实现类
- Factory: 负责创建具体的实现类
- Client: 客户端,只需要知道 Api。 和 Factory
2.2 简单工厂模式示列
我们用培训课程场景,来示范简单工厂模式。
1 先定义客户端需要的功能API 接口: ICourse
public interface ICourse {
/**
* 课程内容
*/
public void content();
}
2 具体的两个实现类 JavaCourse 和 PythonCourse
/**
* Java课程
*/
public class JavaCourse implements ICourse {
public void content() {
System.out.println("java课程");
}
}
/**
* Pythod课程
*/
public class PythonCourse implements ICourse {
public void content() {
System.out.println("Python课程");
}
}
3 工厂类 CourseFactory
/**
* 创建课程工厂
*/
public class CourseFactory {
public static ICourse createCourse(int type){
if (type == 1) {
return new JavaCourse();
} else if (type == 2) {
return new PythonCourse();
}
return null;
}
}
4 客户端使用 Client
public class Client {
public static void main(String[] args) {
// 不需要知道实现类的创建细节
ICourse javaCourse = CourseFactory.createCourse(1);
javaCourse.content();
ICourse pythonCourse = CourseFactory.createCourse(2);
pythonCourse.content();
}
}
2.3 优缺点
优点:(工厂模式解决了哪些问题)
- 隐藏了创建对象的细节
- 客户端与具体实现类解耦,符合面向 接口编程。
缺点:
- 工厂类的职责太多。
- 不符合单一职责。
怎么理解缺点呢?
后期如果再增加怎得课程,增加 if else 判断。 就需要修改 CourseFactory。 造成CourseFactory 代码越来越臃肿, 不利于维护, 不符合开闭原则。
三 、工厂方法
3.1 工厂方法模式解决和什么问题
上面谈到简单工厂模式的缺点:
1 工厂类的职责太多。2 不符合单一职责。3 新增复杂类型时候,不符合开闭原则。
工厂方法模式就是用来解决上面的缺点的,并且功能更加强大。
我们先来看下工厂方法模式的定义:
定义一个用于创建对象的抽象类,让子类决定实例化哪一个类,使一个类的实例化延迟到子类。
UML类图
从UML类图中可以看出 产品和工厂是平行的关系, 有几个产品实现类,就有几个工厂类。
Factory: 抽象类,里面定义一个抽象方法。创建具体的产品,延迟到子类实现。
Factory1/2: 具体创建实例的工厂。
Api: 产品接口。
Apilmpl1/2: 具体产品的实现。
思考下,如果现在我们要新增一个产品该怎么做呢?
我们需要新增一个产品实现类 ApiImpl3 和 工厂实现类 Factory3 就可以了,不需要去修改原来的Factory类。这样就解决了简单工厂模式的缺点: 符合开闭原则,单一职责原则。
3.2 工厂方法模式实例
还是以创建课程实例为列子。
课程接口,和课程实现类和简单工厂没有差别。这里就不做冗余的创建。主要看下工厂类的改造。
1 抽象工厂类 CourseFactory
/**
* 抽象工厂父类
*/
public abstract class CourseFactory {
/**
* 工厂方法,具体实例化对象由子类实现
* @return 具体实例
*/
protected abstract ICourse createCourse();
public ICourse getCourse() {
ICourse course = createCourse();
System.out.println("设置复杂属性...");
return course;
}
}
protected abstract ICourse createCourse(); 延迟到子类实现,创建具体的实例。该方法一般不供外部调用。可以理解为钩子函数。
外部使用 getCourse() 方法。在里面可以进行一些公共的操作。比如 设置一些公共复杂的属性。
2 具体工厂实现类
/**
* java 课程工厂
*/
public class JavaCourseFactory extends CourseFactory {
protected ICourse createCourse() {
return new JavaCourse();
}
}
/**
* python 课程工厂
*/
public class PythonCourseFactory extends CourseFactory {
protected ICourse createCourse() {
return new PythonCourse();
}
}
3 Client端使用
public class Client {
public static void main(String[] args) {
CourseFactory javaCourseFactory = new JavaCourseFactory();
javaCourseFactory.getCourse().content();
System.out.println("----------------------------------");
CourseFactory pythonCourseFactory = new PythonCourseFactory();
pythonCourseFactory.getCourse().content();
}
}
运行结果
3.4 工厂方法模式的优点和缺点
优点:
- 扩展方便,新增产品类。不需要修改工厂方法。符合开闭原则
- 工厂类符合单一职责原则,维护方便。
- 适用于创建一些实例化过程比较复杂的对象。
缺点:
- 类太多,可以看出,工厂方法模式比简单工厂模式 多出了 很多 具体的工厂类。 增加了复杂度。
- 增加了系统的抽象性和理解难度。
四、 抽象工厂模式
4.1 场景
生活中一个常见的列子,组装电脑。 组装电脑需要 CPU 和 主板。先想一下怎么来组装一台电脑呢,简单一点假设组装电脑只需要CPU 和 主板。首先,我们需要CPU 和 主板,然后再将CPU和主板 交给装机工程师进行组装。怎么生产CPU和主板呢,通过上面简单工厂模式和工厂方法模式的学习,我们很自然的想到了用工厂模式创建,代码如下。
1 cpu 接口 和 cpu实现类
/**
* CPU
*/
public interface ICPU {
/**
* 计算能力
*/
public void calcuator();
}
public class IntelCPU implements ICPU {
/**
* 引脚个数
*/
private int pins;
public IntelCPU(int pins) {
this.pins = pins;
}
public void calcuator() {
System.out.println("IntelCPU 计算能力!引脚个数:" + this.pins);
}
}
/**
* AMDcpu
*/
public class AmdCPU implements ICPU {
private int pins;
public AmdCPU(int pins) {
this.pins = pins;
}
public void calcuator() {
System.out.println("AmdCpu 计算能力!引脚个数:" + this.pins);
}
}
2 主板 和 主板实现类
/**
* 主板
*/
public interface IMainboard {
/**
* 示意方法,安装cpu
*/
public void installCPU();
}
public class GAMainboard implements IMainboard{
/**
* 主板卡槽
*/
private int cpuHoles;
public GAMainboard(int cpuHoles) {
this.cpuHoles = cpuHoles;
}
public void installCPU() {
System.out.println("GAMainboard 安装Cpu, 主板卡槽个数:" + this.cpuHoles);
}
}
/**
* 微星主板
*/
public class MSIMainboard implements IMainboard{
/**
* 主板卡槽
*/
private int cpuHoles;
public MSIMainboard(int cpuHoles) {
this.cpuHoles = cpuHoles;
}
public void installCPU() {
System.out.println("MSIMainboard 安装Cpu, 主板卡槽个数:" + this.cpuHoles);
}
}
3 采用简单工厂模式,需要两个工厂
public class CPUFactory {
/**
* 根据type创建cpu
* @param type 0 Intel 1 Amd
* @return cpu具体实例
*/
public static ICPU createCpu(int type) {
if (type == 0) {
return new IntelCPU(1099);
} else {
return new AmdCPU(899);
}
}
}
public class MainboardFactory {
/**
* 根据type创建主板
* @param type 0 GA 1 MSI
* @return 主板具体实例
*/
public static IMainboard createMainboard(int type) {
if (type == 0) {
return new GAMainboard(1099);
} else {
return new MSIMainboard(899);
}
}
}
4 装机工程师
/**
* 组装电脑
*/
public class ComputerEnginer {
private ICPU cpu;
private IMainboard mainboard;
public void assembleComputer(int cpuType, int mainboardType) {
// 准备cpu 和 主板
prepareHardwares(cpuType, mainboardType);
// 组装完成
}
/**
* 准备cpu 和 主板
*
* @param cpuType cpu 类型
* @param mainboardType 主板类型
*/
private void prepareHardwares(int cpuType, int mainboardType) {
cpu = CPUFactory.createCpu(cpuType);
mainboard = MainboardFactory.createMainboard(mainboardType);
// 测试
cpu.calcuator();
mainboard.installCPU();
}
}
5 客户端使用
public class Client {
public static void main(String[] args) {
ComputerEnginer computerEnginer = new ComputerEnginer();
computerEnginer.makeComputer(1,0);
}
}
结果
有何问题 ?
可以看到结果中 Cpu 和 主板卡槽的个数并不匹配。装机电脑其实是有问题的。所以客户端在选择组件的时候就需要考虑到 cpu 和主板之间的以来关系,才能正确的创造出电脑。但是客户端可能并不知道具体的匹配关系。那怎么解决这个问题呢 ?
这个时候我们可以提供几套选择的套餐空客户端选择。套餐里面已经定义好了cpu 和 主板之间的关系。对应的解决方案就是抽象工厂模式。
4.2 抽象工厂模式概念
定义:提供一个创建一系列相关或者相互依赖对象的接口,而无需指定他们具体实现类。
先来看下UML类图
AbstractFactory: 抽象工厂,里面定义了创建产品的抽象方法。
ConcreteFactory1/2: 具体的工厂,相当于前面所说的套餐。
IProductA/B: 产品接口
IProductA/B(1,2): 产品具体实现类。 产品A 和 产品B 之间有相应的对应关系。需要选对匹配。
4.3 来看下具体的实例
基于前面提高的 cpu 和 主板来进行实例。 cpu 和 主板的 接口和实现前面有,这里不在冗余创建。 主要是将工厂类进行改造,两面所创建按的两个静态工厂就不需要了。
1 AbstractFactory 和 实现类
/**
* 抽象工厂模式。抽象工厂
*/
public interface AbstractFactory {
public ICPU createCPU();
public IMainboard createMainboard();
}
/**
* 装机方案一
*/
public class Schem1 implements AbstractFactory {
public ICPU createCPU() {
return new IntelCPU(1099);
}
public IMainboard createMainboard() {
return new GAMainboard(1099);
}
}
/**
* 装机方案二
*/
public class Schem2 implements AbstractFactory {
public ICPU createCPU() {
return new AmdCPU(899);
}
public IMainboard createMainboard() {
return new MSIMainboard(899);
}
}
2 装机工程师
/**
* 组装电脑
*/
public class ComputerEnginer {
private ICPU cpu;
private IMainboard mainboard;
public void makeComputer(AbstractFactory scheme) {
// 准备cpu 和 主板
prepareHardwares(scheme);
// 组装完成
}
/**
* 准备cpu 和 主板
*
* @param scheme 装机方案
*/
private void prepareHardwares(AbstractFactory scheme) {
cpu = scheme.createCPU();
mainboard = scheme.createMainboard();
// 测试
cpu.calcuator();
mainboard.installCPU();
}
}
这里在创建cpu 和 主板的时候,直接使用 AbstractFactory 具体实现类,方案 scheme进行创建就可以了,保证了对应关系。
3 客户端使用
public class Client {
public static void main(String[] args) {
AbstractFactory schem1 = new Schem1(); // 方案一
AbstractFactory schem2 = new Schem2(); // 方案二
ComputerEnginer computerEnginer = new ComputerEnginer();
computerEnginer.makeComputer(schem1);
System.out.println("--------------------------切换装机方案-----------------");
computerEnginer.makeComputer(schem2);
}
}
客户端选择的时候,只需要选择具体的方案就可以了,不需要单独选择 cpu 或 主板。不需要知道它们之间的对应关系。
结果:
4.4 抽象工厂模式的优点和缺点
优点:
- 分离了接口和实现
- 维护了系列产品之间的依赖关系。使得产品切换容易。
缺点:
- 不太容易扩展产品,比如组装笔记本还需要配件内存。那么 AbstractFactry 和 具体的工厂类,还有装机工程师类都需要做更改。
五、 三种工厂模式之间的关系
工厂方法模式, 如果就在具体的工厂父类里面创建实例就退化为 简单工厂模式。
抽象工厂模式,如果产品族中只有一个产品,那么就退化为工厂方法模式。
简单工厂模式 ---> 工厂方法模式---->抽象工厂模式。 解决的问题越来越复杂。
在实际的运用中,根据具体的业务场景选择合适的即可。
参考资料
博客:https://blog.csdn.net/varyall/article/details/82355964