【企业场景】设计模式重点解析

设计模式

在平时的开发中,涉及到设计模式的有两块内容:

  1. 我们平时使用的框架(比如spring、mybatis等)
  2. 我们自己开发业务使用的设计模式。

在平时的业务开发中,其实真正使用设计模式的场景并不多,虽然设计号称有23种之多(不同的纬度可能会更多,不同程序员看法也不同)

但是在项目最常使用的也就几种而已,在面试的过程中,我们主要介绍一种或两种就可以,也不会把你会的都问一遍🤣

重点:什么设计模式应对什么业务场景?

本文重点介绍三种设计模式:

  1. 工厂模式(简单工厂、工厂方法、抽象工厂)
  2. 策略模式
  3. 责任链模式

还有单例模式、建造者模式在开发中的案例,但是不作为重点讲解

1. 工厂模式

1.1 概述

需求:设计一个咖啡店点餐系统。

设计一个咖啡类(Coffee),并定义其两个子类:

  • (美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);

再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。

具体类的设计如下:

在这里插入图片描述

  1. 类图中的符号

    • +:表示public
    • -:表示private
    • #:表示protected
  2. 泛化关系(继承)用带空心三角箭头的实线来表示

  3. 依赖关系使用带箭头的虚线来表示

package com.itheima.factory.simple;public class CoffeeStore {public static void main(String[] args) {Coffee coffee = orderCoffee("latte");System.out.println(coffee.getName());}public static Coffee orderCoffee(String type){Coffee coffee = null;if("american".equals(type)){coffee = new AmericanCoffee();}else if ("latte".equals(type)){coffee = new LatteCoffee();}//添加配料coffee.addMilk();coffee.addSuqar();return coffee;}
}

在 Java 中,万物皆对象,这些对象都需要创建,如果需要实例的时候直接 new 该对象,就会对该对象耦合严重,

  • 假如我们要更换对象,所有 new 对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则

如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;

所以说,工厂模式最大的优点就是:解耦

开闭原则:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。

三种工厂模式:

  1. 简单工厂模式
  2. 工厂方法模式
  3. 抽象工厂模式

1.2 简单工厂模式

简单工厂比较像是一种编程习惯。

1.2.1 结构

简单工厂包含如下角色:

  • 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。
    • 具体产品 :实现或者继承抽象产品的子类
  • 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。
1.2.2 实现

现在使用简单工厂对上面案例进行改进,类图如下:

在这里插入图片描述

工厂类代码如下:

public class SimpleCoffeeFactory {public Coffee createCoffee(String type) {Coffee coffee = null;if("americano".equals(type)) {coffee = new AmericanoCoffee();} else if("latte".equals(type)) {coffee = new LatteCoffee();}return coffee;}
}

咖啡店

package com.itheima.factory.simple;public class CoffeeStore {public Coffee orderCoffee(String type){//通过工厂获得对象,不需要知道对象实现的细节SimpleCoffeeFactory factory = new SimpleCoffeeFactory();Coffee coffee = factory.createCoffee(type);//添加配料coffee.addMilk();coffee.addSuqar();return coffee;}
}

工厂(factory)处理创建对象的细节,一旦有了 SimpleCoffeeFactory,CoffeeStore 类中的 orderCoffee() 就变成此对象的客户,后期如果需要 Coffee 对象直接从工厂中获取即可。

这样也就解除了和 Coffee 实现类的耦合;

同时又产生了新的耦合,CoffeeStore 对象和 SimpleCoffeeFactory 工厂对象的耦合,工厂对象和商品对象的耦合。

后期如果再加新品种的咖啡,我们势必要需求修改 SimpleCoffeeFactory 的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。

1.2.3 优缺点

优点:

  • 封装了创建对象的过程,可以通过参数直接获取对象。
  • 把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

缺点:

  • 增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
1.2.4 扩展

静态工厂

在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是23种设计模式中的。代码如下:

public class SimpleCoffeeFactory {public static Coffee createCoffee(String type) {Coffee coffee = null;if("americano".equals(type)) {coffee = new AmericanoCoffee();} else if("latte".equals(type)) {coffee = new LatteCoffee();}return coffe;}
}

1.3 工厂方法模式

针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。

1.3.1 概念

定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。

1.3.2 结构

工厂方法模式的主要角色:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
    • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
    • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
1.3.3 实现

使用工厂方法模式对上例进行改进,类图如下:

在这里插入图片描述

流程:

在这里插入图片描述

代码如下:

抽象工厂:

public interface CoffeeFactory {Coffee createCoffee();
}

具体工厂:

public class LatteCoffeeFactory implements CoffeeFactory {public Coffee createCoffee() {return new LatteCoffee();}
}public class AmericanCoffeeFactory implements CoffeeFactory {public Coffee createCoffee() {return new AmericanCoffee();}
}

咖啡店类:

public class CoffeeStore {private CoffeeFactory factory;public CoffeeStore(CoffeeFactory factory) {this.factory = factory;}public Coffee orderCoffee() {Coffee coffee = factory.createCoffee();coffee.addMilk();coffee.addsugar();return coffee;}
}

从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。

工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。

在线程池中,也使用了这个设计模式去生产 Thread:

在这里插入图片描述

1.3.4 优缺点

优点:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

1.4 抽象工厂模式

前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。

这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。

本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示

  • 产品族:一个品牌下面的所有产品;例如华为下面的电脑、手机称为华为的产品族;
  • 产品等级:多个品牌下面的同种产品;例如华为和小米都有手机电脑为一个产品等级;

在这里插入图片描述

1.4.1 概念

是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂

1.4.2 结构

抽象工厂模式的主要角色如下:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
    • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
    • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
1.4.3 实现

现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点

  • 同一个产品等级(产品分类)
    • 咖啡:拿铁咖啡、美式咖啡
    • 甜点:提拉米苏、抹茶慕斯
  • 同一个风味,就是同一个产品族(相当于同一个品牌)
    • 美式风味:美式咖啡、抹茶慕斯
    • 意大利风味:拿铁咖啡、提拉米苏

要是按照工厂方法模式,

  • 需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。

所以这个案例可以使用抽象工厂模式实现。类图如下:

在这里插入图片描述

实现关系使用带空心三角箭头的虚线来表示

整体调用思路:

在这里插入图片描述

1.4.4 优缺点

优点:

  • 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:

  • 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
1.4.5 使用场景
  • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

如:输入法换皮肤,一整套一起换。生成不同操作系统的程序。

2 策略模式

先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。

在这里插入图片描述

定义:

  • 该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。
  • 策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

2.2 结构

策略模式的主要角色如下:

  • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

2.3 案例实现

【例】促销活动

一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:

在这里插入图片描述

聚合关系可以用带空心菱形的实线来表示

代码如下:

定义百货公司所有促销活动的共同接口

public interface Strategy {void show();
}

定义具体策略角色(Concrete Strategy):每个节日具体的促销活动

//为春节准备的促销活动A
public class StrategyA implements Strategy {public void show() {System.out.println("买一送一");}
}//为中秋准备的促销活动B
public class StrategyB implements Strategy {public void show() {System.out.println("满200元减50元");}
}//为圣诞准备的促销活动C
public class StrategyC implements Strategy {public void show() {System.out.println("满1000元加一元换购任意200元以下商品");}
}

定义环境角色(Context):用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员

public class SalesMan {                        //持有抽象策略角色的引用                              private Strategy strategy;                 public SalesMan(Strategy strategy) {       this.strategy = strategy;              }                                          //向客户展示促销活动                                public void salesManShow(){                strategy.show();                       }                                          
}                                              

2.4 综合案例

2.4.1 概述

多种方式可以进行登录

  • 用户名密码登录

  • 短信验证码登录

  • 微信登录

  • QQ登录

2.4.2 代码示例

登录接口说明

说明
请求方式POST
路径/api/user/login
参数LoginReq
返回值LoginResp

请求参数:LoginReq

@Data
public class LoginReq {private String name;private String password;private String phone;private String validateCode;//手机验证码private String wxCode;//用于微信登录/*** account : 用户名密码登录* sms : 手机验证码登录* we_chat : 微信登录*/private String type;
}

响应参数:LoginResp

@Data
public class LoginResp{private Integer userId;private String userName;private String roleCode;private String token; //jwt令牌private boolean success;}

控制层LoginController

@RestController
@RequestMapping("/api/user")
public class LoginController {@Autowiredprivate UserService userService;@PostMapping("/login")public LoginResp login(@RequestBody LoginReq loginReq){return userService.login(loginReq);}
}

业务层UserService

@Service
public class UserService {public LoginResp login(LoginReq loginReq){if(loginReq.getType().equals("account")){System.out.println("用户名密码登录");//执行用户密码登录逻辑return new LoginResp();}else if(loginReq.getType().equals("sms")){System.out.println("手机号验证码登录");//执行手机号验证码登录逻辑return new LoginResp();}else if (loginReq.getType().equals("we_chat")){System.out.println("微信登录");//执行用户微信登录逻辑return new LoginResp();}LoginResp loginResp = new LoginResp();loginResp.setSuccess(false);System.out.println("登录失败");return loginResp;}
}

注意:我们重点讲的是设计模式,并不是登录的逻辑,所以以上代码并没有真正的实现登录功能

问题分析

  • 业务层代码大量使用到了if…else,在后期阅读代码的时候会非常不友好,大量使用if…else性能也不高
  • 如果业务发生变更,比如现在新增了 QQ 登录方式,这个时候需要修改业务层代码,违反了开闭原则

解决:

使用 工厂方法设计模式+策略模式 解决

2.4.3 代码改造(工厂+策略)

整体思路

改造之后,不在service中写业务逻辑,让service调用工厂,然后通过service传递不同的参数来获取不同的登录策略(登录方式)

在这里插入图片描述

具体实现

抽象策略类:UserGranter

/*** 抽象策略类*/
public interface UserGranter{/*** 获取数据* @param loginReq 传入的参数* @return map值*/LoginResp login(LoginReq loginReq);}

具体的策略:AccountGranter、SmsGranter、WeChatGranter

/*** 	策略:账号登录**/
@Component
public class AccountGranter implements UserGranter{@Overridepublic LoginResp login(LoginReq loginReq) {System.out.println("登录方式为账号登录" + loginReq);// TODO// 执行业务操作 return new LoginResp();}
}
/*** 策略:短信登录*/
@Component
public class SmsGranter implements UserGranter{@Overridepublic LoginResp login(LoginReq loginReq)  {System.out.println("登录方式为短信登录" + loginReq);// TODO// 执行业务操作return new LoginResp();}
}
/*** 策略:微信登录*/
@Component
public class WeChatGranter implements UserGranter{@Overridepublic LoginResp login(LoginReq loginReq)  {System.out.println("登录方式为微信登录" + loginReq);// TODO// 执行业务操作return new LoginResp();}
}

工程类:UserLoginFactory

/*** 操作策略的上下文环境类 工具类* 将策略整合起来 方便管理*/
@Component
public class UserLoginFactory implements ApplicationContextAware {private static Map<String, UserGranter> granterPool = new ConcurrentHashMap<>();@Autowiredprivate LoginTypeConfig loginTypeConfig;/*** 从配置文件中读取策略信息存储到map中* {* account:accountGranter,* sms:smsGranter,* we_chat:weChatGranter* }** @param applicationContext* @throws BeansException*/@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {loginTypeConfig.getTypes().forEach((k, y) -> {granterPool.put(k, (UserGranter) applicationContext.getBean(y));});}/*** 对外提供获取具体策略** @param grantType 用户的登录方式,需要跟配置文件中匹配* @return 具体策略*/public UserGranter getGranter(String grantType) {UserGranter tokenGranter = granterPool.get(grantType);return tokenGranter;}}

在application.yml文件中新增自定义配置

login:types:account: accountGrantersms: smsGranterwe_chat: weChatGranter

新增读取数据配置类

Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "login")
public class LoginTypeConfig {private Map<String,String> types;}

改造service代码

@Service
public class UserService {@Autowiredprivate UserLoginFactory factory;public LoginResp login(LoginReq loginReq){UserGranter granter = factory.getGranter(loginReq.getType());if(granter == null){LoginResp loginResp = new LoginResp();loginResp.setSuccess(false);return loginResp;}LoginResp loginResp = granter.login(loginReq);return loginResp;}
}

大家可以看到我们使用了设计模式之后,业务层的代码就清爽多了,如果后期有新的需求改动,比如加入了QQ登录,我们只需要添加对应的策略就可以,无需再改动业务层代码。

2.4.4 举一反三

其实像这样的需求,在日常开发中非常常见,场景有很多,以下的情景都可以使用工厂模式+策略模式解决比如:

  • 订单的支付策略
    • 支付宝支付
    • 微信支付
    • 银行卡支付
    • 现金支付
  • 解析不同类型excel
    • xls格式
    • xlsx格式
  • 打折促销
    • 满300元9折
    • 满500元8折
    • 满1000元7折
  • 物流运费阶梯计算
    • 5kg以下
    • 5kg-10kg
    • 10kg-20kg
    • 20kg以上

一句话总结:只要代码中有冗长的 if-else 或 switch 分支判断都可以采用策略模式优化

2.5 Spring 的 SPI

@Component
public class LoginServiceSelector {public static final String WX_LOGIN_TYPE = "WX_JWT"; // r6Vsr0public static final String EMAIL_LOGIN_TYPE = "EMAIL_JWT"; // Rl0p0rprivate final ServiceLoader<LoginService> loginServices = ServiceLoader.load(LoginService.class);public LoginService select(String type) {// 选取服务for (LoginService loginService : loginServices) {if (loginService.match(type)) {return loginService;}}throw new GlobalServiceException(GlobalServiceStatusCode.REQUEST_NOT_VALID);}}

在这里插入图片描述

10分钟让你彻底明白Java SPI,附实例代码演示#安员外很有码_哔哩哔哩_bilibili

JavaSPI机制你真的懂吗?来看看动画版通俗易懂SPI机制讲解_哔哩哔哩_bilibili

3. 责任链模式

3.1 概述

在现实生活中,常常会出现这样的事例:

  • 一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。
  • 例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。

这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等。

定义:

又名职责链模式,为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;

当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

该请求的数据经过一系列的处理者,找到负责的人或者是一步步地被加工/被消耗/被处理,像流水线一样;

比较常见的springmvc中的拦截器,web开发中的filter过滤器

在这里插入图片描述

3.2 结构

职责链模式主要包含以下角色:

  • 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  • 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  • 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

3.2 案例实现

处理订单的操作

在这里插入图片描述

类图:

在这里插入图片描述

代码:

抽象处理者

package com.itheima.designpattern.chain;/*** 抽象处理者*/
public abstract class Handler {protected Handler handler;public void setNext(Handler handler) {this.handler = handler;}/*** 处理过程* 需要子类进行实现*/public abstract void process(OrderInfo order);
}

订单信息类:

package com.itheima.designpattern.chain;import java.math.BigDecimal;public class OrderInfo {private String productId;private String userId;private BigDecimal amount;public String getProductId() {return productId;}public void setProductId(String productId) {this.productId = productId;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public BigDecimal getAmount() {return amount;}public void setAmount(BigDecimal amount) {this.amount = amount;}
}

具体处理者:

/*** 订单校验*/
public class OrderValidition extends Handler {@Overridepublic void process(OrderInfo order) {System.out.println("校验订单基本信息");//校验handler.process(order);}}/*** 补充订单信息*/
public class OrderFill extends Handler {@Overridepublic void process(OrderInfo order) {System.out.println("补充订单信息");handler.process(order);}}/*** 计算金额*/
public class OrderAmountCalcuate extends Handler {@Overridepublic void process(OrderInfo order) {System.out.println("计算金额-优惠券、VIP、活动打折");handler.process(order);}}/*** 订单入库*/
public class OrderCreate extends Handler {@Overridepublic void process(OrderInfo order) {System.out.println("订单入库");}
}

客户类:

public class Application {public static void main(String[] args) {//检验订单Handler orderValidition = new OrderValidition();//补充订单信息Handler orderFill = new OrderFill();//订单算价Handler orderAmountCalcuate = new OrderAmountCalcuate();//订单落库Handler orderCreate = new OrderCreate();//设置责任链路orderValidition.setNext(orderFill);orderFill.setNext(orderAmountCalcuate);orderAmountCalcuate.setNext(orderCreate);//开始执行orderValidition.process(new OrderInfo());}}

3.3 优缺点

优点

  • 降低了对象之间的耦合度

    • 该模式降低了请求发送者和接收者的耦合度。
  • 增强了系统的可扩展性

  • 可以根据需要增加新的请求处理类,满足开闭原则。

  • 增强了给对象指派职责的灵活性

  • 当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。

  • 责任链简化了对象之间的连接

  • 一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。

  • 责任分担

  • 每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

缺点:

  • 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  • 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

3.4 举一反三

  • 内容审核(视频、文章、课程….)
    • 任务分段顺序执行(一步步消费),可以用本地线程对象进行线程内部(责任链节点之间)的数据共享;

在这里插入图片描述

  • 订单创建(一步步加工)

    在这里插入图片描述

  • 简易流程审批(直到负责人处理了)

    在这里插入图片描述

等等还有很多场景,如果你觉得可以用责任链,可以解耦,可以提高扩展性,那就试一下吧!

4. 补充:单例模式案例

比如这个场景,访问一些微信接口,需要有 ACCESS_TOKEN,由于需要经常调用或者更新,所以用到单例模式;

只实例化一个对象,并提供一个全局访问点;

@Getter
@Setter
public class AccessToken {private String token;private long expireIn;//有效期限volatile private static AccessToken ACCESS_TOKEN = null;private AccessToken() {}private void setExpireIn(int expireIn) {// 设置有效期限的时候的时间戳this.expireIn = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(expireIn);}public boolean isExpired() {return System.currentTimeMillis() > this.getExpireIn();}private static void setAccessToken() {if(ACCESS_TOKEN == null) {ACCESS_TOKEN = new AccessToken();}Map<String, Object> map = TokenUtil.getAccessTokenMap();ACCESS_TOKEN.setToken((String) map.get("access_token"));ACCESS_TOKEN.setExpireIn((Integer) map.get("expires_in"));}public static AccessToken getAccessToken() {if(ACCESS_TOKEN == null || ACCESS_TOKEN.isExpired()) {synchronized (AccessToken.class) {if(ACCESS_TOKEN == null || ACCESS_TOKEN.isExpired()) {setAccessToken();}}}return ACCESS_TOKEN;}
}
@Component
public class TokenUtil {// token 的 urlpublic static String URL = "https://api.weixin.qq.com/cgi-bin/token";public static String APP_ID;public static String APP_SECRET;@Value("${wx.appid}")private void setAPP_ID(String appId) {APP_ID = appId;}@Value("${wx.secret}")private void setAPP_SECRET(String appSecret) {APP_SECRET = appSecret;}public static Map<String, Object> getAccessTokenMap() {// 构造参数表Map<String, Object> param = new HashMap<String, Object>(){{this.put("grant_type", "client_credential");this.put("appid", APP_ID);this.put("secret", APP_SECRET);}};// 发起get请求String response = HttpUtil.doGet(URL, param);// 解析jsonreturn JsonUtil.analyzeJson(response, Map.class);}public static String getToken() {return AccessToken.getAccessToken().getToken();}
}

5. 补充:简易版建造者模式案例

Lombok 提供的注解:@Builder

《重学Java设计模式》第6章:建造者模式_哔哩哔哩_bilibili

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Person {private String name;private int age;private String gender;// 自定义方法来设置年龄,并在方法中实现特殊逻辑public static class PersonBuilder {public PersonBuilder age(int age) {if (age < 0 || age > 120) {throw new IllegalArgumentException("Invalid age");}this.age = age;return this;}}
}

之后就直接链式调用即可,像说话一样方便的构造对象;

Person.builder().name("小马").age(18).gender("男")bulde();

在内部定义一个静态内部类,XXXBuilder,实现对应的方法,注解在“添加逻辑”的时候,就会用我们写的;

在这里插入图片描述

建议加上这些注解,因为这个 @Builder 注解会自动定义一个全参构造方法,但是没有无参构造方法,但是如果有构造方法了则没有全参构造方法;

所以加上这两个注解以防万一!

因为有些场景构造方法的存在很重要!

比如 MyBatis,在构造参数的时候需要无参构造方法,如果没有会导致加了 @Builder 后,结果错乱;

在二进制文件中可以查看到:

public class Person {private String name;private int age;private String gender;public static PersonBuilder builder() {return new PersonBuilder();}public String getName() {return this.name;}public int getAge() {return this.age;}public String getGender() {return this.gender;}public Person(final String name, final int age, final String gender) {this.name = name;this.age = age;this.gender = gender;}public Person() {}public static class PersonBuilder {private String name;private int age;private String gender;public PersonBuilder age(int age) {if (age >= 0 && age <= 120) {this.age = age;return this;} else {throw new IllegalArgumentException("Invalid age");}}PersonBuilder() {}public PersonBuilder name(final String name) {this.name = name;return this;}public PersonBuilder gender(final String gender) {this.gender = gender;return this;}public Person build() {return new Person(this.name, this.age, this.gender);}public String toString() {return "Person.PersonBuilder(name=" + this.name + ", age=" + this.age + ", gender=" + this.gender + ")";}}
}

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

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

相关文章

OpenHarmony实战开发-MpChart图表实现案例。

介绍 MpChart是一个包含各种类型图表的图表库&#xff0c;主要用于业务数据汇总&#xff0c;例如销售数据走势图&#xff0c;股价走势图等场景中使用&#xff0c;方便开发者快速实现图表UI。本示例主要介绍如何使用三方库MpChart实现柱状图UI效果。如堆叠数据类型显示&#xf…

C#基础|数据类型、变量

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 01 数据类型 数据类型是为了方便存储数据的&#xff0c;为了将数据按照不同的分类存储&#xff0c;所以引入数据类型。这个在PLC中已经很熟悉了。 数据类型的作用&#xff1a;就是为了更好地管理内存&#xff0c;为…

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记13:RTC实时时钟

系列文章目录 嵌入式|蓝桥杯STM32G431&#xff08;HAL库开发&#xff09;——CT117E学习笔记01&#xff1a;赛事介绍与硬件平台 嵌入式|蓝桥杯STM32G431&#xff08;HAL库开发&#xff09;——CT117E学习笔记02&#xff1a;开发环境安装 嵌入式|蓝桥杯STM32G431&#xff08;…

基于Python长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析、生物量估算与趋势分析

原文链接&#xff1a;基于Python长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析、生物量估算与趋势分析https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247601336&idx4&sn143be5669da8ad336a455a4cca3d4b6a&chksmfa820d5fcdf584491…

【机器学习】机器学习创建算法第6篇:线性回归,学习目标【附代码文档】

机器学习&#xff08;算法篇&#xff09;完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;机器学习算法课程定位、目标&#xff0c;K-近邻算法定位,目标,学习目标,1 什么是K-近邻算法,1 Scikit-learn工具介绍,2 K-近邻算法API。K-近邻算法&#xff0c;1.4 …

【七 (1)指标体系建设-构建高效的故障管理指标体系】

目录 文章导航一、故障概述1、故障&#xff1a;2、故障管理&#xff1a; 二、指标体系概述1、指标2、指标体系 三、指标体系构建难点1、管理视角2、业务视角3、技术视角 四、指标体系构建原则1、与战略目标对齐2、综合和平衡3、数据可获得性4、可操作性5、具体和可衡量6、参与和…

lua学习笔记20(lua中一些自带库的学习)

print("*****************************lua中一些自带库的学习*******************************") print("*************时间***************") --系统时间 print(os.time()) --自己传入参数得到时间 print(os.time({year2011,month4,day5})) --os.data(&qu…

00 【哈工大_操作系统】Bochs 汇编级调试方法及指令

本文将介绍一下哈工大李治军老师《操作系统》课程在完成Lab时所使用到的 Bochs 调试工具的使用方法。这是一款汇编级调试工具&#xff0c;打开调试模式非常简单&#xff0c;只需在终端下输入如下指令&#xff1a; 1、bochs 调试基本指令大全 功能指令举例在某物理地址设置断点…

Xxl-job执行器自动注册不上的问题

今天新建的项目要部署xxl-job&#xff0c;之前部署过好多次&#xff0c;最近没怎么部署&#xff0c;生疏了。部署完之后&#xff0c;服务一直没有注册到执行器管理里面&#xff0c;找了半天也没找到原因&#xff0c;看数据库里的xxl_job_registry表也是一直有数据进来。 后来看…

小白也能看懂的BEV感知(一)

1. 引言 随着人工智能技术的不断发展&#xff0c;自动驾驶越来越多地出现在我们的视野中&#xff0c;智能化和电动化已经成为汽车行业的主旋律。无论是从研究的角度还是从工程的角度来看&#xff0c;它都像是一个巨大的宝藏&#xff0c;等待着我们去探索。本文将介绍这一技术的…

从51到ARM裸机开发实验(009)LPC2138 中断实验

一、场景设计 中断的概念在《从51到ARM裸机开发实验(007) AT89C51 中断实验》中已经介绍过&#xff0c;LPC2138的Keil工程创建在《从51到ARM裸机开发实验(005)LPC2138 GPIO实验》中已经介绍过。本次使用LPC2138来实现一个这样的场景&#xff1a;四个LED依次亮灭&#xff0c;时间…

测试人必看,小程序常见问题

小程序是一种轻盈的存在&#xff0c;用户无需为了使用它而下载和安装。它依附于微信这个强大的平台&#xff0c;只需轻轻一扫或一搜&#xff0c;它便跃然屏上&#xff0c;随时服务。小程序为我们带来更多前所未有的惊喜和便利&#xff0c;以下分享关于小程序相关的热门问题。 …

链表基础3——单链表的逆置

链表的定义 #include <stdio.h> #include <stdlib.h> typedef struct Node { int data; struct Node* next; } Node; Node* createNode(int data) { Node* newNode (Node*)malloc(sizeof(Node)); if (!newNode) { return NULL; } newNode->data …

逆向IDA中Dword,数据提取

我们可以看见数据是这样的&#xff0c;第一个是1cc 但是我们shifte就是 这个因为他的数据太大了&#xff0c;导致高位跑后面去了 这个时候&#xff0c;我们右键——convert——dword 这样就可以提取到争取的数据了 比如第一个数据 0x1cc a0xcc b0x1 print(hex((b<<8…

HarmonyOS开发实例:【事件的订阅和发布】

介绍 本示例主要展示了公共事件相关的功能&#xff0c;实现了一个检测用户部分行为的应用。具体而言实现了如下几点功能&#xff1a; 1.通过订阅系统公共事件&#xff0c;实现对用户操作行为&#xff08;亮灭屏、锁屏和解锁屏幕、断联网&#xff09;的监测&#xff1b; 2.通…

vmware安装ubuntu-18.04系统

一、软件下载 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1fK2kygRdSux1Sr1sOKOtJQ 提取码&#xff1a;twsb 二、安装ubuntu系统 1、把ubuntu-18.04的压缩包下载下来&#xff0c;并且解压 2、打开vmware软件&#xff0c;点击文件-打开 3、选择我们刚刚解…

6. Django 深入模板

6. 深入模板 6.1 Django模板引擎 Django内置的模板引擎包含模板上下文(亦可称为模板变量), 标签和过滤器, 各个功能说明如下: ● 模板上下文是以变量的形式写入模板文件里面, 变量值由视图函数或视图类传递所得. ● 标签是对模板上下文进行控制输出, 比如模板上下文的判断和循…

项目7-音乐播放器1+BCrypt加密

1.创建项目 1.1 引入依赖 1.2 yml相关配置 application.yml spring:profiles:active: prod mybatis:mapper-locations: classpath:mapper/**Mapper.xmlconfiguration:map-underscore-to-camel-case: true #配置驼峰⾃动转换log-impl: org.apache.ibatis.logging.stdout.StdO…

openGauss学习笔记-261 openGauss性能调优-使用Plan Hint进行调优-将部分Error降级为Warning的Hint

文章目录 openGauss学习笔记-261 openGauss性能调优-使用Plan Hint进行调优-将部分Error降级为Warning的Hint261.1 功能描述261.2 语法格式261.3 示例261.3.1 忽略非空约束261.3.2 忽略唯一约束261.3.3 忽略分区表无法匹配到合法分区261.3.4 更新/插入值向目标列类型转换失败 o…

【算法】数组元素循环右移k位,并要求只用一个元素大小的附加存储,元素移动或交换次数为O(n)

两种写法思路&#xff1a; 思路一&#xff1a;三次倒置 前言&#xff1a;C/C函数 reverse 是 左闭右开区间的&#xff0c;作用是将指定范围数组元素全部倒置&#xff0c;数组从 0 开始&#xff0c;这里主要讲解思路&#xff0c;就直接用 函数 reverse 简化过程 这个方法 实现 …