• 设计模式Java实战


    文章目录

    一、前置

    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 + "一直在公路上跑");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 符合单一职责的类
    @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 + "在天上非");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 可以先第一版比较粗的类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{
          //---
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10

        正常情况下,用户在调用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);//删除用户
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        public class UserServiceImpl implements UserService{
        		//CRU功能---
        }
        
        public class ManagerServiceImpl implements UserService, ManagerService{
        		//CRUD功能---
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
    • “接口”的含义:可以是接口中的某个方法

      • 不满足接口隔离

        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;
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12

        count函数功能不单一,包含了max、min、count、avg等多个功能。

        按照接口隔离原则:函数的设计功能单一,不要将多个不同的功能逻辑在一个函数中

      • 满足接口隔离

        将count方法拆分为max()、min()、avg()等方法。如何要想使用复合计算则可以直接使用

        LongSummaryStatistics statistics = new LongSummaryStatistics();
        statistics.accept(1);
        statistics.accept(2);
        statistics.accept(3);
        
        • 1
        • 2
        • 3
        • 4

    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");
                }
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
      • 框架控制程序执行

        //这里类似模板设计
        public abstract class BaseTest {
            public boolean needTest();//预留扩展点
        
            public void run() {
                if (needTest()) {
                    System.out.println("do test");
                } else {
                    System.out.println("not do test");
                }
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        public class UserServiceTest extends BaseTest{
            @Override
            public static boolean needTest() {
                return true;
            }
        }
        
        public class SkuServiceTest extends BaseTest{
            @Override
            public static boolean needTest() {
                return false;
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        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);
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
    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方法)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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) {
              //
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      ​ 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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      Notification notification = new Notification();
      notification.sendMessage("msg");
      
      • 1
      • 2
      • 依赖注入

        • B1、B2(MessgaeProductor接口实现类)
        public interface MessgaeProductor {
            public boolean send(String msg);
        }
        
        • 1
        • 2
        • 3
        // B1:短信生产类
        public class SmsProductor implements MessgaeProductor{
            @Override
            public boolean send(String msg) {
                //发送短信
            }
        }
        
        // B2:微信消息生产类
        public class WeChatProductor implements MessgaeProductor{
            @Override
            public boolean send(String msg) {
                //发送微信信息
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 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);
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 外部
        public class Demo {
            public static void main(String[] args) {
                DaxiangProductor messgaeProductor = new DaxiangProductor();//创建对象b
                Notification notification = new Notification(messgaeProductor);//通过构造函数,将b依赖注入A类中
                notification.sendMessage("msg");
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7

    5、依赖注入框架

    • 产生背景

      • 对比依赖注入和非依赖注入,发现new B()的动作,只不过从在A类中new,变成了在更上次外部类Demo中new,还是需要程序员自己实现
      • 一个项目可能有成百上千个类的创建和依赖注入,如果全部都由程序员自己实现,将变得复杂容易出错
      • 对象的创建和依赖注入动作本身和业务不相关,完全可以抽象成框架来自动完成
    • 常见的依赖注入框架:Spring、Google的Guice

    • 作用

      • 参考背景
      • 只需要通过依赖注入框架提供的扩展点,简单配置一下虽有需要创建的类对象、类与类之间的依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等
    • 举例

      public class A{
          @Resource
          private B b;
          public static void main(String[] args) {
              b.send("msg");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

    通过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 map1,Map 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{
          @Resource
          private 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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
    • 符合设计

      /**
       * 公司
       */
      public class Company{
          @Resource
          private 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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

    三、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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 缺点:可以被反射。最完美的方式是枚举,枚举有构造方法,但是通过反射创建对象即newInstance的时候,会判断如果类型是枚举类型会报错

    • 优点:外部类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);
              }
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    线程级别的单例
    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 同一个线程获取到的对象实例是相同的,不同线程获取到的不同。属于多例
    缺点
    • 单侧不友好,全局变量可能会被修改,造成测试结果相互影响问题
    • 其他

    为了保证全局唯一,除了单例外,我们还可以使用工厂模式来实现

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 优化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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 优化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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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()) {
                // 1
                int 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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 优点:

      • 原本实现是:使用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 () {
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 这种方式缺点很明显,当新增了规则rule,则需要再新增else if判断,再添加desc,不满足开闭原则。
      • 如果规则rule很多,则代码充斥着大量的else if分支判断
    实战-Calendar类

    1、创建Calendar实例

     Calendar instance = Calendar.getInstance();
    
    • 1

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    为什么说这是一种简单工厂模式呢?

    因为静态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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 接口
    public interface ApplicationContext {
        Object getBean(String beanId);
    }
    
    • 1
    • 2
    • 3
    • 实现类
    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放入beanFactory
        private void loadBeanDefinitions(String configLocation) {
            InputStream in = this.getClass().getResourceAsStream("/" + configLocation);
            List<BeanDefinition> beanDefinitions = beanConfigParser.parse(in);
            beansFactory.addBeanDefinitions(beanDefinitions);
        }
    
        // 从beanFactory创建bean
        @Override
        public Object getBean(String beanId) {
            return beansFactory.getBean(beanId);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 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;//懒加载false
    
        public boolean isSingleton() {
            return scope.equals(Scope.SINGLETON);
        }
    
        public static enum Scope {
            SINGLETON,
            PROTOTYPE
        }
        
        @Data
        public static class ConstructorArg {
            private boolean isRef;//bean中是否有对象依赖
            private Class type;//对象依赖类型
            private Object arg;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    1、配置解析
    • xml配置文件
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            https://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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 配置类
    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public class RateLimiter {
        private Redis redis;
    }
    
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class Redis {
        private String ipAddress;
        private int port;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 解析配置类并生成BeanDefination放入BeanFactory

    主要就是将is解析成BD

    public interface BeanConfigParser {
        List<BeanDefinition> parse(InputStream inputStream);
    
    }
    
    public class XmlBeanConfigParser implements BeanConfigParser {
        @Override
        public List<BeanDefinition> parse(InputStream inputStream) {
            String content = null;
            return parse(content);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    2、BeanFactory通过反射创建对象
    public class BeansFactory {
        private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
        private ConcurrentHashMap<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();
    
        // 存储BD
        public 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);
            }
        }
    
        // 获取bean
        public Object getBean(String beanId) {
            BeanDefinition beanDefinition = beanDefinitions.get(beanId);
            return createBean(beanDefinition);
        }
    
        // 反射创建bean
        protected 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对象的bean
                    Class[] 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    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;
        }
    
        @ToString
        private 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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    实战@Accessors
    @Data
    @Accessors(chain = true)
    public class UserDemo {
        private String name;
        private Integer age;
    }
    
    UserDemo m = new UserDemo().setName("m").setAge(18);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    和工厂模式的区别

    工厂模式是创建一系列相同类型的对象

    建造者模式是创建一个复杂属性的对象

    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;
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    public interface User {
        void eat();
    }
    
    public class UserImpl implements User{
        @Override
        public void eat() {
            System.out.println("eat");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    StopWatchProxy stopWatchProxy = new StopWatchProxy();
    User user = (User) stopWatchProxy.creatProxy(new UserImpl());
    user.eat();
    
    • 1
    • 2
    • 3

    3.2.2 装饰器模式

    作用

    给原始类添加增强功能

    和代理模式的区别

    代理模式中代理类附加的是跟原始类无关的功能(日志、权限校验等);装饰器类附加的是跟原始类相关的增强功能(原始类是直接读、装饰类增加的功能是缓存读)

    实现
    • 装饰器类(ADeractor)需要跟原始类(A)继承相同的抽象类(AbstractA) | 接口(IA)。
    • 装饰器类(ADeractor)中组合原始类(A)

    可以对A嵌套使用多个装饰器类

    • 接口|抽象类
    public interface IA {
        void f();
    }
    
    • 1
    • 2
    • 3
    • 原始类
    @Service
    public class A implements IA{
        @Override
        public void f() {
            System.out.println("f");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 装饰类
    @Service
    public class ADecorator implements IA{
        @Resource
        private A a;
    
        @Override
        public void f() {
            // 增强
            System.out.println("增强1");
            a.f();
            // 增强
            System.out.println("增强2");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 使用
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = ApplicationLoader.class)
    public class SpringTest {
        @Resource
        private ADecorator aDecorator;
    
        @Test
        public void test() {
            aDecorator.f();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    场景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();
    
    • 1
    • 2
    • 3
    3、前置背景

    3.1 使用装饰者增强后的read

        @Test
        public void test() throws IOException {
            FileInputStream fis = new FileInputStream(new File(""));
            SonBufferedInputStream bis = new SonBufferedInputStream(fis);
            bis.read();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.3 抽象-read

    public abstract class InputStream implements Closeable {
        public abstract int read() throws IOException;
    }
    
    • 1
    • 2
    • 3

    3.3 A-read

    public class FileInputStream extends InputStream{
        public int read() throws IOException {
            return read0();
        }  
        private native int read0() throws IOException;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3.4 ADecorator -read

    public class SonBufferedInputStream extends FatherFilterInputStream {
        public SonBufferedInputStream(FileInputStream fis) {
            super(fis);
        }
    
        public int read() throws IOException {
            // A的read
            fis.read(null, 1, 1);
            // 增强
            return 1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 虽然ADecorator(SonBufferedInputStream)中没有直接定义属性A(FileInputStrteam),但是其父类中定义了
    @Data
    @AllArgsConstructor
    public class FatherFilterInputStream extends InputStream {
        protected FileInputStream fis;
    
        @Override
        public int read() throws IOException {
            return fis.read();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这样当执行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;
    }
    
    • 1
    • 2
    • 3

    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;//fis
        protected FilterInputStream(InputStream in) {
            this.in = in;//this.fis = fis
        }   
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    ==》等效

    public class FilterInputStream extends InputStream {
        protected volatile FileInputStream fis;
        protected FilterInputStream(FisleInputStream fis) {
            this.fis = fis;
        }   
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这样一来,就相当于子类ADecorator(Buffered)中通过继承父类也具有属性A(FileInputStream)

    4.4 bis.read()

    public synchronized int read() throws IOException {
    	fill();//实现
    	return getBufIfOpen()[pos++] & 0xff;
    }
    
    • 1
    • 2
    • 3
    • 4
    private void fill() throws IOException {
        // 增强功能缓存
        byte[] buffer = getBufIfOpen();
        // 调用属性a 的方法
        int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    private InputStream getInIfOpen() throws IOException {
       InputStream input = in;//fis
       return input;
    }
    
    • 1
    • 2
    • 3
    • 4

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这样一来,ADcorator中使用了buffer增强了fis的read

    4.5 父类作用

    完全可以直接将属性A a放入ADecorator中

    public class BufferedInputStream extends InputStream {
        private FileInputStream fis;
        public int read(){
           //
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    为什么采用,将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");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 适配成什么样子,即目标

      除了add方法还是使用Adaptee的,查询和删除都使用适配后的新方法

    public interface Target {
        void queryNew();
        void add();
        void deleteNew();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 适配器
    @Service
    public class Adapter extends Adaptee implements Target{
        @Override
        public void queryNew() {
            super.query();
        }
    
        @Override
        public void deleteNew() {
            if (true) {
                // 执行新的删除逻辑
                System.out.println("delete new");
            } else {
                super.delete();
            }
        }
        
        // 这里类适配器最大的特点就是:
        理论上需要重写add方法,但是由于继承了父类的add,所以可以不用重写add
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 使用
    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = ApplicationLoader.class)
    @Slf4j
    public class SpringTest {
        @Resource
        private Target target;
    
        @Test
        public void test() throws IOException {
            target.queryNew();
            target.add();
            target.deleteNew();
        }
    }
    query
    add
    delete new
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 使用场景

    目标Target和原Adaptee中大部分方法都一样,没有那么多方法需要适配的时候,使用类继承,这样很多方法(比如像add)都无需重写。

    2、对象适配器-组合
    • 适配器
    @Service
    public class AdapterObj implements Target{
        @Resource
        private Adaptee adaptee;
    
        @Override
        public void queryNew() {
            adaptee.query();
            // 再---
        }
    
        @Override
        public void add() {
            adaptee.add();
        }
    
        @Override
        public void deleteNew() {
            if (true) {
                System.out.println("delete new");
            } else {
                adaptee.delete();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 使用场景

    目标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();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      public interface BlackListServiceTarget {
          List<Object> queryBlackListNew(Long poiId, Integer businessType);
      }
      
      • 1
      • 2
      • 3
      @Service
      public class BlackListServiceAdaptor extends BlackListAdaptee implements BlackListServiceTarget{
          @Override
          public 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());
              }
      
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = ApplicationLoader.class)
      @Slf4j
      public class SpringTest {
          @Resource
          private BlackListServiceAdaptor blackListServiceAdaptor;
          
          @Resource
          private BlackListServiceAdaptee blackListServiceAdaptee;
      
          @Test
          public void test() throws IOException {
              // 原本的业务逻辑
              //List result = blackListServiceAdaptee.queryBlackList(323L);
              // 改为:
              List<Object> result = blackListServiceAdaptor.queryBlackListNew(323L, 1);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 优点:风险可控,一旦方法有问题直接切灰度即可。
      2、统一多个类的接口设计
      • 背景

      敏感词过滤。

      • 实现方式

      引入第三方过滤Jar(A:性关键词相关、B:政治关键字相关)

      public class RishManagement{
      	@Resource
      	private A a;
      	@Resource
      	private B b;
      	public String filterSensitiveWords(String text) {
      		String s = a.filterSexyWords(text);//内部默认实现使用xxx代替敏感词
      		return b.filterPoliticalWords(s,"???");//使用???代替敏感词
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 问题

      当需要环境污染过滤C时,这个时候RishManagement会违背开闭原则

      A依赖提供的方法-单个入参:使用默认实现使用xxx代替敏感词

      B依赖提供的方法-两个入参,第二个入参是replace:使用String replace 代替敏感词

      这样接口的调用也不统一,还需要人为指定replace

      • 解决:使用对象适配器模式统一接口设计

        • Adaptee:AFilterAdaptee、BFilterAdaptee都是第三方依赖
        • Target:制定统一的接口设计,否是单入参
        public interface SensitiveWordsFilterTarget {
            String filter(String text);
        }
        
        • 1
        • 2
        • 3
        • A-Adaptor
        @Service
        public class SexyWordsFilterAdapter implements SensitiveWordsFilterTarget{
        
            @Resource
            private AFilterAdaptee aFilterAdaptee;
        
            @Override
            public String filter(String text) {
                return aFilterAdaptee.filterSexyWords(text);
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • B-Adaptor
        @Service
        public class PoliticalWordsFilterAdapter implements SensitiveWordsFilterTarget{
            @Resource
            private BFilterAdaptee bFilterAdaptee;
        
            @Override
            public String filter(String text) {
                return bFilterAdaptee.filterPoliticalWords(text, "???");
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • RiskManager
        @Service
        public class RiskManager {
            @Resource
            private List<SensitiveWordsFilterTarget> sensitiveWordsFilterTargets;
        
            public String filterWords(String text) {
                String temp = text;
                for (SensitiveWordsFilterTarget filterAdaptor : sensitiveWordsFilterTargets) {
                    temp = filterAdaptor.filter(temp);
                }
                return temp;
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13

        这样当需要过滤:环境污染相关关键词引入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;
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 棋局
        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个棋子
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
      • 问题

      如果游戏有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;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

      棋子类

      @Data
      @AllArgsConstructor
      public class ChessPiece {
          /**
           * 享元类(id、color、name)
           */
          private ChessPieceUnit chessPieceUnit;
      
          /**
           * 棋子在棋局上的位置
           */
          private Integer x;
          private Integer y;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      享元工厂类:存取享元类

      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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      棋局类

      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个棋子
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      棋局类在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);
      
      • 1
      • 2

      2、原理

          public static Integer valueOf(int i) {
              if (i >= IntegerCache.low && i <= IntegerCache.high)
                  return IntegerCache.cache[i + (-IntegerCache.low)];
              return new Integer(i);
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 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();
      }
      
      • 1
      • 2
      • 3

      2、具体观察者

      @Service
      public class Observe1 implements Observe{
          @Override
          public void update() {
              System.out.println("观察者1更新");
          }
      }
      
      @Service
      public class Observe2 implements Observe{
          @Override
          public void update() {
              System.out.println("观察者2更新");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      3、被观察

      @Service
      public class Subject {
          @Resource
          private List<Observe> observes;
      
          public void notice() {
              for (Observe observe : observes) {
                  observe.update();
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      使用

      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = ApplicationLoader.class)
      @Slf4j
      public class SpringTest {
          @Resource
          private Subject subject;
          
          @Test
          public void test(){
              subject.notice();
          }
      }
      观察者1更新
      观察者2更新
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      补充:
      如果想让观察者2线执行,再让观察者1执行。可以在观察者2类加上@Order(1)注解

      实战

      1、背景

      用户注册app成功后,给用户发放新人券

      2、实现

      @RestController
      public class UserAppController {
          @Resource
          private RegisterService registerService;
      
          @Resource
          private RegisterSuccessObserve registerSuccessObserve;
      
          public void register(Long iphone, String pwd) {
              // 注册
              boolean success = registerService.register(iphone, pwd);
              if (success) {
                  registerSuccessObserve.issueNewConsumerCoupon(iphone);
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 注册
      @Service
      public class RegisterService {
          public boolean register(Long iphone, String pwd) {
              return true;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 发送新人优惠券
      @Service
      public class RegisterSuccessObserve {
          public void issueNewConsumerCoupon(Long iphone){
              System.out.println("发送新人优惠券");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      3、问题

      如后续新需求:当用户注册App成功后,除了发新人券还需要发送用户注册成功信息的短信给用户

      那么register方法就必须改动了,违背了开闭原则

      4、使用模式重构

      被观察者维护观察者们的信息,一旦注册成功后,将后续一系列动作推送给观察者们即可

      • 抽象观察者
      public interface RegisterSSuccessObserver {
          void update(Long iphone);
      }
      
      • 1
      • 2
      • 3
      • 观察者们
      @Service
      public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{
          @Override
          public void update(Long iphone) {
              System.out.println("发送新人优惠券");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      @Service
      public class SendMsgObserve implements RegisterSSuccessObserver{
          @Override
          public void update(Long iphone) {
              System.out.println("发送注册成功短信");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 被观察者
      @RestController
      public class UserAppControllerSubject {
          @Resource
          private RegisterService registerService;
      
          @Resource
          private 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);
                  }
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

      这样后续,发送新人券改为发送礼品卡,也只需要新增具体观察者即可。对于register方法,满足开闭原则

      5、优化- 异步非阻塞观察者模式

      • 使用guava的EventBus模式实现异步阻塞

      • 被观察者

      @Slf4j
      @RestController
      public class UserAppControllerSubject {
          @Resource
          private RegisterService registerService;
      
          @Resource
          private 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生产者的send
                  eventBus.post(iphone);
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 抽象观察者
      public interface RegisterSSuccessObserver {
          void receive(Long iphone);
      }
      
      • 1
      • 2
      • 3
      • 观察者们
      @Service
      public class SendMsgObserve implements RegisterSSuccessObserver{
          @Override
          @Subscribe
          public void receive(Long iphone) {
              System.out.println("发送注册成功短信");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      @Service
      public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{
          @Override
          @Subscribe
          public void receive(Long iphone) {
              System.out.println("发送新人优惠券");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 使用
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = ApplicationLoader.class)
      @Slf4j
      public class SpringTest {
      
          @Resource
          private UserAppControllerSubject userAppControllerSubject;
      
          @Test
          public void test(){
              userAppControllerSubject.register(186L, "123mjp");
          }
      }
      发送注册成功短信
      发送新人优惠券
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 优化为非阻塞

      如果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("发送新人优惠券");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16

      启动类@EnableAsync

      @EnableAsync
      @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
      public class ApplicationLoader {
          public static void main(String[] args) {
              SpringApplication springApplication = new SpringApplication(ApplicationLoader.class);
              springApplication.run(args);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      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() {
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 子类重写模板中某一个步骤
      public class A extends Template{
          @Override
          protected void m1() {
              
          }
      
          @Override
          protected void m2() {
      
          }
      }
      B类同理
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      场景
      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;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 子类重写框架中某个步骤

      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方法
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      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:doGet
                      doGet(req, resp);
                  } else {
                      //
                  }
              } else if (method.equals(METHOD_POST)) {
                  // 扩展点2:doPost
                  doPost(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);
              }
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 子类重写模板,利用框架的模板模式,实现扩展功能
      public class MyHttpServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              // 自定义
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
              // 自定义
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      3、实战

      1、背景

      父类定义框架方法:根据图形周长、面积,计算周长 / 面积值

      2、实现

      func(){
         // 计算周长
          double perimerter = perimerter();
         // 计算面积
         double area = area();
         m3(perimerter, area);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      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.执行sql
                    ResultSet 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;
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 问题

        如果想执行update语句,则需要定义updateById(Long id)则整个流程还需要再走一遍。毫无复用性而言

      • 抽取版JDBCUtils

        • jdbc.properties
        driver = com.mysql.jdbc.Driver
        
        url = "jdbc:mysql://127.0.0.1:3306/day21"
        
        user = root
        
        password = root
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 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);
                    }
                }
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
        • 53
        • 54
        • 55
        • 56
        • 57
        • 使用
                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);
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 问题:抽象的仍不彻底。仍有大量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;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
              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);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      2、源码解析JDBCTemplate(简化版)
      • 含回调函数的接口
      @FunctionalInterface
      public interface StatementCallback<T> {
          T doInStatement(Statement stm) throws SQLException, DataAccessException;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 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.各种CRUD
                  T result = action.doInStatement(stmt);
                  this.handleWarnings(stmt);
                  // 5.返回执行结果,可能是对象、List<对象>、int
                  var11 = result;
              } catch (SQLException var9) {
                  //6.关闭资源
              } finally {
                  JdbcUtils.closeStatement(stmt);
                  DataSourceUtils.releaseConnection(con, this.getDataSource());
              }
      
              return var11;
          }
      其中execute方法就属于模板方法,其中12356都是通用方法,只有4是根据不同的sql,stm执行对应的语句
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 查询、更新业务类
      @AllArgsConstructor
      public class CrudClass {
          private JdbcTemplate jdbcTemplate;
      
          /**
           * 查询
           * @param sql
           * @return
           */
          public ResultSet query(String sql) {
              return (ResultSet) jdbcTemplate.execute(new StatementCallback<Object>() {
                  @Override
                  public 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;
              });
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 使用查询、更新类
      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");
      
      • 1
      • 2
      • 3
      • 4
      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>() {
                  @Override
                  public Object mapRow(ResultSet resultSet, int i) throws SQLException {
                      return null;
                  }
              });
      这里jdbcTemplate的query方法  
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      调用了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() {
                  }
      
                  @Nullable
                  public 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中使用的匿名内部类作为对象,这里是使用的内部类作为的对象。无论使用哪种方式,入参对象都有需要重写的方法即回调方法
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 执行顺序:

      JDBCTemplate#query -->> JDBCTemplate#execute -->> 执行步骤123 -->> 步骤4QueryStatementCallback#doInStatement#executeQuery -->> 步骤56

      场景2 JVM Hook

      1、背景

              Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("JVM程序关闭时,会调用我");
                  }
              }));
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      JVM提供了B类b即Runtime#addShutdownHook(),其中方法入参为线程对象(thread对象有需要重写的方法即run方法即回调函数)

      2、作用

      JVM程序关闭时,会执行回调函数

      3、分析addShutdownHook

      static {
              Shutdown.add(1 ,
                      false,
                      new Runnable() {
                          public void run() {
                              runHooks();
                          }
                      }
              );
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
          static void runHooks() {
              // 对所有的hook线程执行start
              for (Thread hook : threads) {
                  hook.start();
              }
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      执行顺序

      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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 定义接口实现类
      @Slf4j
      @Service
      public class FailedTask implements Task {
          @Override
          public TaskStatusEnum getTaskStatus() {
              return TaskStatusEnum.FAILED;
          }
      
          @Override
          public String queryTaskStatus(Integer status) {
              return "网路原因计算失败";
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 定义枚举类(和实现类一一对应)
      @Getter
      @RequiredArgsConstructor
      public enum TaskStatusEnum {
          INIT(1,"初始化"),
          FAILED(2,"失败"),
          SUCCESS(3,"成功");
      
          private final Integer value;
          private final String desc;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 面向接口编程
      @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));
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      这里是使用枚举 和 实现类一一对应的方式,达到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();
      }
      
      • 1
      • 2
      • 3
      • 处理器
      @Service
      public class HandleA implements Handle{
          @Override
          public boolean handle() {
              // A处理器无法处理此请求
              boolean handled = false;
              // 自己的业务
              return handled;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 职责链
      public class HandleChain {
          @Resource
          private List handleList;
      
          public void doXxx() {
              for (Handle handle : handleList) {
                  boolean canDeal = handle.handle();
                  if (canDeal) {
                      break;
                  }
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      2、职责链模式2:无终止

      解释:职责链上的所有处理器都会依次处理此请求

      • 抽象处理器
      public interface Handle {
          void handle();
      }
      
      • 1
      • 2
      • 3
      • 处理器
      @Service
      public class HandleA implements Handle{
          @Override
          public void handle() {
              // 处理请求
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 职责链
      public class HandleChain {
          @Resource
          private List<Handle> handleList;
      
          public void doXxx() {
              for (Handle handle : handleList) {
                  handle.handle();
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      实战:敏感词过滤

      1、背景

      在文本发布时,如果text中如果含有性、政治、广告相关的关键字,则会被处理

      处理方式一:直接禁止本次文本发布

      处理方式二:过滤关键字违规词后,再发布

      2、处理方式一:终止型责任链模式

      • 抽象处理器
      public interface SensitiveWordFilterHandle {
          boolean doFilter(String text);
      }
      
      • 1
      • 2
      • 3
      • 处理器
      @Service
      public class SexyWordFilterHandle implements SensitiveWordFilterHandle{
          @Override
          public boolean doFilter(String text) {
              // 如果text中含有x、x、x词,则任务含有了性相关的敏感词。则会终止职责链
              if (true) {
                  return true;
              }
              return false;
          }
      }
      其他处理器类似
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 职责链
      public class SensitiveFilterHandleChain {
          @Resource
          List<SensitiveWordFilterHandle> sensitiveWordFilterHandles;
          
          public void legalText(String text) {
              for (SensitiveWordFilterHandle handle : sensitiveWordFilterHandles) {
                  boolean legal = handle.doFilter(text);
                  if (legal) {
                      // 允许发布
                  } else {
                      // 禁止
                  }
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      3、处理方式二:无终止型责任链模式

      • 抽象处理器
      public interface SensitiveWordFilterHandle {
          String doFilter(String text);
      }
      
      • 1
      • 2
      • 3
      • 处理器
      @Service
      public class SexyWordFilterHandle implements SensitiveWordFilterHandle{
          @Override
          public String doFilter(String text) {
          	// 如果text包含了a、b、c等性相关词,将这些词替换成xxx
              if (true) {
                  return text.replace(abc, "xxx");
              }
              return text;
          }
      }
      其他处理器类似
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 职责链
      public class SensitiveFilterHandleChain {
          @Resource
          List<SensitiveWordFilterHandle> sensitiveWordFilterHandles;
          
          public String legalText(String text) {
              String temp = text;
              for (SensitiveWordFilterHandle handle : sensitiveWordFilterHandles) {
                  temp = handle.doFilter(temp);
              }
              return temp;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      补充:
      如果想让某个职责链实现类先执行,则可以在类上加上@Order(数字),数字越小优先级越高

      1、接口

      public interface StockUp {
          void make();
      
          void calculate();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      2、实现类

      • 类1
      @Service
      @Order(2)
      public class Xt implements StockUp{
          @Override
          public void make() {
              System.out.println("xt make");
          }
      
          @Override
          public void calculate() {
              System.out.println("xt calculate");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 类2
      @Service
      @Order(1)
      public class Trans implements StockUp {
          @Override
          public void make() {
              System.out.println("trans make");
          }
          
          @Override
          public void calculate() {
              System.out.println("trans calculate");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      3、场景

      • 问题1:假如需求变动,要求Trans转运备货类型,不需要执行make方法,

        如果Trans中仅有make方法,则直接删除Trans类即可。

      但Trans中还有calculate计算方法,所以只能将其make方法内容置空。

      • 问题2:

      在问题1的基础上,假如除了责任链模式用到了Trans#make(),其它地方也用到了Trans#make
      这样就不能清空make方法,因为别处也在用

      public interface StockUp {
          /**
           * 打标逻辑
           */
          void make();
      
          /**
           * 计算逻辑
           */
          void calculate();
      
          /**
           * 参加截单逻辑
           * @return
           */
          default boolean partCutOrder() {
              return true;
          }
      
          /**
           * 参加退货逻辑
           * @return
           */
          default boolean partReturn() {
              return true;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      @Service
      @Order(2)
      public class Xt implements StockUp{
          @Override
          public void make() {
              System.out.println("xt make");
          }
      
          @Override
          public void calculate() {
              System.out.println("xt calculate");
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      @Service
      @Order(1)
      public class Trans implements StockUp{
          @Override
          public void make() {
              System.out.println("trans make");
          }
          @Override
          public void calculate() {
              System.out.println("trans calculate");
          }
      
          public boolean partCutOrder() {
              return false;
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 使用,提前过滤留下本次需要的Service
      @Resource
          private List<StockUp> stockUpList;
      
          private List<StockUp> cutOrderList = Lists.newArrayList();
      
      	// 其他地方调用make方法正常,不影响
          @Resource
          private Trans trans;
      
          @PostConstruct
          public void initCutOrderService() {
              cutOrderList = stockUpList.stream().filter(StockUp::partCutOrder).collect(Collectors.toList());
          }
      
          @Test
          public void test(){
              // 参加截单的
              for (StockUp stockUp : cutOrderList) {
                  stockUp.make();
              }
              // 正常执行
              trans.make();
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      场景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() {}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 处理器
      public class FilterHandler implements Filter {
          @Override
          public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
              // 处理req
      
              // 业务
              chain.doFilter(request, response);
      
              // 处理resp
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 职责链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)方法
           */
          @Override
          public 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 one
              if (pos < n) {
                  // 获取职责链上的下一个Filter处理器
                  ApplicationFilterConfig filterConfig = filters[pos++];
                  try {
                      // 这里就有我们具体的处理器
                      Filter filter = filterConfig.getFilter();
                      filter.doFilter(request, response, this);
              	}
              }
            
              
            // 添加过滤器处理器
            void addFilter(ApplicationFilterConfig filterConfig) {
            }
      
         }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      场景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 {
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 拦截器处理器
      public class MyHandlerInterceptor implements HandlerInterceptor {
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                  throws Exception {
              // 为false会拦截请求,true会放行
              // 业务逻辑
              // eg:根据req内容查询,请求是否合法、用户是否存在等。如果不满足,则请求被拦截掉,return false
              return 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
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 职责链
      public class HandlerExecutionChain {
          // 职责链数组
      	private HandlerInterceptor[] interceptors;
      
      
      
      	public void addInterceptor(HandlerInterceptor interceptor) {
      		initInterceptorList().add(interceptor);
      	}
      
      	// 拦截req
      	boolean 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];
                      // 拦截req
      				if (!interceptor.preHandle(request, response, this.handler)) {
      					triggerAfterCompletion(request, response, null);
      					return false;
      				}
      				this.interceptorIndex = i;
      			}
      		}
      		return true;
      	}
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      场景3:自定义职责链
      • 抽象处理器
      public interface DefinedHandler {
          void doHandle(xxxReq req);
          
          void preHandle(Object req);
          
          // 执行顺序,值越小,优先级越高
          default Integer executeOrder() {
              return Integer.MIN_VALUE;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 处理器
      @Service
      public class LockStatusHandle implements DefinedHandler{
          @Override
          public void preHandle(Object req) {
              // 处理req:比如查询某些数据
          }
      
          @Override
          public void postHandle(Object req) {
              // 处理resp:比如根据req过滤留下符合的数据
          }
      
          public Integer executeOrder() {
              return 10;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      @Service
      public class AIQtyHandle implements DefinedHandler{
          @Override
          public void preHandle(Object req) {
              // 处理req:比如查询某些数据
          }
      
          @Override
          public void postHandle(Object req) {
              // 处理resp:比如根据req过滤留下符合的数据
          }
      
          public Integer executeOrder() {
              return 10;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      @Service
      public class OverStockHandle implements DefinedHandler{
          @Override
          public void preHandle(Object req) {
              // 处理req:比如查询某些数据
          }
      
          @Override
          public void postHandle(Object req) {
              // 处理resp:比如根据req过滤留下符合的数据
          }
      
          public Integer executeOrder() {
              return 20;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 职责链
      @Component
      public class HandlerChain {
          @Resource
          private List<DefinedHandler> definedHandlerList;
          
          private Map<Integer, List<DefinedHandler>> orderAndHandlerMap;
          
          @PostConstruct
          public 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;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 使用
      @RunWith(SpringRunner.class)
      @SpringBootTest(classes = ApplicationLoader.class)
      @Slf4j
      public class SpringTest {
      
          @Resource
          private HandlerChain handlerChain;
      
          private ExecutorService threadPool = Executors.newFixedThreadPool(20);
           
          
          @Test
          public 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());
              });
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 分析

      传统的职责链,例如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 {
      }
      
      • 1
      • 2

      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());
      
              // 动作Action
              System.out.println("更细订单状态为:已取消");
          }
      
          /**
           * 事件Event:用户付款了
           */
          public void clickPay(OrderStatusMachine machine) {
              System.out.println("===Event:用户付款了===");
              // 状态Status: 待付款 -> 待发货
              machine.setOrderStatus(WaitDeliver.getInstance());
      
              // 动作Action
              System.out.println("1、更细订单状态为:待发货");
              System.out.println("2、将钱存入支付宝");
              System.out.println("3、淘宝信息提醒:您的地址信息为xxx请核对");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 取消付款
      /**
       * 取消状态类
       * 此状态为终态,不会再转移:
       */
      public class CancelPay implements OrderStatus {
          private static final CancelPay instance = new CancelPay();
          private CancelPay(){}
          public static CancelPay getInstance() {
              return instance;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 待发货
      /**
       * 待发货状态类
       * 此状态类状态的可能转移为:
       *      待发货 -> 退货退款(用户点击了申请退货退款按钮)
       *      待发货 -> 待收货(仓库提交发货)
       */
      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、发送信息给用户:您的包裹正在快马加鞭的赶来");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 待收货
      /**
       * 待收货状态类
       * 此状态类状态的可能转移为:
       *      待收货 -> 退货退款(用户点击了申请退货退款按钮)
       *      待收货 -> 待评价(确认收货)
       */
      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、发送信息给用户:亲,麻烦评价下商品");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 退货退款
      /**
       * 退货退款状态类
       * 此状态为终态,不会再转移:
       */
      public class Refund implements OrderStatus{
          private static final Refund instance = new Refund();
          private Refund(){}
          public static Refund getInstance() {
              return instance;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 待评价
      /**
       * 待评价状态类
       * 此状态类状态的可能转移为:
       *      待评价 -> 订单完成(用户评价了商品)
       */
      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、用户的积分增加");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 订单完成
      /**
       * 完成状态类
       * 此状态为终态,不会再转移:
       */
      public class Finish implements OrderStatus{
      
          private static final Finish instance = new Finish();
          private Finish(){}
          public static Finish getInstance() {
              return instance;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61

      2.4 使用状态机

      @RunWith(MockitoJUnitRunner.class)
      @Slf4j
      public class BaseTest {
          @Test
          public 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==================");
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      3、总结

      3.1 状态机中

          public void cancelPay() {
              ((WaitPay) this.orderStatus).cancelPay(this);
          }
      
      • 1
      • 2
      • 3

      如果不这样写,那么对于原状态为其他的比如待发货状态,不会有任何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、发送信息给用户:您的包裹正在快马加鞭的赶来");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42

      这样写的好处是:只有原状态为:待付款状态,才会有Event取消付款,才会有后续Action动作。其他状态类不需要关注,内部也不需要定义此方法

      3.2 单例

      private static final WaitDeliver instance = new WaitDeliver();
      
      • 1

      防止反复创建

      3.3 扩展

      对于像WaitPay状态类,当用户付款了后,状态变为:待发货。然后有好几个总做要去做

          /**
           * 事件Event:用户付款了
           */
          public void clickPay(OrderStatusMachine machine) {
              System.out.println("===Event:用户付款了===");
              // 状态Status: 待付款 -> 待发货
              machine.setOrderStatus(WaitDeliver.getInstance());
      
              // 动作Action
              System.out.println("1、更细订单状态为:待发货");
              System.out.println("2、将钱存入支付宝");
              System.out.println("3、淘宝信息提醒:您的地址信息为xxx请核对");
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      当然这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,需要告警

      • 使用
          @Test
          public 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);
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 解释器接口
      public interface Expression {
          boolean intercept(Map<String , Long> map);
      }
      
      • 1
      • 2
      • 3
      • 大于解释器
      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;
          }
      
          @Override
          public boolean intercept(Map<String, Long> map) {
              if (!map.containsKey(key)) {
                  return false;
              }
              Long val = map.get(key);
              return val > value;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 小于解释器
      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;
          }
      
          @Override
          public boolean intercept(Map<String, Long> map) {
              if (!map.containsKey(key)) {
                  return false;
              }
              Long val = map.get(key);
              return val < value;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 等于解释器
      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;
          }
      
          @Override
          public boolean intercept(Map<String, Long> map) {
              if (!map.containsKey(key)) {
                  return false;
              }
              Long val = map.get(key);
              return Objects.equals(val, value);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • &&解释器
      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);
          }
          @Override
          public boolean intercept(Map<String, Long> map) {
              for (Expression expression : expressionList) {
                  if (expression.intercept(map)) {
                      System.out.println(expression + "符合表达式");
                  } else {
                      return false;
                  }
              }
              return true;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • ||解释器
      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);
          }
          @Override
          public boolean intercept(Map<String, Long> map) {
              for (Expression expression : expressionList) {
                  if (expression.intercept(map)) {
                      return true;
                  }
              }
              return false;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 解释器工厂类
      @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();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      场景

      编译器、规则引擎(这里举例阿里的规则引擎QLExpress)、正则表达式

      1、地址:https://github.com/alibaba/QLExpress

              <dependency>
                  <groupId>com.alibaba</groupId>
                  <artifactId>QLExpress</artifactId>
                  <version>3.3.2</version>
              </dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5

      2、 eg1:

          @Test
          public 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"; //true
              Object res = runner.execute(express, context, null, true, false);
              System.out.println(res);
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      eg2:

          @Test
          public 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);//false
      
              String 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();
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 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);
          }
      
      • 1
      • 2
      • 3

      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
      • 代码的可测试性,从侧面也反映出代码的设计是否合理
    • 相关阅读:
      立体相机镜面重建(一)镜面标定
      C++初阶 | [三] 类和对象(中)
      LLVM IR 构建 分析 转换 优化 IRBuilder Pass AI编译器后端代码生成
      vector的模拟实现
      景区门票小程序怎么制作?
      selenium下载安装 -- 使用谷歌驱动碰到的问题
      儿科医生教你做好“过敏三级预防”,孩子过敏不要怕
      基于SpringBoot的医疗预约服务管理系统
      MySQL之事务、存储引擎、索引
      RabbitMQ: 死信队列
    • 原文地址:https://blog.csdn.net/tmax52HZ/article/details/132944506