首页 > 编程语言 >职场编程必备:那些你不可不知的常用设计模式

职场编程必备:那些你不可不知的常用设计模式

时间:2024-12-22 22:26:58浏览次数:7  
标签:编程 return Object param org 设计模式 职场 public 下载

设计模式共23种,可分为创建型、结构型和行为型。这些设计模式并不是要求我们都要记得滚瓜烂熟,可以先掌握工作中常用的几种设计模式,用于解决工作中绝大部分的设计问题,这样在从容应对工作内容之余我们才有更多的精力去深入拓展更多的知识点。

每种设计模式并不是独立的、割裂的,它们是可以组合的。另外,不要为了用设计模式而用,如果场景单一、逻辑简单,比如用基本的if...else逻辑控制语句就能满足业务要求的情况下就没必要强行套模板去使用策略模式。

1. 模板方法+策略模式+责任链(行为型)

模板方法、策略模式、责任链常结合使用,模板定义抽象封装算法、策略即多态实现、责任链用于实现业务校验链。

抽象模板用于定义算法的框架、步骤,对业务进行抽象,我们可以先分析出各个业务场景的实现逻辑都分为1、2、3、4哪几个步骤,将这些“不变”的公共步骤封装在一个方法里,而某些步骤每个场景逻辑都不同的“变”的部分则由子类实现。因此,模板方法也是用于分离“变”和“不变”的一种手段。

策略其实就是多态,不同的业务类型、业务场景有不同的实现逻辑,往往实际工作中每一种场景的实现逻辑又比较复杂,如果直接用if...else进行处理随着业务迭代的开展,不断叠加逻辑,代码会变成一个泥团,不具备扩展性且牵一发动全身,因此需要多态实现,每种场景划分在独立的类中,这样场景逻辑清晰,可扩展。

责任链一般可用于业务校验条件多且复杂的场景中,同理如果直接用if...else逻辑分支实现代码会变得冗长,随着业务场景的叠加后期会变得难以维护,而使用责任链其实就是把每个校验逻辑职责分离,抽象成一个节点,这些节点连成一个链表组合完成了整个校验逻辑,如果后续需要增删校验逻辑则只需要增删节点,这种设计能更好地适应业务需求的变化。

为了简化逻辑,使用下载功能作为举例,有商品SKU、官网订单等各类单据,每类单据的下载有公共逻辑也有个性化逻辑,用户通过页面点击按钮进行下载。设计图如下:

代码样例:
代码样例简化了具体实现,比如出入参直接使用了Object(实际开发中需要具体定义参数类)、方法逻辑只用注释进行说明等,这是因为样例侧重于体现设计模式的应用,而不是具体的代码细节。

package org.example.designpattern;

/**
 * 下载接口
 */
public interface IDownload {
    void download(Object param);
}
package org.example.designpattern;

/**
 * 下载业务抽象模板类:定义下载的框架、步骤
 */
public abstract class AbstractDownloadService implements IDownload {

    @Override
    public void download(Object param) {
        // 1. 业务校验
        String errMsg = this.validate(param);
        if (!"".equals(errMsg)) {
            return;
        }

        // 2. 取数(例如查询用户信息、单据数据、下载模板配置)
        Object downloadData = this.queryDownloadData(param);

        // 3. 组装数据
        Object downloadContent = this.buildDownloadContent(downloadData);

        // 4. 下载
        this.doDownload(downloadContent);

        // 5. 记录操作日志
        this.saveOperateLog(downloadData);
    }

    /**
     * 业务校验抽象方法,由子类提供实现
     *
     * @param param 下载参数
     * @return String 错误信息
     */
    protected abstract String validate(Object param);

    /**
     * 查询要下载的单据数据(父类提供默认实现,子类有个性化需求可以重写)
     *
     * @param param 下载参数
     * @return Object 这里为了简化使用了Object,实际开发中需要自定义一个返参
     */
    protected Object queryDownloadData(Object param) {
        // 1. 模拟根据param中的用户id从db查询用户信息
        // 2. 模拟根据param中的下载模板配置id查询下载模板配置
        // 3. 模拟根据param中的单据表名tableName和whereCondition查询单据数据
        return new Object();
    }

