工厂模式
简介
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。工厂模式主要是解决创建对象的问题,典型的应用就是在spring中的IOC,反转控制,反转控制就是把创建对象的权限交给框架,所以spring就是一个生产对象的工厂。
思路
工厂模式的思路就是设计一个产生对象的机制,让生产对象的过程交给第三方,在工厂模式中,不会对客户端暴露创建逻辑,并且使用通用接口接收新创建的对象。
实现过程
- 新建抽象的接口
- 新建具体的实体类,实现抽象的接口
- 创建实例化对象的工厂
- 在客户端中通过工厂创建具体的实体对象,对象可以用抽象接口接收。
这种方式是最简单的实现方式:
// 创建接口
public interface Shape {
void draw();
}
// 创建实体类Circle
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("drawing a circle");
}
}
// 创建实体类Rectangle
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("drawing a Rectangle");
}
}
// 创建实体类Square
public class Square implements Shape {
@Override
public void draw() {
System.out.println("drawing a square");
}
}
然后创建工厂类,生成对应的实体类
public class ShapeFactory {
public static Shape getShapes(String shapeType) {
if (shapeType == null) {
System.out.println("shapeType is null");
throw new RuntimeException();
} else if (shapeType.equalsIgnoreCase("Rectangle")) {
return new Rectangle();
} else if (shapeType.equalsIgnoreCase("Square")) {
return new Square();
}else if(shapeType.equalsIgnoreCase("Circle")){
return new Circle();
}else {
System.out.println("nothing to do");
return null;
}
}
// 测试简单工厂模式
@Test
public void testSimpleFactoryPattern(){
Shape circle = ShapeFactory.getShapes("circle");
circle.draw();
Shape rectangle = ShapeFactory.getShapes("Rectangle");
rectangle.draw();
Shape square = ShapeFactory.getShapes("Square");
square.draw();
}
}
这种方式实现工厂模式很简单,但是缺点也很明显,比如,在增加一个实现Shape接口的实体类,又需要去修改ShapeFactory中的代码,这样其实不符合设计模式的原则,对扩展开放,对修改关闭。
工厂模式的改进
分析一下,之所以每新增一个类都需要去修改工厂的代码,是因为在工厂中,生成类的代码太具体了,要想改变这种情况,就需要把这个工厂生成类实例的过程变得抽象化,在Java中,生成对象的方法不止一种,还可以利用反射机制,工厂接收的是和类相关的参数,可以把这个参数换成需要生成实例的类,这样工厂中生成类的代码就很抽象了。具体代码如下:
public class ShapeFactory {
public static Shape getClass(Class<? extends Shape> clazz) {
Shape shape = null;
try {
// 通过反射生成一个类的实例
shape = (Shape) Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return shape;
}
// 测试反射改进后的工厂
@Test
public void testReflectShapeFac(){
Shape rectangle = ShapeFactory.getClass(Rectangle.class);
rectangle.draw();
Shape square = ShapeFactory.getClass(Square.class);
square.draw();
Shape circle = ShapeFactory.getClass(Circle.class);
circle.draw();
}
}
这样工厂的生产过程就很抽象了,但是还有一个问题,这个工厂只能生成实现Shape接口的类实例,如果出现了另外一种接口,就有需要新增一个工厂,这样也未尝不可,因为只是扩展而已,但是又出现了一个新的问题,这些工厂中的生产对象的代码都差不多,只是强转的接口不同,代码还是有优化的空间的。工厂可以进一步抽象:
// 改进后的工厂方法
public static <T> T getClass(Class<? extends T> clazz){
T obj = null;
try {
obj= (T) Class.forName(clazz.getName()).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return obj;
}
借助配置文件的工厂模式
到这里,可以去思考一下在spring中,是怎么利用工厂模式的,首先你需要去xml中配置你需要实例化的类,然后读取这个配置文件,通过反射生成这个类的实例返回。其实这里也可以简单模仿一下:
// 解析Properties配置文件
public class PropertiesUtil {
private static Properties properties = new Properties();
public static String getPackageByName(String name) {
return properties.getProperty(name);
}
// 解析配置文件
private static Map<String, String> parseProperties() {
Map<String, String> map = new HashMap<>();
InputStream inputStream = PropertiesUtil.class.getClassLoader().getResourceAsStream("application.properties");
try {
properties.load(inputStream);
} catch (IOException e) {
System.err.println("file is not exists");
e.printStackTrace();
}
return map;
}
}
配置文件中的内容如下:
# 类名和包名的映射
circle=com.factory.pattern.simple.Circle
rectangle=com.factory.pattern.simple.Rectangle
square=com.factory.pattern.simple.Square
然后根据提供的配置文件的报名,通过反射实例化对应的类:
public class ConfigFactory {
// 通过配置文件中的包名生成实例
public static <T> T getNewInstance(String className) {
String packageName = PropertiesUtil.getPackageByName(className);
try {
return (T) Class.forName(packageName).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
这种方式需要需要注意的是,加载配置文件一定是在调用工厂的前面,因为需要读取报名,把对应的数据读到内存中,这也就是spring中先启动容器的原因。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?