工作中常用的6种设计模式

news2024/11/25 4:18:12

背景

  谈起设计模式,你一定会问?这玩意到底有啥用?我好像没用过也不影响做一个码农。也可能项目中实际用了,但是你不自知。虽然Java设计模式有23种,但是工作中常用的可能并没有那麽多。就像新华字典有多少字,你没必要都学一样。本章我们只谈常用的几种设计模式,通过设计模式的理念、规约、到应用,理解实战中如何正确使用设计模式,不论对面试还是实际工作中都有益处。

文章提纲

图片

设计理念

最为Java开发者,程序员基本修养名言绝句:

  • 该露露,该藏藏

  • 该封装的要封装

  • 万事万物兼对象

  • 程序代码要健壮

我们简单归纳为2个核心词:高内聚低耦合

  很小的时候看过动画片,封神演义中哪吒:三头八臂显威力,千征百战斗魔法。(串台了。。。)

图片

我们根据这首歌词抽象一下,哪吒:三头八臂是静态特征,千征百战是动态技能。把这些特征归纳映射一下:类 = 属性 + 方法如下图:

图片

以上是高内聚的概念,什么是低耦合

如果可能,我写一本神话《封神演戏》,说哪吒有:三头九臂。你肯定和我吵吵,要给它再配一把兵器。虽然还没想好是啥,但是有个总则:绝对不影响先前这八臂的演技。这就是低耦合!!!所谓程序健壮、拓展性强,也是这个道理。我们真诚地希望:

图片

上述例子不是特别恰当,但是对于设计模式,我们终级的理念是:封装变化的内容,保留不变的宗旨

设计原则

设计原则可以归纳为2大类:

  • 开闭原则(李氏替换,组合复用,依赖倒置)

规定:软件中的对象(类、模块、函数等等)应该对于扩展是开放的,但是对于修改是封闭的。换句话说,一个实体是允许在不改变它的源代码的前提下变更它的行为。

  • 单一职责(接口隔离,迪米特法则)

规定:一个类只应该有一个职责,只有一个改变它的原因

Spring中的设计模式

在Spring框架中,各种设计模式被广泛应用以支持其强大的功能和灵活性。下面我将结合Spring的源码,鉴赏下Spring中常见的几种设计模式。

1. 单例模式

  Spring框架中的Bean默认就是单例的。Spring IoC容器负责创建对象实例,并确保在整个应用中,针对同一个Bean的ID,只实例化一次对象。DefaultSingletonBeanRegistry类是Spring管理单例Bean的核心类。

// DefaultSingletonBeanRegistry类中的部分源码
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    // ...
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    // ...
    @Override
    public Object getSingleton(String beanName) {
        return getSingleton(beanName, true);
    }
    
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // ... 省略部分代码
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
    // ...
}

2. 工厂模式

Spring使用工厂模式通过BeanFactoryApplicationContext等接口创建和管理Bean对象。DefaultListableBeanFactory是Spring中Bean工厂的实现类。

// DefaultListableBeanFactory类中的部分源码
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
    // ...
    @Override
    public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return doGetBean(name, requiredType, null, false);
    }

    @Override
    public <T> T getBean(Class<T> requiredType) throws BeansException {
        return doGetBean(null, requiredType, null, false);
    }
    
    // ... 省略部分代码
}

3. 代理模式

Spring AOP(面向切面编程)的实现就是基于代理模式。Spring创建目标对象的代理对象,并在代理对象中织入切面逻辑。JdkDynamicAopProxyCglibAopProxy是Spring AOP中创建代理的两个核心类。

// JdkDynamicAopProxy类中的部分源码
public class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {
    // ...
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // ... 省略部分代码
        // 获取AdvisedSupport对象,包含了切面等AOP相关信息
        final AdvisedSupport advised = this.advised;
        // ... 省略部分代码
        // 获取拦截器链(切面链)
        List<Object> chain = advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        // ... 省略部分代码
        // 执行链式调用
        return invokeJoinpointUsingReflection(target, method, args, targetClass, chain);
    }
    // ...
}

4. 观察者模式(监听模式)

在Spring中,事件处理机制就是基于观察者模式实现的。当事件发生时,所有注册的观察者都会收到通知并作出响应。ApplicationEventMulticaster接口和SimpleApplicationEventMulticaster类是Spring事件处理机制的核心。