    /**
     * 组装下载的excel内容抽象方法,由子类提供实现
     *
     * @param downloadData 下载所需的数据,例如单据数据、下载模板配置
     * @return Object 返回excel内容
     */
    protected abstract Object buildDownloadContent(Object downloadData);

    /**
     * 下载excel(父类提供默认实现,子类有个性化需求可以重写)
     *
     * @param downloadContent 文件内容
     */
    protected void doDownload(Object downloadContent) {
        // 1. 模拟将文件内容downloadContent写入到excel完成下载
    }

    /**
     * 保存操作日志(父类提供默认实现,子类有个性化需求可以重写)
     *
     * @param downloadData 下载所需的数据,例如用户信息
     */
    protected void saveOperateLog(Object downloadData) {
        // 1. 保存操作日志,操作用户信息从downloadData获取
    }
}
package org.example.designpattern;

/**
 * 商品sku下载子类
 */
public class SkuDownloadServiceImpl extends AbstractDownloadService {
    @Override
    protected String validate(Object param) {
        BaseDownloadValidator validator = new BaseDownloadValidator();
        return validator.validate(param);
    }

    @Override
    protected Object queryDownloadData(Object param) {
        return super.queryDownloadData(param);
    }

    @Override
    protected Object buildDownloadContent(Object downloadData) {
        // 模拟实现父类抽象方法,根据单据数据、下载模板配置组装出下载的文件内容
        return new Object();
    }

    @Override
    protected void doDownload(Object downloadContent) {
        super.doDownload(downloadContent);
    }

    @Override
    protected void saveOperateLog(Object downloadData) {
        super.saveOperateLog(downloadData);
    }
}
package org.example.designpattern;

/**
 * 财务报表下载子类
 */
public class FinancialReportDownloadServiceImpl extends AbstractDownloadService {
    @Override
    protected String validate(Object param) {
        BaseDownloadValidator validator = new BaseDownloadValidator();
        return validator.validate(param);
    }

    @Override
    protected Object queryDownloadData(Object param) {
        // 重写父类方法,模拟个性化实现查询下载所需的数据
        return new Object();
    }

    @Override
    protected Object buildDownloadContent(Object downloadData) {
        // 模拟实现父类抽象方法,根据单据数据、下载模板配置组装出下载的文件内容
        return new Object();
    }

    @Override
    protected void doDownload(Object downloadContent) {
        super.doDownload(downloadContent);
    }

    @Override
    protected void saveOperateLog(Object downloadData) {
        super.saveOperateLog(downloadData);
    }
}
package org.example.designpattern;

import java.util.Objects;

/**
 * 业务校验函数式接口
 */
@FunctionalInterface
public interface ValidateFunction<T, R> {

    R apply(T param);

    default ValidateFunction<T, R> andThen(ValidateFunction<T, R> after) {
        Objects.requireNonNull(after);
        return (T t) -> {
            R result = apply(t);
            if(!"".equals(result)){
                return result;
            }
            return after.apply(t);
        };
    }

}
package org.example.designpattern;

/**
 * 公共下载校验器(为了简化示例,只定义了一个公共的校验器,实际开发中可以将公共校验逻辑抽取到基类,个性化校验放在子类中)
 * 网络上大多通过定义一个类似链表结构来实现责任链,调用setNext设置下一节点。其实也可以像本示例一样利用函数式接口实现,代码更加简洁
 */
public class BaseDownloadValidator {

    protected final ValidateFunction<Object, String> validateParamNotEmpty = param -> {
        // 1. 入参非空校验
        return "";
    };

    protected final ValidateFunction<Object, String> validateUser = param -> {
        // 2. 用户信息校验
        return "";
    };

    protected final ValidateFunction<Object, String> validateBill = param -> {
        // 3. 单据数据非空校验
        return "";
    };

    protected final ValidateFunction<Object, String> validateDownloadConfig = param -> {
        // 4. 下载配置非空校验
        return "";
    };

    /**
     * 使用函数式接口实现责任链调用
     *
     * @param param 下载参数
     * @return String 错误信息
     */
    public String validate(Object param) {
        return validateParamNotEmpty.andThen(validateUser).andThen(validateBill).andThen(validateDownloadConfig).apply(param);
    }
}
package org.example.designpattern;

