前言

从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫静态工厂模式(Simple Factory Pattern),但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出接口哪一种实现类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。

在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

简单工厂模式只需要向工厂类传入一个正确的参数,就可以获取所需要的对象,而无需知道其实现过程。

  简单工厂模式是在什么场景下使用呢?这里简单的枚举几个使用场景:

1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方;

2、保险产品配置:保险产品往往有很多赔付等级,每个等级对应不同的保额和赔付金额;

3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。

定义中最重要的一句话就是,由一个工厂对象决定创建出哪一种实现类的实例。在文章《Spring注解之@Autowired:注入Arrays, Collections, and Maps》中,小编介绍了Spring Boot如何把某些接口实现类的Bean注入到Map和List等集合中,方便应用的时候直接读取需要的bean。结合@Autowired可以自动注入指定接口实现类到Map中,介绍简单工厂模式的一个实现策略。

简单工厂模式实践

我们将创建一个 Shape 接口和实现 Shape 接口的实体类。下一步是定义工厂类 ShapeFactory。在controller FactoryPatternDemo 中,我们演示使用 ShapeFactory 来获取 Shape 对象。它将向 ShapeFactory 传递信息(circle/rectangle/square),以便获取它所需对象的类型。

新增接口类Shape:

/**
* 定义bean接口
*/
public interface Shape {
void draw();
}

创建Shape的三个实现类,并且都添加注解@Service,将其注册为Spring Bean。

@Service
public class Rectangle implements Shape { @Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
=========== 我是分割线 =============
@Service
public class Square implements Shape { @Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
=========== 我是分割线 =============
@Service
public class Circle implements Shape { @Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}

实现方法一:基于新建对象实现

兹创建一个工厂类,根据调用者的不同,创建不同的实现类并返回。而如果碰到不合法的请求参数,则会返回null。

/**
* 简单工厂类,通过新建对象实现
* 不添加Spring注解,调用的时候直接new
*/
public class ShapeFactory {
/**
* 使用 getShape 方法获取几何图形类型的对象
*/
public static Shape getShape(String shapeType) {
if ("circle".equalsIgnoreCase(shapeType)) {
return new Circle();
} else if ("rectangle".equalsIgnoreCase(shapeType)) {
return new Rectangle();
} else if ("square".equalsIgnoreCase(shapeType)) {
return new Square();
}
return null;
}
}

实现方法二:基于spring注解实现

下面新增一个类ShapeBeanFactory,通过@Autowired注解把bean注入map后,由变量map替换工厂类,实现简单工厂模式。

/**
* 简单工厂类,通过 Spring注解 @Autowired 和 @Service 实现
*/
@Component // 添加Spring注解
public class ShapeBeanFactory { @Autowired
private Map<String, Shape> shapeMap; public Shape getShape(String shapeType) {
Shape bean1 = shapeMap.get(shapeType);
System.out.println(bean1);
return bean1;
} }

通过对比两种实现方案可以发现,使用Spring注解的方式更加简洁,避开了 if else 分支。

客户端调用工厂类

创建一个controller,命名为FactoryPatternDemo,并把两种实现方案封装为不同的私有方法。客户端调用工厂类,传入几何图形类型参数获取几何图形对象并调用该对象的draw方法:

 @GetMapping("/drawMyShape")
public String drawMyShape(){
shapeFactoryDraw();
shapeBeanFactoryDraw();
return "成功";
}
private void shapeFactoryDraw() {
System.out.println("======= shapeFactory ======="); //获取 Circle 的对象,并调用它的 draw 方法
Shape shapeInterface1 = ShapeFactory.getShape("circle");
//调用 Circle 的 draw 方法
shapeInterface1.draw(); Shape shapeInterface2 = ShapeFactory.getShape("rectangle");
shapeInterface2.draw();
} @Autowired
private ShapeBeanFactory factory; // 使用注解注入 private void shapeBeanFactoryDraw() {
System.out.println("======= 实现二 shapeBeanFactory =======");
Shape shapeInterface1 = factory.getShape("circle");
shapeInterface1.draw(); Shape shapeInterface2 = factory.getShape("square");
shapeInterface2.draw();
}

客户端请求draw函数,控制台输出结果如下,说明对工厂类重构成功。

======= shapeFactory =======
Inside Circle::draw() method.
Inside Rectangle::draw() method.
======= 实现二 shapeBeanFactory =======
com.eg.wiener.service.impl.shape.Circle@3a0aa6f6
Inside Circle::draw() method.
com.eg.wiener.service.impl.shape.Square@6d48c8f9
Inside Square::draw() method.

优缺点和适用场景

优点

工厂类是整个模式的关键。包含了必要的逻辑判断,根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面摆脱出来,仅仅需要负责“消费”对象就可以了,而不必管这些对象究竟如何创建及如何组织的,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。明确了各自的职责和权利,有利于整个软件体系结构的优化。

通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

缺点

由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。

当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求。这种对条件和具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;

违背开闭原则。对于上面两种简单工厂模式的实现方法,如果我们要添加新的 几何图形,势必要改动 ShapeFactory的代码,那这是不是违反开闭原则呢?实际上,如果不需要频繁地添加新的几何图形实现类,只是偶尔修改一下代码,稍微不符合开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下,也是可以接受的。这是第一种实现方案的瑕疵,但是,第二种实现方案就没有违背开闭原则,完全由Spring IOC容器管理Bean,彰显了Spring IOC容器的强大之处。

适用场景

在以下场景适合使用简单工厂模式:

工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。

客户端只知道传入创建工厂类所需的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。

Reference

https://www.runoob.com/design-pattern/factory-pattern.html

http://www.cnblogs.com/java-my-life/archive/2012/03/22/2412308.html

https://www.jianshu.com/p/5cb52d84bd6d

Spring Boot中使用注解实现简单工厂模式的更多相关文章

