每日一个设计模式之【工厂模式】
☁️前言🎉🎉🎉
大家好✋,我是知识汲取者😄,今天给大家带来一篇有关工厂模式的学习笔记。众所周知能够熟练使用设计模式是一个优秀程序猿的必备技能,当我们在项目中选择一个或多个合适的设计模式,不仅能大大提高项目的稳健性、可移植性、可维护性,同时还能让你的代码更加精炼,具备艺术美感。
在生活中资本家为了提高效率,将很多人组合到一起形成流水线,从而达到批量生产,于是工厂就诞生了。同样的,在代码的世界,有时候对象创建很频繁,为了提高效率,我们就会想着将对象的创建抽象出来形成一个类,这个类我们将其称之为工厂类。当然这就是工厂的粗略之谈,如果你想跟详细了解工厂模式,就请看完这篇文章吧,相信你一定能有所收获的😉,话不多说大家抓紧时间开搞。
推荐阅读:
由于博主水平有限,如有纰漏、错误还请您能给予指正😊
🌻工厂模式概述
什么是工厂模式?
工厂模式(Factory Pattern)是一种创建型模式,它将对象创建的具体过程给屏蔽,将对象的创建交给工厂,工厂会根据条件创建不同的对象
工厂模式的作用:将”类实例化的操作”与“使用对象的操作”分开,让使用者不用知道具体参数就可以实例化出所需要的“产品”类,从而避免了在客户端代码中显式指定,实现了解耦,即:使用者可直接消费产品而不需要知道其生产的细节
工厂模式的优缺点
优点:
- 让代码更加清晰。工厂模式将对象创建的细节进行了屏蔽,让开发者只需要关注对象创建的结果
- 提高程序的扩展性。如果像增加一个对象,只需要扩展一个工厂即可,不需要去重构代码
- 提高程序的灵活性。一个工厂可以生产出不同类型的对象
- 降低了系统的耦合度。将对象的创建和本身的业务逻辑进行了分离,提高了可移植性、可维护性
……
缺点:提高了系统的复杂度。没多一个对象,就需要多一个工厂类
工厂模式的应用场景
- 如果需要创建大量不同的对象,可以使用
- 如果创建对象的过程很复杂,可以使用
……
生活中的应用:产品在工厂生产,用户只管使用而不需要关注产品的生产过程
Spring中的应用:在创建SqlSession对象时,是使用工厂模式构建的
工厂模式的分类(以下三者抽象程度从上至下抽象程度逐渐提高,并且更具一般性)
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
特别说明:简单工厂其实不是一个标准的的设计模式。GOF 23 种设计模式中只有「工厂方法模式」与「抽象工厂模式」。简单工厂模式可以看为工厂方法模式的一种特例,为了统一整理学习,就都归为工厂模式
🌱工厂模式的实现
🍀简单工厂模式
简单工厂模式(Simple Factory),又叫做静态工厂方法(Static Factory Method)模式,它的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。
简单工厂模式的特点:根据给定的参数,决定究竟应该创建哪个具体类的对象(对象的创建由工厂决定)
简单工厂模式解决的问题:实现了对象创建和使用的职责分离
简单工厂模式存在的问题:违背了“开闭原则”,每次新增产品,都需要去修改原有的工厂类
简单工厂模式适用的场景:
- 一些比较简单的项目
- 如果能够提前知道所有的产品,同时以后也会由产品的新增,可以尝试使用
简单工厂模式中的角色划分
- 工厂(Factory)角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象
- 抽象产品(Abstract Product)角色:它是一个接口,是简单工厂模式所创建的所有对象的父类,负责描述所有实例所共有的公共接口
- 具体产品(Concrete Product)角色:是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例
简单工厂模式的核心步骤
- Step1:创建抽象产品
- Step2:创建具体产品
- Step3:创建工厂类
实例:
问题描述:使用简单工厂模式设计一个可以创建不同几何形状(如圆形、方形和三角形等)绘图工具,每个几何形状都要有绘制draw()和擦除erase() 两个方法,要求在绘制不支持的几何图形时,提示一个UnSupportedShapeException
要求:设计一个简单工厂模式,通过修改配置文件让工厂类创建不同的对象
Step1:创建抽象产品
Shape:
package com.hhxy.product.shape; /** * @author ghp * @date 2022/9/23 * @Title * @description */ public interface Shape { /** * 形状的绘制方法 */ void draw(); /** * 形状的擦除方法 */ void erase(); }
Step2:创建具体产品
1)Circular:
package com.hhxy.product.shape.imp; import com.hhxy.product.shape.Shape; /** * @author ghp * @date 2022/9/23 * @Title 圆形类 * @description */ public class Circular implements Shape { /** * 形状的绘制方法 */ @Override public void draw() { System.out.println("绘制了一个圆形"); } /** * 形状的擦除方法 */ @Override public void erase() { System.out.println("将绘制的圆形擦除了"); } }
2)Rectangle
具体代码请参考Gitee或Github仓库,略……
3)Triangle
具体代码请参考Gitee或Github仓库,略……
Step3:创建工厂类
SimpleFactory
package com.hhxy.factory; import com.hhxy.shape.Shape; import com.hhxy.shape.imp.Circular; import com.hhxy.shape.imp.Rectangle; import com.hhxy.shape.imp.Triangle; /** * @author ghp * @date 2022/9/23 * @Title 简单工厂类 * @description 用于创建Shape对象 */ public class SimpleFactory { public static Shape getShape(String shapeName){ Shape shape; if("circular".equalsIgnoreCase(shapeName)){ shape = new Circular(); }else if ("rectangle".equalsIgnoreCase(shapeName)){ shape = new Rectangle(); }else if("triangle".equalsIgnoreCase(shapeName)){ shape = new Triangle(); }else{ throw new RuntimeException("UnSupportedShapeException"); } return shape; } }
Step4:编写配置文件
shape-config.xml
:<?xml version="1.0" encoding="utf-8" ?> <config> <shapeName>Circular</shapeName> <shapeName>Rectangle</shapeName> <shapeName>Triangle</shapeName> <shapeName>123</shapeName> </config>
Step5:编写配置文件读取类
ReadShapeConfig
:package com.hhxy.read; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; /** * @author ghp * @date 2022/9/23 * @Title * @description 读取用于设置形状对象的配置文件 */ public class ReadShapeConfig { public static String getXML(){ try{ //1、将配置文件加载到内存中,获取DOM对象 //1.1 获取DOM解析器工厂对象DocumentBuilderFactory,用于创建DOM解析器 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); //1.2 获取DOM解析器DocumentBuilder DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); //1.3 加载配置文件 // Document document = documentBuilder.parse(new FileInputStream("Factory/src/shape-config.xml")); //让代码和模块名进行解耦,比上面那种方法要更优 Document document = documentBuilder.parse(ReadShapeConfig.class.getResourceAsStream("/shape-config.xml")); //2、获取配置文件中的数据 //2.1 从DOM中获取指定的结点的结点列表 NodeList nodeList = document.getElementsByTagName("shapeName"); //2.2 获取指定位置的结点 Node classNode = nodeList.item(1).getFirstChild(); //2.3 获取指定结点中的数据(排除空格) String name = classNode.getNodeValue().trim(); //3、返回从配置文件中读取的形状的名字 return name; } catch (Exception e) { //如果异常就打印异常信息,同时返回一个空 e.printStackTrace(); return null; } } }
Step6:测试
package com.hhxy.test; import com.hhxy.factory.SimpleFactory; import com.hhxy.read.ReadShapeConfig; import com.hhxy.shape.Shape; /** * @author ghp * @date 2022/9/23 * @Title 测试类1 * @description 用于测试简单工厂 */ public class Test1 { public static void main(String[] args) { //1、读取配置文件,得到要创建对象的名字 String shapeName = ReadShapeConfig.getXML(); //2、使用简单工厂创建对象 Shape shape = SimpleFactory.getShape(shapeName); //3、测试 shape.draw(); shape.erase(); } }
测试结果:
🍀工厂方法模式
前面的简单工厂模式虽然实现了对象创建和对象操作的分离,但是存在一个严重的不足之处,那就是每次要新增产品,都需要去修改工厂类,这是不符合"开闭原则"的。
针对这一现象,我们可以进行改进,从工厂类中抽象出一个工厂接口,不同的产品对应不同的工厂类,这些工厂类统一实现工厂接口,这样每次新增产品就只需要新增一个工厂类,然后实现一下工厂接口就可以了,而不需要像之前一样还要去修改工厂类的代码,这样就符合开闭原则了😃,使用这种方式实现的工厂模式我们称之为工厂方法模式(Factory Method),简称工厂模式,也称虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式
工厂方法模式的特点:将对象的创建与实例化延迟到子类(对象的创建由子类决定)
工厂方法模式解决的问题:不符合开闭原则(由于增加产品需要去修改原有代码,变成现在只需要添加代码)
工厂方法模式存在的问题:增加了系统的复杂度。每次新增产品都需要编写一个具体工厂类和具体产品类,系统中类变多了,自然而然就让系统的复杂度增多了
工厂方法模式的适用场景:
- 产品不是特别多,同时产品的添加频率不高的系统
- 想要将类的创建延迟到子类的产品,可以使用工厂方法模式
- 想要创建对象,只知道对象的父类名,不知道对象的具体类名,可以使用
工厂方法模式中的角色划分:
- 抽象工厂(Abstract Factory):它是工厂方法模式的核心,是具体工厂角色必须实现的接口或者必须继承的父类,在 Java 中它由抽象类或者接口来实现(一般都是以接口来的形式呈现)
- 具体工厂(Concrete Factory):被应用程序调用以创建具体产品的对象,含有和具体业务逻辑有关的代码
- 抽象产品(Abstract Product):是具体产品继承的父类或实现的接口,在 Java 中一般有抽象类或者接口来实现
- 具体产品(Concrete Product):具体工厂角色所创建的对象就是此角色的实例
工厂模式的核心步骤:
- Step1:创建抽象产品
- Step2:创建一个类,实现抽象产品
- Step3:创建抽象工厂
- Step4:创建一个类,实现抽象工厂
示例:
问题描述:宝马(BMW)工厂制造宝马汽车,奔驰(Benz)工厂制造奔驰汽车,使用工厂方法模式模拟改场景,并用Java语言实现。并且扩展一个比亚迪工厂生产比亚迪汽车
要求:设计一个工厂方法模式,抽象工厂对象能够根据配置文件中的内容创建不同的对象
Step1:创建抽象产品
Car:
package com.hhxy.product.car; /** * @author ghp * @date 2022/9/26 * @Title * @description */ public interface Car { /** * 汽车启动的方法 */ void start(); }
Step2:创建具体产品
1)Benz
package com.hhxy.product.car.imp; import com.hhxy.product.car.Car; /** * @author ghp * @date 2022/9/26 * @Title * @description */ public class Benz implements Car { @Override public void start() { System.out.println("奔驰汽车启动了!"); } }
2)Bmw
和上面类似,具体代码请参考Gitee或Github仓库,略……
Step3:创建抽象工厂
Factory:
package com.hhxy.factory; import com.hhxy.product.car.Car; /** * @author ghp * @date 2022/9/23 * @Title 工厂类接口 * @description 用于宝马类工厂和奔驰类工厂实现, */ public interface Factory { /** * 获取汽车对象的方法 */ Car getCar(); }
Step4:创建具体工厂
1)BenzFactory:
package com.hhxy.factory.imp; import com.hhxy.product.car.Car; import com.hhxy.product.car.imp.Benz; import com.hhxy.factory.Factory; /** * @author ghp * @date 2022/9/23 * @Title 奔驰工厂类 * @description 用于生产奔驰类的实例化对象 */ public class BenzFactory implements Factory { /** * 获取奔驰汽车对象的方法 */ @Override public Car getCar() { return new Benz(); } }
2)BmwFactory
和上面的类似,具体代码请参考Gitee或Github仓库,略……
Step5:编写配置文件
car-config.xml:
<?xml version="1.0" encoding="utf-8" ?> <config> <factoryName>com.hhxy.factory.imp.BenzFactory</factoryName> <factoryName>com.hhxy.factory.imp.BmwFactory</factoryName> <factoryName>test</factoryName> </config>
Step6:编写配置文件读取类
ReadCarConfig:
package com.hhxy.read; import com.hhxy.factory.Factory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.lang.reflect.Constructor; /** * @author ghp * @date 2022/9/23 * @Title * @description 读取用于设置汽车对象的配置文件,同时返回一个Factory对象 */ public class ReadCarConfig { public static Object getFactory(){ try{ //1、将配置文件加载到内存中,获取DOM对象 //1.1 获取DOM解析器工厂对象DocumentBuilderFactory,用于创建DOM解析器 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); //1.2 获取DOM解析器DocumentBuilder DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); //1.3 加载配置文件 // Document document = documentBuilder.parse(new FileInputStream("Factory/src/car-config.xml")); //让代码和模块名进行解耦,比上面那种方法更优 Document document = documentBuilder.parse(ReadCarConfig.class.getResourceAsStream("/car-config.xml")); //2、获取配置文件中的数据 //2.1 从DOM中获取指定的结点的结点列表 NodeList nodeList = document.getElementsByTagName("factoryName"); //2.2 获取指定位置的结点 Node classNode = nodeList.item(1).getFirstChild(); //2.3 获取指定结点中的数据(排除空格) String factoryName = classNode.getNodeValue().trim(); //3、使用反射获取获取Factory对象 //3.1 获取类对象 Class cls = Class.forName(factoryName); //3.2 获取该类对象的构造器对象 Constructor constructor = cls.getDeclaredConstructor(); //3.3 暴力反射,防止构造器私有化导致无法创建对象 constructor.setAccessible(true); //3.4 获取工厂对象 Factory factory = (Factory) constructor.newInstance(); //4、返回通过配置文件获取的工厂对象 return factory; } catch (Exception e) { //如果异常就打印异常信息,同时返回一个空 e.printStackTrace(); throw new RuntimeException("未找到该工厂类,请检查配置文件或者添加一个工厂类!"); } } }
Step7:测试
package com.hhxy.test; import com.hhxy.car.Car; import com.hhxy.factory.Factory; import com.hhxy.read.ReadCarConfig; /** * @author ghp * @date 2022/9/26 * @Title 测试类2 * @description 用于测试工厂方法 */ public class Test2 { public static void main(String[] args) { //1、读取配置文件,获取要创建对象的工厂对象 Factory factory = (Factory) ReadCarConfig.getFactory(); //2、使用工厂获取对应的汽车对象 Car car = factory.getCar(); //3、测试输出 car.start(); } }
测试结果:
🍀抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式
前置知识:
- 产品族:指由同一个工厂生产的,位于不同产品等级结构中的一组产品
- 产品等级结构:产品等级结构指的是产品的继承结构,例如一个空调抽象类,它有海尔空调、格力空调、美的空调等一系列的子类,那么这个空调抽象类和他的子类就构成了一个产品等级结构
- 抽象工厂模式的特点:一个工厂能够生产多种不同的产品(前提是这些产品属于一个产品族)
- 抽象工厂模式解决的问题:系统的复杂度高(以前是一个产品需要配置一个工厂,现在是一个产品族只需要一个工厂)
- 抽象工厂模式存在的缺点:增加新的产品等级结构很麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”
- 抽象工厂模式的适用场景:
- 存在产品族,强烈推荐使用
- 产品等级结构固定,或者产品等级结构很稳定,推荐使用
- 简单工厂模式、工厂方法模式能用的,抽象工厂模式都能用
- 抽象工厂模式的角色划分:和工厂方法模式是一致的……
示例:
问题描述:小米,华为,联想四家公司都能够生产电视、手机、冰箱三件产品,,使用抽象工厂模式模拟以上场景,并用Java语言实现
要求:使用抽象工厂模式进行设计,通过读取配置文件获取工厂对象
Step1:创建抽象产品
具体代码请参考Gitee或Github仓库,略……
Step2:创建具体产品
具体代码请参考Gitee或Github仓库,略……
Step3:创建抽象工厂
AbstractFactory:
package com.hhxy.factory; import com.hhxy.product.conditioner.Conditioner; import com.hhxy.product.phone.Phone; import com.hhxy.product.tv.TV; /** * @author ghp * @date 2022/9/28 * @Title * @description */ public interface AbstractFactory { /** * 创建手机对象的方法 */ Phone getPhone(); /** * 创建空调对象的方法 */ Conditioner getConditioner(); /** * 创建电视对象的方法 */ TV getTV(); }
Step4:创建具体工厂
1)HuaweiAbstractFactory:
package com.hhxy.factory.imp; import com.hhxy.factory.AbstractFactory; import com.hhxy.product.conditioner.Conditioner; import com.hhxy.product.conditioner.imp.HuaweiConditioner; import com.hhxy.product.phone.Phone; import com.hhxy.product.phone.imp.HuaweiPhone; import com.hhxy.product.tv.TV; import com.hhxy.product.tv.imp.HuaweiTV; /** * @author ghp * @date 2022/9/28 * @Title * @description */ public class HuaweiAbstractFactory implements AbstractFactory { /** * 创建华为手机对象的方法 */ @Override public Phone getPhone() { return new HuaweiPhone(); } /** * 创建华为空调对象的方法 */ @Override public Conditioner getConditioner() { return new HuaweiConditioner(); } /** * 创建电视对象的方法 */ @Override public TV getTV() { return new HuaweiTV(); } }
2)XiaomiAbstractFactory
和上面类似,具体请参考Gitee或Github,略……
Step5:编写配置文件
factory-config.xml:
<?xml version="1.0" encoding="UTF-8" ?> <config> <abstractFactoryName>com.hhxy.factory.imp.HuaweiAbstractFactory</abstractFactoryName> <abstractFactoryName>com.hhxy.factory.imp.XiaomiAbstractFactory</abstractFactoryName> <abstractFactoryName>com.hhxy.factory.imp.LianXiangAbstractFactory</abstractFactoryName> <abstractFactoryName>test</abstractFactoryName> </config>
Step6:编写配置文件读取类
和工厂方法模式的类似,具体请参考Gitee或Github,略……
Step7:测试
package com.hhxy.test; import com.hhxy.factory.AbstractFactory; import com.hhxy.read.ReadFactoryConfig; /** * @author ghp * @date 2022/9/28 * @Title 测试类3 * @description 用于测试抽象工厂模式 */ public class Test3 { public static void main(String[] args) { //测试使用华为工厂创建华为这一产品族的对象 /* 方式一: AbstractFactory huaweiAbstractFactory = new HuaweiAbstractFactory(); huaweiAbstractFactory.getTV().watch(); huaweiAbstractFactory.getPhone().call(); huaweiAbstractFactory.getConditioner().open(); */ //方式二:通过读取配置文件获取工厂对象。这种方式能够降低耦合度,提高了代码的复用性,很方便测试 AbstractFactory abstractFactory = ReadFactoryConfig.getAbstractFactory(); abstractFactory.getTV().watch(); abstractFactory.getPhone().call(); abstractFactory.getConditioner().open(); } }
测试结果:
🌲总结
三个工厂模式的比较:
- 简单工厂模式实现起来最简单,只适合很小的项目,它不属于23种设计模式。对象的创建由工厂决定,实现了对象创建和对象操作的分离,产品的增加需要修改原有的工厂类代码,不符合开闭原则
- 工厂方法模式适合用于小、中型项目,但对于大型项目会增加系统的复杂度。对象实例化被延迟到了子类,符合开闭行原则,且不需要明确对象对应的类名,每增加一个产品都需要增加一个具体工厂
- 抽象工厂模式适合小、中以及大型项目,特别是项目中的产品具有产品族的时候更加能体现该模式的优越性。一个工厂能生产多个产品(前提这些产品都属于同一个产品族),对于产品族的增加只需要添加一个产品和一个具体工厂就可以了(符合开闭原则),但是如果想要添加一个新的产品等级结构,就需要修改原来的代码,不符合开闭原则
- 当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式
总的来讲三种模式各有优劣,从上往下抽象等级依次增加,这也表明往下更具一般性,适用范围更广
自此,文章就结束了,如果觉得本文对你有一丢丢帮助的话😄,欢迎点赞👍+评论✍,您的支持将是我写出更加优秀文章的动力O(∩_∩)O
上一篇:每日一个设计模式之【原型模式】
下一篇:每日一个设计模式之【建造者模式】
参考文章:
- 【源码分析设计模式 3】JDK中的工厂模式
- Java设计模式之创建型:工厂模式详解(简单工厂+工厂方法+抽象工厂)
- [工厂模式——这一篇真够了](https://xie.infoq.cn/article/88c926822394aa1c80847dd2a#:~:text=工厂模式可以分为三类:. 简单工厂模式(Simple Factory). 工厂方法模式(Factory Method). 抽象工厂模式(Abstract,Factory). 简单工厂其实不是一个标准的的设计模式。. GOF 23种设计模式中只有「工厂方法模式」与「抽象工厂模式」。. 简单工厂模式可以看为工厂方法模式的一种特例,为了统一整理学习,就都归为工厂模式。. 这三种工厂模式在设计模式的分类中都属于创建型模式,三种模式从上到下逐步抽象。.)
在此致谢