/**
 * 下载策略类
 */
public class DownloadStrategy {

    public void download(Object param) {
        // 假设从下载参数获取的单据类型billType为sku,根据类型获取具体的业务类
        IDownload downloadService = getServiceByType("sku");
        // 调用下载接口
        downloadService.download(param);
    }

    /**
     * 根据单据类型billType获取具体的下载业务类
     *
     * @param billType 单据类型
     * @return IDownload 返回实现IDownload接口的实现子类
     */
    private IDownload getServiceByType(String billType) {
        if ("sku".equals(billType)) {
            return new SkuDownloadServiceImpl();
        }
        if ("financialReport".equals(billType)) {
            return new FinancialReportDownloadServiceImpl();
        }
        throw new UnsupportedOperationException("不支持的单据类型");
    }
}


2. 代理模式(结构型)

代理就是你授权给我,我帮你干一些活,比如现实生活中房屋中介就是介于租客和房东间的代理例子。在Spring项目开发中我们通过AOP实现代理,通常在Aspect类中编写业务方法拦截逻辑对原方法进行增强,比如打印方法执行耗时。

Spring代码样例:

package org.example.designpattern.proxy;

import org.springframework.stereotype.Service;

/**
 * 被代理的目标类
 */
@Service
public class TargetService {

    public void doSomething(){
        System.out.println("执行业务逻辑");
    }
}
package org.example.designpattern.proxy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * 切面类
 */
@Aspect
@Component
public class LoggingAspect {

    /**
     * 拦截业务方法打印方法执行耗时
     */
    @Around("execution(* org.example.designpattern.proxy.TargetService.doSomething(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        long executionTime = System.currentTimeMillis() - start;
        System.out.println(joinPoint.getSignature() + " 方法耗时:" + executionTime + "ms");
        return proceed;
    }
}


3. 工厂方法(创建型)

工厂方法在代码实现上和策略模式很相似,都涉及定义接口、多个实现该接口的实现类以及运行时的多态特性。但是它们的应用场景不同,策略模式侧重于算法或者说行为的选择,通过策略类选择具体的算法实现类的具体方法执行,而工厂方法侧重于对象的创建,通过工厂类提供的生产对象的方法获取到具体对象,强调对象的创建和使用的解耦,而不是行为(方法)的选择。

日常业务开发中使用工厂方法的频率可能没有上面提到的模板、策略、责任链这几种这么高,因为大多场景下我们都是直接用的Spring的依赖注入,只有当对象的创建过程比较复杂时使用工厂方法才能获得较大的收益,比如需要根据各种复杂业务规则决定具体创建哪个类的对象实例。

代码样例:

package org.example.designpattern.factory;

/**
 * 促销活动接口
 */
public interface IPromotion {
    void promote();
}
package org.example.designpattern.factory;

/**
 * 满减促销活动
 */
public class SpendToGetDiscountPromotion implements IPromotion {
    @Override
    public void promote() {

    }
}
package org.example.designpattern.factory;

/**
 * 抽奖促销活动
 */
public class LotteryPromotion implements IPromotion {
    @Override
    public void promote() {

    }
}
package org.example.designpattern.factory;

/**
 * 创建促销活动的工厂类
 */
public class PromotionFactory {

    /**
     * 根据促销类型创建促销活动
     *
     * @param promotionType 促销类型
     * @return IPromotion 促销活动实例
     */
    public IPromotion createPromotion(String promotionType) {
        if ("1".equals(promotionType)) {
            return new SpendToGetDiscountPromotion();
        }
        if ("2".equals(promotionType)) {
            return new LotteryPromotion();
        }
        throw new UnsupportedOperationException("不支持的促销类型");
    }
}


4. 单例模式(创建型)

平常业务开发可能较少直接使用单例模式来创建对象,但是我们要做有准备的人,当工作中需要我们设计一个技术组件或中台框架时,可能单例模式就派上了用场,比如框架的上下文类、业务线程池类等。

推荐采用静态内部类方式,利用了类加载的机制来保证线程安全和延迟初始化:

