工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
优点: 一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要纵向拓展,增加一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,这里讲的是横向拓展,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
Spring的运用:BeanFacrory
业务场景
工厂模式一般配合策略模式一起使用。用来去优化大量的if…else…或switch…case…条件语句。
我们就取第一小节中策略模式那个例子吧。根据不同的文件解析类型,创建不同的解析对象
IFileStrategy getFileStrategy(FileTypeResolveEnum fileType){
IFileStrategy fileStrategy ;
if(fileType=FileTypeResolveEnum.File_A_RESOLVE){
// 根据fileType创建对象AFileResolve, 客户端只需要调用getFileStrategy()方法 而不需要知道是怎么创建的。
fileStrategy = new AFileResolve();
}else if(fileType=FileTypeResolveEnum.File_A_RESOLV){
fileStrategy = new BFileResolve();
}else{
fileStrategy = new DefaultFileResolve();
}
return fileStrategy;
}
其实这就是工厂模式,定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
策略模式的例子,没有使用上一段代码,而是借助spring的特性,搞了一个工厂模式,哈哈,小伙伴们可以回去那个例子细品一下,我把代码再搬下来,小伙伴们再品一下吧:
@Component
public class StrategyUseService implements ApplicationContextAware{
private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();
//把所有的文件类型解析的对象,放到map,需要使用时,信手拈来即可。这就是工厂模式的一种体现啦
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);
tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService));
}
}
定义工厂模式:
①一个工厂接口,提供一个创建不同对象的方法。
②其子类实现工厂接口,构造不同对象
一个工厂接口:
interface IFileResolveFactory{
void resolve();
}
不同子类实现工厂接口:
class AFileResolve implements IFileResolveFactory{
void resolve(){
System.out.println("文件A类型解析");
}
}
class BFileResolve implements IFileResolveFactory{
void resolve(){
System.out.println("文件B类型解析");
}
}
class DefaultFileResolve implements IFileResolveFactory{
void resolve(){
System.out.println("默认文件类型解析");
}
}
具体怎么使用工厂模式:
IFileResolveFactory fileResolveFactory;
if(fileType=“A”){
// 工厂模式
fileResolveFactory = new AFileResolve();
}else if(fileType=“B”){
fileResolveFactory = new BFileResolve();
}else{
fileResolveFactory = new DefaultFileResolve();
}
// 使用工厂模式
fileResolveFactory.resolve();
一般情况下,对于工厂模式,你不会看到以上的代码。工厂模式会跟配合其他设计模式如策略模式一起出现的。
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以其在运行时找到对应的策略算法实现特有的算法逻辑。这种类型的设计模式属于行为型模式。(区别于模板模式,模板模式是共同骨架)。
具体作用:
定义一个个的算法,也就是定义多个类,每个类的实现逻辑都不一样,具体走哪个算法哪个类的实现逻辑,需要看运行时传入的参数,切换到具体的实现类实现业务逻辑。
主要解决:
在有多种算法的情况下,使用 if…else 所带来的复杂和难以维护。
何时使用:
一个系统有许多许多类,而区分它们的只是他们直接的行为。
优点:
1、算法可以自由切换。
2、避免使用多重条件判断。
3、扩展性良好。
缺点:
1、策略类会增多。
2、所有策略类都需要对外暴露。@Component,交由spring管理。
直接上代码:
简单介绍一下项目使用场景:需要处理多种业务类型的发送,每种发送逻辑有70%的处理发送逻辑是不一样的,我选择将每种业务类型的业务逻辑抽取成一个个的单独算法,每个具体实现类实现自己的业务逻辑,这样根据前端传入的标识字符串可以切换到具体的算法也就是具体的实现类,实现自己的发送逻辑。
本次结合了工厂模式+数据库新建的策略表(当然也可以使用反射)+策略,具体实现看代码。
2、创建策略模式的上下文-获取具体的实现策略(获取具体的策略也可以用反射实现)
3、创建实现类
具体策略类1:重写接口方法,实现自己的业务逻辑(策略2同理)
使用:
4、模板模式(相同的骨架)
子类实现逻辑相同的代码抽到一个父类,定义成一个公共模板,子类实现特有的业务逻辑。
作用:
封装不变部分,拓展可变部分。
提取公共代码,去除子类的重复代码,便于维护。
父类调用子类实现的操作,通过子类拓展新的行为,符合“开放封闭原则”。
缺点:拓展类增加,系统变得庞大。
业务场景:
具体实现:
第一步,定义一个抽象类或者接口,定义一个模板,封装相同代码,比如日志收集
第二步,子类继承父类,重写父类的抽象方法,实现子类的业务逻辑
第三步,定义一个工厂,通过工厂获取子类 子类需要注入spring容器
通过spring上下文获取具体子类
业务场景
单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。I/O与数据库的连接,一般就用单例模式实现的。Windows里面的Task Manager(任务管理器)也是很典型的单例模式。
来看一个单例模式的例子
public class LanHanSingleton {
private static LanHanSingleton instance;
private LanHanSingleton(){
}
public static LanHanSingleton getInstance(){
if (instance == null) {
instance = new LanHanSingleton();
}
return instance;
}
}
以上的例子,就是懒汉式的单例实现。实例在需要用到的时候,才去创建,就比较懒。如果有则返回,没有则新建,需要加下 synchronized关键字,要不然可能存在线性安全问题。
单例模式的经典写法
其实单例模式还有有好几种实现方式,如饿汉模式,双重校验锁,静态内部类,枚举等实现方式。
① 饿汉模式
public class EHanSingleton {
// 实例在初始化的时候就已经new了
private static EHanSingleton instance = new EHanSingleton();
private EHanSingleton(){
}
public static EHanSingleton getInstance() {
return instance;
}
}
饿汉模式,它比较饥饿、比较勤奋,实例在初始化的时候就已经建好了,不管你后面有没有用到,都先新建好实例再说。这个就没有线程安全的问题,但是呢,浪费内存空间呀。
6.2.2 双重校验锁
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton instance;
private DoubleCheckSingleton() { }
public static DoubleCheckSingleton getInstance(){
if (instance == null) { // 第一重null检查
synchronized (DoubleCheckSingleton.class) { // 加锁
if (instance == null) { // 第二重null检查
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
双重校验锁实现的单例模式,综合了懒汉式和饿汉式两者的优缺点。以上代码例子中,在synchronized关键字内外都加了一层 if条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
6.2.3 静态内部类
public class InnerClassSingleton {
private static class InnerClassSingletonHolder{
private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
}
private InnerClassSingleton(){}
public static final InnerClassSingleton getInstance(){
return InnerClassSingletonHolder.INSTANCE;
}
}
静态内部类的实现方式,效果有点类似双重校验锁。但这种方式只适用于静态域场景,双重校验锁方式可在实例域需要延迟初始化时使用。
6.2.4 枚举
public enum SingletonEnum {
INSTANCE;
public SingletonEnum getInstance(){
return INSTANCE;
}
}
枚举实现的单例,代码简洁清晰。并且它还自动支持序列化机制,绝对防止多次实例化。
定义:
客户端发送一个请求,链上的对象都有机会来处理这个请求,属于链式传递,直到有对象处理这个请求为止,其实是一个递归调用。客户端不需要知道是谁处理了这个请求,达到了请求者和接收者的解耦,并且客户端可以实现动态的组合职责链。
主要特点:
有多个对象对同一个任务进行处理;
链式结构存储这些处理对象,每个对象知道自己的下一个对象;
一个对象对任务进行处理,可以添加一些操作后也就是实现这个对象的独有的业务逻辑,然后传递给下一个对象,也可以在这个对象上结束任务的处理;
客户端负责组装链式结构,但是客户端不需要关心是哪个对象处理了任务。
使用场景:
1、ERP系统的流程审批:组长->人事经理->项目经理->总经理
2、多条件流程判断:权限控制
3、Java过滤器的底层实现Filter:比如在Java过滤器中客户端发送请求到服务器端,过滤会经过参数过滤、session过滤、表单过滤、隐藏过滤、检测请求头过滤等。
4、网关权限控制:网关作为微服务程序的入口,拦截客户端所有的请求实现权限控制,比如先判断API接口限流、黑名单、用户会话、参数过滤。
使用链表保证执行顺序。
获取第一个handler保证链表按照顺序执行。
以网关作为例子:
第二步、定义具体的责任链处理对象,重写抽象方法实现各个子类的业务逻辑(每个子类中责任链的下一个对象的设置和执行下一个对象,这些共同操作我们结合模板方法设计模式将它定义成一个共同的骨架,抽取出来,解除强耦合)
实现演示2:如果使用的是第一种演示案例去实现,会遇到这么一个问题,所有的具体责任链对象我们是直接在代码中定义屎了,后期拓展非常麻烦,不符合开放-封闭原则,所以我们进行一个改造,采用数据库的方式进行一个动态责任链的构造。
责任链模式+数据库代替工厂方法+模板方法模式实现:
递归构造责任链对象:
Filter接口源码使用了责任链模式:
这里的filterChain.doFilter()方法等同于我们的nextService()方法,指向下一个责任链对象
模板方法:这个FilterChain等同于我们的GateWayHandler
各个责任链对象进行了一些列的校验:
比如会经过参数过滤、session过滤、表单过滤、隐藏过滤、检测请求头过滤等。
实际业务中,业务逻辑可能很复杂,核心业务+N个子业务,如果代码都堆积在一起,代码可能很复杂,维护起来有点费力气,耦合度也很高。还有一些场景就是不需要在一次请求中同步完成,比如发送邮件、发送短信等场景,根据业务场景实现部分的松耦合,实现方法可以是用Spring Event(Application Event)。
Spring Event同步使用:
①自定义事件
import lombok.Data;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;
/**
* @author 晴日朗
* @date 2022/4/22 18:00
* @description 定义事件,继承 ApplicationEvent 的类成为一个事件类
*/
public class OrderProductEvent extends ApplicationEvent {
/** 该类型事件携带的信息 */
private String orderId;
public OrderProductEvent(Object source, String orderId) {
super(source);
this.orderId = orderId;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
}
②自定义事件监听器
/**
* 实现 ApplicationListener 接口,并指定监听的事件类型
*
* @author 晴日朗
* @date 2022/4/24 09:09
* @description
*/
@Component
public class OrderProductListener implements ApplicationListener<OrderProductEvent> {
/**
* 使用 onApplicationEvent 方法对消息进行接收处理
*/
@SneakyThrows
@Override
public void onApplicationEvent(OrderProductEvent event) {
String orderId = event.getOrderId();
long start = System.currentTimeMillis();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(orderId + ":校验订单商品价格耗时:" + (end - start) + "毫秒");
}
}
③自定事件发布者
package com.hermes.job;
import com.hermes.utils.SpringUtils;
import org.springframework.stereotype.Service;
/**
* @author 晴日朗
* @date 2022/4/24 09:25
* @description
*/
@Service
public class OrderService {
/**
* 下单
*
* @param orderId 订单ID
*/
public static String buyOrder(String orderId) {
long start = System.currentTimeMillis();
// 1.查询订单详情
// 2.检验订单价格 (同步处理)
SpringUtils.getApplicationContext().publishEvent(new OrderProductEvent("this", orderId));
// 3.短信通知(异步处理)
long end = System.currentTimeMillis();
System.out.println("任务全部完成,总耗时=" + (end - start) + "毫秒");
return "购买成功";
}
}
单元测试
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class OrderServiceTest {
@Autowired private OrderService orderService;
@Test
public void buyOrderTest() {
orderService.buyOrder("123");
}
}
测试结果:同步执行
2022-04-24 10:13:17.535 INFO 44272 --- [ main] c.c.m.e.listener.OrderProductListener : 123:校验订单商品价格耗时:(2008)毫秒
2022-04-24 10:13:17.536 INFO 44272 --- [ main] c.c.mingyue.event.service.OrderService : 任务全部完成,总耗时:(2009)毫秒
Spring Event异步使用:
有些业务场景不需要在一次请求中同步完成,比如邮件发送、短信发送等。
①自定义事件
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class MsgEvent {
/** 该类型事件携带的信息 */
public String orderId;
}
②自定义事件监听器(使用了注解@EventListener)
import com.csp.mingyue.event.events.MsgEvent;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
@Component
public class MsgListener {
@EventListener(MsgEvent.class)
public void sendMsg(MsgEvent event) {
String orderId = event.getOrderId();
long start = System.currentTimeMillis();
sout("开发发送短信");
sout("开发发送邮件");
Thread.sleep(4000);
long end = System.currentTimeMillis();
sout(orderId+":发送短信、邮件耗时:"+(end - start)+"毫秒")
}
}
③自定事件发布者
/**
* 下单
*
* @param orderId 订单ID
*/
public String buyOrder(String orderId) {
long start = System.currentTimeMillis();
// 1.查询订单详情
// 2.检验订单价格 (同步处理)
applicationContext.publishEvent(new OrderProductEvent(this, orderId));
// 3.短信通知(异步处理)
applicationContext.publishEvent(new MsgEvent(orderId));
long end = System.currentTimeMillis();
sout("任务全部完成,总耗时:"+(end - start)"毫秒");
return "购买成功";
}
④同步单元测试
@Test
public void buyOrderTest() {
orderService.buyOrder("123");
}
结果:同步执行
2022-04-24 10:24:13.905 INFO 54848 --- [ main] c.c.m.e.listener.OrderProductListener : 732171109:校验订单商品价格耗时:(2004)毫秒
2022-04-24 10:24:13.906 INFO 54848 --- [ main] c.c.mingyue.event.listener.MsgListener : 开发发送短信
2022-04-24 10:24:13.907 INFO 54848 --- [ main] c.c.mingyue.event.listener.MsgListener : 开发发送邮件
2022-04-24 10:24:17.908 INFO 54848 --- [ main] c.c.mingyue.event.listener.MsgListener : 732171109:发送短信、邮件耗时:(4002)毫秒
2022-04-24 10:24:17.908 INFO 54848 --- [ main] c.c.mingyue.event.service.OrderService : 任务全部完成,总耗时:(6008)毫秒
⑤基于springBoot开启异步:
启动类增加 @EnableAsync 注解
@EnableAsync
@SpringBootApplication
public class MingYueSpringbootEventApplication {
public static void main(String[] args) {
SpringApplication.run(MingYueSpringbootEventApplication.class, args);
}
}
Listener 类需要开启异步的方法增加 @Async 注解
@Async
@EventListener(MsgEvent.class)
public void sendMsg(MsgEvent event) {
String orderId = event.getOrderId();
long start = System.currentTimeMillis();
sout("开发发送短信");
sout("开发发送邮件");
try{
Thread.sleep(4000);
}catch{
e.printStackTrace();
}
long end = System.currentTimeMillis();
sout(orderId+":发送短信、邮件耗时:"+(end - start)+"毫秒");
}
异步单元测试
2022-04-24 10:30:59.002 INFO 59448 --- [ main] c.c.m.e.listener.OrderProductListener : 732171109:校验订单商品价格耗时:(2009)毫秒
2022-04-24 10:30:59.009 INFO 59448 --- [ main] c.c.mingyue.event.service.OrderService : 任务全部完成,总耗时:(2017)毫秒
2022-04-24 10:30:59.028 INFO 59448 --- [ task-1] c.c.mingyue.event.listener.MsgListener : 开发发送短信
2022-04-24 10:30:59.028 INFO 59448 --- [ task-1] c.c.mingyue.event.listener.MsgListener : 开发发送邮件
其他的用到了再记录,加油啊噢力给!