// SimpleApplicationEventMulticaster类中的部分源码
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    // ...
    @Override
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {```java
            // 调用监听器的方法处理事件
            invokeListener(listener, event);
        }
    }

    private void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        ErrorHandler errorHandler = getErrorHandler();
        if (errorHandler != null) {
            try {
                doInvokeListener(listener, event);
            } catch (Throwable err) {
                errorHandler.handleError(err);
            }
        } else {
            doInvokeListener(listener, event);
        }
    }

    private void doInvokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        try {
            // 调用监听器的onApplicationEvent方法
            listener.onApplicationEvent(event);
        } catch (ClassCastException ex) {
            // ... 省略部分代码,处理类型不匹配异常
        }
    }
    // ...
}

5. 责任链模式

在Spring中,HandlerInterceptorHandlerInterceptorAdapter等类在处理请求拦截时,采用的就是责任链模式。一个请求会按照定义的拦截器顺序,逐个被处理,直到找到对应的处理器或者遍历完所有的拦截器。

// HandlerInterceptor接口定义
public interface HandlerInterceptor {
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception;

    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception;

    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception;
}

// 实现HandlerInterceptor接口的自定义拦截器
public class CustomInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        // 在这里执行前置处理逻辑
        return true; // 返回true表示继续向下执行,返回false表示中断请求
    }

    // ... 其他方法实现
}

6. 模版模式

Spring中的JdbcTemplateHibernateTemplate等类就是模版模式的典型应用。它们定义了一个操作数据库或Hibernate的骨架方法,允许子类在不改变算法结构的情况下重定义某些步骤的具体内容。

// JdbcTemplate部分源码
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations, BeanFactoryAware {
    // ...
    public <T> T query(String sql, RowMapper<T> rowMapper) {
        return query(sql, new Object[0], rowMapper);
    }

    public <T> T query(String sql, Object[] args, RowMapper<T> rowMapper) {
        return query(sql, args, rowMapper, true);
    }

    // ... 省略部分代码,这里是模版方法的实现
    // 真正的SQL执行和结果集处理逻辑在这里,但是允许子类通过RowMapper来定制结果集的处理方式
    // ...
}

// 自定义RowMapper实现
public class CustomRowMapper implements RowMapper<MyObject> {
    @Override
    public MyObject mapRow(ResultSet rs, int rowNum) throws SQLException {
        // 在这里定制如何从ResultSet中映射到MyObject对象
        return new MyObject(/* 映射逻辑 */);
    }
}

这些设计模式在Spring框架中被广泛应用,能够灵活地应对各种复杂场景,提供强大且可扩展的功能。

实战应用

假设有这样一个需求:

  1. 业务登录商城用户鉴权

  2. 购买产品下订单

  3. 校验订单填写是否合法

  4. 记录接口中的参数

  5. 订单确认后给买家发短信通知

根据业务场景,我们大致可拆分为:用户流程订单流程

用户登录流程

图片

在用户登录流程中,可能用到拦截器做鉴权校验,日志记录接口参数等,使用了一些常见的设计模式。

场景一、用户鉴权校验

责任链模式

拦截器通常按照定义的顺序执行,每个拦截器检查特定的条件或执行特定的任务。

// 拦截器接口
public interface Interceptor {
    boolean intercept(AuthenticationContext context);
}

// 用户校验拦截器
public class UserValidationInterceptor implements Interceptor {
    @Override
    public boolean intercept(AuthenticationContext context) {
        // 用户校验逻辑
        if (isValidUser(context.getUser())) {
            return true;
        }
        return false;
    }

    private boolean isValidUser(User user) {
        // 校验用户是否有效
        return true; // 示例,实际中应有具体校验逻辑
    }
}

// 鉴权校验拦截器
public class AuthorizationInterceptor implements Interceptor {
    @Override
    public boolean intercept(AuthenticationContext context) {
        // 鉴权校验逻辑
        if (isAuthorized(context.getUser(), context.getCredentials())) {
            return true;
        }
        return false;
    }

    private boolean isAuthorized(User user, Credentials credentials) {
        // 校验用户是否有权限
        return true; // 示例,实际中应有具体校验逻辑
    }
}

// 拦截器链
public class InterceptorChain {
    private List<Interceptor> interceptors = new ArrayList<>();

    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

    public boolean execute(AuthenticationContext context) {
        for (Interceptor interceptor : interceptors) {
            if (!interceptor.intercept(context)) {
                // 如果拦截器返回false,则中断链的执行
                return false;
            }
        }
        return true;
    }
}

场景二、记录用户登录信息

单例模式

日志记录器通常设计为单例,确保全局只有一个实例。

Logger logger = LoggerFactory.getLoggerFactoryInstance().getLogger();  
logger.log("This is a user log message");

在这行代码中,体现了单例模式的核心思想,确保了无论多少次调用LoggerFactory.getLoggerFactoryInstance(),都只会返回一个LoggerFactory实例。事实上,我们从源码中也可以看到。

图片

订单流程

根据业务场景,核心订单流程如下:

图片

场景三、订单校验

工厂模式

使用工厂模式实现的CheckOrderFactory,它用于创建不同类型的订单校验服务实例。

同时,我们定义一个校验接口ICheckOrderService,并创建了两个实现类:购买数量校验:CountCheckOrder和订单参数校验:ParamCheckOrder

public interface ICheckOrderService {
    boolean checkOrder(Object order);
    String getErrorMessage();
}

购买数量校验的实现类CountCheckOrder

public class CountCheckOrder implements ICheckOrderService {
    @Override
    public boolean checkOrder(Object order) {
        // 假设order是一个包含购买数量的对象
        int quantity = ((Order) order).getQuantity();
        return quantity > 0; // 只允许购买数量大于0
    }

    @Override
    public String getErrorMessage() {
        return "购买数量必须大于0。";
    }
}

订单参数校验的实现类ParamCheckOrder

public class ParamCheckOrder implements ICheckOrderService {
    @Override
    public boolean checkOrder(Object order) {
        // 假设order是一个包含各种订单参数的对象
        // 这里可以添加具体的订单参数校验逻辑
        return true; // 示例代码,默认返回true
    }

    @Override
    public String getErrorMessage() {
        return "订单参数校验失败。";
    }
}

工厂类CheckOrderFactory,用于创建不同类型的校验服务实例:

public class CheckOrderFactory {
    public static ICheckOrderService createCheckOrderService(String type) {
        switch (type) {
            case "count":
                return new CountCheckOrder();
            case "param":
                return new ParamCheckOrder();
            default:
                throw new IllegalArgumentException("不支持的校验类型: " + type);
        }
    }
}

使用工厂模式有利于业务类的实现和拓展,但是有时候也存在过度设计,导致写了很多的业务类。

场景四、短信通知

观察者模式(监听模式)

在Spring框架中,我们可以使用ApplicationEventApplicationListener来实现事件发布和监听的功能。

假设我们要下发一个购买成功的短信提醒,那么就可以发布一个自定义的PurchaseSuccessEvent事件。

import org.springframework.context.ApplicationEvent;

public class PurchaseSuccessEvent extends ApplicationEvent {
    private final String buyerPhoneNumber;
    private final String orderId;

    public PurchaseSuccessEvent(Object source, String buyerPhoneNumber, String orderId) {
        super(source);
        this.buyerPhoneNumber = buyerPhoneNumber;
        this.orderId = orderId;
    }

    public String getBuyerPhoneNumber() {
        return buyerPhoneNumber;
    }

    public String getOrderId() {
        return orderId;
    }
}

接着定义一个SmsNotificationListener类,它实现了ApplicationListener接口,用于监听PurchaseSuccessEvent事件:

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class SmsNotificationListener implements ApplicationListener<PurchaseSuccessEvent> {
    
    @Override
    public void onApplicationEvent(PurchaseSuccessEvent event) {
        String message = "亲爱的买家,您的订单 " + event.getOrderId() + " 购买成功!";
        sendSms(event.getBuyerPhoneNumber(), message);
    }

    private void sendSms(String phoneNumber, String message) {
        // 在这里实现发送短信的逻辑
        System.out.println("Sending SMS to " + phoneNumber + ": " + message);
    }
}

然后,在Spring的配置中启用事件发布功能。配置一个ApplicationEventPublisher的bean:

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;

@Configuration
public class AppConfig {
    
    @Bean
    public ApplicationEventPublisher applicationEventPublisher() {
        return new GenericApplicationContext();
    }
}

最后,在业务逻辑中发布PurchaseSuccessEvent事件。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class PurchaseService {

    private final ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    public PurchaseService(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void completePurchase(String buyerPhoneNumber, String orderId) {
        // 模拟购买完成的业务逻辑
        // ...

        // 发布购买成功事件
        applicationEventPublisher.publishEvent(new PurchaseSuccessEvent(this, buyerPhoneNumber, orderId));
    }
}

总结

  • 使用设计模式的宗旨:封装变化的部分,维护不变的宗旨

  • 好处:提高代码拓展性,程序更优雅更健壮

  • 对开源框架设计模式的使用,要提高鉴赏能力

  • 考虑可读性,不可为了设计模式而过度设计

  • 采石之人,应有建设大教堂的愿景

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1493481.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot使用MongoTemplate详解

1.pom.xml引入Jar包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> 2.MongoDbHelper封装 /*** MongoDB Operation class* author HyoJung* date …

群晖 NAS 安装 Ghost Blog

通过群晖的 docker 容器下载 ghost 映像&#xff0c;并配置后启动。 一、下载映像文件 由于网络环境&#xff0c;无法直接连接 docker 的映像服务器&#xff0c;因此使用境内代理&#xff0c;通过 url 方式下载。 打开 docker 控制台&#xff0c;点击左边导航栏的 “映像”&…

2024大厂Android面试真题集锦,算法真题解析:美团+Tencent+字节跳动+阿里+360+拼多多

前言 IT行业薪水高&#xff0c;这是众所周知的&#xff0c;所以很多人大学都选择IT相关专业&#xff0c;即使非该专业的人&#xff0c;毕业了也想去一个培训机构镀镀金&#xff0c;进入这一行业。 但是有关这个行业35岁就退休的说法&#xff0c;也一直盛传。 加上这几年不断…

苹果曝出两个 iOS 系统 0-Day 漏洞

最近&#xff0c;苹果公司发布了紧急安全更新&#xff0c;解决了两个 iOS 零日漏洞。这些漏洞存在于 iOS 内核&#xff08;CVE-2024-23225&#xff09;和 RTKit&#xff08;CVE-2024-23296&#xff09;中&#xff0c;威胁攻击者可利用其绕过内核内存保护&#xff0c;这就给了具…

MySQL 表锁问题

MySQL 表锁解决 查看哪些表被锁&#xff0c;字段 In_use 表示有多少线程在使用这张表&#xff0c;字段 name_locked 表示表格是否被锁&#xff0c;0 代表锁定状态 mysql> show OPEN TABLES where In_use > 0; -------------------------------------------------------…

音视频学习笔记——ffmpeg解码流程

✊✊&#x1f308;大家好&#xff01;本篇文章主要记录自己在进行音视频学习中&#xff0c;整理的部分ffmpeg解码相关的内容重点&#x1f607;。 首先重新梳理了ffmpeg解码流程&#xff0c;重点学习avcodec_send_packet()、avcodec_receive_frame()在解码中的应用&#xff0c;以…

【QT】重载的信号槽/槽函数做lambda表达式

重载的信号槽 函数指针&#xff1a; int fun(int a,long b) int (*funp)(int, long) fun; 实现回调函数就需要函数指针 信号重载 派生类槽函数发送两个信号 派生类给父类发两个信号 void (SubWidget::*mysigsub)() &SubWidget::sigSub;connect(&subw,mysigsub,t…

网络编程:select、poll

.1、select完成TCP并发服务器 程序代码&#xff1a; #include <myhead.h> #define SER_IP "192.168.125.234" //服务端IP #define SER_PORT 8888 //服务端端口号int main(int argc, const char *argv[]) {//1.创建用于连接的套接字int sfds…

1-安装rabbitmq

rabbitmq官网&#xff1a; https://www.rabbitmq.com/docs/download 本机环境&#xff1a;mac&#xff0c;使用orbstack提供的docker 使用docker部署rabbitmq docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.13-management 然后报错&#xf…

bun 单元测试

bun test Bun 附带了一个快速、内置、兼容 Jest 的测试运行程序。测试使用 Bun 运行时执行&#xff0c;并支持以下功能。 TypeScript 和 JSX生命周期 hooks快照测试UI 和 DOM 测试使用 --watch 的监视模式使用 --preload 预加载脚本 Bun 旨在与 Jest 兼容&#xff0c;但并非所…

SD-WAN: 灵活部署,助力云服务

随着Office 365、Salesforce、Webex和SAP等云托管应用程序的迅速发展&#xff0c;企业正加速将业务关键应用程序迁移到云端。这种转变需要为遍布各地的员工提供安全可靠的云服务网络连接。本文将介绍SD-WAN如何助力企业的云服务访问。 传统的网络架构&#xff0c;特别是基于MPL…

【AI视野·今日Robot 机器人论文速览 第八十二期】Tue, 5 Mar 2024

AI视野今日CS.Robotics 机器人学论文速览 Tue, 5 Mar 2024 Totally 63 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;双臂机器人拧瓶盖, (from 伯克利) website: https://toruowo.github.io/bimanual-twist &#x1f4da;水下抓取器, (from …

总结:大模型技术栈---算法与原理

原文地址&#xff1a;大模型技术栈-算法与原理 1. tokenizer方法 word-level char-level subword-level BPE WordPiece UniLM SentencePiece ByteBPE2. position encoding 绝对位置编码 ROPE AliBi 相对位置编码 Transformer-XL T5/TUPE DeBERTa3. 注意力机制 Mamba,H3,Hyena…

Linux下下载安装JDK配置Java环境变量

Linux下下载安装JDK配置Java环境变量 1. 下载JDK 下载链接&#xff1a;(https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) 2. 上传至服务器并解压 可通过shell工具进行上传&#xff0c;我这里是上传安装在/opt目录 解压jdk-17.0.10_linux-x64_b…

【外汇天眼】外汇交易策略:最容易获利的行情,原来是这一段!

不随便抄底抓顶 不能仅因为价格大跌而抄底&#xff0c;是对市场风险的尊重。 市场走势是有理由的&#xff0c;每轮下跌背后都有其深刻的基本面。 我在看书时印象深刻的是一位国外著名炒手谈到他的经历。 有一年咖啡丰收&#xff0c;价格跌得惨不忍睹&#xff0c;甚至到了一袋…

阿里二面,redis宕机了,如何快速恢复数据

背景 有个同学阿里二面&#xff0c;面试官问&#xff1a;redis宕机了&#xff0c;如何恢复数据&#xff1f; 这位同学当时一脸懵&#xff0c;不知道如何回答。 分析分析这个问题&#xff0c;redis宕机&#xff0c;要想恢复数据&#xff0c;首先redis的数据有没有做持久化&…

【AI视野·今日CV 计算机视觉论文速览 第302期】Tue, 5 Mar 2024

AI视野今日CS.CV 计算机视觉论文速览 Tue, 5 Mar 2024 Totally 177 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Brand Visibility in Packaging: A Deep Learning Approach for Logo Detection, Saliency-Map Prediction, and Logo Plac…

图论例题解析

1.图论基础概念 概念 &#xff08;注意连通非连通情况&#xff0c;1节点&#xff09; 无向图&#xff1a; 度是边的两倍&#xff08;没有入度和出度的概念&#xff09; 1.完全图&#xff1a; 假设一个图有n个节点&#xff0c;那么任意两个节点都有边则为完全图 2.连通图&…

计算机网络——24路由器组成

路由器组成 路由器的结构概况 高层面(非常简化的)通用路由器体系架构 路由&#xff1a;运行路由选择算法&#xff0f;协议 (RIP, OSPF, BGP) - 生成 路由表转发&#xff1a;从输入到输出链路交换数据报 - 根据路由表进行分组的转发 输入端口功能 分布式交换&#xff1a; 根…

【风格迁移】对比度保持连贯性损失 CCPL:解决图像局部失真、视频帧间的连贯性和闪烁

对比度保持连贯性损失 CCPL&#xff1a;解决图像局部失真、视频帧间的连贯性和闪烁 提出背景解法&#xff1a;对比度保持连贯性损失&#xff08;CCPL&#xff09; 局部一致性假设 对比学习机制 邻域调节策略 互信息最大化对比学习&#xff1a;在无需标签的情况下有效学习区分…