package org.example.designpattern.singleton;

/**
 * 静态内部类实现单例模式
 */
public class Singleton {
    private Singleton() {
    }

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

标签:编程,return,Object,param,org,设计模式,职场,public,下载
From: https://blog.csdn.net/u012012134/article/details/144619628

相关文章

  • SMMU软件指南SMMU编程之事件队列
    安全之安全(security²)博客目录导读如果发生一组配置错误和故障,这些将记录在事件队列中。这些错误和故障包括因设备流量引发的事件,例如:当收到设备流量时,发现配置错误因设备流量地址引发的页面错误每个安全状态都有一个事件队列。当事件队列从空状态变为非空状态时,SMMU会生......
  • SMMU软件指南SMMU编程之命令队列
    安全之安全(security²)博客目录导读SMMU通过内存中的循环命令队列进行控制。例如,当软件更改STE或翻译时,需要在SMMU中失效相关缓存。这可以通过向命令队列发出相应的失效命令来实现。有关命令类型的详细信息,请参见“命令”部分。在SMMUv3.3之前,每个安全状态只有一个命令队列......
  • 卡西欧 FX-991 CN X 科学计算器,是否算作无编程、无储存功能的计算器吗?
    起因是:矩阵分析老师说“可以带最简单的,没存储上网等功能的计算器”https://www.cncalc.org/thread-23924-1-1.htmlhttps://www.zhihu.com/question/291618906综上,能带。......
  • Java转C++之模板元编程
    模板元编程(TemplateMetaprogramming)入门指南:针对Java程序员的讲解作为一个从Java转到C++的程序员,理解模板元编程(TemplateMetaprogramming,简称TMP)可能会感到有些挑战,特别是其中的语法和概念有很多与Java非常不同的地方。模板元编程是一种强大的技术,它允许我们在编译时......
  • 实验6 C语言结构体、枚举应用编程
    实验任务四:task4.c源代码:1#include<stdio.h>2#defineN1034typedefstruct{5charisbn[20];//isbn号6charname[80];//书名7charauthor[80];//作者8doublesales_price;//售价9int......
  • AOP面向切面编程
    1.1动态代理​动态代理是指:程序在整个运行过程中根本就不存在目标类的代理类,目标对象的代理只是由代理生成工具(不是真实定义的类)在程序运行时由JVM根据反射等机制动态生成,代理对象与目标对象的代理关系在程序运行时确定。实现方式:jdk动态代理,使用jdk中的Proxy,Met......
  • Shell脚本编程题(八道)
    Shell脚本编程题(八道)第一题1.编程实现从键盘获取一个整数,按下列情况输出对应结果:(1)当该数值为6或7时,输出“休息日(RestDay)”。(2)当该数值为1至5时,输出“工作日(WorkingDay)”。(3)其他情况输出“输入错误(ErrorInput)”#!/bin/shreaddatetimecase"$datatime"in......
  • C++, 模板元编程, 凭借辅助的模板结构的特化 , 从而以类型控制模板类的分支
    u++真是学无止境,遍地地雷,哦不,遍地黄金。看ue序列化中的TArray有感,特节取部分代码保存,希望能多切近ue的编码经验半分。 //...template<typenameT>structTCanBulkSerialize{enum{Value=false};};template<>structTCanBulkSerialize<unsignedint>{enum{Value......
  • 实验6 C语言结构体、枚举应用编程
    4.实验任务4#include<stdio.h>#defineN10typedefstruct{charisbn[20];//isbn号charname[80];//书名charauthor[80];//作者doublesales_price;//售价intsales_count;//销售册数}Book;voidoutput(Bookx[],intn);voidsort(Bookx[],intn);......
  • 我在使用Rust编程时,只在编译期提示要手动标注生命周期时才进行标注,这样可以吗?
    在使用Rust编程时,只在编译器提示需要标注生命周期时才进行标注是一种可以接受的初学者实践,但随着经验积累和代码复杂度增加,这种方式可能会有以下问题和限制:1.优点:简单、快速入门编译器友好:Rust的借用检查器非常强大,大部分情况下会推断出正确的生命周期,尤其是在简单的函数......