设计模式Java实战
文章目录
- 一、前置
- 1.1 目的
- 1.2 面向对象
- 1.3 接口和抽象类
- 二、七大设计原则
- 2.1 单一职责
- 2.2 接口隔离原则
- 2.3 依赖倒转原则
- 2.4 里氏替换原则
- 2.5 开闭原则
- 2.6 不要重复原则
- 2.7 迪米特最少知道法则
- 三、23种设计模式
- 3.1创建型:创建对象
- 3.1.1 单例模式
- 定义
- 最佳实践
- 场景
- 线程级别的单例
- 缺点
- 3.1.2 工厂模式(简单工厂)
- 场景1:解析配置
- 场景2:解析表达式
- 实战-Calendar类
- 总结:
- 3.1.3DI依赖注入
- 定义
- 和简单工厂区别
- 手动实现一个DI容器
- 使用
- 1、配置解析
- 2、BeanFactory通过反射创建对象
- 3、对象生命周期管理
- 3.1.4 Builder建造者模式
- 和set以及构造器的区别
- 建造者模式创建对象
- 实战@Accessors
- 和工厂模式的区别
- 3.2结构型:类或对象的组合
- 3.2.1Proxy代理模式
- 定义
- 场景
- 动态代理接口实现
- 3.2.2 装饰器模式
- 作用
- 和代理模式的区别
- 实现
- 场景IO流
- 1、结构
- 2、源码结构
- 3、前置背景
- 4、源码解析
- 3.2.3 适配器模式
- 定义
- 实现
- 1、类适配器-继承
- 2、对象适配器-组合
- 场景
- 1、兼容老接口
- 2、统一多个类的接口设计
- 3.2.4 享元模式
- 定义
- 作用
- 实战
- 场景
- Integer实战
- 总结
- 3.3行为型
- 3.3.1 观察者模式
- 定义
- 作用
- 和生产者消费者的区别
- 和 订阅-发布模式的区别
- 被观察者-观察者模板
- 实战
- 3.3.2 模板模式
- 定义
- 作用
- 使用模板
- 场景
- 1、复用
- 2、扩展框架
- 3、实战
- 3.3.3回调函数
- 定义
- 作用
- 场景1
- 1、背景JDBCTemplate的演变
- 2、源码解析JDBCTemplate(简化版)
- 3、分析
- 场景2 JVM Hook
- 3.3.4策略模式:
- 实战1
- 背景
- 实战2
- 实战3
- 3.3.5 职责链模式
- 定义
- 作用
- 模板
- 1、职责链模式1:带终止
- 2、职责链模式2:无终止
- 实战:敏感词过滤
- 场景1:Servlet-Filter
- 作用
- 作用域
- 解析
- 场景2:MVC-Interceptor
- 作用:同Filter
- 作用域
- 和Servlet的Filter区别
- 解析
- 场景3:自定义职责链
- 3.3.6 状态模式
- 定义
- 作用
- 使用场景
- 实战-电商订单
- 1、状态机
- 2、实现
- 3、总结
- 3.3.7 解释器模式
- 定义
- 作用
- 实战
- 背景
- 分析
- 实现
- 场景
- 3.3.8 中介模式
- 定义
- 作用
- 场景
- 背景
- 中介模式
- 四、其它
- 4.1 系统设计
- 4.1.1合理将功能划分到不同模块
- 4.1.2模块之间的交互关系
- 4.1.3业务开发
- 4.2 重构
- 4.2.1what
- 4.1.2why
- 4.1.3 context
- 4.1.4 when
- 4.1.5 如何避免重构出问题
- 4.2 重构
- 4.2.1what
- 4.2.2why
- 4.2.3 context
- 4.2.4 when
- 4.2.5 如何避免重构出问题
一、前置
1.1 目的
1、写出好的代码,个人认为依次重要程度为:
-
健壮性
-
个人理解为最重要的之一,好的代码,首先是无bug代码
-
代码中,常见可能引起问题的点(重要程度不分先后):
-
性能:并发会不会存在问题;并发度大小(大了可能对下游压力过大,提前对其好SLA);是否需要异步处理(写OP日志、推送消息等)
-
降级、兜底:下游接口拿不到数据或不可用,产品侧是否有兜底数据、技术侧是否有兜底方案
-
限流:对上游是否需要限流,保护我们的服务
-
数据量评估:数据量增大,甚至极端场景下,会不会有慢查询,索引是否合理
-
幂等:消息是否可能重复
-
一致性:上下游的状态是否一致(eg:服务A将任务状态置为终态,如果上游系统B有业务动作依赖A任务的状态,那A也要告诉上游B任务终态了。否则上游B发现下游A的任务一直未终态,他们可能有自己的重试机制等)
-
主从延时:写完读
-
接口异常:是否强依赖、重试等
-
中间件异常:是否强依赖redis,如果redis短期不可用,业务上是否可以降级跳过redis的卡控逻辑。使用中间件生成单据号,如果中间件短时间不可用,是否有备用方案生成单据号等
-
边界条件:while循环为了防止死循环,结合业务要设置最大的循环次数;终止条件最好是>=或<=,防止并发时跳过了;日期判断或者日期作为查询条件也要特别注意;集合get(0)首先是npe其次是集合的所有元素是否都一致,不一致就不能拿第一个元素的内容去赋值;switch要有default;if要和else if() else if()最好加上条件,避免落到if else中
-
数据库:字段类型(是否大小写敏感)、大小(是否需要截断)、update是否需要updateSelective、查询in(为空)则可能查询全量数据
-
参数校验:api接口一定不要相信传参
-
事务失效问题:rpc写和本地写、以及其它
-
单位问题:精度、元分。kg和mg等
-
npe问题:常见可能造成npe的点
-
锁的释放:超时时间是否设定、异常流程是否释放锁
-
/0
-
list转map,list的字段可能重复,作为map的key则可能Duplicate key异常
-
-
-
可读性
- 代码终究是给人读的
- 主要是代码的整洁之道那些内容
-
可复用性
- 不写重复的代码
- 高内聚低耦合(模块内部内聚,模块之间解耦)
- eg:网关层封装的查询方法应该做的事项有:
封装并发(调用方只需要传入所有的skuIdList,gateway自己按照200、200拆分sku并发查询)
返回数据要自定义(如果rpc调用的返回对象为集合List其中A有很多的属性,可能我们并不关心,那么gateway就需要自己定义对象DTO,属性只有几个我们关心的字段即可)
-
可扩展性
- 设计模式的内容
- eg:需求需要对规则进行新增、删除,只需要调整枚举类,不需要修改现有代码逻辑
-
兼容性:尤其是字段调整,要遵循新增而非删除(eg:skuId变为skuIdList,一般是新增字段,然后做好上线过度)
2、设计原则、设计模式等,目的都是为了写“好”代码
1.2 面向对象
1、看似面向对象,实则面向过程的做法
-
滥用get、set方法,违反了面向对象的特征:封装。除非需要否则,不要给属性定义setter
-
Constants常量类:不要单独设计此常量类。好的 做法:哪个类用的用到了某个常量,在此类汇总定义即可
否则,不易维护:改一个常量,影响太多地方,不能轻易修改;不易复用:要在另一个项目中复用本项目的某类,此类中又依赖Constants,相当于把整个Constants都一并引入了
2、面向对象编程步骤
以:对接口进行鉴权为例
-
分析实现步骤
- 调用方进行接口请求的时,将URL、useId、pwd、ts时间戳拼接在一起传递过来;通过加密生成token;并将token、useId、ts拼接在URL中传递
- 接收到请求后,解析URL,获取token、useId、ts
- 校验ts和当前时间,是否在合理的时间窗口内(生成的ts和当前时间间隔1min则认为token失效),失效则拒绝
- 通过useId去缓存或db中获取pwd,通过同样的方式生成token,与调用传递的token对比,不一致则拒绝
-
划分职责,识别出有哪些类
-
如果是大需求,涉及多个模块,则需要先把需求按照模块划分。eg:逆向计划自动建单分为(触发模块、获取可退sku、计算可退量、合单、下发、回掉等多个模块)
-
将需求转换为每个模块要实现的内容;并拆解为小的功能,一条一条列出来,这里以接口鉴权为例
1、把URL、useId、pwd、ts拼接为子串
2、通过字符串,加密生成token
3、将useId、token、ts形成新的url
4、解析url,获取ts、useId、token
5、根据useId去存储介质中获取pwd
6、根据ts判断token是否在有效的窗口内
7、根据获取的pwd同样方式生成token,比较和传递过来的token是否一致
-
其中1、2、6、7和token相关,负责token的生成和比对 ; 3、4和URL相关,负责url的拼接、解析等;5是单独的获取pwd。
这样,我们就定义了三个主要的类:AuthToken、Url、UseStorage
-
这里体现了高内聚(将小的功能点理清楚到底属于哪个类,相同的都放在一起),低耦合(不属于这个类的属性和方法,则不要放在这个类里,比如URL信息,useId不应该属于Token,不要作为他的属性)
-
-
定义类 和 属性、方法
-
AuthToken:定义属性和方法:
- 1和2:createToken(String url, Long useId,String pwd)
- 6:isExpireed(Long ts)
- 7:match(String token)
-
ApiUrl
-
3:buildUrl()
-
4:getTokenFromUrl(String url)
:getUseIdFromUrl(String url)
:getTsFromUrl(String url)
-
-
-
定义类和类之间的交互关系(继承、实现、聚合、组合、依赖等)
-
思考:
我理解的面向对象编程,就好比要外出旅游,将这个需求分为:衣食住行四个模块
- 衣:带什么衣服(上衣、裤子等)
- 行:是坐火车还是飞机,如果是坐火车,如何去火车站等
- 住:是住酒店还是民宿,住的地方和旅游景点的远近、交通的便利等
就是在未出发之前,衣食住行模块都想好,方法也想好(先公交、再火车),类之间如何衔接(对应类之间的关系)。然后按照这些去旅游。
面向过程编程,则是准备去旅游。
- 行模块:早上起来看有飞机航班么,没有则坐火车,最近的一趟火车,出发。
- 住:到了目的地,随便找个地方先住下来
类似这种,我理解为面向过程。
1.3 接口和抽象类
1、什么时候使用接口
- 需要将接口和实现相分离,封住不稳定的实现,暴露稳定的接口
- 上游系统面向接口编程,这样接口实现发生变化时,上游系统代码基本不需要改动。降低了代码的耦合性
- 提升了扩展性
2、要用接口和抽象类时,选择哪个
- 要表示is a(三角形是图形,圆形是图形—),同时目的是为了解决代码的复用性,则使用抽象类;
- 表示has a,并且为了解耦,而非代码的复用,则使用接口
3、基于接口编程注意事项
- 函数名不要暴露实现细节,否则后续需求变化,名称可能词不达意甚至描述有误。所以尽量抽象。eg:uploadPicture而非uploadPicture2Yun
- 封装具体的实现细节。
eg:sku查询算法值(不同的sku对应的供货链路不同,不同的供货链路,对应查询不同的算法类型值),则queryAlQty(Integer supplyType,Long skuId)不如
queryAlQty(Long skuId):内部封装了查询供货链路。
二、七大设计原则
(设计模式本身的原则)
2.1 单一职责
1、概念:一个类只负责一项职责。如果负责了多个,就需要拆分成多个类
2、举例:OrderRepository中不要涉及对SkuDO的CRUD
3、作用:
- 不会使一个类过于庞大;
- 可维护性:改了Order相关内容不会影响Sku相关内容,否则可能会相影响;
- 高内聚(提高代码,缩小功能改动导致的代码改动范围)。
4、编码实现
- 不符合单一职责的代码
原因:显然飞机不能一直在公路上跑。应该拆分为陆、海、空三个单一职责的交通工具类
@Data
public class Single {public static void main(String[] args) {Vehicle vehicle = new Vehicle();vehicle.run("汽车");vehicle.run("飞机");}static class Vehicle{public void run(String vehicle) {System.out.println(vehicle + "一直在公路上跑");}}
}
- 符合单一职责的类
@Data
public class Single {public static void main(String[] args) {Vehicle1 vehicle1 = new Vehicle1();vehicle1.run("汽车");Vehicle2 vehicle2 = new Vehicle2();vehicle2.run("飞机");}static class VehicleRoad{public void run(String vehicle) {System.out.println(vehicle + "在公路上跑");}}static class VehicleAir{public void run(String vehicle) {System.out.println(vehicle + "在天上非");}}
}
5、思考
逆序计划流程 = 1触发建单 + 2【触发oih + 落sku + 计算可退量 + 合单并下发+回掉】
做了RDC退、协同退、PC退之后,发现流程2是完成可以复用。但是流程1,不同的触发源尤其是RDC退和PC退,很多代码都写在一个类中,实际上违背了单一职责。改动PC退的流程1代码有可能影响RDC退。
6、如何定义一个类,以及如何根据单一职责,判断一个类是否需要拆分
public class UserInfo {private Long userId;private String name;private Long createTime;private Long lastLoginTime;private String email;private Long phoneNo;private String province;private String city;private String region;private String detailAddress;
}
-
可以先第一版比较粗的类UserInfo。随着业务迭代持续重构:比如后续有了物流业务,则用户的地址信息可以抽取出来独立类;
比如:后续有了论坛、金融等业务需要对用户进行登录校验,则可以将email、phone拆出来独立类
-
代码属性过多、代码的行数过多(>200)、代码的方法过多,则需要考虑是否对类进行拆分
-
依赖的其它类过多。为了低耦合,考虑是否拆
-
私有方法过多,为了复用性,可以抽取出来放到新类中作为public方法
-
类已经找不到合适的词来形容了,职责定义已经不清晰了,可拆
-
类中大量的方法都在对某几个属性进行操作,则可以考虑将这几个属性抽取出来单独成一个类
2.2 接口隔离原则
1、概念:接口的调用者,不应该被强迫依赖它不需要的接口
2、作用:
- 提高灵活性:一个类是可以同时实现多个接口的,所以将一个臃肿的接口分割为若干个小接口,通过小接口的不同组合可以满足更多的需求
- 高内聚
3、满足接口隔离原则code
-
"接口"含义:一个接口中的多个方法
-
不满足接口隔离
public interface UserService{boolean register(String phone, String pwd);boolean login(String phone, String pwd);UserInfo getUserInfo(String phone);boolean deleteUser(String phone, String pwd);//删除用户 }public UserServiceImpl implements UserService{//--- }
正常情况下,用户在调用UserService接口中的方法时,一般不会也不允许调用deleteUser方法,只会用到CRU功能。
根据接口隔离原则:接口的调用者,不应该强迫依赖他不需要的接口即deleteUser方法
-
满足接口隔离
后端管理系统ManagerUserImpl才需要CRUD功能
public interface UserService{boolean register(String phone, String pwd);boolean login(String phone, String pwd);UserInfo getUserInfo(String phone); }public interface ManagerService{boolean deleteUser(String phone, String pwd);//删除用户 }
public class UserServiceImpl implements UserService{//CRU功能--- }public class ManagerServiceImpl implements UserService, ManagerService{//CRUD功能--- }
-
-
“接口”的含义:可以是接口中的某个方法
-
不满足接口隔离
public class Statistics {private Long max;private Long min;private double avg;private Integer count;public Statistics count(Collection data) {Statistics statistics = new Statistics();// 计算逻辑return statistics;} }
count函数功能不单一,包含了max、min、count、avg等多个功能。
按照接口隔离原则:函数的设计功能单一,不要将多个不同的功能逻辑在一个函数中
-
满足接口隔离
将count方法拆分为max()、min()、avg()等方法。如何要想使用复合计算则可以直接使用
LongSummaryStatistics statistics = new LongSummaryStatistics(); statistics.accept(1); statistics.accept(2); statistics.accept(3);
-
2.3 依赖倒转原则
1、概念:高层模块(调用者)不要依赖低层模块(被调用者),二者应该通过抽象(接口)互相依赖
eg:Tomcat:高层模块,编程的Web应用程序(低层模块)只需要部署在Tomcat容器下,便可以被Tomcat调用运行。
Tomcat不依赖Web应用程序,只要Web应用程序满足Servlet接口规范,那么无论你是啥Web应用程序,都可以在Tomcat上运行。
Tomcat和Web应用程序通过Servlet接口互相依赖
2、作用:通用性好、扩展性好
3、控制翻转IOC
-
定义:原本是程序员自己控制整个程序的执行,使用框架之后,框架来控制程序流程。流程的控制权从程序员反转到了框架
-
举例:
-
程序员控制程序执行
public class UserServiceTest {public static boolean needTest() {return true;}public static void main(String[] args) {if (needTest()) {System.out.println("do test");} else {System.out.println("not do test");}} }public class SkuServiceTest {public static boolean needTest() {return false;}public static void main(String[] args) {if (needTest()) {System.out.println("do test");} else {System.out.println("not do test");}} }
-
框架控制程序执行
//这里类似模板设计 public abstract class BaseTest {public boolean needTest();//预留扩展点public void run() {if (needTest()) {System.out.println("do test");} else {System.out.println("not do test");}} }
public class UserServiceTest extends BaseTest{@Overridepublic static boolean needTest() {return true;} }public class SkuServiceTest extends BaseTest{@Overridepublic static boolean needTest() {return false;} }
public class ApplicationLoader {public static void main(String[] args) {SpringApplication.run(ApplicationLoader.class, args);private static final List<BaseTest> LIST = new ArrayList<>();for (BaseTest test : LIST) {test.run()}}public void register(BaseTest test) {LIST.add(test);} }
-
ApplicationLoader.register(new UserServiceTest());
ApplicationLoader.register(new UserServiceTest());1、在BaseTest预约扩展点
2、不同的Test类,实现自己业务相关的功能(是否needTest),不需要再写用于执行流程的main函数了
3、将不同的Test类,添加到ApplicationLoader
4、在ApplicationLoader启动的时候执行main函数,会遍历执行所有Test的run方法
程序的执行(main函数执行),由程序员控制(写在不同Test中),反转到框架控制(统一register到Application,它启动的时候,会执行所有Test的run方法)
4、依赖注入DI
-
定义:
A类中使用B类,不同new B()的方法创建b,而是将B在外部创建好后,通过new A(b)构造函数、函数参数func(B b)、set属性等方式传递(注入)给A类使用
-
和控制反转的关系:
控制反转不是具体的实现技巧,而是一种用于指导框架设计的思想。而DI则是具体的编码技巧,是IOC的具体实现
-
依赖注入 和 非依赖注入
- 背景:
Notification类负责将商品的促销、验证码消息等给用户。它依赖MessageProductor生产者类发送消息
- 非依赖注入
- B类(MessageProductor)
public class MessgaeProductor {public boolean send(String msg) {//} }
A类(Notification)
public class Notification {private MessgaeProductor messgaeProductor;public Notification() {this.messgaeProductor = new MessgaeProductor();//A类中使用B类,通过new方式在A类中创建}public void sendMessage(String msg) {this.messgaeProductor.send(msg);} }
Notification notification = new Notification(); notification.sendMessage("msg");
-
依赖注入
- B1、B2(MessgaeProductor接口实现类)
public interface MessgaeProductor {public boolean send(String msg); }
// B1:短信生产类 public class SmsProductor implements MessgaeProductor{@Overridepublic boolean send(String msg) {//发送短信} }// B2:微信消息生产类 public class WeChatProductor implements MessgaeProductor{@Overridepublic boolean send(String msg) {//发送微信信息} }
- A(Notification类)
public class Notification {private MessgaeProductor messgaeProductor;public Notification(MessgaeProductor messgaeProductor) {this.messgaeProductor = messgaeProductor;//A类中使用B类,通过构造器将b注入A中}public void sendMessage(String msg) {this.messgaeProductor.send(msg);} }
- 外部
public class Demo {public static void main(String[] args) {DaxiangProductor messgaeProductor = new DaxiangProductor();//创建对象bNotification notification = new Notification(messgaeProductor);//通过构造函数,将b依赖注入A类中notification.sendMessage("msg");} }
5、依赖注入框架
-
产生背景
- 对比依赖注入和非依赖注入,发现new B()的动作,只不过从在A类中new,变成了在更上次外部类Demo中new,还是需要程序员自己实现
- 一个项目可能有成百上千个类的创建和依赖注入,如果全部都由程序员自己实现,将变得复杂容易出错
- 对象的创建和依赖注入动作本身和业务不相关,完全可以抽象成框架来自动完成
-
常见的依赖注入框架:Spring、Google的Guice
-
作用
- 参考背景
- 只需要通过依赖注入框架提供的扩展点,简单配置一下虽有需要创建的类对象、类与类之间的依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等
-
举例
public class A{@Resourceprivate B b;public static void main(String[] args) {b.send("msg");} }
通过Spring框架提供的扩展点-后置处理器,@Resource private B b,就可以实现B的创建和生命周期的管理,同时后置处理器通过set的将b注入A类中
2.4 里氏替换原则
1、概念: 子类对象能够替换程序中父类对象出现的任何地方,并且保证原来的逻辑行为不变且正确性不被破坏。
一句话:子类重写父类的方法,不要改变原有方法的逻辑(方法声明、输入、输出、对异常的处理等约定)
2、作用:指导子类如何设计,不改变父类的逻辑
3、子类重写父类方法时,常见的违背里氏替换原则的场景有
-
违背父类的输入
父类输入Integer是整数,子类输入Integer要求是正整数
-
违背父类的输出
父类catch代码块中return的是空集合,子类重写方法中catch块中return的是null
-
违背父类方法的声明
父类sortBySkuId,查询结果按照skuId排序。子类sortBySkuId查询结果按照实时销量排序了
-
违背父类异常的处理
父类valid参数时,不满足时,抛出的是ArguementNullException。子类抛出的是illeagalException
4、思考:
2.5 开闭原则
1、概念:一个模块、类、方法对修改关闭,对扩展开放。添加一个新功能,应该是新增代码,而非 修改代码。
补充:有的时候新增功能,是改变了类,对于类而言是被改变了,但是对于方法来说没有改变,也满足开闭。
2、作用:提升代码的扩展性,23种设计模式的目的都是为了满足开闭原则。尽量让修改更为集中、给小、更上层。核心的、复杂的逻辑尽量不修改少修改
3、编码:可以参考RuleExpressHelper,通过遍历规则枚举类,将枚举类code、desc提前put至Map<String,Integer> map1,Map<Integer,String> map2,其内部提供了将"lo&&s"转换lo、s,再通过map1转换为[1,2],将[1,2]转换为"lo&&s",将"lo&&s"转换为[“小于OR可以修改”,“锁库不可更改”]等,规则表示之间的转换方法。这样新增规则时,只需要对应新增枚举类即可
4、如何做到满足开闭原则
-
业务层面:扩展意识、抽象意识很重要
-
多想下,这块逻辑,后续会有哪些需求变更,设计代码结构的时候,可以提前预留好扩展点,以便将来改动小,新的代码可灵活的插入
eg1:规则中心,现有n个规则,如果后续新增规则,是不是不改变现有代码逻辑实现,仅仅通过新增枚举类就可以实现页面的CRUD。
eg2:退供下发执行,后续会不会有逆向调拨下发执行。可以提前设计下发逻辑,抽象出接口。
-
但是对于未来不确定的功能点,当下没必要过度设计,后续持续重构即可
-
-
技术层面:提升代码的可扩展性即基于接口编程、设计原则、设计模式(策略、模板、状态、装饰着、职责链等)
感触最深的就是策略模式,定义行为(接口方法),新增功能,只需要新增对应的新实现,不需要改动原本的行为实现
2.6 不要重复原则
1、常见重复场景
-
实现逻辑重复:代码完全一样。
eg:可能是不同的人开发,不知道有这个功能的代码,场景的是枚举定义一样、网关定义一样
-
功能语义重复
-
代码不一样了,但是两个函数是一样功能。
eg:checkAddressIsVali()和isValidAddress()
-
同一个功能的枚举类,定义了多个。
eg:逆向计划中,任务的触发源类型:OriginTypeEnum 和 TriggerSourceEnum。这样以后枚举内容修改了,多处都要修改,否则有问题
-
-
代码重复执行
已经在request中校验了poiId不能为null,又在构造criteria的时候,再次校验if(request.getPoiId() != null)
对于这种情况,个人建议是可以多次校验的,因为不排除某天,入参request中允许这个字段为null了
2、如何提升代码的复用性
-
高内聚、低耦合
大而全的类,依赖它的代码就多。进而增加了代码的耦合度,影响代码的复用。粒度越小的代码,通用性越好(DateUtil中)。越容易被复用
-
业务和非业务逻辑分离
越是和业务无关的代码,越容易复用。
eg:生成单据号、查询仓、品类、日期
-
代码下沉
-
下沉的代码尽量通用。
eg:根据仓id和skuIdList查询sku信息,方法的内部实现封装了并发查询逻辑。
-
-
继承、多态、抽象、封装
-
封装:同上代码下沉。即使后续,下游rpc接口只允许sku 20个批量查询,调用此查询方法方也无需感知
-
继承:公共代码抽取到父类,子类复用父类的方法和属性。
eg:模板模式,通用的都抽取到父类,不同的继承,实现自己具体内容
-
多态:使用多态可以动态的复用一段代码的部分逻辑。
eg:Collection接口的通用方法,集合都可以使用
-
抽象:越抽象、越不依赖具体实现的代码,越容易复用
eg:入参为List,复用性高于ArrayList
eg:send(HtmlRequest req),复用性不如send(String address, Byte[] data)。因为后续数据,可能服务于别的发送,不仅仅是html的发送
-
-
复用意识:
- 设计一个方法的时候,要把它想象成类似于对外提供的API方法那样的复用性(不同方、不同业务都可能会调用你)
- 多思考,编写的这部分代码是否可以抽取出来,作为一个独立的方法提供给其他地方使用
2.7 迪米特最少知道法则
1、定义:一个对象应该对其他对象有最少的了解,即最小知道。或只是直接的朋友交流
- 直接朋友:出现在类属性、方法入参和出参中的类
- 间接朋友:出现在局部变量的类,和他们的交流(使用)就会违背迪米特法则
2、作用:低耦合、高内聚
- 高内聚、单一职责原则:相近功能放在一个类中,不相近的功能不放在一个类中。相近的功能往往会被同时修改,这样改动点比较集中
- 低耦合:类和类之间的关系简单清晰,一个类的改动,不会或很少会导致依赖它的类也需要跟着改动
- eg:基于接口编程,接口内部实现类变化了,但是对外提供的api不会变
- eg:接口隔离,接口被拆为更细化的接口。不拆分之前接口影响多个依赖方,拆分成多个更细的接口后,某个点-对应更细接口的变动,影响的依赖方更少
3、代码:
-
背景:公司,让部门经理,打印此部门的员工姓名
-
违反迪米特法则的设计
Employee作为局部变量出现在Company中,属于Company的间接朋友,违反了迪米特
/*** 公司*/ public class Company{@Resourceprivate Manager manager;public void printEmployee(String departmentName) {List<Employee> employeeList = manager.getAllEmployeeInfoByDepartmentName(departmentName);for (Employee e : employeeList) {System.out.println(e.getName());}} }/*** 部门经理*/ public class Manager{public List<Employee> getAllEmployeeInfoByDepartmentName(String departmentName) {// 内部实现:获取员工信息} }/*** 员工*/ public class Employee{private String name; }
-
符合设计
/*** 公司*/ public class Company{@Resourceprivate Manager manager;public void printEmployee(String departmentName) {manager.printEmployee(departmentName); //Company之和直接直接朋友Manager交流} }/*** 部门经理*/ public class Manager{public void getAllEmployeeInfoByDepartmentName(String departmentName) {// 1.获取员工信息(Manager内部实现)List<Employee> employeeList = getAllEmployeeInfoByDepartmentName(departmentName);// 2.打印员工姓名(Manager内部实现)printEmployeeName(employeeList);} }/*** 员工*/ public class Manager{private String name; }
三、23种设计模式
3.1创建型:创建对象
单例、工厂、建造者
3.1.1 单例模式
定义
一个类只允许创建唯一一个对象。这里唯一性作用的范围是进程
最佳实践
public class SkuDTO {private SkuDTO(){}private static class SkuDTOHolder {private static final SkuDTO INSTANCE = new SkuDTO();//静态内部类不会再外部类被JVM加载到内存的时候一并被加载。什么时候调用什么时候加载,解决了饿汉问题//JVM本身保证了SkuDTO只会在被类加载器加载时初始化一次,所以是线程安全的}public static SkuDTO getInstance() {return SkuDTOHolder.INSTANCE;}public static void main(String[] args) {for (int i = 0; i < 100; i++) {new Thread(() -> System.out.println(getInstance().hashCode())//都是同一个对象).start();}}
}
-
缺点:可以被反射。最完美的方式是枚举,因为枚举无构造方法,反射也无法创建新的对象
-
优点:外部类SkuDTO被加载的时候不会创建INSTANCE实例。只要调用getInstance()方法的时候才会去创建实例。满足懒加载
JVM保证了INSTANCE的唯一性、线程安全性
场景
-
表示全局唯一类
- 配置类、ID生成器类等
-
处理共享资源访问冲突(写日志、共享数据库连接池等)
eg
- 复现:同时写日志到txt文件中,可能出现内容被覆盖的情况。原因:多线程并发写的时候,线程1和线程2都创建了FileWriter实例,获取到相同的pos待写入位置,都是从这个位置写入,造成内容覆盖
-
解决:线程1和2使用单例模式创建FileWriter,FileWriter本身是线程安全的,其内部实现了对象级别的锁即相同的FileWriter实例,在写操作是线程安全的,不会被覆盖。
public void write(String str, int off, int len) throws IOException {synchronized (lock) {char cbuf[];if (len <= WRITE_BUFFER_SIZE) {if (writeBuffer == null) {writeBuffer = new char[WRITE_BUFFER_SIZE];}cbuf = writeBuffer;} else { // Don't permanently allocate very large buffers.cbuf = new char[len];}str.getChars(off, (off + len), cbuf, 0);write(cbuf, 0, len);}}
线程级别的单例
public class IDGenerator {private static final AtomicLong id = new AtomicLong(0);private static final ThreadLocal<IDGenerator> tl = new ThreadLocal<>();public IDGenerator getInstance() {tl.set(new IDGenerator());return tl.get();}public Long getId() {return id.incrementAndGet();}
}
- 同一个线程获取到的对象实例是相同的,不同线程获取到的不同。属于多例
缺点
- 单侧不友好,全局变量可能会被修改,造成测试结果相互影响问题
- 其他
为了保证全局唯一,除了单例外,我们还可以使用工厂模式来实现
3.1.2 工厂模式(简单工厂)
场景1:解析配置
将不同后缀的配置文件解析成类
-
根据文件路径x.x.Redis.properties | x.x.MySQL.yaml,创建properties 后缀和yaml后置对应的Parse解析类,解析文件内容成对象
-
代码实现
public class Config{public Config load(String configFilePath) {// 1.获取配置文件后缀String fileSuffix = getFileSuffix(configFilePath);//(返回properties、yaml、xml等)// 2.根据后置,创建对应的解析类Configparser parser = createConfigParser(fileSuffix);// 3.解析文件内容return parser.parse(fileSuffix);}public Configparser createConfigParser(String fileSuffix) {Configparser parser;if ("xml".equalsIgnoreCase(fileSuffix)) {parser = new XmlConfigparser();} else if ("yaml".equalsIgnoreCase(fileSuffix)) {parser = new YamlConfigparser();} else if ("properties".equalsIgnoreCase(fileSuffix)) {parser = new PropertiesConfigparser();}return parser;}
}
- 优化1:createConfigParser显然不属于Config类的内容。为了满足高内聚低耦合,需要将createConfigParser方法抽取到独立类中。这个类专门负责Configparser的创建,这个类就是简单工厂类
public class ConfigparserFactory{public Configparser createConfigParser(String fileSuffix) {Configparser parser;if ("xml".equalsIgnoreCase(fileSuffix)) {parser = new XmlConfigparser();} else if ("yaml".equalsIgnoreCase(fileSuffix)) {parser = new YamlConfigparser();} else if ("properties".equalsIgnoreCase(fileSuffix)) {parser = new PropertiesConfigparser();}return parser;}
}
- 优化2:
上述代码每次createConfigParser都会new一个新的Configparser对象。我们可以提前将Configparser对象创建好放到map中缓存起来,当调用createConfigParser方法时,直接从缓存中拿去。
public class ConfigParserFactory {private static final Map<String, Configparser> map = new HashMap<>();static {map.put("xml", new XmlConfigparser());map.put("yaml", new YamlConfigparser());map.put("properties", new PropertiesConfigparser());}// 这里Configparser是接口,XmlConfigparser是接口实现类public Configparser createConfigParser(String fileSuffix) {return map.get(fileSuffix);}
}
1、创建名称特点:create、getInstance、newInstance、valueOf、of、as
2、优点:当新增了YmlConfigparser解析类,只需要实现Configparser接口重写parse方法即可,然后将其添加到map中。满足开闭原则
场景2:解析表达式
规则中心定义卡控最大售卖量规则,规则解析
1、规则枚举类
@Getter
@AllArgsConstructor
public enum RuleExpEnum{NOT_ALLOW(1,"n","不允许修改"),ALLOW(2,"a","允许修改"),OR_MODEL(3,"o","修改值大于等于补货算法可修改");public final int value;public final String rule;public final String desc;
}
2、使用场景
- 前后端交互:前端传[1,2,3],后端需要解析为[“n”,“a”,“o”]
- 前后端交互:后端查db数据为[“n”,“a”,“o”],需要展示为[“不允许修改”,“允许修改”,“修改值大于等于补货算法可修改”]
3、简单工厂类
@UtilityClass
public class RuleExpFactory {private static final Map<Integer, String> val2RuleMap = new HashMap<>();private static final Map<String, Integer> rule2ValMap = new HashMap<>();private static final Map<String, String> rule2DescMap = new HashMap<>();private static final String AND = "&&";static {for (RuleExpEnum ruleExpEnum : RuleExpEnum.values()) {// 1int value = ruleExpEnum.getValue();// "n"String ruleExp = ruleExpEnum.getRule();// "不允许修改"String desc = ruleExpEnum.getDesc();val2RuleMap.put(value, ruleExp);rule2ValMap.put(ruleExp, value);rule2DescMap.put(ruleExp, desc);}}/*** 1.将n&&o -> "不允许修改且修改值大于等于补货算法可修改"* 2. 将o -> "修改值大于等于补货算法可修改"** @param ruleExp 规则表达式"n"* @return 规则desc"不允许修改"*/public String rule2Desc(String ruleExp) {if (StringUtils.isBlank(ruleExp)) {return StringUtils.EMPTY;}List<String> ruleList = Splitter.on(AND).splitToList(ruleExp);return ruleList.stream().map(rule2DescMap::get).collect(Collectors.joining("且"));}/*** 1.将[1,2] -> "n&&a"* 2. 将[1] -> "n"** @param valueList [1,2,3]* @return "n&&a&&o"*/public String value2Rule(List<Integer> valueList) {if (CollectionUtils.isEmpty(valueList)) {return StringUtils.EMPTY;}return valueList.stream().map(val2RuleMap::get).collect(Collectors.joining(AND));}
}
-
优点:
- 原本实现是:使用StringBuilder进行append
StringBuilder sb = new StringBuilder(); if(Objects.equals(rule,"a")) {sb.append("允许修改") } else if(Objects.equals(rule,"n")) {sb.append("且");sb,append("不允许修改") } else if () {}
- 这种方式缺点很明显,当新增了规则rule,则需要再新增else if判断,再添加desc,不满足开闭原则。
- 如果规则rule很多,则代码充斥着大量的else if分支判断
实战-Calendar类
1、创建Calendar实例
Calendar instance = Calendar.getInstance();
2、简答工厂模式
private static Calendar createCalendar(TimeZone zone,Locale aLocale){//这里zone和aLocale(zh_CN)都是默认值Calendar cal = null;//根据地区的语言和国家来判断日历类型if (cal == null) {if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {cal = new BuddhistCalendar(zone, aLocale);} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"&& aLocale.getCountry() == "JP") {cal = new JapaneseImperialCalendar(zone, aLocale);} else {// 其他情况一律返回公历cal = new GregorianCalendar(zone, aLocale);}}return cal;}
为什么说这是一种简单工厂模式呢?
因为静态createCalendar() 创建实例时,根据该方法传入的参数来返回对应的 Calendar 实现类,符合工厂模式的思想(类似场景1)
总结:
当代码中存在大量if - else,根据A获取|创建B的场景,则可以考虑使用简单工厂模式
3.1.3DI依赖注入
定义
相当于一个大型工厂,负责在程序启动时,根据各种配置信息,创建对象。因为它持有一堆对象,所以又叫容器
和简单工厂区别
- 简单工厂负责一类(eg不同文本类型)对象的创建。一般要创建哪些对象,都是代码提前写死的new好
- DI容器负责的是整个应用程序所有对象的创建。除此之外,它还要负责对象生命周期的管理。DI事先不知道要创建哪些对象,是根据解析配置来动态创建对象
手动实现一个DI容器
使用
- Demo
public class Demo {public static void main(String[] args) {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");RateLimiter rateLimiter = (RateLimiter) applicationContext.getBean("rateLimit");rateLimiter.func();}
}
- 接口
public interface ApplicationContext {Object getBean(String beanId);
}
- 实现类
public class ClassPathXmlApplicationContext implements ApplicationContext {private BeanConfigParser beanConfigParser;private BeansFactory beansFactory;public ClassPathXmlApplicationContext(String configLocation) {this.beansFactory = new BeansFactory();this.beanConfigParser = new XmlBeanConfigParser();loadBeanDefinitions(configLocation);}// 解析器,读取xml配置为BD,并将BD放入beanFactoryprivate void loadBeanDefinitions(String configLocation) {InputStream in = this.getClass().getResourceAsStream("/" + configLocation);List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in);beansFactory.addBeanDefinitions(beanDefinitions);}// 从beanFactory创建bean@Overridepublic Object getBean(String beanId) {return beansFactory.getBean(beanId);}
}
- DeanDefination
@Data
public class BeanDefinition {private String id;private String className;private List<ConstructorArg> constructorArgs = new ArrayList<>();private Scope scope = Scope.SINGLETON;//单例private boolean lazyInit = false;//懒加载falsepublic boolean isSingleton() {return scope.equals(Scope.SINGLETON);}public static enum Scope {SINGLETON,PROTOTYPE}@Datapublic static class ConstructorArg {private boolean isRef;//bean中是否有对象依赖private Class type;//对象依赖类型private Object arg;}
}
1、配置解析
- xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="rateLimiter" class="com.xxx.lean.RateLimiter"><constructor-arg ref="redis"></constructor-arg></bean><bean id="redis" class="com.mjp.lean.Redis"><constructor-arg type="java.lang.String" value="127.0.0.1"/><constructor-arg type="int" value="6001"/></bean>
</beans>
- 配置类
@NoArgsConstructor
@AllArgsConstructor
@Data
public class RateLimiter {private Redis redis;
}@AllArgsConstructor
@NoArgsConstructor
@Data
public class Redis {private String ipAddress;private int port;
}
- 解析配置类并生成BeanDefination放入BeanFactory
主要就是将is解析成BD
public interface BeanConfigParser {List<BeanDefinition> parse(InputStream inputStream);}public class XmlBeanConfigParser implements BeanConfigParser {@Overridepublic List<BeanDefinition> parse(InputStream inputStream) {String content = null;return parse(content);}
}
2、BeanFactory通过反射创建对象
public class BeansFactory {private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();// 存储BDpublic void addBeanDefinitions(List<BeanDefinition> beanDefinitionList) {for (BeanDefinition beanDefinition : beanDefinitionList) {this.beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition);}for (BeanDefinition beanDefinition : beanDefinitionList) {if (beanDefinition.isLazyInit() == false && beanDefinition.isSingleton())createBean(beanDefinition);}}// 获取beanpublic Object getBean(String beanId) {BeanDefinition beanDefinition = beanDefinitions.get(beanId);return createBean(beanDefinition);}// 反射创建beanprotected Object createBean(BeanDefinition beanDefinition) {// 单例则直接从池中拿取对象并返回if (beanDefinition.isSingleton() && singletonObjects.contains(beanDefinition)) {return singletonObjects.get(beanDefinition.getId());}Object bean = null;try {Class beanClass = Class.forName(beanDefinition.getClassName());List<BeanDefinition.ConstructorArg> args = beanDefinition.getConstructorArgs();// 如果此bean没有依赖的bean,则直接创建对象即可if (args.isEmpty()) {bean = beanClass.newInstance();} else {// 否则,需要按个创建依赖的BD对象的beanClass[] argClasses = new Class[args.size()];Object[] argObjects = new Object[args.size()];for (int i = 0; i < args.size(); ++i) {BeanDefinition.ConstructorArg arg = args.get(i);if (arg.isRef()) {// 当此bean对象的构造函数中参数是ref类型时,则递归创建ref属性指向的对象BeanDefinition refBeanDefinition = beanDefinitions.get(arg.getArg());argClasses[i] = Class.forName(refBeanDefinition.getClassName());//依赖BD的ref类型(User.class)argObjects[i] = createBean(refBeanDefinition);//依赖BD的具体值User("mjp",18)} else {argClasses[i] = arg.getType();argObjects[i] = arg.getArg();}}// 通过反射获取有参构造器,然后再通过newInstance传递构造器入参值,创建对象bean = beanClass.getConstructor(argClasses).newInstance(argObjects);}} catch (Exception e) {}// 如果对象时单例的,则需要放入缓存池中if (bean != null && beanDefinition.isSingleton()) {singletonObjects.putIfAbsent(beanDefinition.getId(), bean);return singletonObjects.get(beanDefinition.getId());}return bean;}
}
3、对象生命周期管理
- 单例
- 懒加载:lazy-init = false。所有对象在应用启动的时候就创建好
- init-method 和 destroy-method
比如 initmethod=loadProperties(),在创建好对象后,会主动调用 init-method属性指定的方法来初始化对象。
destroy-method=updateConfigFile(),在对象被最终销毁之前,会主动调用 destroy-method 属性指定的方法来做一些清理工作(释放数据库连接池、关闭文件)。
3.1.4 Builder建造者模式
和set以及构造器的区别
1、构造器的缺点
如果类中有很多的属性,则new X(太多的属性,容易赋值错)
2、set方法的缺点
- 即使对象被final修饰,也是对象指向的地址是不可变的,但是堆地址的内容还是可以通过set赋值可变。
当要求对象一旦被new其属性值就不允许被修改,则不能对外暴露set
- set方法也无法校验传递参数是否正确,更无法校验多个属性之间的关系(eg:最大线程数 必须 > 核心线程数)
3、建造者的缺点:
建造者内部类中也需要再定义一遍和外部类中一样的属性
建造者模式创建对象
1、private 构造器
2、只提供get方法,不提供set
3、定义成员内部类Builder类
- 单个setXxx中可以校验某个属性
- 最终build方法new 对象之前,可以校验各个属性之间的关系
@Getter
@ToString
public class ThreadConfig {private String name;private Integer coreCount;private Integer maxCount;private ThreadConfig(ThreadConfigBuilder threadConfigBuilder) {this.name = threadConfigBuilder.name;this.coreCount = threadConfigBuilder.coreCount;this.maxCount = threadConfigBuilder.maxCount;}@ToStringprivate class ThreadConfigBuilder {public String name;public Integer coreCount;public Integer maxCount;public ThreadConfigBuilder setName(String name) {if (StringUtils.isBlank(name)) {throw new IllegalArgumentException("线程池名称不能为空");}this.name = name;return this;}public ThreadConfigBuilder setCoreCount(Integer coreCount) {if (coreCount == null || coreCount <= 0) {throw new IllegalArgumentException("线程池核心线程数必须为正整数");}this.coreCount = coreCount;return this;}public ThreadConfigBuilder setMaxCount(Integer maxCount) {if (maxCount == null || maxCount <= 0) {throw new IllegalArgumentException("线程池最大线程数必须为正整数");}this.maxCount = maxCount;return this;}public ThreadConfig build() {if (coreCount > maxCount) {throw new IllegalArgumentException("线程池最大线程数必须大于核心线程数");}return new ThreadConfig(this);}}
}
实战@Accessors
@Data
@Accessors(chain = true)
public class UserDemo {private String name;private Integer age;
}UserDemo m = new UserDemo().setName("m").setAge(18);
和工厂模式的区别
工厂模式是创建一系列相同类型的对象
建造者模式是创建一个复杂属性的对象
3.2结构型:类或对象的组合
代理、装饰者、适配器、享元
3.2.1Proxy代理模式
定义
在不改变原有类的情况下,引入代理类来给原始类附加功能
场景
日志打印、权限校验、限流、事务、最大努力重试
动态代理接口实现
为给个接口方法的执行,计算花费的时间
public class StopWatchProxy {public Object creatProxy(Object target) {Class<?> aClass = target.getClass();ClassLoader classLoader = aClass.getClassLoader();Class<?>[] interfaces = aClass.getInterfaces();return Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args) -> {long start = System.currentTimeMillis();Object result = method.invoke(target, args);long end = System.currentTimeMillis();System.out.println((end - start));return result;});}
}
public interface User {void eat();
}public class UserImpl implements User{@Overridepublic void eat() {System.out.println("eat");}
}
StopWatchProxy stopWatchProxy = new StopWatchProxy();
User user = (User) stopWatchProxy.creatProxy(new UserImpl());
user.eat();
3.2.2 装饰器模式
作用
给原始类添加增强功能
和代理模式的区别
代理模式中代理类附加的是跟原始类无关的功能(日志、权限校验等);装饰器类附加的是跟原始类相关的增强功能(原始类是直接读、装饰类增加的功能是缓存读)
实现
- 装饰器类(ADeractor)需要跟原始类(A)继承相同的抽象类(AbstractA) | 接口(IA)。
- 装饰器类(ADeractor)中组合原始类(A)
可以对A嵌套使用多个装饰器类
- 接口|抽象类
public interface IA {void f();
}
- 原始类
@Service
public class A implements IA{@Overridepublic void f() {System.out.println("f");}
}
- 装饰类
@Service
public class ADecorator implements IA{@Resourceprivate A a;@Overridepublic void f() {// 增强System.out.println("增强1");a.f();// 增强System.out.println("增强2");}
}
- 使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
public class SpringTest {@Resourceprivate ADecorator aDecorator;@Testpublic void test() {aDecorator.f();}
}
场景IO流
1、结构
- 字节流-读InpuStream
- FileInputStream
- ByteArrayInputStream
- FilterInputStream
- BufferedInputStream
- DateInputStream
- 字符流-读Reader
- BufferedReader
- InputStreamReader
- FileReader
2、源码结构
抽象类:InputStream
A:FileInputStream
ADecorator:BufferedInputStream、DateInputStream
FileInputStream fis = new FileInputStream(new File("xxx.txt"));
BufferedInputStream bis = new BufferedInputStream(fis);
bis.read();
3、前置背景
3.1 使用装饰者增强后的read
@Testpublic void test() throws IOException {FileInputStream fis = new FileInputStream(new File(""));SonBufferedInputStream bis = new SonBufferedInputStream(fis);bis.read();}
3.3 抽象-read
public abstract class InputStream implements Closeable {public abstract int read() throws IOException;
}
3.3 A-read
public class FileInputStream extends InputStream{public int read() throws IOException {return read0();} private native int read0() throws IOException;
}
3.4 ADecorator -read
public class SonBufferedInputStream extends FatherFilterInputStream {public SonBufferedInputStream(FileInputStream fis) {super(fis);}public int read() throws IOException {// A的readfis.read(null, 1, 1);// 增强return 1;}
}
- 虽然ADecorator(SonBufferedInputStream)中没有直接定义属性A(FileInputStrteam),但是其父类中定义了
@Data
@AllArgsConstructor
public class FatherFilterInputStream extends InputStream {protected FileInputStream fis;@Overridepublic int read() throws IOException {return fis.read();}
}
这样当执行SonBufferedInputStream bis = new SonBufferedInputStream(fis)时
- super(fis)
- 即将A(FileInputStrteam)赋值给了其父类属性A a,这样等效子类ADecorator也具有了A a属性值
- 所以,当执行bis.read()时,一方面执行了a.read,另一方面执行了增强方法。实现了装饰功能
4、源码解析
4.1 抽象类InputStream-read()
4.2 A : read(是个nativate方法)
public class FileInputStream extends InputStream{private native int read() throws IOException;
}
4.3 ADecorator(BufferedInputStream)-read
这里的BufferedInputStream bis = new BufferedInputStream(fis);
=》public BufferedInputStream(InputStream in, int size) {super(in);//super(fis)buf = new byte[size];}
=>
public class FilterInputStream extends InputStream {//即ADecorator中组合了A(fis)protected volatile InputStream in;//fisprotected FilterInputStream(InputStream in) {this.in = in;//this.fis = fis}
}
==》等效
public class FilterInputStream extends InputStream {protected volatile FileInputStream fis;protected FilterInputStream(FisleInputStream fis) {this.fis = fis;}
}
这样一来,就相当于子类ADecorator(Buffered)中通过继承父类也具有属性A(FileInputStream)
4.4 bis.read()
public synchronized int read() throws IOException {fill();//实现return getBufIfOpen()[pos++] & 0xff;
}
private void fill() throws IOException {// 增强功能缓存byte[] buffer = getBufIfOpen();// 调用属性a 的方法int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
}
private InputStream getInIfOpen() throws IOException {InputStream input = in;//fisreturn input;
}
getInIfOpen().read(buffer, pos, buffer.length - pos) ==相当于使用fis.read(buffer, pos, buffer.length - pos)
public class FileInputStream{public int read(byte b[], int off, int len) throws IOException {return readBytes(b, off, len);}private native int readBytes(byte b[], int off, int len) throws IOException;
}
这样一来,ADcorator中使用了buffer增强了fis的read
4.5 父类作用
完全可以直接将属性A a放入ADecorator中
public class BufferedInputStream extends InputStream {private FileInputStream fis;public int read(){//}
}
为什么采用,将A a 放入父类中,然后子类继承父类属性的方式从而子类ADecorator也具有了属性A。
父类作用:
-
让自子类Buffered、Data只需要关注A(Fis)中需要增强的方法,比如A中的read方法的Buffered为其增强为具有缓存功能的字节流读。
-
A类的其它不需要增强的方法都交给父类FilterInputStream的去关注去实现,这样众多装饰者子类就无需重写
3.2.3 适配器模式
定义
将不兼容的接口转为可兼容的接口,让原本因为接口不兼容无法在一起工作的类可以一起工作
实现
1、类适配器-继承
- 需要配适配的
public class Adaptee {public void query() {System.out.println("query");}public void add() {System.out.println("add");}public void delete() {System.out.println("delete");}
}
-
适配成什么样子,即目标
除了add方法还是使用Adaptee的,查询和删除都使用适配后的新方法
public interface Target {void queryNew();void add();void deleteNew();
}
- 适配器
@Service
public class Adapter extends Adaptee implements Target{@Overridepublic void queryNew() {super.query();}@Overridepublic void deleteNew() {if (true) {// 执行新的删除逻辑System.out.println("delete new");} else {super.delete();}}// 这里类适配器最大的特点就是:理论上需要重写add方法,但是由于继承了父类的add,所以可以不用重写add
}
- 使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class SpringTest {@Resourceprivate Target target;@Testpublic void test() throws IOException {target.queryNew();target.add();target.deleteNew();}
}
query
add
delete new
- 使用场景
目标Target和原Adaptee中大部分方法都一样,没有那么多方法需要适配的时候,使用类继承,这样很多方法(比如像add)都无需重写。
2、对象适配器-组合
- 适配器
@Service
public class AdapterObj implements Target{@Resourceprivate Adaptee adaptee;@Overridepublic void queryNew() {adaptee.query();// 再---}@Overridepublic void add() {adaptee.add();}@Overridepublic void deleteNew() {if (true) {System.out.println("delete new");} else {adaptee.delete();}}
}
- 使用场景
目标Target和原Adaptee中大部分方法都不一样即定义不同
场景
1、兼容老接口
- 背景
原本查询黑名单接口BlackListService#queryBlackList(Long poiId)根据仓id查询仓下的所有很名单。
本次需求查询黑名单时,除了需要仓id外,还需要businessType
-
实现方式1
直接修改BlackListService#queryBlackList(Long poiId,Integer businessType)方法声明和逻辑
-
优点:无需重构
-
缺点:风险大,线上很多业务使用到这个方法,一旦方法有问题相当于全量了,风险不可控
-
-
实现方式2:适配器模式
@Service public class BlackListAdaptee {public List<Object> queryBlackList(Long poiId){return Lists.newArrayList();} }
public interface BlackListServiceTarget {List<Object> queryBlackListNew(Long poiId, Integer businessType); }
@Service public class BlackListServiceAdaptor extends BlackListAdaptee implements BlackListServiceTarget{@Overridepublic List<Object> queryBlackListNew(Long poiId, Integer businessType) {if (true) {//命中了灰度仓System.out.println("根据poi和businessType查询黑名单");return Lists.newArrayList();} else {// 非灰度仓走老逻辑查询,查询结果按照businessType进行过滤即可List<Object> blackList = super.queryBlackList(poiId);return blackList.stream().filter(Objects::nonNull).collect(Collectors.toList());}} }
@RunWith(SpringRunner.class) @SpringBootTest(classes = ApplicationLoader.class) @Slf4j public class SpringTest {@Resourceprivate BlackListServiceAdaptor blackListServiceAdaptor;@Resourceprivate BlackListServiceAdaptee blackListServiceAdaptee;@Testpublic void test() throws IOException {// 原本的业务逻辑//List<Object> result = blackListServiceAdaptee.queryBlackList(323L);// 改为:List<Object> result = blackListServiceAdaptor.queryBlackListNew(323L, 1);} }
- 优点:风险可控,一旦方法有问题直接切灰度即可。
2、统一多个类的接口设计
- 背景
敏感词过滤。
- 实现方式
引入第三方过滤Jar(A:性关键词相关、B:政治关键字相关)
public class RishManagement{@Resourceprivate A a;@Resourceprivate B b;public String filterSensitiveWords(String text) {String s = a.filterSexyWords(text);//内部默认实现使用xxx代替敏感词return b.filterPoliticalWords(s,"???");//使用???代替敏感词}
}
- 问题
当需要环境污染过滤C时,这个时候RishManagement会违背开闭原则
A依赖提供的方法-单个入参:使用默认实现使用xxx代替敏感词
B依赖提供的方法-两个入参,第二个入参是replace:使用String replace 代替敏感词
这样接口的调用也不统一,还需要人为指定replace
-
解决:使用对象适配器模式统一接口设计
- Adaptee:AFilterAdaptee、BFilterAdaptee都是第三方依赖
- Target:制定统一的接口设计,否是单入参
public interface SensitiveWordsFilterTarget {String filter(String text); }
- A-Adaptor
@Service public class SexyWordsFilterAdapter implements SensitiveWordsFilterTarget{@Resourceprivate AFilterAdaptee aFilterAdaptee;@Overridepublic String filter(String text) {return aFilterAdaptee.filterSexyWords(text);} }
- B-Adaptor
@Service public class PoliticalWordsFilterAdapter implements SensitiveWordsFilterTarget{@Resourceprivate BFilterAdaptee bFilterAdaptee;@Overridepublic String filter(String text) {return bFilterAdaptee.filterPoliticalWords(text, "???");} }
- RiskManager
@Service public class RiskManager {@Resourceprivate List<SensitiveWordsFilterTarget> sensitiveWordsFilterTargets;public String filterWords(String text) {String temp = text;for (SensitiveWordsFilterTarget filterAdaptor : sensitiveWordsFilterTargets) {temp = filterAdaptor.filter(temp);}return temp;} }
这样当需要过滤:环境污染相关关键词引入C时,不需要修改RiskManager,只需要创建C-EnvironmentWordsFilterAdapter即可
3.2.4 享元模式
定义
被共享的单元(比如类的属性,当这些属性是通用且不可变时,可以组成元,让系统共享使用)
作用
系统复用不可变队形-享元,节省内存
实战
-
背景
建设一个象棋棋牌室游戏,同时在线1w个房间,每个房间是一盘对局(棋局类),对局中需要棋子(棋子类)
- 棋子
@Data @AllArgsConstructor public class ChessPiece {/*** 棋子编号1-32(红黑各16)*/private Integer id;private Color color;/*** 将、士、车、马---*/private String name;/*** 棋子在棋局上的位置*/private Integer x;private Integer y;public enum Color{RED,BLACk;} }
- 棋局
public class ChessBoard {private Map<Integer, ChessPiece> pieceIdMap = new HashMap<>();public ChessBoard() {init();}private void init() {pieceIdMap.put(1, new ChessPiece(1, ChessPiece.Color.RED, "车", 0 , 1));pieceIdMap.put(2, new ChessPiece(2, ChessPiece.Color.BLACk, "跑", 7 , 4));// 剩下30个棋子} }
-
问题
如果游戏有1w个房间,则有1w个棋局,再创建每个棋局的时候,都需要创建32个棋子对象。所以需要创建32w个棋子对象。占用很大内存。
- 解决
我们发现棋子属性只有x、y坐标属性,对于不同房间的棋局棋子的坐标是不同的
id、颜色、名称,对于不同棋局来说都是相同的属性,这些属性都是不可变的可以共享,可以抽取为享元类
享元类
@Data
@AllArgsConstructor
public class ChessPieceUnit {/*** 棋子编号1-32(红黑各16)*/private Integer id;private ChessPiece.Color color;/*** 将、士、车、马---*/private String name;public enum Color{RED,BLACk;}
}
棋子类
@Data
@AllArgsConstructor
public class ChessPiece {/*** 享元类(id、color、name)*/private ChessPieceUnit chessPieceUnit;/*** 棋子在棋局上的位置*/private Integer x;private Integer y;
}
享元工厂类:存取享元类
public class ChessPieceUnitFactory {private static final Map<Integer, ChessPieceUnit> pieceIdMap = new HashMap<>();static {pieceIdMap.put(1, new ChessPieceUnit(1, ChessPieceUnit.Color.RED, "车"));pieceIdMap.put(2, new ChessPieceUnit(2, ChessPieceUnit.Color.BLACk, "跑"));// 剩下30个棋子}private ChessPieceUnit getUnitByChessPieceId(Integer id) {return pieceIdMap.get(id);}
}
棋局类
public class ChessBoard {private static final Map<Integer, ChessPiece> pieceIdAndPieceMap = new HashMap<>();public ChessBoard() {init();}private void init() {pieceIdAndPieceMap.put(1, new ChessPiece(ChessPieceUnitFactory.pieceIdMap.get(1), 0 , 1));pieceIdAndPieceMap.put(2, new ChessPiece(ChessPieceUnitFactory.pieceIdMap.get(2), 0 , 1));// 剩下30个棋子}
}
棋局类在put棋子的时候,棋子的享元部分属性是通过享元工厂获取的。
-
棋局A:在put棋子的时候,享元类属性是通过享元类工厂缓存map获取的,map只需要第一次创建时new一个享元类
-
棋局B:后续棋局B使用享元类时,直接存缓存map获取即可,无需再创建。
-
即1w棋局,需要创建32w个棋子类,但是棋子类中大量的属性即享元属性只需要创建1次,这样无疑大大减少内存的占用。(1w房间,1w棋局类、32w棋子类、32个享元属性)
场景
Integer实战
1、背景
Integer i1 = new Integer(123);
Integer i2 = 123;//等价Integer i3 = Integer.valueOf(123);
2、原理
public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}
-
low:-128
-
high:127
-
当i的值在 -128-127之间,则从IntegerCache.cache缓存数组中获取,这个数据大小是256
[-128,-127,—0,1,—,127]
不在这个区间,则new一个新的对象
-
Integer.valueOf(1)即cache[1-(-128)] = cache[129] = 1
-
new Integer(123),不使用IntegerCache.cache缓存,直接创建对象
3、作用
若需要创建1w个-128-127的数字。方式1:new1w个对象
方式2|3则只需要new 256个对象
4、其他
String字符串常量池同理
总结
将共享的,再创建完成后,使用缓存(数组、map)存储起来。后续直接从缓存中取
3.3行为型
观察者、模板、策略、职责链、迭代器、状态模式
3.3.1 观察者模式
定义
对象之间定义一个1 vs n的依赖,当1的状态改变时,所有依赖于它的n对象,都会接收到通知并更新
被观察者:subject
观察者: observe
作用
将被观察者和观察者解耦
和生产者消费者的区别
1、生产消费模式:
- 生产和消费是不同的线程。二者通过队列通信
- 多对多(生产者可以多个:杂志的投稿者,消费者也可以多个)
- 为了解耦 和 并发
- 生产者推、消费者拉
2、观察者模式
- 被观察者和观察者,在同一个线程中
- 1对多
- 为了解耦被观察者和观察者
- 被观察者内部组合了观察者,需要维护观察者的信息,属于被观察者直接将信息推送给观察者们
和 订阅-发布模式的区别
1、订阅-发布模式
- 是观察者的别名,但是后续演变成一种新的设计模式
- 发布者,不再维护订阅者们的信息,不会再直接将信息推送个发布者们,实现了二者的完全解耦
- 发布者和订阅者之间,存在中间件:调度中心Broker
- 发布者只需要告诉Broker,我要发送的信息,topic为A
- 订阅者只需要告诉Broker,我订阅的消息,topic为A
- 当Broker接收到Topic为A的消息时,会统一调度那些订阅了Topic为A的订阅者们注册到Broker的处理代码
- eg:你在微博上关注了A,其他人也关注了A。当A发布动态,即发送消息到微博调度中心Broker时,Broker就会为你们推送A的动态
被观察者-观察者模板
被观察者需要维护观察者们的信息
1、抽象观察者
public interface Observe {void update();
}
2、具体观察者
@Service
public class Observe1 implements Observe{@Overridepublic void update() {System.out.println("观察者1更新");}
}@Service
public class Observe2 implements Observe{@Overridepublic void update() {System.out.println("观察者2更新");}
}
3、被观察
@Service
public class Subject {@Resourceprivate List<Observe> observes;public void notice() {for (Observe observe : observes) {observe.update();}}
}
使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class SpringTest {@Resourceprivate Subject subject;@Testpublic void test(){subject.notice();}
}
观察者1更新
观察者2更新
实战
1、背景
用户注册app成功后,给用户发放新人券
2、实现
@RestController
public class UserAppController {@Resourceprivate RegisterService registerService;@Resourceprivate RegisterSuccessObserve registerSuccessObserve;public void register(Long iphone, String pwd) {// 注册boolean success = registerService.register(iphone, pwd);if (success) {registerSuccessObserve.issueNewConsumerCoupon(iphone);}}
}
- 注册
@Service
public class RegisterService {public boolean register(Long iphone, String pwd) {return true;}
}
- 发送新人优惠券
@Service
public class RegisterSuccessObserve {public void issueNewConsumerCoupon(Long iphone){System.out.println("发送新人优惠券");}
}
3、问题
如后续新需求:当用户注册App成功后,除了发新人券还需要发送用户注册成功信息的短信给用户
那么register方法就必须改动了,违背了开闭原则
4、使用模式重构
被观察者维护观察者们的信息,一旦注册成功后,将后续一系列动作推送给观察者们即可
- 抽象观察者
public interface RegisterSSuccessObserver {void update(Long iphone);
}
- 观察者们
@Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{@Overridepublic void update(Long iphone) {System.out.println("发送新人优惠券");}
}
@Service
public class SendMsgObserve implements RegisterSSuccessObserver{@Overridepublic void update(Long iphone) {System.out.println("发送注册成功短信");}
}
- 被观察者
@RestController
public class UserAppControllerSubject {@Resourceprivate RegisterService registerService;@Resourceprivate List<RegisterSSuccessObserver> registerSSuccessObservers;public void register(Long iphone, String pwd) {// 注册boolean success = registerService.register(iphone, pwd);if (success) {for (RegisterSSuccessObserver observer : registerSSuccessObservers) {observer.update(iphone);}}}
}
这样后续,发送新人券改为发送礼品卡,也只需要新增具体观察者即可。对于register方法,满足开闭原则
5、优化- 异步非阻塞观察者模式
-
使用guava的EventBus模式实现异步阻塞
-
被观察者
@Slf4j
@RestController
public class UserAppControllerSubject {@Resourceprivate RegisterService registerService;@Resourceprivate List<RegisterSSuccessObserver> registerSSuccessObservers;private ExecutorService threadPool = Executors.newFixedThreadPool(2);private EventBus eventBus;public UserAppControllerSubject() {eventBus = new AsyncEventBus(threadPool, (e, context) -> {log.error("consumer{}, receive{},msg{} 流程异常", context.getSubscriber(), context.getSubscriberMethod() ,context.getEvent(), e);});}public void register(Long iphone, String pwd) {// 注册成功boolean success = registerService.register(iphone, pwd);if (success) {for (RegisterSSuccessObserver observer : registerSSuccessObservers) {// 订阅者-类似消费组eventBus.register(observer);}// 发布者send发布-类似MQ生产者的sendeventBus.post(iphone);}}
}
- 抽象观察者
public interface RegisterSSuccessObserver {void receive(Long iphone);
}
- 观察者们
@Service
public class SendMsgObserve implements RegisterSSuccessObserver{@Override@Subscribepublic void receive(Long iphone) {System.out.println("发送注册成功短信");}
}
@Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{@Override@Subscribepublic void receive(Long iphone) {System.out.println("发送新人优惠券");}
}
- 使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class SpringTest {@Resourceprivate UserAppControllerSubject userAppControllerSubject;@Testpublic void test(){userAppControllerSubject.register(186L, "123mjp");}
}
发送注册成功短信
发送新人优惠券
- 优化为非阻塞
如果IssueNewConsumerCouponObserve观察者的receive方法内部流程较长,执行的慢。可能会影响整体的性能,则需要非阻塞模式,即其receive方法异步执行
@Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{private ExecutorService myThreadPool = Executors.newFixedThreadPool(2);@Override@Subscribe@Async("myThreadPool")public void receive(Long iphone) {try {TimeUnit.SECONDS.sleep(5L);} catch (Exception e) {}System.out.println("发送新人优惠券");}
}
启动类@EnableAsync
@EnableAsync
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class ApplicationLoader {public static void main(String[] args) {SpringApplication springApplication = new SpringApplication(ApplicationLoader.class);springApplication.run(args);}
}
3.3.2 模板模式
定义
在抽象类中定义一个逻辑框架由a、b、c等组成。子类在不改变整体框架的情况下,重新定义业务的某些步骤
作用
扩展、复用
使用模板
- 父类模板
public abstract class Template {public void func() {m1();m2();m3();}protected abstract void m1();protected abstract void m2();private void m3() {}
}
- 子类重写模板中某一个步骤
public class A extends Template{@Overrideprotected void m1() {}@Overrideprotected void m2() {}
}
B类同理
场景
1、复用
1.1 io
- 父类框架
public abstract class InputStream implements Closeable {// 父类定义一个方法框架public int read(byte b[], int off, int len) throws IOException {// m1();read();// m3();}// 其中m2()即read方法,是抽象方法,由不同子类自己去实现public abstract int read() throws IOException;
}
- 子类重写框架中某个步骤
eg:ByteArrayInputStream会重写read方法
1.2 AbstractList
- 父类框架
public boolean addAll(int index, Collection<? extends E> c) {rangeCheckForAdd(index);boolean modified = false;for (E e : c) {add(index++, e);modified = true;}return modified;}其中add方法等效abstract类型方法,原因如下public void add(int index, E element) {throw new UnsupportedOperationException();}
即AbstractList中的add()直接抛出异常,即如果其子类不重写add方法,那么就会调用父类AbstractList的add,直接抛异常。这就和abstract关键字一样,强制要求子类重写add方法
2、扩展框架
2.1 背景
-
Web项目中的SpringMvc中的XxxController#xxxFunc。请求url对应类上注解 + 方法上注解,流量就能打到
-
这是SpringMvc封装了Servlet实现。自己也可以通过Servlet的扩展功能实现上述功能
2.2 扩展点
Servlet通过模板模式,留下了doGet、doPost等扩展点。让用户再不修改框架的情况下,通过继承HttpServlet重写扩展点方法,将用户自己的业务代码嵌入整个框架中
2.3 源码解析
- 自定义XxxServlet#xxxFunc
- 在web.xml中定义url和Servlet的映射关系
- 当请求打进来时,会首先走到HttpServlet的service方法
protected void service(HttpServletRequest req, HttpServletResponse resp){if (method.equals(METHOD_GET)) {if (lastModified == -1) {// 扩展点1:doGetdoGet(req, resp);} else {//}} else if (method.equals(METHOD_POST)) {// 扩展点2:doPostdoPost(req, resp);} else if (method.equals(METHOD_PUT)) {//}}// doGet等效abstract doGet// 因为doGet方法什么都没实现,就是报错。所以HttpServlet的子类就必须重写doGet,这一点类似AbstractList#addAll中的add方法protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{String protocol = req.getProtocol();String msg = lStrings.getString("http.method_get_not_supported");if (protocol.endsWith("1.1")) {resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);} else {resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);}}
- 子类重写模板,利用框架的模板模式,实现扩展功能
public class MyHttpServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 自定义}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{// 自定义}
}
3、实战
1、背景
父类定义框架方法:根据图形周长、面积,计算周长 / 面积值
2、实现
func(){// 计算周长double perimerter = perimerter();// 计算面积double area = area();m3(perimerter, area);
}
3.3.3回调函数
定义
A类中的a调用B类b方法时,B类的b方法会反过来调用A类中注册给它的f方法
作用
复用
由于和模板方法的复用功能一样,所以很多回调方式直接叫XxxTemplate
场景1
1、背景JDBCTemplate的演变
-
普通版JDBCDemo
- 实现
public class JDBCDemo {public List<User> queryUserById(Long id) {Connection con = null;Statement stm = null;List<User> ans = null;try {// 1.注册驱动Class.forName("com.mysql.jdbc.Driver");// 2.获取数据库连接对象con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/day21", "root", "root");// 3.获取sql语句的执行对象String sql = "select * from table_user where id = " + id;stm = con.prepareStatement(sql);// 4.执行sqlResultSet result = stm.executeQuery(sql);// 5.处理查询结果while (result.next()) {User user = new User().setName(result.getString("name"));ans.add(user);}return ans;} catch (Exception e) {} finally {// 6.关闭资源if (stm != null) {try {stm.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (con != null) {try {con.close();} catch (SQLException e) {throw new RuntimeException(e);}}}return ans;} }
- 问题
如果想执行update语句,则需要定义updateById(Long id)则整个流程还需要再走一遍。毫无复用性而言
-
抽取版JDBCUtils
- jdbc.properties
driver = com.mysql.jdbc.Driverurl = "jdbc:mysql://127.0.0.1:3306/day21"user = rootpassword = root
- Util
public class JDBCUtil {private static String driver;private static String url;private static String user;private static String password;/*** 注册驱动 + 获取数据库连接对象con的前置配置*/static {ClassLoader classLoader = JDBCUtil.class.getClassLoader();InputStream is = classLoader.getResourceAsStream("D:\\CodeBetter\\src\\main\\resources\\jdbc.properties");Properties properties = new Properties();try {properties.load(is);driver = properties.getProperty("driver");Class.forName(driver);url = properties.getProperty("url");user = properties.getProperty("user");password = properties.getProperty("password");} catch (Exception e) {}}/*** 获取数据库连接对象* * @return* @throws SQLException*/public static Connection getConnection() throws SQLException {return DriverManager.getConnection(url, user, password);}/*** 关闭资源* * @param con* @param stm*/public static void close(Connection con , Statement stm){if (stm != null) {try {stm.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (con != null) {try {con.close();} catch (SQLException e) {throw new RuntimeException(e);}}} }
- 使用
Connection con = JDBCUtil.getConnection();PreparedStatement pst;// 1.查询String sql = "select * from table_user where id = " + 1;pst = con.prepareStatement(sql);ResultSet resultSet = pst.executeQuery();// 2.更新String updateSql = "update from table_user set name = mjp where id = 1";pst = con.prepareStatement(updateSql);int result = pst.executeUpdate();// 3.关闭JDBCUtil.close(con, pst);
- 问题:抽象的仍不彻底。仍有大量con、stm相关对象
-
JDBCTemplate版本
public class DataSourceDemo {private static DataSource ds;private static Properties properties;static {ClassLoader classLoader = DataSourceDemo.class.getClassLoader();InputStream is = classLoader.getResourceAsStream("D:\\CodeBetter\\src\\main\\resources\\jdbc.properties");properties = new Properties();try {// 这里会把properties中所有属性都读取到properties.load(is);} catch (Exception e) {}}public static DataSource getDataSource() {try {ds = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {throw new RuntimeException(e);}return ds;}
}
JdbcTemplate jtl = new JdbcTemplate(DataSourceDemo.getDataSource());// 1.查询String sql = "select * from table_user where id = " + 1;Map<String, Object> map = jtl.queryForMap(sql, 1);// 2.更新String updateSql = "update from table_user set name = mjp where id = 1";int result = jtl.update(updateSql, 1);
2、源码解析JDBCTemplate(简化版)
- 含回调函数的接口
@FunctionalInterface
public interface StatementCallback<T> {T doInStatement(Statement stm) throws SQLException, DataAccessException;
}
- JDBCTemplate
public <T> T execute(StatementCallback<T> action) throws DataAccessException {// 1、2加载数据库驱动、创建数据库连接Connection con = DataSourceUtils.getConnection(this.obtainDataSource());Statement stmt = null;Object var11;try {// 3.创建sql语句执行对象stmt = con.createStatement();this.applyStatementSettings(stmt);// 4.使用回调方法执行stm.各种CRUDT result = action.doInStatement(stmt);this.handleWarnings(stmt);// 5.返回执行结果,可能是对象、List<对象>、intvar11 = result;} catch (SQLException var9) {//6.关闭资源} finally {JdbcUtils.closeStatement(stmt);DataSourceUtils.releaseConnection(con, this.getDataSource());}return var11;}
其中execute方法就属于模板方法,其中12356都是通用方法,只有4是根据不同的sql,stm执行对应的语句
- 查询、更新业务类
@AllArgsConstructor
public class CrudClass {private JdbcTemplate jdbcTemplate;/*** 查询* @param sql* @return*/public ResultSet query(String sql) {return (ResultSet) jdbcTemplate.execute(new StatementCallback<Object>() {@Overridepublic Object doInStatement(Statement stm) throws SQLException, DataAccessException {ResultSet resultSet = stm.executeQuery(sql);return resultSet;}});}/*** 更新* @param sql* @return*/public Integer update(String sql) {return (Integer) jdbcTemplate.execute((StatementCallback<Object>) stm -> {int result = stm.executeUpdate(sql);return result;});}
}
- 使用查询、更新类
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceDemo.getDataSource());
CrudClass crudClass = new CrudClass(jdbcTemplate);
ResultSet resultSet = crudClass.query("select * from tb_user where id = 32");
Integer result = crudClass.update("update from tb_user set name = mjp where id = 32");
3、分析
-
回调函数定义:A类中的a调用B类b方法时,B类的b方法会反过来调用A类中注册给它的f方法
-
JDBCTemplate应用定义:即CrudClass类中的query调用JdbcTemplate类execute方法时,execute方法会反过来调用CrudClass类中注册给它的doInStatement方法
-
概括:a在调用b方法时,b方法的入参为:对象实例(此对象有需要Override的方法即回调方法f)
补充:其实真正的JDBCtemplate,又充当了A类又充当了B类。其中作为B类b方法即jdbcTemplate.execute和上述事例一样,作为A类a方法则如下:
jdbcTemplate.query("", new RowMapper<Object>() {@Overridepublic Object mapRow(ResultSet resultSet, int i) throws SQLException {return null;}});
这里jdbcTemplate的query方法
调用了jdbcTemplate类的execute方法,其中execute方法的入参为:A类(JDBCTemplate)注册给它的StatementCallback接口的实例对象QueryStatementCallback(这个对象有需要重写的回调方法doInStatement),回调方法内部是各种CRUD的执行
public <T> T query(final String sql, final ResultSetExtractor<T> rse){class QueryStatementCallback implements StatementCallback<T>, SqlProvider {QueryStatementCallback() {}@Nullablepublic T doInStatement(Statement stmt) throws SQLException {ResultSet rs = null;Object var3;try {rs = stmt.executeQuery(sql);var3 = rse.extractData(rs);} return var3;}}return this.execute((StatementCallback)(new QueryStatementCallback()));}
我们CRUDClass中使用的匿名内部类作为对象,这里是使用的内部类作为的对象。无论使用哪种方式,入参对象都有需要重写的方法即回调方法
- 执行顺序:
JDBCTemplate#query -->> JDBCTemplate#execute -->> 执行步骤123 -->> 步骤4QueryStatementCallback#doInStatement#executeQuery -->> 步骤56
场景2 JVM Hook
1、背景
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {@Overridepublic void run() {System.out.println("JVM程序关闭时,会调用我");}}));
JVM提供了B类b即Runtime#addShutdownHook(),其中方法入参为线程对象(thread对象有需要重写的方法即run方法即回调函数)
2、作用
JVM程序关闭时,会执行回调函数
3、分析addShutdownHook
static {Shutdown.add(1 ,false,new Runnable() {public void run() {runHooks();}});}
static void runHooks() {// 对所有的hook线程执行startfor (Thread hook : threads) {hook.start();}}
执行顺序
Runtime#addShutdownHook() -->> static{} -->> runHooks() -->> hook.start() -->> 执行hook线程的回调方法run方法
3.3.4策略模式:
实战1
背景
业务任务有1、2、3、6、7、8 六种状态。其中6和8是成功状态。剩余任务状态都是失败状态。现在想提供一个接口可以根据任务号,查询某个失败任务失败的原因。
接口入参为任务号 + 失败任务的状态
- 定义接口
public interface Task {TaskStatusEnum getTaskStatus();String queryTaskStatus(Integer status);
}
- 定义接口实现类
@Slf4j
@Service
public class FailedTask implements Task {@Overridepublic TaskStatusEnum getTaskStatus() {return TaskStatusEnum.FAILED;}@Overridepublic String queryTaskStatus(Integer status) {return "网路原因计算失败";}
}
- 定义枚举类(和实现类一一对应)
@Getter
@RequiredArgsConstructor
public enum TaskStatusEnum {INIT(1,"初始化"),FAILED(2,"失败"),SUCCESS(3,"成功");private final Integer value;private final String desc;
}
- 面向接口编程
@Resource
private List<Task> tasks;
private Map<TaskStatusEnum, Task> map;@PostConstruct
private void initMap() {map = tasks.stream().collect(Collectors.toMap(Task::getTaskStatus, Function.identity()));
}@Test
public void t() {TaskStatusEnum status = TaskStatusEnum.FAILED;Task task = map.get(status);System.out.println(task.queryTaskStatus(1));
}
这里是使用枚举 和 实现类一一对应的方式,达到set效果。
实战2
1、背景
通过n分钟内业务告警m次来定级业务失败的严重程度。不同程度的告警 有不同的处理方式
3min内触发2次 ==》严重P1 ==》 电话告警
3min内触发1次 ==> 紧急P2 ==》 短信告警
10min内触发2次 ==> 正常P3 ==》 大象告警
2、思路
规则引擎 + 策略模式
通过规则引擎来判定出严重程度
根据不同的严重程度,使用策略模式,做不同的处理
实战3
- 根据前端入参,判断CRUD哪个场景(枚举值),执行对应的接口实现类(CRUD)
- 根据单据的类型,判断是调拨还是退供(1、2)在,执行对应的接口实现(逆向调拨、退供)
3.3.5 职责链模式
定义
一个请求经过A处理器处理 -->> 然后再把请求传递给B处理器处理 -->> 再传给C处理器。以此类推,形成一个链条
作用
复用、扩展
模板
1、职责链模式1:带终止
解释:有一个处理器可以处理此请求,则结束整个职责链。后续的处理器不会再被调用
- 抽象处理器
public interface Handle {boolean handle();
}
- 处理器
@Service
public class HandleA implements Handle{@Overridepublic boolean handle() {// A处理器无法处理此请求boolean handled = false;// 自己的业务return handled;}
}
- 职责链
public class HandleChain {@Resourceprivate List<Handle> handleList;public void doXxx() {for (Handle handle : handleList) {boolean canDeal = handle.handle();if (canDeal) {break;}}}
}
2、职责链模式2:无终止
解释:职责链上的所有处理器都会依次处理此请求
- 抽象处理器
public interface Handle {void handle();
}
- 处理器
@Service
public class HandleA implements Handle{@Overridepublic void handle() {// 处理请求}
}
- 职责链
public class HandleChain {@Resourceprivate List<Handle> handleList;public void doXxx() {for (Handle handle : handleList) {handle.handle();}}
}
实战:敏感词过滤
1、背景
在文本发布时,如果text中如果含有性、政治、广告相关的关键字,则会被处理
处理方式一:直接禁止本次文本发布
处理方式二:过滤关键字违规词后,再发布
2、处理方式一:终止型责任链模式
- 抽象处理器
public interface SensitiveWordFilterHandle {boolean doFilter(String text);
}
- 处理器
@Service
public class SexyWordFilterHandle implements SensitiveWordFilterHandle{@Overridepublic boolean doFilter(String text) {// 如果text中含有x、x、x词,则任务含有了性相关的敏感词。则会终止职责链if (true) {return true;}return false;}
}
其他处理器类似
- 职责链
public class SensitiveFilterHandleChain {@ResourceList<SensitiveWordFilterHandle> sensitiveWordFilterHandles;public void legalText(String text) {for (SensitiveWordFilterHandle handle : sensitiveWordFilterHandles) {boolean legal = handle.doFilter(text);if (legal) {// 允许发布} else {// 禁止}}}
}
3、处理方式二:无终止型责任链模式
- 抽象处理器
public interface SensitiveWordFilterHandle {String doFilter(String text);
}
- 处理器
@Service
public class SexyWordFilterHandle implements SensitiveWordFilterHandle{@Overridepublic String doFilter(String text) {// 如果text包含了a、b、c等性相关词,将这些词替换成xxxif (true) {return text.replace(abc, "xxx");}return text;}
}
其他处理器类似
- 职责链
public class SensitiveFilterHandleChain {@ResourceList<SensitiveWordFilterHandle> sensitiveWordFilterHandles;public String legalText(String text) {String temp = text;for (SensitiveWordFilterHandle handle : sensitiveWordFilterHandles) {temp = handle.doFilter(temp);}return temp;}
}
场景1:Servlet-Filter
作用
可以实现对Http的请求过滤(鉴权、限流、参数验证)、对返回结果过滤(打印日志)等
作用域
支持Servlet的Web容器(tomcat、jetty)
解析
- 抽象处理器
public interface Filter {public default void init(FilterConfig filterConfig) throws ServletException {}public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException;public default void destroy() {}
}
- 处理器
public class FilterHandler implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 处理req// 业务chain.doFilter(request, response);// 处理resp}
}
- 职责链FilterChain
FilterChain是个规范,tomcat具体实现是ApplicationFilterChain
public final class ApplicationFilterChain implements FilterChain {// 当前执行到哪个Filter处理器private int pos = 0;//Filter处理器的个数private int n = 0;//职责链数组private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];/*** 即chain.doFilter(request, response)方法*/@Overridepublic void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {// ---internalDoFilter(request,response);}private void internalDoFilter(ServletRequest request,ServletResponse response)throws IOException, ServletException {// Call the next filter if there is oneif (pos < n) {// 获取职责链上的下一个Filter处理器ApplicationFilterConfig filterConfig = filters[pos++];try {// 这里就有我们具体的处理器Filter filter = filterConfig.getFilter();filter.doFilter(request, response, this);}}// 添加过滤器处理器void addFilter(ApplicationFilterConfig filterConfig) {}}
场景2:MVC-Interceptor
作用:同Filter
作用域
MVC框架的一部分
和Servlet的Filter区别
- Filter对req、resp的过滤都在doFilter方法中,而HandlerInterceptor对req的拦截在preHandle方法中,对resp的拦截在postHandle中,是分开的
- 执行顺序
http请求 -->> Filter -->>> doChain过滤req、resp -->> Servlet的service()中的doPost|doGet(如果自定义Servlet继承了HttpServlet) -->> DispatcherServlet的doDsipatcher()内含applyPreHandle|appltPostHandle -->> MVC HandlerIntercept的preHandle -->> XxxController
解析
- 抽象拦截器处理器
public interface HandlerInterceptor {default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return true;}default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception {}default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception {}
}
- 拦截器处理器
public class MyHandlerInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {// 为false会拦截请求,true会放行// 业务逻辑// eg:根据req内容查询,请求是否合法、用户是否存在等。如果不满足,则请求被拦截掉,return falsereturn true;}public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable ModelAndView modelAndView) throws Exception {}public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,@Nullable Exception ex) throws Exception {// 一定会执行,类似finally}
}
- 职责链
public class HandlerExecutionChain {// 职责链数组private HandlerInterceptor[] interceptors;public void addInterceptor(HandlerInterceptor interceptor) {initInterceptorList().add(interceptor);}// 拦截reqboolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {// 获取职责链数组HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {// 获取拦截器处理器HandlerInterceptor interceptor = interceptors[i];// 拦截reqif (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}}return true;}
}
场景3:自定义职责链
- 抽象处理器
public interface DefinedHandler {void doHandle(xxxReq req);void preHandle(Object req);// 执行顺序,值越小,优先级越高default Integer executeOrder() {return Integer.MIN_VALUE;}
}
- 处理器
@Service
public class LockStatusHandle implements DefinedHandler{@Overridepublic void preHandle(Object req) {// 处理req:比如查询某些数据}@Overridepublic void postHandle(Object req) {// 处理resp:比如根据req过滤留下符合的数据}public Integer executeOrder() {return 10;}
}
@Service
public class AIQtyHandle implements DefinedHandler{@Overridepublic void preHandle(Object req) {// 处理req:比如查询某些数据}@Overridepublic void postHandle(Object req) {// 处理resp:比如根据req过滤留下符合的数据}public Integer executeOrder() {return 10;}
}
@Service
public class OverStockHandle implements DefinedHandler{@Overridepublic void preHandle(Object req) {// 处理req:比如查询某些数据}@Overridepublic void postHandle(Object req) {// 处理resp:比如根据req过滤留下符合的数据}public Integer executeOrder() {return 20;}
}
- 职责链
@Component
public class HandlerChain {@Resourceprivate List<DefinedHandler> definedHandlerList;private Map<Integer, List<DefinedHandler>> orderAndHandlerMap;@PostConstructpublic void init() {// 职责链正排序(值越小,越先执行。值相同的处理器一同并发执行)definedHandlerList.sort((h1, h2) -> NumberUtils.compare(h1.executeOrder(), h2.executeOrder()));orderAndHandlerMap = definedHandlerList.stream().collect(Collectors.groupingBy(DefinedHandler::executeOrder, TreeMap::new, Collectors.toList()));}public Map<Integer, List<DefinedHandler>> getHandlerMap() {return orderAndHandlerMap;}
}
- 使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class SpringTest {@Resourceprivate HandlerChain handlerChain;private ExecutorService threadPool = Executors.newFixedThreadPool(20);@Testpublic void test(){// 获取职责链上所有处理器Map<Integer, List<DefinedHandler>> handlerMap = handlerChain.getHandlerMap();// 按照处理器的值大小执行处理器(值越小的处理器,先执行。相同值的处理器并发执行)handlerMap.forEach((order, handlerList) ->{handlerList.stream().map(handler -> CompletableFuture.runAsync(() ->{handler.preHandle(null);}, threadPool)).collect(Collectors.toList());});}
}
- 分析
传统的职责链,例如Filter和HandlerInterceptor都是使用的数组存储的。然后从数组中按照处理器存储的index先后顺序一个一个取,直到全部处理器都执行完毕。
区别:这里的职责链不是数组[]存储,然后遍历index获取处理器。而是通过在抽象处理器中定义executeOrder值,每个具体的处理器,自己根据业务优先级自定义自己的处理顺序。优先级高的处理器先执行。相同优先级并行执行
3.3.6 状态模式
定义
状态机由3部分组成
1、状态Status
2、事件Event
3、动作Action
其中事件Event也被称为状态转移条件。一旦触发了事件,则一定伴随着状态改变Status A -> B,可能会有相应的动作Action产生
作用
- 避免了大量if-else逻辑
- 可扩展性好:后续新增状态或状态的改变带来动作的执行,只需要新增状态类 和 动作执行类即可
使用场景
1、不建议使用场景
对象的状态,单行道变换。举例:逆向任务的状态,初始化 -> 已落表 -> 已计算 -> 成功|部分成功|失败
2、建议使用场景
当一个对象的状态<=5,而且状态之间可以相互转换,当达到不同的状态会产生不同的动作时,建议使用状态模式
实战-电商订单
1、状态机
2、实现
2.1 抽象状态类
public interface OrderStatus {
}
2.2 状态类
- 待付款
/*** 待付款状态类* 此状态类状态的可能转移为:* 待付款 -> 取消付款(用户点击了取消付款按钮)* 待付款 -> 待发货(用户付款了)*/
public class WaitPay implements OrderStatus {private static final WaitPay instance = new WaitPay();private WaitPay(){}public static WaitPay getInstance() {System.out.println("订单生成,30分钟内有效");return instance;}/*** 事件Event:用户取消付款了*/public void cancelPay(OrderStatusMachine machine) {System.out.println("===Event:用户取消了付款===");// 状态Status: 待付款 -> 取消付款machine.setOrderStatus(CancelPay.getInstance());// 动作ActionSystem.out.println("更细订单状态为:已取消");}/*** 事件Event:用户付款了*/public void clickPay(OrderStatusMachine machine) {System.out.println("===Event:用户付款了===");// 状态Status: 待付款 -> 待发货machine.setOrderStatus(WaitDeliver.getInstance());// 动作ActionSystem.out.println("1、更细订单状态为:待发货");System.out.println("2、将钱存入支付宝");System.out.println("3、淘宝信息提醒:您的地址信息为xxx请核对");}
}
- 取消付款
/*** 取消状态类* 此状态为终态,不会再转移:*/
public class CancelPay implements OrderStatus {private static final CancelPay instance = new CancelPay();private CancelPay(){}public static CancelPay getInstance() {return instance;}
}
- 待发货
/*** 待发货状态类* 此状态类状态的可能转移为:* 待发货 -> 退货退款(用户点击了申请退货退款按钮)* 待发货 -> 待收货(仓库提交发货)*/
public class WaitDeliver implements OrderStatus{private static final WaitDeliver instance = new WaitDeliver();private WaitDeliver(){}public static WaitDeliver getInstance() {return instance;}/*** 事件Event:用户点击了申请退货退款按钮*/public void waitDeliverApplyRefund(OrderStatusMachine machine) {System.out.println("===Event:用户申请了退货退款===");// 状态Status: 待发货 -> 退货退款machine.setOrderStatus(Refund.getInstance());// 2.动作System.out.println("1、更新订单状态为:退货退款");System.out.println("2、支付宝将钱原路退回给用户");}/*** 事件Event:仓库提交发货*/public void submitDelivery(OrderStatusMachine machine) {System.out.println("===Event:仓库提交了发货===");// 状态Status: 待发货 -> 待收货machine.setOrderStatus(WaitReceive.getInstance());// 2.动作System.out.println("1、更细订单状态为:待收货");System.out.println("2、发送信息给用户:您的包裹正在快马加鞭的赶来");}
}
- 待收货
/*** 待收货状态类* 此状态类状态的可能转移为:* 待收货 -> 退货退款(用户点击了申请退货退款按钮)* 待收货 -> 待评价(确认收货)*/
public class WaitReceive implements OrderStatus{private static final WaitReceive instance = new WaitReceive();private WaitReceive(){}public static WaitReceive getInstance() {return instance;}/*** 事件Event:用户点击了申请退货退款按钮*/public void waitReceiveApplyRefund(OrderStatusMachine machine) {System.out.println("===Event:用户申请了退货退款===");// 状态Status: 待收货 -> 退货退款machine.setOrderStatus(Refund.getInstance());// 2.动作System.out.println("1、更新订单状态为:退货退款");System.out.println("2、支付宝将钱原路退回给用户");}/*** 事件Event:用户确认收货*/public void confirmReceive(OrderStatusMachine machine) {System.out.println("===Event:用户确认了收货===");// 状态: 待收货 -> 待评价machine.setOrderStatus(WaitReview.getInstance());// 动作System.out.println("1、更细订单状态为:待评价");System.out.println("2、发送信息给用户:亲,麻烦评价下商品");}
}
- 退货退款
/*** 退货退款状态类* 此状态为终态,不会再转移:*/
public class Refund implements OrderStatus{private static final Refund instance = new Refund();private Refund(){}public static Refund getInstance() {return instance;}
}
- 待评价
/*** 待评价状态类* 此状态类状态的可能转移为:* 待评价 -> 订单完成(用户评价了商品)*/
public class WaitReview implements OrderStatus{private static final WaitReview instance = new WaitReview();private WaitReview(){}public static WaitReview getInstance() {return instance;}/*** 事件Event:用户评价商品*/public void reviewGoods(OrderStatusMachine machine) {System.out.println("===Event:用户评价了商品===");// 状态: 待评价 -> 完成machine.setOrderStatus(Finish.getInstance());// 动作System.out.println("1、更细订单状态为:已完成");System.out.println("2、支付宝将钱打给商家");System.out.println("3、用户的积分增加");}
}
- 订单完成
/*** 完成状态类* 此状态为终态,不会再转移:*/
public class Finish implements OrderStatus{private static final Finish instance = new Finish();private Finish(){}public static Finish getInstance() {return instance;}
}
2.3 状态机
每个非终态的状态类中的方法,都需要在状态机中定义下
@Data
public class OrderStatusMachine {private OrderStatus orderStatus;public OrderStatusMachine(){this.orderStatus = WaitPay.getInstance();}/*** 事件Event:用户取消付款了*/public void cancelPay() {((WaitPay) this.orderStatus).cancelPay(this);}/*** 事件Event:用户付款了*/public void clickPay() {((WaitPay) this.orderStatus).clickPay(this);}/*** 事件Event:待发货时,用户点击了申请退货退款*/public void waitDeliverApplyRefund() {((WaitDeliver) this.orderStatus).waitDeliverApplyRefund(this);}/*** 事件Event:用户确认收货*/public void submitDelivery() {((WaitDeliver) this.orderStatus).submitDelivery(this);}/*** 事件Event:待收货时,用户点击了申请退货退款*/public void waitReceiveApplyRefund() {((WaitReceive) this.orderStatus).waitReceiveApplyRefund(this);}/*** 事件Event:用户评价商品*/public void confirmReceive() {((WaitReceive) this.orderStatus).confirmReceive(this);}/*** 事件Event:用户评价商品*/public void reviewGoods() {// 状态: 待评价 -> 订单完成((WaitReview) this.orderStatus).reviewGoods(this);}
}
2.4 使用状态机
@RunWith(MockitoJUnitRunner.class)
@Slf4j
public class BaseTest {@Testpublic void test() {System.out.println("==================李四的订单start===========");OrderStatusMachine m1 = new OrderStatusMachine();// 1.李四(付款 -> 仓库发货 -> 确认收货 -> 评价)m1.clickPay();m1.submitDelivery();m1.confirmReceive();m1.reviewGoods();System.out.println("==================李四的订单end===========");System.out.println("==================王五的订单start==================");OrderStatusMachine m2 = new OrderStatusMachine();// 2.王五(付款 -> 退货退款)m2.clickPay();m2.waitDeliverApplyRefund();System.out.println("==================王五的订单end==================");System.out.println("==================赵六的订单start==================");OrderStatusMachine m3 = new OrderStatusMachine();// 3.赵六(付款 -> 仓库发货 -> 退货退款)m3.clickPay();m3.submitDelivery();m3.waitReceiveApplyRefund();System.out.println("==================赵六的订单end==================");}
}
3、总结
3.1 状态机中
public void cancelPay() {((WaitPay) this.orderStatus).cancelPay(this);}
如果不这样写,那么对于原状态为其他的比如待发货状态,不会有任何Event事件,更不会有Action动作,但是发货状态类也需要定义WaitPay方法,只不过方法内没东西
public class WaitDeliver implements OrderStatus{private static final WaitDeliver instance = new WaitDeliver();private WaitDeliver(){}public static WaitDeliver getInstance() {return instance;}// 因为对于WaitDeliver代发货状态而言,他没有取消付款事件,所以定义个空方法。// 同理,他也没有评价事件。public void cancelPay() {}/*** 事件Event:用户点击了申请退货退款按钮*/public void waitDeliverApplyRefund(OrderStatusMachine machine) {System.out.println("===Event:用户申请了退货退款===");// 状态Status: 待发货 -> 退货退款machine.setOrderStatus(Refund.getInstance());// 2.动作System.out.println("1、更新订单状态为:退货退款");System.out.println("2、支付宝将钱原路退回给用户");}/*** 事件Event:仓库提交发货*/public void submitDelivery(OrderStatusMachine machine) {System.out.println("===Event:仓库提交了发货===");// 状态Status: 待发货 -> 待收货machine.setOrderStatus(WaitReceive.getInstance());// 2.动作System.out.println("1、更细订单状态为:待收货");System.out.println("2、发送信息给用户:您的包裹正在快马加鞭的赶来");}
}
这样写的好处是:只有原状态为:待付款状态,才会有Event取消付款,才会有后续Action动作。其他状态类不需要关注,内部也不需要定义此方法
3.2 单例
private static final WaitDeliver instance = new WaitDeliver();
防止反复创建
3.3 扩展
对于像WaitPay状态类,当用户付款了后,状态变为:待发货。然后有好几个总做要去做
/*** 事件Event:用户付款了*/public void clickPay(OrderStatusMachine machine) {System.out.println("===Event:用户付款了===");// 状态Status: 待付款 -> 待发货machine.setOrderStatus(WaitDeliver.getInstance());// 动作ActionSystem.out.println("1、更细订单状态为:待发货");System.out.println("2、将钱存入支付宝");System.out.println("3、淘宝信息提醒:您的地址信息为xxx请核对");}
当然这3个动作都可以在clickPay方法中定义,但是如果后续,用户付款后,新增加动作,或者原动作不执行了。则需要改clickPay中代码,违背了开闭原则。
- 解决:责任链模式
参考责任链模式中场景3:自定义责任链。可以根据动作的执行先后,对应处理器的先后执行。
这样不同的动作,都实现了抽象动作,这样新增、删除等,直接增加对应的动作 - 处理器即可,满足开闭原则。
补充:前提是,clickPay事件Event触发后,动作确认存在频繁变动的场景
3.3.7 解释器模式
定义
描述如何 构建一个简单的"语言"解释器
作用
解释自定义“语言”
实战
背景
开发一个监控业务系统。当每分钟接口出错数目超过10,或者每分钟API的调用总数大于10w,则触发告警。当然具体的告警可以是短信、电话等
分析
我们可以把自定义的告警规则当做一种“语言”的语法规则,然后实现一个解释器即可。
针对用户的输入,判断是否触发告警
实现
- 举例:
String exp = “key1 > 100 && key2 > 100000 || key3 == 404”;
表达式含义:
key1即每分钟的错误数大于100 ,同时key2即每分钟的接口调用量大于10w
或者即key3接口返回404
上述场景则返回true,需要告警
- 使用
@Testpublic void test() {String exp = "key1 > 100 && key2 > 100000 || key3 == 404";Expression expression = RuleExpressionFactory.getExpression(exp);Map<String, Long> map = new HashMap<>();map.put("key1", 200L);map.put("key2", 1000L);map.put("key3", 404L);boolean intercept = expression.intercept(map);System.out.println(intercept);}
- 解释器接口
public interface Expression {boolean intercept(Map<String , Long> map);
}
- 大于解释器
public class GreaterExpression implements Expression{private String key;private Long value;public GreaterExpression(String strExpression) {String[] elements = strExpression.trim().split("\\s+");this.key = elements[0].trim();this.value = NumberUtils.toLong(elements[2].trim());}public GreaterExpression(String key, Long value) {this.key = key;this.value = value;}@Overridepublic boolean intercept(Map<String, Long> map) {if (!map.containsKey(key)) {return false;}Long val = map.get(key);return val > value;}
}
- 小于解释器
public class LessExpression implements Expression{private String key;private Long value;public LessExpression(String strExpression) {String[] elements = strExpression.trim().split("\\s+");this.key = elements[0].trim();this.value = NumberUtils.toLong(elements[2].trim());}public LessExpression(String key, Long value) {this.key = key;this.value = value;}@Overridepublic boolean intercept(Map<String, Long> map) {if (!map.containsKey(key)) {return false;}Long val = map.get(key);return val < value;}
}
- 等于解释器
public class EqualsExpression implements Expression{private String key;private Long value;public EqualsExpression(String strExpression) {String[] elements = strExpression.trim().split("\\s+");this.key = elements[0].trim();this.value = NumberUtils.toLong(elements[2].trim());}public EqualsExpression(String key, Long value) {this.key = key;this.value = value;}@Overridepublic boolean intercept(Map<String, Long> map) {if (!map.containsKey(key)) {return false;}Long val = map.get(key);return Objects.equals(val, value);}
}
- &&解释器
public class AndExpression implements Expression{private List<Expression> expressionList = new ArrayList<>();public AndExpression(String expression) {String[] elements = expression.split("&&");for (String ele : elements) {if (ele.contains("||")) {expressionList.add(new OrExpression(ele));} else if (ele.contains(">")) {expressionList.add(new GreaterExpression(ele));} else if (ele.contains("<")) {expressionList.add(new LessExpression(ele));} else if (ele.contains("==")) {expressionList.add(new EqualsExpression(ele));} else {throw new RuntimeException("错误的表达式");}}}public AndExpression(List<Expression> expressionList) {this.expressionList.addAll(expressionList);}@Overridepublic boolean intercept(Map<String, Long> map) {for (Expression expression : expressionList) {if (expression.intercept(map)) {System.out.println(expression + "符合表达式");} else {return false;}}return true;}
}
- ||解释器
public class OrExpression implements Expression{private List<Expression> expressionList = new ArrayList<>();public OrExpression(String expression) {String[] elements = expression.split("\\|\\|");for (String exp : elements) {Expression ruleExpression = RuleExpressionFactory.getExpression(exp);expressionList.add(ruleExpression);}}public OrExpression(List<Expression> expressionList) {this.expressionList.addAll(expressionList);}@Overridepublic boolean intercept(Map<String, Long> map) {for (Expression expression : expressionList) {if (expression.intercept(map)) {return true;}}return false;}
}
- 解释器工厂类
@UtilityClass
public class RuleExpressionFactory {public Expression getExpression(String exp) {if (exp.contains("&&")) {return new AndExpression(exp);} else if (exp.contains("||")) {return new OrExpression(exp);} else if (exp.contains(">")) {return new GreaterExpression(exp);} else if (exp.contains("<")) {return new LessExpression(exp);} else if (exp.contains("==")) {return new EqualsExpression(exp);}throw new RuntimeException();}
}
场景
编译器、规则引擎(这里举例阿里的规则引擎QLExpress)、正则表达式
1、地址:https://github.com/alibaba/QLExpress
<dependency><groupId>com.alibaba</groupId><artifactId>QLExpress</artifactId><version>3.3.2</version></dependency>
2、 eg1:
@Testpublic void t() throws Exception {ExpressRunner runner = new ExpressRunner();DefaultContext<String, Object> context = new DefaultContext<>();context.put("a", Boolean.TRUE);context.put("l", Boolean.TRUE);context.put("lo", Boolean.TRUE);context.put("s", Boolean.FALSE);String express = "a&&l&&lo&&s";//false
// String express = "a&&llo||s"; //trueObject res = runner.execute(express, context, null, true, false);System.out.println(res);}
eg2:
@Testpublic void t() throws Exception {DefaultContext<String, MetaRuleResult> context = new DefaultContext<>();context.put("o", MetaRuleResult.builder().skuId(1L).result(true).metaRule("o").failureReason("").build());context.put("l", MetaRuleResult.builder().skuId(1L).result(false).metaRule("l").failureReason("锁库存不可更改").build());context.put("s", MetaRuleResult.builder().skuId(1L).result(true).metaRule("s").failureReason("").build());context.put("w", MetaRuleResult.builder().skuId(1L).result(false).metaRule("w").failureReason("售罄预警不可更改").build());context.put("lo", MetaRuleResult.builder().skuId(1L).result(true).metaRule("lo").failureReason("").build());context.put("llo", MetaRuleResult.builder().skuId(1L).result(false).metaRule("llo").failureReason("锁库且修改值小于等于OR值可以更改").build());ExpressRunner runner = new ExpressRunner();Object result;DefaultContext<String, Object> computeContext = new DefaultContext<>();for (Map.Entry<String, MetaRuleResult> entry : context.entrySet()) {computeContext.put(entry.getKey(), entry.getValue().getResult());}String ruleExpress = "o&&l&&s&&w&&lo&&llo";result = runner.execute(ruleExpress, computeContext, null, true, false);Boolean bResult = (Boolean) result;System.out.println(bResult);//falseString failReason = buildFailureReason(ruleExpress, context);System.out.println(failReason);//售罄预警且锁库存不可更改且锁库且修改值小于等于OR值可以更改}private String buildFailureReason(String ruleExpress, DefaultContext<String, MetaRuleResult> context) {StringBuilder sb = new StringBuilder();sb.append("修改失败原因如下:");context.forEach((rule, meta) -> {if (! meta.getResult()) {sb.append(meta.getFailureReason() + "; ");}});return sb.toString();}
-
context
key为规则rule内容eg:”a“允许、"llo"锁库且小于OR允许、"s"即20点后修改值小于可履约库存允许修改,
value为,rpc查询依赖的各个数据,判断当前sku,是否满足这个规则,满足为true,不满足为false
eg:key = “s”,此时为20点后修改最大售卖量,想把最大售卖量从50 -> 30,计算发现可履约库存为40,30 < 40,则允许修改,即MetaRuleResult的result值为true
-
express
表达式为修改规则的组合;
db中规则为"a&&llo"、"llo&&s&&t"等等
-
execute执行
当满足所有的规则,全部为true,则本次此sku允许修改最大售卖量,一个规则不满足,最终结果res都会为false,不允许修改最大售卖量
然后将每个规则,不满足的原因都记录下来 ”且“,返回给档期展示即可。
3.3.8 中介模式
定义
定义一个中介对象,用其来封装一组对象之间的交互。
将这组对象之间的交互 =》 这组对象与中介的交互。
作用
- 避免这组对象之间的大量直接交互。解耦这组对象之间的交互
- 将一组对象之间的交互图从网状关系,转换为星状
场景
航空管制:参与者之间的交互关系错综复杂,维护成本很高。
背景
为了让飞机在飞行的时候互相不干扰,每架飞机必须知道其他飞机每时每刻的位置,这样就需要飞机之间时刻通信。通信网络就会非常复杂
中介模式
- 中介:塔台
- 一组对象:飞机们
- 通信:采用星形关系,每架飞机只和塔台交互,发送自己的位置给塔台,由中介来负责每架飞机的航线调度,这样就大大简化了对象们之间的交互
- 风险:中介类可能变得异常复杂且庞大
四、其它
4.1 系统设计
4.1.1合理将功能划分到不同模块
-
eg:逆向计划中触发模块、计算模块、合单下发模块。模块内部高内聚,模块之间MQ交互低耦合
-
如何判断模块的划分是否合理
如果某个功能的修改或添加,经常需要跨系统才能完成,说明模划分不合理,职责不清晰,耦合严重。
- good case:计划侧定量、oih定向
- bad case:逆向计划 和 frc触发模块。原本链路为:前端 -> 物流 -> frc -> 计划。链路长,重试机制,校验机制,状态一致性复杂。重构后:前端 -> 计划
4.1.2模块之间的交互关系
- 同步:接口(上下游)
- 异步:MQ(同层、系统内部)
- 也可以同步接口调用下游,下游简单校验req,然后返回resp。然后下游内部处理完成后,MQ再回掉我们本次请求的结果
4.1.3业务开发
-
接口:设计原则 + 设计模式
-
业务逻辑:
- POExample放在Repository层,不要出现在Service业务逻辑层;Mapper中,只写CRUD;Repository中通过构建example调用Mapper的CRUD
Service中对查询出的PO,进行Convert成BO|DTO
-
接口的TReq不要渗透到底层
-
每一层,只提供最基本的查询。至于查询的结处理成什么样,由上一层调用方自己决定。这样,每一层的方法复用性才更好。
- 网关层,只写rpc查询。并发查询,业务侧自己封装
- Repository只写查询出的List list。Service自己对list进行convert2DTO或别的
-
数据体
- PO -> BO|DTO -> VO
- PO(id、name、age、sex、pwd、edu) -> BO(name, age, sex, edu) -> DTO(“mjp”, 1, “man”, “大学”) -> VO(“mjp”, “青年”, “男士”, “本科”)
- BO 和 DTO基本等效 ; 数据需要返回给前端且大量字段需要后端优化展示内容才需要VO,正常DTO即可
4.2 重构
4.2.1what
在保持功能不变的前提下,利用设计原则、设计模式等理论,来修改设计上的不足 和 提高代码质量
4.1.2why
- 避免一开始的过度设计
- 运用模式
初级:在原有框架下写
高级:从零开始设计代码结构,搭建代码框架
资深:发觉代码、框架等存在的问题,重构
4.1.3 context
1、大型重构
-
涉及到的面
架构(商品、物流)、模块(触发、计算、合单下发)、代码结构(单据和单据明细)、类之间的交互(同步、异步)
-
how如何大型重构
- 使用设计思想、原则 和模式。常见手段有
- 模块化(计划内部划分为3个模块)
- 解耦(计划内部,通过MQ解耦)
- 抽象可复用(单据下发抽象成接口)
- 分层(Controller、Service、Dao)
- 解耦手段:封装、抽象、模块化、中间层
- 作用
- 哪些代码需要解耦
- 如何解耦
- 列出计划,分阶段重构
- 每个阶段完成一小部分代码的重构,然后UT,再继续下一阶段的重构,让代码始终可运行
- 每个阶段要控制重构影响的代码范围,考虑好新老兼容。切记不可无计划重构
- 使用设计思想、原则 和模式。常见手段有
2、小型重构
- 涉及到的面
- 类(单一职责、提取公共 代码)
- 函数(里氏替换原则:Repository中方法只写查询,具体查询到的结果交于上层自己去convert,这样方法复用性更好)
- 变量等(代码规范)
4.1.4 when
- 新需求,涉及对老代码的改动。如果时间充足 + 老代码设计或质量存在问题,则重构
- 重构意识,把重构作为开发的一部分,成为一种习惯。对自己和代码都好
4.1.5 如何避免重构出问题
UT
- 本身也是一次CR
- 代码的可测试性,从侧面也反映出代码的设计是否合理
ecute(express, context, null, true, false);System.out.println(res);}
4.2 重构
4.2.1what
在保持功能不变的前提下,利用设计原则、设计模式等理论,来修改设计上的不足 和 提高代码质量
4.2.2why
- 避免一开始的过度设计
- 运用模式
初级:在原有框架下写
高级:从零开始设计代码结构,搭建代码框架
资深:发觉代码、框架等存在的问题,重构
4.2.3 context
1、大型重构
-
涉及到的面
架构(逆向链路:商品、物流)、模块(触发、计算、合单下发)、代码结构(单据和单据明细)、类之间的交互(同步、异步)
-
how如何大型重构
- 使用设计思想、原则 和模式。常见手段有
- 模块化(逆向计划内部划分为3个模块)
- 解耦(逆向计划内部,通过MQ解耦)
- 抽象可复用(单据下发抽象成接口)
- 分层(Controller、Service、Dao)
- 解耦手段:封装、抽象、模块化、中间层
- 作用
- 哪些代码需要解耦
- 如何解耦
- 列出计划,分阶段重构
- 每个阶段完成一小部分代码的重构,然后UT,再继续下一阶段的重构,让代码始终可运行
- 每个阶段要控制重构影响的代码范围,考虑好新老兼容。切记不可无计划重构
- 使用设计思想、原则 和模式。常见手段有
2、小型重构
- 涉及到的面
- 类(单一职责、提取公共 代码)
- 函数(里氏替换原则:Repository中方法只写查询,具体查询到的结果交于上层自己去convert,这样方法复用性更好)
- 变量等(代码规范)
4.2.4 when
- 新需求,涉及对老代码的改动。如果时间充足 + 老代码设计或质量存在问题,则重构
- 重构意识,把重构作为开发的一部分,成为一种习惯。对自己和代码都好
4.2.5 如何避免重构出问题
UT
- 本身也是一次CR
- 代码的可测试性,从侧面也反映出代码的设计是否合理
相关文章:
设计模式Java实战
文章目录 一、前置1.1 目的1.2 面向对象1.3 接口和抽象类 二、七大设计原则2.1 单一职责2.2 接口隔离原则2.3 依赖倒转原则2.4 里氏替换原则2.5 开闭原则2.6 不要重复原则2.7 迪米特最少知道法则 三、23种设计模式3.1创建型:创建对象3.1.1 单例模式定义最佳实践场景…...
外国固定资产管理系统功能有哪些
很多公司都在寻找提高自己资产管理效益的方法。为了满足这一要求,国外的固定资产管理系统已经发展成多种形式。以下是国外一些常见的固定资产管理系统的特点:自动化和智能化:许多现代固定资产管理系统采用自动化和数字化技术,以简化流程,减少…...
Postman应用——控制台调试
当你在测试脚本中遇到错误或意外行为时,Postman控制台可以帮助你识别,通过将console.log调试语句与你的测试断言相结合,你可以检查http请求和响应的内容,以及变量之类的。 通常可以使用控制台日志来标记代码执行,有时…...
如何制作思维导图?
思维导图是一种非常有用的工具,可以被广泛应用于不同领域的人群。以下是一些常见的使用人群:学生、教育工作人员、各领域的专业人员,法律、商业、医学等等,创作者、艺术家、个人自我成长管理。 由此可见,思维导图可以做…...
【力扣每日一题】2023.9.21 收集树中金币
目录 题目: 示例: 分析: 代码: 题目: 示例: 分析: 题目给我们一棵树,不过这棵树不是普通的树,而是无向无根树。给我们一个二维数组表示节点之间的连接关系ÿ…...
Python与数据分析--每天绘制Matplotlib库实例图片3张-第1天
目录 1.实例1--Bar color demo 2.实例2--Bar Label Demo 3.实例3--Grouped bar chart with labels 1.实例1--Bar color demo import matplotlib.pyplot as plt # 支持中文 plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus…...
pycharm 中package, directory, sources root, resources root的区别
【遇到的问题】 导入yolov5中有utils文件,自己的代码中也有utils文件,使得yolov5中的这部分引用出错了。 【解决方案】 单独建立detection文件夹,把检测相关的都放在这里,yolov5是github上拉取的源码,发现yolov5中fr…...
【谢希尔 计算机网络】第2章 物理层
目录 通信基础 基本概念 两个公式lim 奈氏准则 香农定理 奈氏准则 VS 香农定理 编码与调制 编辑 物理层下面的传输媒体 导引型传输媒体 1. 双绞线 2. 同轴电缆 3. 光缆 非导引型传输媒体 无线电微波通信 卫星通信 无线局域网使用的 ISM 频段 信道复用技术 …...
Eclipse工具使用技巧
1、常用快捷键 ctrlshifto 快速导包 CtrlSpace 内容助理 说明:内容助理。提供对方法,变量,参数,javadoc等得提示,应运在多种场合,总之需要提示的时候可先按此快捷键。注:避免输入法的切换设置与此设置冲突 CtrlShiftSpace 变量提示 Ctrl/ 添加/消除//注释 CtrlShift/ 添加…...
python LeetCode 刷题记录 94
题目 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 代码 递归 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.righ…...
滴滴可观测平台 Metrics 指标实时计算如何实现了又准又省?
在滴滴,可观测平台的 Metrics 数据有一些实时计算的需求,承载这些实时计算需求的是一套又一套的 Flink 任务。之所以会有多套 Flink 任务,是因为每个服务按照其业务观测需要不同的指标计算,也就对应了不同数据处理拓扑。我们尽力抽…...
每天几道Java面试题:IO流(第五天)
目录 第五幕 、第一场)街边 友情提醒 背面试题很枯燥,加入一些戏剧场景故事人物来加深记忆。PS:点击文章目录可直接跳转到文章指定位置。 第五幕 、 第一场)街边 【衣衫褴褛老者,保洁阿姨,面试者老王】 衣衫褴褛老…...
js/axios/umi-request 根据后端返回的二进制流下载文件
type ResponseType {data: Blob;headers: {content-disposition?: string;}; }; // 下载 (创建a标签) export const downloadBlob (response: ResponseType) > {const blob response.data; // 获取响应中的 Blob 数据const contentDisposition response.headers[conten…...
软件评测师之流水线
目录 一.概念二.周期三.执行时间的计算 一.概念 程序在执行时多条指令可以层叠并行的技术。 二.周期 取指→分析→执行 指令执行的各个阶段里面,执行时间最长的为流水线的周期。 三.执行时间的计算 n条指令执行的总时间流水线计算公式:单条指令所需…...
Linux系统编程——网络编程的学习
Linux系统编程学习相关博文 Linux系统编程——文件编程的学习Linux系统编程——进程的学习Linux系统编程——进程间通信的学习Linux系统编程——线程的学习 Linux系统编程——网络编程的学习 一、概述1. TCP/UDP2. 端口号3. 字节序4. Sockt服务器和客户端的开发步骤1. 服务器2…...
Vue中的ref 和$refs的使用
ref 和$refs 作用:利用ref 和$refs可以用于获取dom元素,或组件实例 特点:查找范围→当前组件内(更精确稳定,原生的dom在vue子组件中查找最终也会扫描到父组件) 1. 获取dom 目标标签–添加ref 属性 <…...
Hive【非交互式使用、三种参数配置方式】
前言 今天开始学习 Hive,因为毕竟但凡做个项目基本就避不开用 Hive ,争取这学期结束前做个小点的项目。 第一篇博客内容还是比较少的,环境的搭建配置太琐碎没有写。 Hive 常用使用技巧 交互式使用 就是我们正常的进入 hive 命令行下的使用…...
基于Yolov8的工业小目标缺陷检测(1)
目录 1.工业油污数据集介绍 1.1 小目标定义 1.2 难点 1.3 工业缺陷检测算法介绍 1.3.1 YOLOv8...
Python文件操作和管理指南:打开、读取、写入和管理文件
文章目录 文件(File)打开文件使用 with ... as 语句打开文件读取文件内容读取大文件的方式逐行读取和读取全部行写文件操作文件定位seek()tell() 关闭文件 文件管理获取目录结构获取当前目录切换当前所在目录创建目录删除目录删除文件重命名文件 总结pyt…...
WebGL 用鼠标控制物体旋转
目录 鼠标控制物体旋转 如何实现物体旋转 示例程序(RotateObject.js) 代码详解 示例效果 鼠标控制物体旋转 有时候,WebGL程序需要让用户通过鼠标操作三维物体。这一节来分析示例程序RotateObject,该程序允许用户通过拖动&…...
Spring Boot魔法:简化Java应用的开发与部署
文章目录 什么是Spring Boot?1. 自动配置(Auto-Configuration)2. 独立运行(Standalone)3. 生产就绪(Production Ready)4. 大量的起步依赖(Starter Dependencies) Spring …...
参议院算法Java
Dota2 的世界里有两个阵营: Radiant(天辉)和 Dire(夜魇) Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定,他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项: 禁止一名参议员的权利:参…...
前端提交规范 ESLint + Prettier + husky + lint-staged
如何统一代码风格,规范提交呢? 推荐使用前端规范全家桶 ESLint Prettier husky lint-staged。 eslint (github.com/eslint/esli…)JavaScript 代码检测工具,检测并提示错误或警告信息prettier (github.com/prettier/pr…) 代码自动化格式…...
python实现命令tree的效果
把所有的文档都传到了git上,但是内容过多找起来不方便,突发奇想如果能在readme中,递归列出所有文件同时添加上对应的地址,这样只需要搜索到对应的文件点击就能跳转过去了… 列出文件总得有个显示格式,所以就按照tree的来了… 用python实现命令tree的效果 首先,这是tree的效果…...
Deformable DETR(2020 ICLR)
Deformable DETR(2020 ICLR) detr训练epochs缩小十倍,小目标性能更好 Deformable attention 结合变形卷积的稀疏空间采样和Transformer的关系建模能力 使用多层级特征层特征,不需要使用FPN的设计(直接使用backbone多层级输出&a…...
springboot01
目录 新建Maven工程,什么都不选 pom.xml加上 新建包top.cjz.controller 新建类HelloController 新建类HelloApplication 运行浏览器访问 新建Maven工程,什么都不选 pom.xml加上 <!--springboot工程需要继承的父工程--> <parent…...
虚拟机中window/ubuntu系统如何联网?
以下内容源于网络资源的学习与整理,如有侵权请告知删除。 参考博客 (1)VMware虚拟机中Windows11无法连接网络 (2)图解vmware虚拟机win8无线上网 (3)VMware中VMnet0、VMnet1、VMnet8是什么 &…...
计算物理专题----随机游走实战
计算物理专题----随机游走实战 Problem 1 Implement the 3D random walk 拟合线 自旋的 拟合函数(没有数学意义) 参数:0.627,3.336,0.603,-3.234 自由程满足在一定范围内的均匀分布以标准自由程为单位长度,…...
《思维与智慧》简介及投稿邮箱
《思维与智慧》自1982年创刊,经国家新闻出版署批准,由河北省教育厅主管,河北行知文化传媒有限责任公司主办的益智励 志类大众文化期刊。 《思维与智慧》办刊宗旨是:“开发思维,启迪智慧,滋润心灵”&#x…...
flask+python快速搭建
app.py """APP 入口模块""" from traceback import format_excfrom api_limiter import limiter from flask import Flask, jsonify import loggingfrom controller import api_sql_blueapp Flask(__name__) limiter.init_app(app) app.regist…...
南京网站网站建设学校/专业模板建站
爬虫理解 爬虫我认为其实就是把网上的数据给爬取下来,无外乎就是文本、图片、音频,这三大类,而爬虫种类比较多的的是文本,图片和音频重要的是路径。 爬虫分类 1、爬虫文本 (1)其中最简单的就是 同步获取&…...
网站开发上线ftp怎么用/怎么做网址
editTable.js 提供编辑表格当前行、添加一行、删除当前行的操作,其中可以设置参数,如: operatePos 用于设置放置操作的列,从0开始,-1表示以最后一列作为放置操作的列;(这里的操作包括 编辑当前行…...
网站建立项目步骤/品牌推广与传播怎么写
文章目录 1、什么是 Spring Boot?2、Spring Boot有哪些优点?3、什么是 JavaConfig?4、如何重新加载Spring Boot上的更改,而无需重新启动服务器?5、Spring Boot中的监视器是什么?6、如何在Spring Boot中禁用Actuator端点安全性?7、如何在自定义端口上运行Spring Boot应用…...
广西高端网站建设公司/百度收录提交工具
1、使用printf应当说是类型不安全的。所以才引入了C的流输入输出。 比如: #include "stdint.h" #include "iostream" using namespace std; int main() { int64_t a 1; int b 2; uint32_t uin 1; printf("%p %p\n", &a, &…...
专业网站建设网页推广/百度搜索风云榜排名
lsof -i :80 转载于:https://www.cnblogs.com/iosdev/p/3352067.html...
辽宁做网站/百度推广怎么注册账号
滚动条的组成: ::-webkit-scrollbar //滚动条整体部分 ::-webkit-scrollbar-thumb // 滚动条里面的小方块,能上下左右移动(取决于是垂直滚动条还是水平滚动条) ::-webkit-scrollbar-track //滚动条的轨道…...