  1. Spring Boot中@Scheduled注解的使用方法

    Spring Boot中@Scheduled注解的使用方法 一.定时任务注解为@Scheduled,使用方式举例如下 //定义一个按时间执行的定时任务,在每天16:00执行一次. @Scheduled ...

  2. spring boot 中@Autowired注解无法自动注入的错误

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/huihuilovei/article/de ...

  3. Spring Boot中的注解

    文章来源:http://www.tuicool.com/articles/bQnMra 在Spring Boot中几乎可以完全弃用xml配置文件,本文的主题是分析常用的注解. Spring最开始是为了 ...

  4. Spring Boot中自定义注解+AOP实现主备库切换

    摘要: 本篇文章的场景是做调度中心和监控中心时的需求,后端使用TDDL实现分表分库,需求:实现关键业务的查询监控,当用Mybatis查询数据时需要从主库切换到备库或者直接连到备库上查询,从而减小主库的 ...

  5. Spring Boot中使用redis的发布/订阅模式

    原文:https://www.cnblogs.com/meetzy/p/7986956.html redis不仅是一个非常强大的非关系型数据库,它同时还拥有消息中间件的pub/sub功能,在sprin ...

  6. Spring Boot中@ConfigurationProperties注解实现原理源码解析

    0. 开源项目推荐 Pepper Metrics是我与同事开发的一个开源工具(https://github.com/zrbcool/pepper-metrics),其通过收集jedis/mybatis ...

  7. Spring Boot 中 @SpringBootApplication注解背后的三体结构探秘

    概 述 SpringBoot 约定大于配置 的功力让我们如沐春风,在我之前写的文章<从SpringBoot到SpringMVC> 也对比过 SpringBoot 和 SpringMVC 这 ...

  8. Spring Boot中的缓存支持(一)注解配置与EhCache使用

    Spring Boot中的缓存支持(一)注解配置与EhCache使用 随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决 ...

  9. Spring Boot中使用缓存

    Spring Boot中使用缓存 随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一. 原始的使 ...

  10. Spring Boot中快速操作Mongodb

    Spring Boot中快速操作Mongodb 在Spring Boot中集成Mongodb非常简单,只需要加入Mongodb的Starter包即可,代码如下: <dependency> ...

随机推荐

  1. Qt读取Oracle中的中文乱码问题

    Qt读取oracle中的中文 因为有的时候我们的oracle数据库里面的值是一个varchar2格式的,这就是一个ascii码,但是我们qt一般不是ascii码 解决方法如下 先使用utl_raw.c ...

  2. 魔方求解器程序(层先法,java版本)

    实现了一个三阶魔方的层先法求解程序:https://github.com/davelet/java-puzzle-resolver 欢迎试用. 用法 1. 随机试用 不关注起始状态的话可以用程序的随机 ...

  3. JS中数组的操作方法大全

    常见的一些数组操作push . pop.unshift. shift push 语法: array.push(item1, item2, -, itemX) push( )方法:可以将一个或者更多的参 ...

  4. golang interface 转 string、int、float64

    interface{} interface{} 接口.interface{} 类型很多人都会混淆.interface{} 类型是没有方法的接口.由于没有 implements 关键字,所以说所有的类型 ...

  5. Ubuntu安装GPU驱动+CUDA+cuDNN的安装方法

    一台有GPU的虚拟机如果没有安装CUDA的驱动,是需要我们手动去进行安装的,介绍Ubuntu操作系统的安装教程. 1. 下载安装文件 NVIDIA CUDA Toolkit Archive 点击上面链 ...

  6. TCP协议详细介绍

    TCP报文格式: 字段介绍: 源/目的端口:用来标识主机上的程序 序号(seq):4个byte,指当前tcp报文段中第一个字节的序号(tcp报文中每个字节都有一个编号) 确认号(ack):4个byte ...

  7. [源码系列:手写spring] IOC第七节:加载xml文件中定义的Bean

    目录 主要内容 代码分支 核心代码 BeanDefinitionReader AbstractBeanDefinitionReader XmlBeanDefinitionReader 测试 bean定 ...

  8. 内部类--成员内部类、静态内部类、局部内部类--java进阶day03

    1.内部类 内部类分为4种,成员内部类用处不大,静态内部类和局部内部类更是鸡肋,唯有匿名内部类是需要我们重点掌握的 1.成员内部类 Inter类要访问Outer类的成员可以直接访问,而Outer要访问 ...

  9. Spring AI 增加混元 embedding 向量功能

    上次我们讨论了如何将自己的开源项目发布到 Maven 中央仓库,确保其能够方便地被其他开发者使用和集成.而我们的项目 spring-ai-hunyuan 已经具备了正常的聊天对话功能,包括文本聊天和图 ...

  10. 附042.Kubernetes_v1.32.3生成环境高可用部署

    目录 部署组件 kubeadm介绍 kubelet介绍 kubectl介绍 方案概述 方案介绍 部署规划 节点规划 主机名配置 变量准备 互信配置 环境预配置 部署高可用组件 HAProxy安装 Ke ...