工厂模式作为常见且非常重要的设计模式(其实设计模式都挺重要的),值得多次回顾与深入理解,以下为我自己对工厂模式的一些理解及分析
工厂模式整体分为简单工厂,工厂方法,抽象工厂等,但简单工厂其实不能算是设计模式,而是一种编码习惯,但很多情况下会将简单工厂误认为是工厂模式的一种,这里也一起涵盖,接下来以电脑组装为例分别分析
首先是简单工厂,简单工厂其实是一种编码习惯,上文已经说过,简单工厂的做法是将创建对象的代码单独拆分开成为一部分,单独维护,来看一个例子
// 电脑品牌枚举
public enum ComputerEnum {
MSI,Lenovo,Hp
}
// 电脑接口,定义一台电脑的组成部分
public interface Computer {
//获取CPU
void CPU();
//获取主板
void Motherboard();
//获取散热器
void Radiator();
//获取内存条
void RAM();
//获取显卡
void GPU();
//获取硬盘
void HDD();
//获取电源
void PowerSupply();
//获取机箱
void MachineBox();
//组装电脑
void Assemble();
}
// 微星
public class MsiComputer implements Computer {
@Override
public void CPU() {
System.out.println("CPU---MSI");
}
@Override
public void Motherboard() {
System.out.println("主板---MSI");
}
@Override
public void Radiator() {
System.out.println("散热器---MSI");
}
@Override
public void RAM() {
System.out.println("内存---MSI");
}
@Override
public void GPU() {
System.out.println("GPU---MSI");
}
@Override
public void HDD() {
System.out.println("HDD---MSI");
}
@Override
public void PowerSupply() {
System.out.println("电源---MSI");
}
@Override
public void MachineBox() {
System.out.println("机箱---MSI");
}
@Override
public void Assemble() {
System.out.println("组装完成---MSI");
}
}
// 惠普
public class HpComputer implements Computer {
@Override
public void CPU() {
System.out.println("CPU---Hp");
}
@Override
public void Motherboard() {
System.out.println("主板---Hp");
}
@Override
public void Radiator() {
System.out.println("散热器---Hp");
}
@Override
public void RAM() {
System.out.println("内存---Hp");
}
@Override
public void GPU() {
System.out.println("GPU---Hp");
}
@Override
public void HDD() {
System.out.println("HDD---Hp");
}
@Override
public void PowerSupply() {
System.out.println("电源---Hp");
}
@Override
public void MachineBox() {
System.out.println("机箱---Hp");
}
@Override
public void Assemble() {
System.out.println("组装完成---Hp");
}
}
//联想
public class LenovoComputer implements Computer {
@Override
public void CPU() {
System.out.println("CPU---Lenovo");
}
@Override
public void Motherboard() {
System.out.println("主板---Lenovo");
}
@Override
public void Radiator() {
System.out.println("散热器---Lenovo");
}
@Override
public void RAM() {
System.out.println("内存---Lenovo");
}
@Override
public void GPU() {
System.out.println("GPU---Lenovo");
}
@Override
public void HDD() {
System.out.println("HDD---Lenovo");
}
@Override
public void PowerSupply() {
System.out.println("电源---Lenovo");
}
@Override
public void MachineBox() {
System.out.println("机箱---Lenovo");
}
@Override
public void Assemble() {
System.out.println("组装完成---Lenovo");
}
}
// 简单工厂
public class SimpleComputerFactory {
public Computer createComputer(ComputerEnum computerEnum){
return SelectComputer(computerEnum);
}
private Computer SelectComputer(ComputerEnum computerEnum) {
switch (computerEnum) {
case MSI:
return new MsiComputer();
case Hp:
return new HpComputer();
case Lenovo:
return new LenovoComputer();
default:
return null;
}
}
}
从代码上可以看出 SimpleComputerFactory 其实是将Computer对象的创建过程抽出来,根据输入参数ComputerEnum枚举类型来判断返回什么品牌的电脑,先看调用及返回,再来聊聊这种方式的利弊
首先是调用方法
@Test
public void testSimpleFactory(){
SimpleComputerFactory simpleComputerFactory=new SimpleComputerFactory();
Computer msiComputer=simpleComputerFactory.createComputer(ComputerEnum.MSI);//想要生产一台MSI
if (!StringUtils.isEmpty(msiComputer)){//使用简单工厂生产出来的对象可能为NULL,这里需要做一个判断
//查看电脑配置
msiComputer.CPU();
msiComputer.Motherboard();
msiComputer.Radiator();
msiComputer.RAM();
msiComputer.GPU();
msiComputer.HDD();
msiComputer.PowerSupply();
msiComputer.MachineBox();
msiComputer.Assemble();
}
}
这里调用简单工厂生产一台MSI,参数传入的是MSI枚举,如果想生产惠普则传入Hp枚举值,来看下输出结果:
CPU---MSI
主板---MSI
散热器---MSI
内存---MSI
GPU---MSI
HDD---MSI
电源---MSI
机箱---MSI
组装完成---MSI
没生产错,问题不大.只要没传错参数就能得到结果.就目前情况来看,还算满足,这个简单工厂可以生产三种品牌的电脑,惠普,联想,微星.但如果我想要一台戴尔呢?别无他法,只能改这个简单工厂,在switch条件中添加一种,并且去维护枚举中的值,添加一个戴尔.下次如果想添加新的品牌,也是这个流程,这就意味着,这个简单工厂可能会被频繁的改动.这便是简单工厂的弊端.
那么解决办法呢?那就来看看工厂模式中的工厂方法
解决方法很简单,让工厂专注干一件事,比如需要MSI电脑就创建MSI的工厂,需要Hp就创建Hp的工厂
详情先看下面的代码,这次将Computer对象换了一种方式约束,广义上的实现接口,指的是实现某一个超类型的某个方法,这个超类型,可以是类也可以是定义的接口.
//首先是定义电脑类型的超类
@Data
public abstract class ComputerModel {
//CPU
private String CPU;
//主板
private String Motherboard;
//散热器
private String Radiator;
//内存条
private String RAM;
//显卡
private String GPU;
//硬盘
private String HDD;
//电源
private String PowerSupply;
//机箱
private String MachineBox;
//组装电脑
public abstract void assemble();
}
//然后是MSI的电脑类型
public class MsiComputer extends ComputerModel {
public MsiComputer(String CPU,String GPU){
//打上MSI的标记
setCPU(CPU);
setMotherboard("Motherboard---MSI");
setRadiator("Radiator---MSI");
setRAM("RAM---MSI");
setGPU(GPU);
setHDD("HDD---MSI");
setPowerSupply("PowerSupply---MSI");
setMachineBox("MachineBox---MSI");
}
@Override
public void assemble() {
System.out.println("----来自MSI工厂流水线操作---");
System.out.println(this.toString());
}
}
//电脑工厂接口
public interface ComputerFactory {
ComputerModel getComputer();
}
//MSI工厂
public class MsiComputerFactory implements ComputerFactory {
@Override
public ComputerModel getComputer() {
return new MsiComputer("i7-9700K","RTX2070");
}
}
这里声明了一个电脑类型,里面包括若干属性及一个方法,同时定义了MSI的工厂,先看调用方法逻辑
@Test
public void testFactory(){
/*
如果直接创建的话
ComputerModel msiComputer=new MsiComputer();
这就对MsiComputer本身产生了依赖,
而使用工厂的话则不需要关注这些,将组装等操作从调用方法这解耦出去,依赖倒置
*/
ComputerFactory msiComputerFactory=new MsiComputerFactory();//创建一个Msi工厂
ComputerModel msiComputer=msiComputerFactory.getComputer();//工厂生产Msi电脑
//如果需要惠普电脑或联想电脑,在这里引入惠普或联想工厂即可
msiComputer.assemble();//组装并查看
}
@Test
public void testMsi(){
String CPU="i7-9700K";
String GPU="RTX2070";
ComputerModel msi=new MsiComputer(CPU,GPU);
msi.assemble();
}
这里的两个测试方法都可以创建出一个MsiComputer对象,但区别在哪呢?
答案是没有区别.
这两个方法创建的对象属性值都一样,但是使用工厂方法的话可以解耦,就如同我在代码注释中所写一样,使用工厂方法创建对象时对MsiComputer对象没有依赖,而第二个测试方法,里面不仅仅依赖了MsiComputer对象还对CPU,GPU对象有依赖,这就意味着一旦产品有变动,比如我想换RTX2080了,就需要修改这里的GPU对象.而工厂方法则不需要关注这些,这便是解耦,那依赖倒置怎么解释呢?从第二个测试方法来看,testMsi方法为调用客户端,它依赖了CPU,GPU,MsiComputer,如果创建更多则可能还会依赖HpComputer,LenovoComputer,而和testMsi同样能创建电脑对象的工厂方法呢?testFactory依赖了 ComputerModel,而ComputerModel 是其他具体电脑类型的超类,具体类型的电脑(MsiComputer,HpComputer,LenovoComputer)依赖ComouterModel,这样就依赖倒置了,有空我把图补上.总结起来,本来高层(testFactory方法)依赖所有低层对象(MsiComputer等),使用工厂方法后变成了高层依赖一个超类(ComputerModel),而其他低层也依赖这个超类,从依赖关系上来看,倒置了,这便是依赖倒置.
说回工厂方法,工厂方法可以解耦,依赖倒置,但是添加了其他类型的产品后就必须添加新的工厂.
聊完工厂方法,再说说抽象方法,和工厂方法对比,抽象工厂其实更加突出产品组合.工厂方法生产一件产品,而抽象工厂生产有关联的一族产品
老样子,先看一组代码,下面修改了电脑的构造方法,将CPU,GPU,主板参数传入.并且定义了电脑配件工厂,里面定义了CPU,GPU,主板的生产方法
//电脑工厂接口
public interface ComputerFactory {
ComputerModel getComputer(ComputerPartsFactory computerPartsFactory);
}
//电脑组件工厂接口
public interface ComputerPartsFactory {
String getCPU();
String getGpu();
String getMotherboard();
}
//MSI电脑工厂
public class MsiComputerFactory implements ComputerFactory {
@Override
public ComputerModel getComputer(ComputerPartsFactory computerPartsFactory) {
String CPU=computerPartsFactory.getCPU();//从工厂拿到CPU
String GPU=computerPartsFactory.getGpu();//从工厂拿到GPU
String Motherboard=computerPartsFactory.getMotherboard();//从工厂拿到主板
return new MsiComputer(CPU,GPU,Motherboard);
}
}
//MSI电脑产品,这里修改了构造方法
public class MsiComputer extends ComputerModel {
public MsiComputer(String CPU,String GPU,String Motherboard){
setCPU(CPU);
setGPU(GPU);
setMotherboard(Motherboard);
}
@Override
public void assemble() {
System.out.println("----来自MSI工厂流水线操作---");
System.out.println(this.toString());
}
}
//游戏版电脑配件工厂
public class GameComputerPartsFactory implements ComputerPartsFactory {
@Override
public String getCPU() {
return "i7 9700K---Game";
}
@Override
public String getGpu() {
return "RTX 2070---Game";
}
@Override
public String getMotherboard() {
return "Z390H---Game";
}
}
//学习版电脑配件工厂
public class StudyComputerPartsFactory implements ComputerPartsFactory {
@Override
public String getCPU() {
return "i5 7500---Study";
}
@Override
public String getGpu() {
return "GTX 1050---Study";
}
@Override
public String getMotherboard() {
return "B360---Study";
}
}
再看调用方法
@Test
public void testAbstractFactory(){
ComputerFactory msiComputerFactory=new MsiComputerFactory();//创建一个Msi工厂
ComputerPartsFactory gamePartsFactory=new GameComputerPartsFactory();//创建一个游戏配件厂
ComputerPartsFactory studyPartsFactory=new StudyComputerPartsFactory();//创建一个游戏配件厂
ComputerModel msiComputerGame=msiComputerFactory.getComputer(gamePartsFactory);//工厂生产Msi电脑游戏版
ComputerModel msiComputerStudy=msiComputerFactory.getComputer(studyPartsFactory);//工厂生产Msi电脑学习版
System.out.println("-----------游戏版----------");
msiComputerGame.assemble();//组装并查看
System.out.println("-----------学习版----------");
msiComputerStudy.assemble();
}
运行结果如下:
-----------游戏版----------
----来自MSI工厂流水线操作---
ComputerModel(CPU=i7 9700K---Game, Motherboard=Z390H---Game, Radiator=null, RAM=null, GPU=RTX 2070---Game, HDD=null, PowerSupply=null, MachineBox=null)
-----------学习版----------
----来自MSI工厂流水线操作---
ComputerModel(CPU=i5 7500---Study, Motherboard=B360---Study, Radiator=null, RAM=null, GPU=GTX 1050---Study, HDD=null, PowerSupply=null, MachineBox=null)
从这里可以看出,抽象工厂将同一组(游戏版,学习版)的产品组合在一起,创建对象使用的是工厂方法.如果需要修改产品族类型,修改工厂的实现方法就行,不需要修改其他代码.但是抽象工厂也存在着问题,比如说,我觉得光只有CPU,GPU和主板三个组件不太够,现在希望把内存也维护进去,那么麻烦事就来了,在抽象工厂(ComputerPartsFactory)里添加一个方法,意味着所有实现类(GameComputerPartsFactory,StudyComputerPartsFactory等)全部需要修改,这对程序维护来说是非常麻烦的.
总结一下,简单工厂不属于工厂模式,他是一种编码习惯;工厂方法可以将客户端代码从需要实例化的具体类中解耦,可以依赖倒置;抽象工厂可以将一族相关产品集合起来,经常与工厂方法结合使用,能够有效解耦.