ppt模板免费下载网站有哪些,建设一个商城网站的成本,图书馆网站信息化建设,wordpress音乐播放器代码文章目录 一、前置1.1 目的1.2 面向对象1.3 接口和抽象类 二、七大设计原则2.1 单一职责2.2 接口隔离原则2.3 依赖倒转原则2.4 里氏替换原则2.5 开闭原则2.6 不要重复原则2.7 迪米特最少知道法则 三、23种设计模式3.1创建型#xff1a;创建对象3.1.1 单例模式定义最佳实践场景… 文章目录 一、前置1.1 目的1.2 面向对象1.3 接口和抽象类 二、七大设计原则2.1 单一职责2.2 接口隔离原则2.3 依赖倒转原则2.4 里氏替换原则2.5 开闭原则2.6 不要重复原则2.7 迪米特最少知道法则 三、23种设计模式3.1创建型创建对象3.1.1 单例模式定义最佳实践场景线程级别的单例缺点 3.1.2 工厂模式简单工厂场景1解析配置场景2解析表达式实战-Calendar类总结 3.1.3DI依赖注入定义和简单工厂区别手动实现一个DI容器使用1、配置解析2、BeanFactory通过反射创建对象3、对象生命周期管理 3.1.4 Builder建造者模式和set以及构造器的区别建造者模式创建对象实战Accessors和工厂模式的区别 3.2结构型类或对象的组合3.2.1Proxy代理模式定义场景动态代理接口实现 3.2.2 装饰器模式作用和代理模式的区别实现场景IO流1、结构2、源码结构3、前置背景4、源码解析 3.2.3 适配器模式定义实现1、类适配器-继承2、对象适配器-组合 场景1、兼容老接口2、统一多个类的接口设计 3.2.4 享元模式定义作用实战场景Integer实战 总结 3.3行为型3.3.1 观察者模式定义作用和生产者消费者的区别和 订阅-发布模式的区别被观察者-观察者模板实战 3.3.2 模板模式定义作用使用模板场景1、复用2、扩展框架3、实战 3.3.3回调函数定义作用场景11、背景JDBCTemplate的演变2、源码解析JDBCTemplate简化版3、分析 场景2 JVM Hook 3.3.4策略模式实战1背景 实战2实战3 3.3.5 职责链模式定义作用模板1、职责链模式1带终止2、职责链模式2无终止 实战敏感词过滤场景1Servlet-Filter作用作用域解析 场景2MVC-Interceptor作用同Filter作用域和Servlet的Filter区别解析 场景3自定义职责链 3.3.6 状态模式定义作用使用场景实战-电商订单1、状态机2、实现3、总结 3.3.7 解释器模式定义作用实战背景分析实现 场景 3.3.8 中介模式定义作用场景背景中介模式 四、其它4.1 系统设计4.1.1合理将功能划分到不同模块4.1.2模块之间的交互关系4.1.3业务开发 4.2 重构4.2.1what4.1.2why4.1.3 context4.1.4 when4.1.5 如何避免重构出问题 4.2 重构4.2.1what4.2.2why4.2.3 context4.2.4 when4.2.5 如何避免重构出问题 一、前置
1.1 目的
1、写出好的代码个人认为依次重要程度为 健壮性 个人理解为最重要的之一好的代码首先是无bug代码 代码中常见可能引起问题的点重要程度不分先后 性能并发会不会存在问题并发度大小大了可能对下游压力过大提前对其好SLA是否需要异步处理写OP日志、推送消息等 降级、兜底下游接口拿不到数据或不可用产品侧是否有兜底数据、技术侧是否有兜底方案 限流对上游是否需要限流保护我们的服务 数据量评估数据量增大甚至极端场景下会不会有慢查询索引是否合理 幂等消息是否可能重复 一致性上下游的状态是否一致eg服务A将任务状态置为终态如果上游系统B有业务动作依赖A任务的状态那A也要告诉上游B任务终态了。否则上游B发现下游A的任务一直未终态他们可能有自己的重试机制等 主从延时写完读 接口异常是否强依赖、重试等 中间件异常是否强依赖redis如果redis短期不可用业务上是否可以降级跳过redis的卡控逻辑。使用中间件生成单据号如果中间件短时间不可用是否有备用方案生成单据号等 边界条件while循环为了防止死循环结合业务要设置最大的循环次数终止条件最好是或防止并发时跳过了日期判断或者日期作为查询条件也要特别注意集合get(0)首先是npe其次是集合的所有元素是否都一致不一致就不能拿第一个元素的内容去赋值switch要有defaultif要和else if else if最好加上条件避免落到if else中 数据库字段类型是否大小写敏感、大小是否需要截断、update是否需要updateSelective、查询in为空则可能查询全量数据 参数校验api接口一定不要相信传参 事务失效问题rpc写和本地写、以及其它 单位问题精度、元分。kg和mg等 npe问题常见可能造成npe的点 锁的释放超时时间是否设定、异常流程是否释放锁 /0 list转maplist的字段可能重复作为map的key则可能Duplicate key异常 可读性 代码终究是给人读的主要是代码的整洁之道那些内容 可复用性 不写重复的代码高内聚低耦合模块内部内聚模块之间解耦eg网关层封装的查询方法应该做的事项有 封装并发调用方只需要传入所有的skuIdListgateway自己按照200、200拆分sku并发查询 返回数据要自定义如果rpc调用的返回对象为集合List其中A有很多的属性可能我们并不关心那么gateway就需要自己定义对象DTO属性只有几个我们关心的字段即可 可扩展性 设计模式的内容eg需求需要对规则进行新增、删除只需要调整枚举类不需要修改现有代码逻辑 兼容性尤其是字段调整要遵循新增而非删除egskuId变为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和2createTokenString url Long useIdString pwd6isExpireedLong ts7matchString token ApiUrl 3buildUrl 4getTokenFromUrl(String url) getUseIdFromUrl(String url) getTsFromUrl(String url) 定义类和类之间的交互关系继承、实现、聚合、组合、依赖等 思考 我理解的面向对象编程就好比要外出旅游将这个需求分为衣食住行四个模块 衣带什么衣服上衣、裤子等行是坐火车还是飞机如果是坐火车如何去火车站等住是住酒店还是民宿住的地方和旅游景点的远近、交通的便利等 就是在未出发之前衣食住行模块都想好方法也想好先公交、再火车类之间如何衔接对应类之间的关系。然后按照这些去旅游。 面向过程编程则是准备去旅游。 行模块早上起来看有飞机航班么没有则坐火车最近的一趟火车出发。住到了目的地随便找个地方先住下来 类似这种我理解为面向过程。
1.3 接口和抽象类
1、什么时候使用接口
需要将接口和实现相分离封住不稳定的实现暴露稳定的接口上游系统面向接口编程这样接口实现发生变化时上游系统代码基本不需要改动。降低了代码的耦合性提升了扩展性
2、要用接口和抽象类时选择哪个
要表示is a三角形是图形圆形是图形—同时目的是为了解决代码的复用性则使用抽象类表示has a并且为了解耦而非代码的复用则使用接口
3、基于接口编程注意事项
函数名不要暴露实现细节否则后续需求变化名称可能词不达意甚至描述有误。所以尽量抽象。eguploadPicture而非uploadPicture2Yun封装具体的实现细节。
egsku查询算法值不同的sku对应的供货链路不同不同的供货链路对应查询不同的算法类型值则queryAlQty(Integer supplyType,Long skuId)不如
queryAlQty(Long skuId)内部封装了查询供货链路。
二、七大设计原则
设计模式本身的原则
2.1 单一职责
1、概念一个类只负责一项职责。如果负责了多个就需要拆分成多个类
2、举例OrderRepository中不要涉及对SkuDO的CRUD
3、作用
不会使一个类过于庞大可维护性改了Order相关内容不会影响Sku相关内容否则可能会相影响高内聚提高代码缩小功能改动导致的代码改动范围。
4、编码实现
不符合单一职责的代码
原因显然飞机不能一直在公路上跑。应该拆分为陆、海、空三个单一职责的交通工具类
Data
public class Single {public static void main(String[] args) {Vehicle vehicle new Vehicle();vehicle.run(汽车);vehicle.run(飞机);}static class Vehicle{public void run(String vehicle) {System.out.println(vehicle 一直在公路上跑);}}
}符合单一职责的类
Data
public class Single {public static void main(String[] args) {Vehicle1 vehicle1 new Vehicle1();vehicle1.run(汽车);Vehicle2 vehicle2 new Vehicle2();vehicle2.run(飞机);}static class VehicleRoad{public void run(String vehicle) {System.out.println(vehicle 在公路上跑);}}static class VehicleAir{public void run(String vehicle) {System.out.println(vehicle 在天上非);}}
}5、思考
逆序计划流程 1触发建单 2【触发oih 落sku 计算可退量 合单并下发回掉】
做了RDC退、协同退、PC退之后发现流程2是完成可以复用。但是流程1不同的触发源尤其是RDC退和PC退很多代码都写在一个类中实际上违背了单一职责。改动PC退的流程1代码有可能影响RDC退。
6、如何定义一个类以及如何根据单一职责判断一个类是否需要拆分
public class UserInfo {private Long userId;private String name;private Long createTime;private Long lastLoginTime;private String email;private Long phoneNo;private String province;private String city;private String region;private String detailAddress;
}可以先第一版比较粗的类UserInfo。随着业务迭代持续重构比如后续有了物流业务则用户的地址信息可以抽取出来独立类 比如后续有了论坛、金融等业务需要对用户进行登录校验则可以将email、phone拆出来独立类 代码属性过多、代码的行数过多200、代码的方法过多则需要考虑是否对类进行拆分 依赖的其它类过多。为了低耦合考虑是否拆 私有方法过多为了复用性可以抽取出来放到新类中作为public方法 类已经找不到合适的词来形容了职责定义已经不清晰了可拆 类中大量的方法都在对某几个属性进行操作则可以考虑将这几个属性抽取出来单独成一个类
2.2 接口隔离原则
1、概念接口的调用者不应该被强迫依赖它不需要的接口
2、作用
提高灵活性:一个类是可以同时实现多个接口的所以将一个臃肿的接口分割为若干个小接口通过小接口的不同组合可以满足更多的需求高内聚
3、满足接口隔离原则code 接口含义一个接口中的多个方法 不满足接口隔离 public interface UserService{boolean register(String phone, String pwd);boolean login(String phone, String pwd);UserInfo getUserInfo(String phone);boolean deleteUser(String phone, String pwd);//删除用户
}public UserServiceImpl implements UserService{//---
}正常情况下用户在调用UserService接口中的方法时一般不会也不允许调用deleteUser方法只会用到CRU功能。 根据接口隔离原则接口的调用者不应该强迫依赖他不需要的接口即deleteUser方法 满足接口隔离 后端管理系统ManagerUserImpl才需要CRUD功能 public interface UserService{boolean register(String phone, String pwd);boolean login(String phone, String pwd);UserInfo getUserInfo(String phone);
}public interface ManagerService{boolean deleteUser(String phone, String pwd);//删除用户
}public class UserServiceImpl implements UserService{//CRU功能---
}public class ManagerServiceImpl implements UserService, ManagerService{//CRUD功能---
}“接口”的含义可以是接口中的某个方法 不满足接口隔离 public class Statistics {private Long max;private Long min;private double avg;private Integer count;public Statistics count(Collection data) {Statistics statistics new Statistics();// 计算逻辑return statistics;}
}count函数功能不单一包含了max、min、count、avg等多个功能。 按照接口隔离原则函数的设计功能单一不要将多个不同的功能逻辑在一个函数中 满足接口隔离 将count方法拆分为max()、min()、avg()等方法。如何要想使用复合计算则可以直接使用 LongSummaryStatistics statistics new LongSummaryStatistics();
statistics.accept(1);
statistics.accept(2);
statistics.accept(3);2.3 依赖倒转原则
1、概念高层模块调用者不要依赖低层模块被调用者二者应该通过抽象接口)互相依赖
egTomcat高层模块编程的Web应用程序低层模块只需要部署在Tomcat容器下便可以被Tomcat调用运行。
Tomcat不依赖Web应用程序只要Web应用程序满足Servlet接口规范那么无论你是啥Web应用程序都可以在Tomcat上运行。
Tomcat和Web应用程序通过Servlet接口互相依赖
2、作用通用性好、扩展性好
3、控制翻转IOC 定义原本是程序员自己控制整个程序的执行使用框架之后框架来控制程序流程。流程的控制权从程序员反转到了框架 举例 程序员控制程序执行 public class UserServiceTest {public static boolean needTest() {return true;}public static void main(String[] args) {if (needTest()) {System.out.println(do test);} else {System.out.println(not do test);}}
}public class SkuServiceTest {public static boolean needTest() {return false;}public static void main(String[] args) {if (needTest()) {System.out.println(do test);} else {System.out.println(not do test);}}
}框架控制程序执行 //这里类似模板设计
public abstract class BaseTest {public boolean needTest();//预留扩展点public void run() {if (needTest()) {System.out.println(do test);} else {System.out.println(not do test);}}
}public class UserServiceTest extends BaseTest{Overridepublic static boolean needTest() {return true;}
}public class SkuServiceTest extends BaseTest{Overridepublic static boolean needTest() {return false;}
}public class ApplicationLoader {public static void main(String[] args) {SpringApplication.run(ApplicationLoader.class, args);private static final ListBaseTest LIST new ArrayList();for (BaseTest test : LIST) {test.run()}}public void register(BaseTest test) {LIST.add(test);}
}ApplicationLoader.register(new UserServiceTest());
ApplicationLoader.register(new UserServiceTest());1、在BaseTest预约扩展点
2、不同的Test类实现自己业务相关的功能是否needTest不需要再写用于执行流程的main函数了
3、将不同的Test类添加到ApplicationLoader
4、在ApplicationLoader启动的时候执行main函数会遍历执行所有Test的run方法
程序的执行main函数执行由程序员控制写在不同Test中反转到框架控制统一register到Application它启动的时候会执行所有Test的run方法4、依赖注入DI 定义 A类中使用B类不同new B()的方法创建b而是将B在外部创建好后通过new A(b)构造函数、函数参数func(B b)、set属性等方式传递注入给A类使用 和控制反转的关系 控制反转不是具体的实现技巧而是一种用于指导框架设计的思想。而DI则是具体的编码技巧是IOC的具体实现 依赖注入 和 非依赖注入 背景 Notification类负责将商品的促销、验证码消息等给用户。它依赖MessageProductor生产者类发送消息 非依赖注入 B类MessageProductor public class MessgaeProductor {public boolean send(String msg) {//}
} A类Notification public class Notification {private MessgaeProductor messgaeProductor;public Notification() {this.messgaeProductor new MessgaeProductor();//A类中使用B类通过new方式在A类中创建}public void sendMessage(String msg) {this.messgaeProductor.send(msg);}
}Notification notification new Notification();
notification.sendMessage(msg);依赖注入 B1、B2MessgaeProductor接口实现类 public interface MessgaeProductor {public boolean send(String msg);
}// B1:短信生产类
public class SmsProductor implements MessgaeProductor{Overridepublic boolean send(String msg) {//发送短信}
}// B2:微信消息生产类
public class WeChatProductor implements MessgaeProductor{Overridepublic boolean send(String msg) {//发送微信信息}
}ANotification类 public class Notification {private MessgaeProductor messgaeProductor;public Notification(MessgaeProductor messgaeProductor) {this.messgaeProductor messgaeProductor;//A类中使用B类通过构造器将b注入A中}public void sendMessage(String msg) {this.messgaeProductor.send(msg);}
}外部 public class Demo {public static void main(String[] args) {DaxiangProductor messgaeProductor new DaxiangProductor();//创建对象bNotification notification new Notification(messgaeProductor);//通过构造函数将b依赖注入A类中notification.sendMessage(msg);}
}5、依赖注入框架 产生背景 对比依赖注入和非依赖注入发现new B()的动作只不过从在A类中new变成了在更上次外部类Demo中new还是需要程序员自己实现一个项目可能有成百上千个类的创建和依赖注入如果全部都由程序员自己实现将变得复杂容易出错对象的创建和依赖注入动作本身和业务不相关完全可以抽象成框架来自动完成 常见的依赖注入框架Spring、Google的Guice 作用 参考背景只需要通过依赖注入框架提供的扩展点简单配置一下虽有需要创建的类对象、类与类之间的依赖关系就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等 举例 public class A{Resourceprivate B b;public static void main(String[] args) {b.send(msg);}
}通过Spring框架提供的扩展点-后置处理器Resource private B b就可以实现B的创建和生命周期的管理同时后置处理器通过set的将b注入A类中
2.4 里氏替换原则
1、概念 子类对象能够替换程序中父类对象出现的任何地方并且保证原来的逻辑行为不变且正确性不被破坏。
一句话子类重写父类的方法不要改变原有方法的逻辑方法声明、输入、输出、对异常的处理等约定
2、作用指导子类如何设计不改变父类的逻辑
3、子类重写父类方法时常见的违背里氏替换原则的场景有 违背父类的输入 父类输入Integer是整数子类输入Integer要求是正整数 违背父类的输出 父类catch代码块中return的是空集合子类重写方法中catch块中return的是null 违背父类方法的声明 父类sortBySkuId查询结果按照skuId排序。子类sortBySkuId查询结果按照实时销量排序了 违背父类异常的处理 父类valid参数时不满足时抛出的是ArguementNullException。子类抛出的是illeagalException
4、思考
2.5 开闭原则
1、概念一个模块、类、方法对修改关闭对扩展开放。添加一个新功能应该是新增代码而非 修改代码。
补充有的时候新增功能是改变了类对于类而言是被改变了但是对于方法来说没有改变也满足开闭。
2、作用提升代码的扩展性23种设计模式的目的都是为了满足开闭原则。尽量让修改更为集中、给小、更上层。核心的、复杂的逻辑尽量不修改少修改
3、编码可以参考RuleExpressHelper通过遍历规则枚举类将枚举类code、desc提前put至MapString,Integer map1,MapInteger,String map2其内部提供了将los转换lo、s再通过map1转换为[1,2]将[1,2]转换为los将los转换为[“小于OR可以修改”,“锁库不可更改”]等规则表示之间的转换方法。这样新增规则时只需要对应新增枚举类即可
4、如何做到满足开闭原则 业务层面扩展意识、抽象意识很重要 多想下这块逻辑后续会有哪些需求变更设计代码结构的时候可以提前预留好扩展点以便将来改动小新的代码可灵活的插入 eg1规则中心现有n个规则如果后续新增规则是不是不改变现有代码逻辑实现仅仅通过新增枚举类就可以实现页面的CRUD。 eg2退供下发执行后续会不会有逆向调拨下发执行。可以提前设计下发逻辑抽象出接口。 但是对于未来不确定的功能点当下没必要过度设计后续持续重构即可 技术层面提升代码的可扩展性即基于接口编程、设计原则、设计模式策略、模板、状态、装饰着、职责链等 感触最深的就是策略模式定义行为接口方法新增功能只需要新增对应的新实现不需要改动原本的行为实现
2.6 不要重复原则
1、常见重复场景 实现逻辑重复代码完全一样。 eg可能是不同的人开发不知道有这个功能的代码场景的是枚举定义一样、网关定义一样 功能语义重复 代码不一样了但是两个函数是一样功能。 egcheckAddressIsVali()和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模板模式通用的都抽取到父类不同的继承实现自己具体内容 多态使用多态可以动态的复用一段代码的部分逻辑。 egCollection接口的通用方法集合都可以使用 抽象越抽象、越不依赖具体实现的代码越容易复用 eg入参为List复用性高于ArrayList egsendHtmlRequest req复用性不如send(String address, Byte[] data)。因为后续数据可能服务于别的发送不仅仅是html的发送 复用意识 设计一个方法的时候要把它想象成类似于对外提供的API方法那样的复用性不同方、不同业务都可能会调用你多思考编写的这部分代码是否可以抽取出来作为一个独立的方法提供给其他地方使用
2.7 迪米特最少知道法则
1、定义一个对象应该对其他对象有最少的了解即最小知道。或只是直接的朋友交流
直接朋友出现在类属性、方法入参和出参中的类间接朋友出现在局部变量的类和他们的交流使用就会违背迪米特法则
2、作用低耦合、高内聚
高内聚、单一职责原则相近功能放在一个类中不相近的功能不放在一个类中。相近的功能往往会被同时修改这样改动点比较集中低耦合类和类之间的关系简单清晰一个类的改动不会或很少会导致依赖它的类也需要跟着改动 eg基于接口编程接口内部实现类变化了但是对外提供的api不会变eg接口隔离接口被拆为更细化的接口。不拆分之前接口影响多个依赖方拆分成多个更细的接口后某个点-对应更细接口的变动影响的依赖方更少
3、代码 背景公司让部门经理打印此部门的员工姓名 违反迪米特法则的设计 Employee作为局部变量出现在Company中属于Company的间接朋友违反了迪米特 /*** 公司*/
public class Company{Resourceprivate Manager manager;public void printEmployee(String departmentName) {ListEmployee employeeList manager.getAllEmployeeInfoByDepartmentName(departmentName);for (Employee e : employeeList) {System.out.println(e.getName());}}
}/*** 部门经理*/
public class Manager{public ListEmployee getAllEmployeeInfoByDepartmentName(String departmentName) {// 内部实现获取员工信息}
}/*** 员工*/
public class Employee{private String name;
}符合设计 /*** 公司*/
public class Company{Resourceprivate Manager manager;public void printEmployee(String departmentName) {manager.printEmployee(departmentName); //Company之和直接直接朋友Manager交流}
}/*** 部门经理*/
public class Manager{public void getAllEmployeeInfoByDepartmentName(String departmentName) {// 1.获取员工信息(Manager内部实现)ListEmployee employeeList getAllEmployeeInfoByDepartmentName(departmentName);// 2.打印员工姓名Manager内部实现printEmployeeName(employeeList);}
}/*** 员工*/
public class Manager{private String name;
}三、23种设计模式
3.1创建型创建对象
单例、工厂、建造者
3.1.1 单例模式
定义
一个类只允许创建唯一一个对象。这里唯一性作用的范围是进程
最佳实践
public class SkuDTO {private SkuDTO(){}private static class SkuDTOHolder {private static final SkuDTO INSTANCE new SkuDTO();//静态内部类不会再外部类被JVM加载到内存的时候一并被加载。什么时候调用什么时候加载解决了饿汉问题//JVM本身保证了SkuDTO只会在被类加载器加载时初始化一次所以是线程安全的}public static SkuDTO getInstance() {return SkuDTOHolder.INSTANCE;}public static void main(String[] args) {for (int i 0; i 100; i) {new Thread(() - System.out.println(getInstance().hashCode())//都是同一个对象).start();}}
}缺点可以被反射。最完美的方式是枚举因为枚举无构造方法反射也无法创建新的对象 优点外部类SkuDTO被加载的时候不会创建INSTANCE实例。只要调用getInstance()方法的时候才会去创建实例。满足懒加载 JVM保证了INSTANCE的唯一性、线程安全性
场景 表示全局唯一类 配置类、ID生成器类等 处理共享资源访问冲突写日志、共享数据库连接池等 eg 复现同时写日志到txt文件中可能出现内容被覆盖的情况。原因多线程并发写的时候线程1和线程2都创建了FileWriter实例获取到相同的pos待写入位置都是从这个位置写入造成内容覆盖 解决线程1和2使用单例模式创建FileWriterFileWriter本身是线程安全的其内部实现了对象级别的锁即相同的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 { // Dont permanently allocate very large buffers.cbuf new char[len];}str.getChars(off, (off len), cbuf, 0);write(cbuf, 0, len);}}线程级别的单例
public class IDGenerator {private static final AtomicLong id new AtomicLong(0);private static final ThreadLocalIDGenerator tl new ThreadLocal();public IDGenerator getInstance() {tl.set(new IDGenerator());return tl.get();}public Long getId() {return id.incrementAndGet();}
}同一个线程获取到的对象实例是相同的不同线程获取到的不同。属于多例
缺点
单侧不友好全局变量可能会被修改造成测试结果相互影响问题其他
为了保证全局唯一除了单例外我们还可以使用工厂模式来实现
3.1.2 工厂模式简单工厂
场景1解析配置
将不同后缀的配置文件解析成类 根据文件路径x.x.Redis.properties | x.x.MySQL.yaml创建properties 后缀和yaml后置对应的Parse解析类解析文件内容成对象 代码实现
public class Config{public Config load(String configFilePath) {// 1.获取配置文件后缀String fileSuffix getFileSuffix(configFilePath);//(返回properties、yaml、xml等)// 2.根据后置创建对应的解析类Configparser parser createConfigParser(fileSuffix);// 3.解析文件内容return parser.parse(fileSuffix);}public Configparser createConfigParser(String fileSuffix) {Configparser parser;if (xml.equalsIgnoreCase(fileSuffix)) {parser new XmlConfigparser();} else if (yaml.equalsIgnoreCase(fileSuffix)) {parser new YamlConfigparser();} else if (properties.equalsIgnoreCase(fileSuffix)) {parser new PropertiesConfigparser();}return parser;}
}优化1createConfigParser显然不属于Config类的内容。为了满足高内聚低耦合需要将createConfigParser方法抽取到独立类中。这个类专门负责Configparser的创建这个类就是简单工厂类
public class ConfigparserFactory{public Configparser createConfigParser(String fileSuffix) {Configparser parser;if (xml.equalsIgnoreCase(fileSuffix)) {parser new XmlConfigparser();} else if (yaml.equalsIgnoreCase(fileSuffix)) {parser new YamlConfigparser();} else if (properties.equalsIgnoreCase(fileSuffix)) {parser new PropertiesConfigparser();}return parser;}
}优化2
上述代码每次createConfigParser都会new一个新的Configparser对象。我们可以提前将Configparser对象创建好放到map中缓存起来当调用createConfigParser方法时直接从缓存中拿去。
public class ConfigParserFactory {private static final MapString, Configparser map new HashMap();static {map.put(xml, new XmlConfigparser());map.put(yaml, new YamlConfigparser());map.put(properties, new PropertiesConfigparser());}// 这里Configparser是接口XmlConfigparser是接口实现类public Configparser createConfigParser(String fileSuffix) {return map.get(fileSuffix);}
}1、创建名称特点create、getInstance、newInstance、valueOf、of、as
2、优点当新增了YmlConfigparser解析类只需要实现Configparser接口重写parse方法即可然后将其添加到map中。满足开闭原则
场景2解析表达式
规则中心定义卡控最大售卖量规则规则解析
1、规则枚举类
Getter
AllArgsConstructor
public enum RuleExpEnum{NOT_ALLOW(1,n,不允许修改),ALLOW(2,a,允许修改),OR_MODEL(3,o,修改值大于等于补货算法可修改);public final int value;public final String rule;public final String desc;
}2、使用场景
前后端交互前端传[1,2,3]后端需要解析为[“n”,“a”,“o”]前后端交互后端查db数据为[“n”,“a”,“o”]需要展示为[“不允许修改”,“允许修改”,“修改值大于等于补货算法可修改”]
3、简单工厂类
UtilityClass
public class RuleExpFactory {private static final MapInteger, String val2RuleMap new HashMap();private static final MapString, Integer rule2ValMap new HashMap();private static final MapString, String rule2DescMap new HashMap();private static final String AND ;static {for (RuleExpEnum ruleExpEnum : RuleExpEnum.values()) {// 1int value ruleExpEnum.getValue();// nString ruleExp ruleExpEnum.getRule();// 不允许修改String desc ruleExpEnum.getDesc();val2RuleMap.put(value, ruleExp);rule2ValMap.put(ruleExp, value);rule2DescMap.put(ruleExp, desc);}}/*** 1.将no - 不允许修改且修改值大于等于补货算法可修改* 2. 将o - 修改值大于等于补货算法可修改** param ruleExp 规则表达式n* return 规则desc不允许修改*/public String rule2Desc(String ruleExp) {if (StringUtils.isBlank(ruleExp)) {return StringUtils.EMPTY;}ListString ruleList Splitter.on(AND).splitToList(ruleExp);return ruleList.stream().map(rule2DescMap::get).collect(Collectors.joining(且));}/*** 1.将[1,2] - na* 2. 将[1] - n** param valueList [1,2,3]* return nao*/public String value2Rule(ListInteger valueList) {if (CollectionUtils.isEmpty(valueList)) {return StringUtils.EMPTY;}return valueList.stream().map(val2RuleMap::get).collect(Collectors.joining(AND));}
}优点 原本实现是使用StringBuilder进行append StringBuilder sb new StringBuilder();
if(Objects.equals(rule,a)) {sb.append(允许修改)
} else if(Objects.equals(rule,n)) {sb.append(且);sb,append(不允许修改)
} else if () {}这种方式缺点很明显当新增了规则rule则需要再新增else if判断再添加desc不满足开闭原则。如果规则rule很多则代码充斥着大量的else if分支判断
实战-Calendar类
1、创建Calendar实例 Calendar instance Calendar.getInstance();2、简答工厂模式
private static Calendar createCalendar(TimeZone zone,Locale aLocale){//这里zone和aLocalezh_CN都是默认值Calendar cal null;//根据地区的语言和国家来判断日历类型if (cal null) {if (aLocale.getLanguage() th aLocale.getCountry() TH) {cal new BuddhistCalendar(zone, aLocale);} else if (aLocale.getVariant() JP aLocale.getLanguage() ja aLocale.getCountry() JP) {cal new JapaneseImperialCalendar(zone, aLocale);} else {// 其他情况一律返回公历cal new GregorianCalendar(zone, aLocale);}}return cal;}为什么说这是一种简单工厂模式呢
因为静态createCalendar() 创建实例时根据该方法传入的参数来返回对应的 Calendar 实现类符合工厂模式的思想类似场景1
总结
当代码中存在大量if - else根据A获取|创建B的场景则可以考虑使用简单工厂模式
3.1.3DI依赖注入
定义
相当于一个大型工厂负责在程序启动时根据各种配置信息创建对象。因为它持有一堆对象所以又叫容器
和简单工厂区别
简单工厂负责一类eg不同文本类型对象的创建。一般要创建哪些对象都是代码提前写死的new好DI容器负责的是整个应用程序所有对象的创建。除此之外它还要负责对象生命周期的管理。DI事先不知道要创建哪些对象是根据解析配置来动态创建对象
手动实现一个DI容器
使用
Demo
public class Demo {public static void main(String[] args) {ApplicationContext applicationContext new ClassPathXmlApplicationContext(beans.xml);RateLimiter rateLimiter (RateLimiter) applicationContext.getBean(rateLimit);rateLimiter.func();}
}接口
public interface ApplicationContext {Object getBean(String beanId);
}实现类
public class ClassPathXmlApplicationContext implements ApplicationContext {private BeanConfigParser beanConfigParser;private BeansFactory beansFactory;public ClassPathXmlApplicationContext(String configLocation) {this.beansFactory new BeansFactory();this.beanConfigParser new XmlBeanConfigParser();loadBeanDefinitions(configLocation);}// 解析器读取xml配置为BD并将BD放入beanFactoryprivate void loadBeanDefinitions(String configLocation) {InputStream in this.getClass().getResourceAsStream(/ configLocation);ListBeanDefinition beanDefinitions beanConfigParser.parse(in);beansFactory.addBeanDefinitions(beanDefinitions);}// 从beanFactory创建beanOverridepublic Object getBean(String beanId) {return beansFactory.getBean(beanId);}
}DeanDefination
Data
public class BeanDefinition {private String id;private String className;private ListConstructorArg constructorArgs new ArrayList();private Scope scope Scope.SINGLETON;//单例private boolean lazyInit false;//懒加载falsepublic boolean isSingleton() {return scope.equals(Scope.SINGLETON);}public static enum Scope {SINGLETON,PROTOTYPE}Datapublic static class ConstructorArg {private boolean isRef;//bean中是否有对象依赖private Class type;//对象依赖类型private Object arg;}
}1、配置解析
xml配置文件
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdbean idrateLimiter classcom.xxx.lean.RateLimiterconstructor-arg refredis/constructor-arg/beanbean idredis classcom.mjp.lean.Redisconstructor-arg typejava.lang.String value127.0.0.1/constructor-arg typeint value6001//bean
/beans配置类
NoArgsConstructor
AllArgsConstructor
Data
public class RateLimiter {private Redis redis;
}AllArgsConstructor
NoArgsConstructor
Data
public class Redis {private String ipAddress;private int port;
}解析配置类并生成BeanDefination放入BeanFactory
主要就是将is解析成BD
public interface BeanConfigParser {ListBeanDefinition parse(InputStream inputStream);}public class XmlBeanConfigParser implements BeanConfigParser {Overridepublic ListBeanDefinition parse(InputStream inputStream) {String content null;return parse(content);}
}2、BeanFactory通过反射创建对象
public class BeansFactory {private ConcurrentHashMapString, Object singletonObjects new ConcurrentHashMap();private ConcurrentHashMapString, BeanDefinition beanDefinitions new ConcurrentHashMap();// 存储BDpublic void addBeanDefinitions(ListBeanDefinition beanDefinitionList) {for (BeanDefinition beanDefinition : beanDefinitionList) {this.beanDefinitions.putIfAbsent(beanDefinition.getId(), beanDefinition);}for (BeanDefinition beanDefinition : beanDefinitionList) {if (beanDefinition.isLazyInit() false beanDefinition.isSingleton())createBean(beanDefinition);}}// 获取beanpublic Object getBean(String beanId) {BeanDefinition beanDefinition beanDefinitions.get(beanId);return createBean(beanDefinition);}// 反射创建beanprotected Object createBean(BeanDefinition beanDefinition) {// 单例则直接从池中拿取对象并返回if (beanDefinition.isSingleton() singletonObjects.contains(beanDefinition)) {return singletonObjects.get(beanDefinition.getId());}Object bean null;try {Class beanClass Class.forName(beanDefinition.getClassName());ListBeanDefinition.ConstructorArg args beanDefinition.getConstructorArgs();// 如果此bean没有依赖的bean则直接创建对象即可if (args.isEmpty()) {bean beanClass.newInstance();} else {// 否则需要按个创建依赖的BD对象的beanClass[] argClasses new Class[args.size()];Object[] argObjects new Object[args.size()];for (int i 0; i args.size(); i) {BeanDefinition.ConstructorArg arg args.get(i);if (arg.isRef()) {// 当此bean对象的构造函数中参数是ref类型时则递归创建ref属性指向的对象BeanDefinition refBeanDefinition beanDefinitions.get(arg.getArg());argClasses[i] Class.forName(refBeanDefinition.getClassName());//依赖BD的ref类型User.classargObjects[i] createBean(refBeanDefinition);//依赖BD的具体值User(mjp,18)} else {argClasses[i] arg.getType();argObjects[i] arg.getArg();}}// 通过反射获取有参构造器然后再通过newInstance传递构造器入参值创建对象bean beanClass.getConstructor(argClasses).newInstance(argObjects);}} catch (Exception e) {}// 如果对象时单例的则需要放入缓存池中if (bean ! null beanDefinition.isSingleton()) {singletonObjects.putIfAbsent(beanDefinition.getId(), bean);return singletonObjects.get(beanDefinition.getId());}return bean;}
}3、对象生命周期管理
单例懒加载lazy-init false。所有对象在应用启动的时候就创建好init-method 和 destroy-method
比如 initmethodloadProperties(),在创建好对象后会主动调用 init-method属性指定的方法来初始化对象。
destroy-methodupdateConfigFile(),在对象被最终销毁之前,会主动调用 destroy-method 属性指定的方法来做一些清理工作(释放数据库连接池、关闭文件)。
3.1.4 Builder建造者模式
和set以及构造器的区别
1、构造器的缺点
如果类中有很多的属性则new X太多的属性容易赋值错
2、set方法的缺点
即使对象被final修饰也是对象指向的地址是不可变的但是堆地址的内容还是可以通过set赋值可变。
当要求对象一旦被new其属性值就不允许被修改则不能对外暴露set
set方法也无法校验传递参数是否正确更无法校验多个属性之间的关系(eg:最大线程数 必须 核心线程数)
3、建造者的缺点
建造者内部类中也需要再定义一遍和外部类中一样的属性
建造者模式创建对象
1、private 构造器
2、只提供get方法不提供set
3、定义成员内部类Builder类
单个setXxx中可以校验某个属性最终build方法new 对象之前可以校验各个属性之间的关系
Getter
ToString
public class ThreadConfig {private String name;private Integer coreCount;private Integer maxCount;private ThreadConfig(ThreadConfigBuilder threadConfigBuilder) {this.name threadConfigBuilder.name;this.coreCount threadConfigBuilder.coreCount;this.maxCount threadConfigBuilder.maxCount;}ToStringprivate class ThreadConfigBuilder {public String name;public Integer coreCount;public Integer maxCount;public ThreadConfigBuilder setName(String name) {if (StringUtils.isBlank(name)) {throw new IllegalArgumentException(线程池名称不能为空);}this.name name;return this;}public ThreadConfigBuilder setCoreCount(Integer coreCount) {if (coreCount null || coreCount 0) {throw new IllegalArgumentException(线程池核心线程数必须为正整数);}this.coreCount coreCount;return this;}public ThreadConfigBuilder setMaxCount(Integer maxCount) {if (maxCount null || maxCount 0) {throw new IllegalArgumentException(线程池最大线程数必须为正整数);}this.maxCount maxCount;return this;}public ThreadConfig build() {if (coreCount maxCount) {throw new IllegalArgumentException(线程池最大线程数必须大于核心线程数);}return new ThreadConfig(this);}}
}实战Accessors
Data
Accessors(chain true)
public class UserDemo {private String name;private Integer age;
}UserDemo m new UserDemo().setName(m).setAge(18);和工厂模式的区别
工厂模式是创建一系列相同类型的对象
建造者模式是创建一个复杂属性的对象
3.2结构型类或对象的组合
代理、装饰者、适配器、享元
3.2.1Proxy代理模式
定义
在不改变原有类的情况下引入代理类来给原始类附加功能
场景
日志打印、权限校验、限流、事务、最大努力重试
动态代理接口实现
为给个接口方法的执行计算花费的时间
public class StopWatchProxy {public Object creatProxy(Object target) {Class? aClass target.getClass();ClassLoader classLoader aClass.getClassLoader();Class?[] interfaces aClass.getInterfaces();return Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args) - {long start System.currentTimeMillis();Object result method.invoke(target, args);long end System.currentTimeMillis();System.out.println((end - start));return result;});}
}public interface User {void eat();
}public class UserImpl implements User{Overridepublic void eat() {System.out.println(eat);}
}StopWatchProxy stopWatchProxy new StopWatchProxy();
User user (User) stopWatchProxy.creatProxy(new UserImpl());
user.eat();3.2.2 装饰器模式
作用
给原始类添加增强功能
和代理模式的区别
代理模式中代理类附加的是跟原始类无关的功能日志、权限校验等装饰器类附加的是跟原始类相关的增强功能原始类是直接读、装饰类增加的功能是缓存读
实现
装饰器类ADeractor需要跟原始类A继承相同的抽象类AbstractA | 接口IA。装饰器类ADeractor中组合原始类A
可以对A嵌套使用多个装饰器类
接口|抽象类
public interface IA {void f();
}原始类
Service
public class A implements IA{Overridepublic void f() {System.out.println(f);}
}装饰类
Service
public class ADecorator implements IA{Resourceprivate A a;Overridepublic void f() {// 增强System.out.println(增强1);a.f();// 增强System.out.println(增强2);}
}使用
RunWith(SpringRunner.class)
SpringBootTest(classes ApplicationLoader.class)
public class SpringTest {Resourceprivate ADecorator aDecorator;Testpublic void test() {aDecorator.f();}
}场景IO流
1、结构
字节流-读InpuStream FileInputStreamByteArrayInputStreamFilterInputStream BufferedInputStreamDateInputStream 字符流-读Reader BufferedReaderInputStreamReader FileReader
2、源码结构
抽象类InputStream
AFileInputStream
ADecoratorBufferedInputStream、DateInputStream
FileInputStream fis new FileInputStream(new File(xxx.txt));
BufferedInputStream bis new BufferedInputStream(fis);
bis.read();3、前置背景
3.1 使用装饰者增强后的read Testpublic void test() throws IOException {FileInputStream fis new FileInputStream(new File());SonBufferedInputStream bis new SonBufferedInputStream(fis);bis.read();}3.3 抽象-read
public abstract class InputStream implements Closeable {public abstract int read() throws IOException;
}3.3 A-read
public class FileInputStream extends InputStream{public int read() throws IOException {return read0();} private native int read0() throws IOException;
}3.4 ADecorator -read
public class SonBufferedInputStream extends FatherFilterInputStream {public SonBufferedInputStream(FileInputStream fis) {super(fis);}public int read() throws IOException {// A的readfis.read(null, 1, 1);// 增强return 1;}
}虽然ADecoratorSonBufferedInputStream中没有直接定义属性AFileInputStrteam但是其父类中定义了
Data
AllArgsConstructor
public class FatherFilterInputStream extends InputStream {protected FileInputStream fis;Overridepublic int read() throws IOException {return fis.read();}
}这样当执行SonBufferedInputStream bis new SonBufferedInputStream(fis)时
super(fis)即将AFileInputStrteam赋值给了其父类属性A a这样等效子类ADecorator也具有了A a属性值所以当执行bis.read()时一方面执行了a.read另一方面执行了增强方法。实现了装饰功能
4、源码解析
4.1 抽象类InputStream-read()
4.2 A : read是个nativate方法
public class FileInputStream extends InputStream{private native int read() throws IOException;
}4.3 ADecoratorBufferedInputStream-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中组合了Afisprotected volatile InputStream in;//fisprotected FilterInputStream(InputStream in) {this.in in;//this.fis fis}
}》等效
public class FilterInputStream extends InputStream {protected volatile FileInputStream fis;protected FilterInputStream(FisleInputStream fis) {this.fis fis;}
}这样一来就相当于子类ADecoratorBuffered中通过继承父类也具有属性AFileInputStream
4.4 bis.read()
public synchronized int read() throws IOException {fill();//实现return getBufIfOpen()[pos] 0xff;
}private void fill() throws IOException {// 增强功能缓存byte[] buffer getBufIfOpen();// 调用属性a 的方法int n getInIfOpen().read(buffer, pos, buffer.length - pos);
}private InputStream getInIfOpen() throws IOException {InputStream input in;//fisreturn input;
}getInIfOpen().read(buffer, pos, buffer.length - pos) 相当于使用fis.read(buffer, pos, buffer.length - pos)
public class FileInputStream{public int read(byte b[], int off, int len) throws IOException {return readBytes(b, off, len);}private native int readBytes(byte b[], int off, int len) throws IOException;
}这样一来ADcorator中使用了buffer增强了fis的read
4.5 父类作用
完全可以直接将属性A a放入ADecorator中
public class BufferedInputStream extends InputStream {private FileInputStream fis;public int read(){//}
}为什么采用将A a 放入父类中然后子类继承父类属性的方式从而子类ADecorator也具有了属性A。
父类作用 让自子类Buffered、Data只需要关注AFis中需要增强的方法比如A中的read方法的Buffered为其增强为具有缓存功能的字节流读。 A类的其它不需要增强的方法都交给父类FilterInputStream的去关注去实现这样众多装饰者子类就无需重写
3.2.3 适配器模式
定义
将不兼容的接口转为可兼容的接口让原本因为接口不兼容无法在一起工作的类可以一起工作
实现
1、类适配器-继承
需要配适配的
public class Adaptee {public void query() {System.out.println(query);}public void add() {System.out.println(add);}public void delete() {System.out.println(delete);}
}适配成什么样子即目标 除了add方法还是使用Adaptee的查询和删除都使用适配后的新方法
public interface Target {void queryNew();void add();void deleteNew();
}适配器
Service
public class Adapter extends Adaptee implements Target{Overridepublic void queryNew() {super.query();}Overridepublic void deleteNew() {if (true) {// 执行新的删除逻辑System.out.println(delete new);} else {super.delete();}}// 这里类适配器最大的特点就是理论上需要重写add方法但是由于继承了父类的add所以可以不用重写add
}使用
RunWith(SpringRunner.class)
SpringBootTest(classes ApplicationLoader.class)
Slf4j
public class SpringTest {Resourceprivate Target target;Testpublic void test() throws IOException {target.queryNew();target.add();target.deleteNew();}
}
query
add
delete new使用场景
目标Target和原Adaptee中大部分方法都一样没有那么多方法需要适配的时候使用类继承这样很多方法比如像add都无需重写。
2、对象适配器-组合
适配器
Service
public class AdapterObj implements Target{Resourceprivate Adaptee adaptee;Overridepublic void queryNew() {adaptee.query();// 再---}Overridepublic void add() {adaptee.add();}Overridepublic void deleteNew() {if (true) {System.out.println(delete new);} else {adaptee.delete();}}
}使用场景
目标Target和原Adaptee中大部分方法都不一样即定义不同
场景
1、兼容老接口
背景
原本查询黑名单接口BlackListService#queryBlackList(Long poiId)根据仓id查询仓下的所有很名单。
本次需求查询黑名单时除了需要仓id外还需要businessType 实现方式1 直接修改BlackListService#queryBlackList(Long poiIdInteger businessType)方法声明和逻辑 优点无需重构 缺点风险大线上很多业务使用到这个方法一旦方法有问题相当于全量了风险不可控 实现方式2适配器模式 Service
public class BlackListAdaptee {public ListObject queryBlackList(Long poiId){return Lists.newArrayList();}
}public interface BlackListServiceTarget {ListObject queryBlackListNew(Long poiId, Integer businessType);
}Service
public class BlackListServiceAdaptor extends BlackListAdaptee implements BlackListServiceTarget{Overridepublic ListObject queryBlackListNew(Long poiId, Integer businessType) {if (true) {//命中了灰度仓System.out.println(根据poi和businessType查询黑名单);return Lists.newArrayList();} else {// 非灰度仓走老逻辑查询查询结果按照businessType进行过滤即可ListObject blackList super.queryBlackList(poiId);return blackList.stream().filter(Objects::nonNull).collect(Collectors.toList());}}
}RunWith(SpringRunner.class)
SpringBootTest(classes ApplicationLoader.class)
Slf4j
public class SpringTest {Resourceprivate BlackListServiceAdaptor blackListServiceAdaptor;Resourceprivate BlackListServiceAdaptee blackListServiceAdaptee;Testpublic void test() throws IOException {// 原本的业务逻辑//ListObject result blackListServiceAdaptee.queryBlackList(323L);// 改为ListObject result blackListServiceAdaptor.queryBlackListNew(323L, 1);}
}优点风险可控一旦方法有问题直接切灰度即可。
2、统一多个类的接口设计
背景
敏感词过滤。
实现方式
引入第三方过滤JarA性关键词相关、B政治关键字相关
public class RishManagement{Resourceprivate A a;Resourceprivate B b;public String filterSensitiveWords(String text) {String s a.filterSexyWords(text);//内部默认实现使用xxx代替敏感词return b.filterPoliticalWords(s???);//使用???代替敏感词}
}问题
当需要环境污染过滤C时这个时候RishManagement会违背开闭原则
A依赖提供的方法-单个入参使用默认实现使用xxx代替敏感词
B依赖提供的方法-两个入参第二个入参是replace使用String replace 代替敏感词
这样接口的调用也不统一还需要人为指定replace 解决使用对象适配器模式统一接口设计 AdapteeAFilterAdaptee、BFilterAdaptee都是第三方依赖Target制定统一的接口设计否是单入参 public interface SensitiveWordsFilterTarget {String filter(String text);
}A-Adaptor Service
public class SexyWordsFilterAdapter implements SensitiveWordsFilterTarget{Resourceprivate AFilterAdaptee aFilterAdaptee;Overridepublic String filter(String text) {return aFilterAdaptee.filterSexyWords(text);}
}B-Adaptor Service
public class PoliticalWordsFilterAdapter implements SensitiveWordsFilterTarget{Resourceprivate BFilterAdaptee bFilterAdaptee;Overridepublic String filter(String text) {return bFilterAdaptee.filterPoliticalWords(text, ???);}
}RiskManager Service
public class RiskManager {Resourceprivate ListSensitiveWordsFilterTarget sensitiveWordsFilterTargets;public String filterWords(String text) {String temp text;for (SensitiveWordsFilterTarget filterAdaptor : sensitiveWordsFilterTargets) {temp filterAdaptor.filter(temp);}return temp;}
}这样当需要过滤环境污染相关关键词引入C时不需要修改RiskManager只需要创建C-EnvironmentWordsFilterAdapter即可
3.2.4 享元模式
定义
被共享的单元比如类的属性当这些属性是通用且不可变时可以组成元让系统共享使用
作用
系统复用不可变队形-享元节省内存
实战 背景 建设一个象棋棋牌室游戏同时在线1w个房间每个房间是一盘对局棋局类对局中需要棋子棋子类 棋子 Data
AllArgsConstructor
public class ChessPiece {/*** 棋子编号1-32红黑各16*/private Integer id;private Color color;/*** 将、士、车、马---*/private String name;/*** 棋子在棋局上的位置*/private Integer x;private Integer y;public enum Color{RED,BLACk;}
}棋局 public class ChessBoard {private MapInteger, ChessPiece pieceIdMap new HashMap();public ChessBoard() {init();}private void init() {pieceIdMap.put(1, new ChessPiece(1, ChessPiece.Color.RED, 车, 0 , 1));pieceIdMap.put(2, new ChessPiece(2, ChessPiece.Color.BLACk, 跑, 7 , 4));// 剩下30个棋子}
}问题
如果游戏有1w个房间则有1w个棋局再创建每个棋局的时候都需要创建32个棋子对象。所以需要创建32w个棋子对象。占用很大内存。
解决
我们发现棋子属性只有x、y坐标属性对于不同房间的棋局棋子的坐标是不同的
id、颜色、名称对于不同棋局来说都是相同的属性这些属性都是不可变的可以共享可以抽取为享元类
享元类
Data
AllArgsConstructor
public class ChessPieceUnit {/*** 棋子编号1-32红黑各16*/private Integer id;private ChessPiece.Color color;/*** 将、士、车、马---*/private String name;public enum Color{RED,BLACk;}
}棋子类
Data
AllArgsConstructor
public class ChessPiece {/*** 享元类id、color、name*/private ChessPieceUnit chessPieceUnit;/*** 棋子在棋局上的位置*/private Integer x;private Integer y;
}享元工厂类存取享元类
public class ChessPieceUnitFactory {private static final MapInteger, ChessPieceUnit pieceIdMap new HashMap();static {pieceIdMap.put(1, new ChessPieceUnit(1, ChessPieceUnit.Color.RED, 车));pieceIdMap.put(2, new ChessPieceUnit(2, ChessPieceUnit.Color.BLACk, 跑));// 剩下30个棋子}private ChessPieceUnit getUnitByChessPieceId(Integer id) {return pieceIdMap.get(id);}
}棋局类
public class ChessBoard {private static final MapInteger, ChessPiece pieceIdAndPieceMap new HashMap();public ChessBoard() {init();}private void init() {pieceIdAndPieceMap.put(1, new ChessPiece(ChessPieceUnitFactory.pieceIdMap.get(1), 0 , 1));pieceIdAndPieceMap.put(2, new ChessPiece(ChessPieceUnitFactory.pieceIdMap.get(2), 0 , 1));// 剩下30个棋子}
}棋局类在put棋子的时候棋子的享元部分属性是通过享元工厂获取的。 棋局A在put棋子的时候享元类属性是通过享元类工厂缓存map获取的map只需要第一次创建时new一个享元类 棋局B后续棋局B使用享元类时直接存缓存map获取即可无需再创建。 即1w棋局需要创建32w个棋子类但是棋子类中大量的属性即享元属性只需要创建1次这样无疑大大减少内存的占用。1w房间1w棋局类、32w棋子类、32个享元属性
场景
Integer实战
1、背景
Integer i1 new Integer(123);
Integer i2 123;//等价Integer i3 Integer.valueOf(123);2、原理 public static Integer valueOf(int i) {if (i IntegerCache.low i IntegerCache.high)return IntegerCache.cache[i (-IntegerCache.low)];return new Integer(i);}low-128 high127 当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的数字。方式1new1w个对象
方式2|3则只需要new 256个对象
4、其他
String字符串常量池同理
总结
将共享的再创建完成后使用缓存数组、map存储起来。后续直接从缓存中取
3.3行为型
观察者、模板、策略、职责链、迭代器、状态模式
3.3.1 观察者模式
定义
对象之间定义一个1 vs n的依赖当1的状态改变时所有依赖于它的n对象都会接收到通知并更新
被观察者subject
观察者 observe
作用
将被观察者和观察者解耦
和生产者消费者的区别
1、生产消费模式
生产和消费是不同的线程。二者通过队列通信多对多生产者可以多个杂志的投稿者消费者也可以多个为了解耦 和 并发生产者推、消费者拉
2、观察者模式
被观察者和观察者在同一个线程中1对多为了解耦被观察者和观察者被观察者内部组合了观察者需要维护观察者的信息属于被观察者直接将信息推送给观察者们
和 订阅-发布模式的区别
1、订阅-发布模式
是观察者的别名但是后续演变成一种新的设计模式发布者不再维护订阅者们的信息不会再直接将信息推送个发布者们实现了二者的完全解耦发布者和订阅者之间存在中间件调度中心Broker 发布者只需要告诉Broker我要发送的信息topic为A订阅者只需要告诉Broker我订阅的消息topic为A当Broker接收到Topic为A的消息时会统一调度那些订阅了Topic为A的订阅者们注册到Broker的处理代码eg你在微博上关注了A其他人也关注了A。当A发布动态即发送消息到微博调度中心Broker时Broker就会为你们推送A的动态
被观察者-观察者模板
被观察者需要维护观察者们的信息
1、抽象观察者
public interface Observe {void update();
}2、具体观察者
Service
public class Observe1 implements Observe{Overridepublic void update() {System.out.println(观察者1更新);}
}Service
public class Observe2 implements Observe{Overridepublic void update() {System.out.println(观察者2更新);}
}3、被观察
Service
public class Subject {Resourceprivate ListObserve observes;public void notice() {for (Observe observe : observes) {observe.update();}}
}使用
RunWith(SpringRunner.class)
SpringBootTest(classes ApplicationLoader.class)
Slf4j
public class SpringTest {Resourceprivate Subject subject;Testpublic void test(){subject.notice();}
}
观察者1更新
观察者2更新实战
1、背景
用户注册app成功后给用户发放新人券
2、实现
RestController
public class UserAppController {Resourceprivate RegisterService registerService;Resourceprivate RegisterSuccessObserve registerSuccessObserve;public void register(Long iphone, String pwd) {// 注册boolean success registerService.register(iphone, pwd);if (success) {registerSuccessObserve.issueNewConsumerCoupon(iphone);}}
}注册
Service
public class RegisterService {public boolean register(Long iphone, String pwd) {return true;}
}发送新人优惠券
Service
public class RegisterSuccessObserve {public void issueNewConsumerCoupon(Long iphone){System.out.println(发送新人优惠券);}
}3、问题
如后续新需求当用户注册App成功后除了发新人券还需要发送用户注册成功信息的短信给用户
那么register方法就必须改动了违背了开闭原则
4、使用模式重构
被观察者维护观察者们的信息一旦注册成功后将后续一系列动作推送给观察者们即可
抽象观察者
public interface RegisterSSuccessObserver {void update(Long iphone);
}观察者们
Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{Overridepublic void update(Long iphone) {System.out.println(发送新人优惠券);}
}Service
public class SendMsgObserve implements RegisterSSuccessObserver{Overridepublic void update(Long iphone) {System.out.println(发送注册成功短信);}
}被观察者
RestController
public class UserAppControllerSubject {Resourceprivate RegisterService registerService;Resourceprivate ListRegisterSSuccessObserver registerSSuccessObservers;public void register(Long iphone, String pwd) {// 注册boolean success registerService.register(iphone, pwd);if (success) {for (RegisterSSuccessObserver observer : registerSSuccessObservers) {observer.update(iphone);}}}
}这样后续发送新人券改为发送礼品卡也只需要新增具体观察者即可。对于register方法满足开闭原则
5、优化- 异步非阻塞观察者模式 使用guava的EventBus模式实现异步阻塞 被观察者
Slf4j
RestController
public class UserAppControllerSubject {Resourceprivate RegisterService registerService;Resourceprivate ListRegisterSSuccessObserver registerSSuccessObservers;private ExecutorService threadPool Executors.newFixedThreadPool(2);private EventBus eventBus;public UserAppControllerSubject() {eventBus new AsyncEventBus(threadPool, (e, context) - {log.error(consumer{}, receive{},msg{} 流程异常, context.getSubscriber(), context.getSubscriberMethod() ,context.getEvent(), e);});}public void register(Long iphone, String pwd) {// 注册成功boolean success registerService.register(iphone, pwd);if (success) {for (RegisterSSuccessObserver observer : registerSSuccessObservers) {// 订阅者-类似消费组eventBus.register(observer);}// 发布者send发布-类似MQ生产者的sendeventBus.post(iphone);}}
}抽象观察者
public interface RegisterSSuccessObserver {void receive(Long iphone);
}观察者们
Service
public class SendMsgObserve implements RegisterSSuccessObserver{OverrideSubscribepublic void receive(Long iphone) {System.out.println(发送注册成功短信);}
}Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{OverrideSubscribepublic void receive(Long iphone) {System.out.println(发送新人优惠券);}
}使用
RunWith(SpringRunner.class)
SpringBootTest(classes ApplicationLoader.class)
Slf4j
public class SpringTest {Resourceprivate UserAppControllerSubject userAppControllerSubject;Testpublic void test(){userAppControllerSubject.register(186L, 123mjp);}
}
发送注册成功短信
发送新人优惠券优化为非阻塞
如果IssueNewConsumerCouponObserve观察者的receive方法内部流程较长执行的慢。可能会影响整体的性能则需要非阻塞模式即其receive方法异步执行
Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{private ExecutorService myThreadPool Executors.newFixedThreadPool(2);OverrideSubscribeAsync(myThreadPool)public void receive(Long iphone) {try {TimeUnit.SECONDS.sleep(5L);} catch (Exception e) {}System.out.println(发送新人优惠券);}
}启动类EnableAsync
EnableAsync
SpringBootApplication(exclude {DataSourceAutoConfiguration.class})
public class ApplicationLoader {public static void main(String[] args) {SpringApplication springApplication new SpringApplication(ApplicationLoader.class);springApplication.run(args);}
}3.3.2 模板模式
定义
在抽象类中定义一个逻辑框架由a、b、c等组成。子类在不改变整体框架的情况下重新定义业务的某些步骤
作用
扩展、复用
使用模板
父类模板
public abstract class Template {public void func() {m1();m2();m3();}protected abstract void m1();protected abstract void m2();private void m3() {}
}子类重写模板中某一个步骤
public class A extends Template{Overrideprotected void m1() {}Overrideprotected void m2() {}
}
B类同理场景
1、复用
1.1 io
父类框架
public abstract class InputStream implements Closeable {// 父类定义一个方法框架public int read(byte b[], int off, int len) throws IOException {// m1();read();// m3();}// 其中m2()即read方法是抽象方法由不同子类自己去实现public abstract int read() throws IOException;
}子类重写框架中某个步骤
egByteArrayInputStream会重写read方法
1.2 AbstractList
父类框架 public boolean addAll(int index, Collection? extends E c) {rangeCheckForAdd(index);boolean modified false;for (E e : c) {add(index, e);modified true;}return modified;}其中add方法等效abstract类型方法原因如下public void add(int index, E element) {throw new UnsupportedOperationException();}
即AbstractList中的add()直接抛出异常即如果其子类不重写add方法那么就会调用父类AbstractList的add直接抛异常。这就和abstract关键字一样强制要求子类重写add方法2、扩展框架
2.1 背景 Web项目中的SpringMvc中的XxxController#xxxFunc。请求url对应类上注解 方法上注解流量就能打到 这是SpringMvc封装了Servlet实现。自己也可以通过Servlet的扩展功能实现上述功能
2.2 扩展点
Servlet通过模板模式留下了doGet、doPost等扩展点。让用户再不修改框架的情况下通过继承HttpServlet重写扩展点方法将用户自己的业务代码嵌入整个框架中
2.3 源码解析
自定义XxxServlet#xxxFunc在web.xml中定义url和Servlet的映射关系当请求打进来时会首先走到HttpServlet的service方法
protected void service(HttpServletRequest req, HttpServletResponse resp){if (method.equals(METHOD_GET)) {if (lastModified -1) {// 扩展点1doGetdoGet(req, resp);} else {//}} else if (method.equals(METHOD_POST)) {// 扩展点2doPostdoPost(req, resp);} else if (method.equals(METHOD_PUT)) {//}}// doGet等效abstract doGet// 因为doGet方法什么都没实现就是报错。所以HttpServlet的子类就必须重写doGet这一点类似AbstractList#addAll中的add方法protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException{String protocol req.getProtocol();String msg lStrings.getString(http.method_get_not_supported);if (protocol.endsWith(1.1)) {resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);} else {resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);}}子类重写模板利用框架的模板模式实现扩展功能
public class MyHttpServlet extends HttpServlet {Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 自定义}Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{// 自定义}
}3、实战
1、背景
父类定义框架方法根据图形周长、面积计算周长 / 面积值
2、实现
func(){// 计算周长double perimerter perimerter();// 计算面积double area area();m3(perimerter, area);
}3.3.3回调函数
定义
A类中的a调用B类b方法时B类的b方法会反过来调用A类中注册给它的f方法
作用
复用
由于和模板方法的复用功能一样所以很多回调方式直接叫XxxTemplate
场景1
1、背景JDBCTemplate的演变 普通版JDBCDemo 实现 public class JDBCDemo {public ListUser queryUserById(Long id) {Connection con null;Statement stm null;ListUser ans null;try {// 1.注册驱动Class.forName(com.mysql.jdbc.Driver);// 2.获取数据库连接对象con DriverManager.getConnection(jdbc:mysql://127.0.0.1:3306/day21, root, root);// 3.获取sql语句的执行对象String sql select * from table_user where id id;stm con.prepareStatement(sql);// 4.执行sqlResultSet result stm.executeQuery(sql);// 5.处理查询结果while (result.next()) {User user new User().setName(result.getString(name));ans.add(user);}return ans;} catch (Exception e) {} finally {// 6.关闭资源if (stm ! null) {try {stm.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (con ! null) {try {con.close();} catch (SQLException e) {throw new RuntimeException(e);}}}return ans;}
}问题 如果想执行update语句则需要定义updateById(Long id)则整个流程还需要再走一遍。毫无复用性而言 抽取版JDBCUtils jdbc.properties driver com.mysql.jdbc.Driverurl jdbc:mysql://127.0.0.1:3306/day21user rootpassword rootUtil public class JDBCUtil {private static String driver;private static String url;private static String user;private static String password;/*** 注册驱动 获取数据库连接对象con的前置配置*/static {ClassLoader classLoader JDBCUtil.class.getClassLoader();InputStream is classLoader.getResourceAsStream(D:\\CodeBetter\\src\\main\\resources\\jdbc.properties);Properties properties new Properties();try {properties.load(is);driver properties.getProperty(driver);Class.forName(driver);url properties.getProperty(url);user properties.getProperty(user);password properties.getProperty(password);} catch (Exception e) {}}/*** 获取数据库连接对象* * return* throws SQLException*/public static Connection getConnection() throws SQLException {return DriverManager.getConnection(url, user, password);}/*** 关闭资源* * param con* param stm*/public static void close(Connection con , Statement stm){if (stm ! null) {try {stm.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (con ! null) {try {con.close();} catch (SQLException e) {throw new RuntimeException(e);}}}
}使用 Connection con JDBCUtil.getConnection();PreparedStatement pst;// 1.查询String sql select * from table_user where id 1;pst con.prepareStatement(sql);ResultSet resultSet pst.executeQuery();// 2.更新String updateSql update from table_user set name mjp where id 1;pst con.prepareStatement(updateSql);int result pst.executeUpdate();// 3.关闭JDBCUtil.close(con, pst);问题抽象的仍不彻底。仍有大量con、stm相关对象 JDBCTemplate版本
public class DataSourceDemo {private static DataSource ds;private static Properties properties;static {ClassLoader classLoader DataSourceDemo.class.getClassLoader();InputStream is classLoader.getResourceAsStream(D:\\CodeBetter\\src\\main\\resources\\jdbc.properties);properties new Properties();try {// 这里会把properties中所有属性都读取到properties.load(is);} catch (Exception e) {}}public static DataSource getDataSource() {try {ds DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {throw new RuntimeException(e);}return ds;}
}JdbcTemplate jtl new JdbcTemplate(DataSourceDemo.getDataSource());// 1.查询String sql select * from table_user where id 1;MapString, Object map jtl.queryForMap(sql, 1);// 2.更新String updateSql update from table_user set name mjp where id 1;int result jtl.update(updateSql, 1);2、源码解析JDBCTemplate简化版
含回调函数的接口
FunctionalInterface
public interface StatementCallbackT {T doInStatement(Statement stm) throws SQLException, DataAccessException;
}JDBCTemplate
public T T execute(StatementCallbackT action) throws DataAccessException {// 1、2加载数据库驱动、创建数据库连接Connection con DataSourceUtils.getConnection(this.obtainDataSource());Statement stmt null;Object var11;try {// 3.创建sql语句执行对象stmt con.createStatement();this.applyStatementSettings(stmt);// 4.使用回调方法执行stm.各种CRUDT result action.doInStatement(stmt);this.handleWarnings(stmt);// 5.返回执行结果可能是对象、List对象、intvar11 result;} catch (SQLException var9) {//6.关闭资源} finally {JdbcUtils.closeStatement(stmt);DataSourceUtils.releaseConnection(con, this.getDataSource());}return var11;}
其中execute方法就属于模板方法其中12356都是通用方法只有4是根据不同的sqlstm执行对应的语句查询、更新业务类
AllArgsConstructor
public class CrudClass {private JdbcTemplate jdbcTemplate;/*** 查询* param sql* return*/public ResultSet query(String sql) {return (ResultSet) jdbcTemplate.execute(new StatementCallbackObject() {Overridepublic Object doInStatement(Statement stm) throws SQLException, DataAccessException {ResultSet resultSet stm.executeQuery(sql);return resultSet;}});}/*** 更新* param sql* return*/public Integer update(String sql) {return (Integer) jdbcTemplate.execute((StatementCallbackObject) stm - {int result stm.executeUpdate(sql);return result;});}
}使用查询、更新类
JdbcTemplate jdbcTemplate new JdbcTemplate(DataSourceDemo.getDataSource());
CrudClass crudClass new CrudClass(jdbcTemplate);
ResultSet resultSet crudClass.query(select * from tb_user where id 32);
Integer result crudClass.update(update from tb_user set name mjp where id 32);3、分析 回调函数定义A类中的a调用B类b方法时B类的b方法会反过来调用A类中注册给它的f方法 JDBCTemplate应用定义即CrudClass类中的query调用JdbcTemplate类execute方法时execute方法会反过来调用CrudClass类中注册给它的doInStatement方法 概括a在调用b方法时b方法的入参为对象实例此对象有需要Override的方法即回调方法f 补充其实真正的JDBCtemplate又充当了A类又充当了B类。其中作为B类b方法即jdbcTemplate.execute和上述事例一样作为A类a方法则如下
jdbcTemplate.query(, new RowMapperObject() {Overridepublic Object mapRow(ResultSet resultSet, int i) throws SQLException {return null;}});
这里jdbcTemplate的query方法 调用了jdbcTemplate类的execute方法其中execute方法的入参为A类JDBCTemplate注册给它的StatementCallback接口的实例对象QueryStatementCallback这个对象有需要重写的回调方法doInStatement回调方法内部是各种CRUD的执行
public T T query(final String sql, final ResultSetExtractorT rse){class QueryStatementCallback implements StatementCallbackT, SqlProvider {QueryStatementCallback() {}Nullablepublic T doInStatement(Statement stmt) throws SQLException {ResultSet rs null;Object var3;try {rs stmt.executeQuery(sql);var3 rse.extractData(rs);} return var3;}}return this.execute((StatementCallback)(new QueryStatementCallback()));}
我们CRUDClass中使用的匿名内部类作为对象这里是使用的内部类作为的对象。无论使用哪种方式入参对象都有需要重写的方法即回调方法执行顺序
JDBCTemplate#query -- JDBCTemplate#execute -- 执行步骤123 -- 步骤4QueryStatementCallback#doInStatement#executeQuery -- 步骤56
场景2 JVM Hook
1、背景 Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {Overridepublic void run() {System.out.println(JVM程序关闭时会调用我);}}));JVM提供了B类b即Runtime#addShutdownHook()其中方法入参为线程对象thread对象有需要重写的方法即run方法即回调函数
2、作用
JVM程序关闭时会执行回调函数
3、分析addShutdownHook
static {Shutdown.add(1 ,false,new Runnable() {public void run() {runHooks();}});}static void runHooks() {// 对所有的hook线程执行startfor (Thread hook : threads) {hook.start();}}执行顺序
Runtime#addShutdownHook() -- static{} -- runHooks() -- hook.start() -- 执行hook线程的回调方法run方法
3.3.4策略模式
实战1
背景
业务任务有1、2、3、6、7、8 六种状态。其中6和8是成功状态。剩余任务状态都是失败状态。现在想提供一个接口可以根据任务号查询某个失败任务失败的原因。
接口入参为任务号 失败任务的状态
定义接口
public interface Task {TaskStatusEnum getTaskStatus();String queryTaskStatus(Integer status);
}定义接口实现类
Slf4j
Service
public class FailedTask implements Task {Overridepublic TaskStatusEnum getTaskStatus() {return TaskStatusEnum.FAILED;}Overridepublic String queryTaskStatus(Integer status) {return 网路原因计算失败;}
}定义枚举类和实现类一一对应
Getter
RequiredArgsConstructor
public enum TaskStatusEnum {INIT(1,初始化),FAILED(2,失败),SUCCESS(3,成功);private final Integer value;private final String desc;
}面向接口编程
Resource
private ListTask tasks;
private MapTaskStatusEnum, Task map;PostConstruct
private void initMap() {map tasks.stream().collect(Collectors.toMap(Task::getTaskStatus, Function.identity()));
}Test
public void t() {TaskStatusEnum status TaskStatusEnum.FAILED;Task task map.get(status);System.out.println(task.queryTaskStatus(1));
}这里是使用枚举 和 实现类一一对应的方式达到set效果。
实战2
1、背景
通过n分钟内业务告警m次来定级业务失败的严重程度。不同程度的告警 有不同的处理方式
3min内触发2次 》严重P1 》 电话告警
3min内触发1次 紧急P2 》 短信告警
10min内触发2次 正常P3 》 大象告警
2、思路
规则引擎 策略模式
通过规则引擎来判定出严重程度
根据不同的严重程度使用策略模式做不同的处理
实战3
根据前端入参判断CRUD哪个场景枚举值执行对应的接口实现类CRUD根据单据的类型判断是调拨还是退供1、2在执行对应的接口实现逆向调拨、退供
3.3.5 职责链模式
定义
一个请求经过A处理器处理 -- 然后再把请求传递给B处理器处理 -- 再传给C处理器。以此类推形成一个链条
作用
复用、扩展
模板
1、职责链模式1带终止
解释有一个处理器可以处理此请求则结束整个职责链。后续的处理器不会再被调用
抽象处理器
public interface Handle {boolean handle();
}处理器
Service
public class HandleA implements Handle{Overridepublic boolean handle() {// A处理器无法处理此请求boolean handled false;// 自己的业务return handled;}
}职责链
public class HandleChain {Resourceprivate ListHandle handleList;public void doXxx() {for (Handle handle : handleList) {boolean canDeal handle.handle();if (canDeal) {break;}}}
}2、职责链模式2无终止
解释职责链上的所有处理器都会依次处理此请求
抽象处理器
public interface Handle {void handle();
}处理器
Service
public class HandleA implements Handle{Overridepublic void handle() {// 处理请求}
}职责链
public class HandleChain {Resourceprivate ListHandle handleList;public void doXxx() {for (Handle handle : handleList) {handle.handle();}}
}实战敏感词过滤
1、背景
在文本发布时如果text中如果含有性、政治、广告相关的关键字则会被处理
处理方式一直接禁止本次文本发布
处理方式二过滤关键字违规词后再发布
2、处理方式一终止型责任链模式
抽象处理器
public interface SensitiveWordFilterHandle {boolean doFilter(String text);
}处理器
Service
public class SexyWordFilterHandle implements SensitiveWordFilterHandle{Overridepublic boolean doFilter(String text) {// 如果text中含有x、x、x词则任务含有了性相关的敏感词。则会终止职责链if (true) {return true;}return false;}
}
其他处理器类似职责链
public class SensitiveFilterHandleChain {ResourceListSensitiveWordFilterHandle sensitiveWordFilterHandles;public void legalText(String text) {for (SensitiveWordFilterHandle handle : sensitiveWordFilterHandles) {boolean legal handle.doFilter(text);if (legal) {// 允许发布} else {// 禁止}}}
}3、处理方式二无终止型责任链模式
抽象处理器
public interface SensitiveWordFilterHandle {String doFilter(String text);
}处理器
Service
public class SexyWordFilterHandle implements SensitiveWordFilterHandle{Overridepublic String doFilter(String text) {// 如果text包含了a、b、c等性相关词将这些词替换成xxxif (true) {return text.replace(abc, xxx);}return text;}
}
其他处理器类似职责链
public class SensitiveFilterHandleChain {ResourceListSensitiveWordFilterHandle sensitiveWordFilterHandles;public String legalText(String text) {String temp text;for (SensitiveWordFilterHandle handle : sensitiveWordFilterHandles) {temp handle.doFilter(temp);}return temp;}
}场景1Servlet-Filter
作用
可以实现对Http的请求过滤鉴权、限流、参数验证、对返回结果过滤打印日志等
作用域
支持Servlet的Web容器tomcat、jetty
解析
抽象处理器
public interface Filter {public default void init(FilterConfig filterConfig) throws ServletException {}public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException;public default void destroy() {}
}处理器
public class FilterHandler implements Filter {Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 处理req// 业务chain.doFilter(request, response);// 处理resp}
}职责链FilterChain
FilterChain是个规范tomcat具体实现是ApplicationFilterChain
public final class ApplicationFilterChain implements FilterChain {// 当前执行到哪个Filter处理器private int pos 0;//Filter处理器的个数private int n 0;//职责链数组private ApplicationFilterConfig[] filters new ApplicationFilterConfig[0];/*** 即chain.doFilter(request, response)方法*/Overridepublic void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {// ---internalDoFilter(request,response);}private void internalDoFilter(ServletRequest request,ServletResponse response)throws IOException, ServletException {// Call the next filter if there is oneif (pos n) {// 获取职责链上的下一个Filter处理器ApplicationFilterConfig filterConfig filters[pos];try {// 这里就有我们具体的处理器Filter filter filterConfig.getFilter();filter.doFilter(request, response, this);}}// 添加过滤器处理器void addFilter(ApplicationFilterConfig filterConfig) {}}场景2MVC-Interceptor
作用同Filter
作用域
MVC框架的一部分
和Servlet的Filter区别
Filter对req、resp的过滤都在doFilter方法中而HandlerInterceptor对req的拦截在preHandle方法中对resp的拦截在postHandle中是分开的执行顺序
http请求 -- Filter -- doChain过滤req、resp -- Servlet的service()中的doPost|doGet如果自定义Servlet继承了HttpServlet -- DispatcherServlet的doDsipatcher()内含applyPreHandle|appltPostHandle -- MVC HandlerIntercept的preHandle -- XxxController
解析
抽象拦截器处理器
public interface HandlerInterceptor {default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return true;}default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,Nullable ModelAndView modelAndView) throws Exception {}default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,Nullable Exception ex) throws Exception {}
}拦截器处理器
public class MyHandlerInterceptor implements HandlerInterceptor {public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {// 为false会拦截请求true会放行// 业务逻辑// eg根据req内容查询请求是否合法、用户是否存在等。如果不满足则请求被拦截掉return falsereturn true;}public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,Nullable ModelAndView modelAndView) throws Exception {}public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,Nullable Exception ex) throws Exception {// 一定会执行类似finally}
}职责链
public class HandlerExecutionChain {// 职责链数组private HandlerInterceptor[] interceptors;public void addInterceptor(HandlerInterceptor interceptor) {initInterceptorList().add(interceptor);}// 拦截reqboolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {// 获取职责链数组HandlerInterceptor[] interceptors getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i 0; i interceptors.length; i) {// 获取拦截器处理器HandlerInterceptor interceptor interceptors[i];// 拦截reqif (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex i;}}return true;}
}场景3自定义职责链
抽象处理器
public interface DefinedHandler {void doHandle(xxxReq req);void preHandle(Object req);// 执行顺序值越小优先级越高default Integer executeOrder() {return Integer.MIN_VALUE;}
}处理器
Service
public class LockStatusHandle implements DefinedHandler{Overridepublic void preHandle(Object req) {// 处理req比如查询某些数据}Overridepublic void postHandle(Object req) {// 处理resp比如根据req过滤留下符合的数据}public Integer executeOrder() {return 10;}
}Service
public class AIQtyHandle implements DefinedHandler{Overridepublic void preHandle(Object req) {// 处理req比如查询某些数据}Overridepublic void postHandle(Object req) {// 处理resp比如根据req过滤留下符合的数据}public Integer executeOrder() {return 10;}
}Service
public class OverStockHandle implements DefinedHandler{Overridepublic void preHandle(Object req) {// 处理req比如查询某些数据}Overridepublic void postHandle(Object req) {// 处理resp比如根据req过滤留下符合的数据}public Integer executeOrder() {return 20;}
}职责链
Component
public class HandlerChain {Resourceprivate ListDefinedHandler definedHandlerList;private MapInteger, ListDefinedHandler orderAndHandlerMap;PostConstructpublic void init() {// 职责链正排序值越小越先执行。值相同的处理器一同并发执行definedHandlerList.sort((h1, h2) - NumberUtils.compare(h1.executeOrder(), h2.executeOrder()));orderAndHandlerMap definedHandlerList.stream().collect(Collectors.groupingBy(DefinedHandler::executeOrder, TreeMap::new, Collectors.toList()));}public MapInteger, ListDefinedHandler getHandlerMap() {return orderAndHandlerMap;}
}使用
RunWith(SpringRunner.class)
SpringBootTest(classes ApplicationLoader.class)
Slf4j
public class SpringTest {Resourceprivate HandlerChain handlerChain;private ExecutorService threadPool Executors.newFixedThreadPool(20);Testpublic void test(){// 获取职责链上所有处理器MapInteger, ListDefinedHandler handlerMap handlerChain.getHandlerMap();// 按照处理器的值大小执行处理器值越小的处理器先执行。相同值的处理器并发执行handlerMap.forEach((order, handlerList) -{handlerList.stream().map(handler - CompletableFuture.runAsync(() -{handler.preHandle(null);}, threadPool)).collect(Collectors.toList());});}
}分析
传统的职责链例如Filter和HandlerInterceptor都是使用的数组存储的。然后从数组中按照处理器存储的index先后顺序一个一个取直到全部处理器都执行完毕。 区别这里的职责链不是数组[]存储然后遍历index获取处理器。而是通过在抽象处理器中定义executeOrder值每个具体的处理器自己根据业务优先级自定义自己的处理顺序。优先级高的处理器先执行。相同优先级并行执行
3.3.6 状态模式
定义
状态机由3部分组成
1、状态Status
2、事件Event
3、动作Action
其中事件Event也被称为状态转移条件。一旦触发了事件则一定伴随着状态改变Status A - B可能会有相应的动作Action产生
作用
避免了大量if-else逻辑可扩展性好后续新增状态或状态的改变带来动作的执行只需要新增状态类 和 动作执行类即可
使用场景
1、不建议使用场景
对象的状态单行道变换。举例逆向任务的状态初始化 - 已落表 - 已计算 - 成功|部分成功|失败
2、建议使用场景
当一个对象的状态5而且状态之间可以相互转换当达到不同的状态会产生不同的动作时建议使用状态模式
实战-电商订单
1、状态机 2、实现
2.1 抽象状态类
public interface OrderStatus {
}2.2 状态类
待付款
/*** 待付款状态类* 此状态类状态的可能转移为* 待付款 - 取消付款用户点击了取消付款按钮* 待付款 - 待发货(用户付款了)*/
public class WaitPay implements OrderStatus {private static final WaitPay instance new WaitPay();private WaitPay(){}public static WaitPay getInstance() {System.out.println(订单生成,30分钟内有效);return instance;}/*** 事件Event用户取消付款了*/public void cancelPay(OrderStatusMachine machine) {System.out.println(Event:用户取消了付款);// 状态Status: 待付款 - 取消付款machine.setOrderStatus(CancelPay.getInstance());// 动作ActionSystem.out.println(更细订单状态为已取消);}/*** 事件Event用户付款了*/public void clickPay(OrderStatusMachine machine) {System.out.println(Event:用户付款了);// 状态Status: 待付款 - 待发货machine.setOrderStatus(WaitDeliver.getInstance());// 动作ActionSystem.out.println(1、更细订单状态为待发货);System.out.println(2、将钱存入支付宝);System.out.println(3、淘宝信息提醒您的地址信息为xxx请核对);}
}取消付款
/*** 取消状态类* 此状态为终态不会再转移*/
public class CancelPay implements OrderStatus {private static final CancelPay instance new CancelPay();private CancelPay(){}public static CancelPay getInstance() {return instance;}
}待发货
/*** 待发货状态类* 此状态类状态的可能转移为* 待发货 - 退货退款用户点击了申请退货退款按钮* 待发货 - 待收货(仓库提交发货)*/
public class WaitDeliver implements OrderStatus{private static final WaitDeliver instance new WaitDeliver();private WaitDeliver(){}public static WaitDeliver getInstance() {return instance;}/*** 事件Event用户点击了申请退货退款按钮*/public void waitDeliverApplyRefund(OrderStatusMachine machine) {System.out.println(Event:用户申请了退货退款);// 状态Status: 待发货 - 退货退款machine.setOrderStatus(Refund.getInstance());// 2.动作System.out.println(1、更新订单状态为退货退款);System.out.println(2、支付宝将钱原路退回给用户);}/*** 事件Event仓库提交发货*/public void submitDelivery(OrderStatusMachine machine) {System.out.println(Event:仓库提交了发货);// 状态Status: 待发货 - 待收货machine.setOrderStatus(WaitReceive.getInstance());// 2.动作System.out.println(1、更细订单状态为待收货);System.out.println(2、发送信息给用户您的包裹正在快马加鞭的赶来);}
}待收货
/*** 待收货状态类* 此状态类状态的可能转移为* 待收货 - 退货退款用户点击了申请退货退款按钮* 待收货 - 待评价(确认收货)*/
public class WaitReceive implements OrderStatus{private static final WaitReceive instance new WaitReceive();private WaitReceive(){}public static WaitReceive getInstance() {return instance;}/*** 事件Event用户点击了申请退货退款按钮*/public void waitReceiveApplyRefund(OrderStatusMachine machine) {System.out.println(Event:用户申请了退货退款);// 状态Status: 待收货 - 退货退款machine.setOrderStatus(Refund.getInstance());// 2.动作System.out.println(1、更新订单状态为退货退款);System.out.println(2、支付宝将钱原路退回给用户);}/*** 事件Event用户确认收货*/public void confirmReceive(OrderStatusMachine machine) {System.out.println(Event:用户确认了收货);// 状态: 待收货 - 待评价machine.setOrderStatus(WaitReview.getInstance());// 动作System.out.println(1、更细订单状态为待评价);System.out.println(2、发送信息给用户亲麻烦评价下商品);}
}退货退款
/*** 退货退款状态类* 此状态为终态不会再转移*/
public class Refund implements OrderStatus{private static final Refund instance new Refund();private Refund(){}public static Refund getInstance() {return instance;}
}待评价
/*** 待评价状态类* 此状态类状态的可能转移为* 待评价 - 订单完成用户评价了商品*/
public class WaitReview implements OrderStatus{private static final WaitReview instance new WaitReview();private WaitReview(){}public static WaitReview getInstance() {return instance;}/*** 事件Event用户评价商品*/public void reviewGoods(OrderStatusMachine machine) {System.out.println(Event:用户评价了商品);// 状态: 待评价 - 完成machine.setOrderStatus(Finish.getInstance());// 动作System.out.println(1、更细订单状态为已完成);System.out.println(2、支付宝将钱打给商家);System.out.println(3、用户的积分增加);}
}订单完成
/*** 完成状态类* 此状态为终态不会再转移*/
public class Finish implements OrderStatus{private static final Finish instance new Finish();private Finish(){}public static Finish getInstance() {return instance;}
}2.3 状态机
每个非终态的状态类中的方法都需要在状态机中定义下
Data
public class OrderStatusMachine {private OrderStatus orderStatus;public OrderStatusMachine(){this.orderStatus WaitPay.getInstance();}/*** 事件Event用户取消付款了*/public void cancelPay() {((WaitPay) this.orderStatus).cancelPay(this);}/*** 事件Event用户付款了*/public void clickPay() {((WaitPay) this.orderStatus).clickPay(this);}/*** 事件Event待发货时用户点击了申请退货退款*/public void waitDeliverApplyRefund() {((WaitDeliver) this.orderStatus).waitDeliverApplyRefund(this);}/*** 事件Event用户确认收货*/public void submitDelivery() {((WaitDeliver) this.orderStatus).submitDelivery(this);}/*** 事件Event待收货时用户点击了申请退货退款*/public void waitReceiveApplyRefund() {((WaitReceive) this.orderStatus).waitReceiveApplyRefund(this);}/*** 事件Event用户评价商品*/public void confirmReceive() {((WaitReceive) this.orderStatus).confirmReceive(this);}/*** 事件Event用户评价商品*/public void reviewGoods() {// 状态: 待评价 - 订单完成((WaitReview) this.orderStatus).reviewGoods(this);}
}2.4 使用状态机
RunWith(MockitoJUnitRunner.class)
Slf4j
public class BaseTest {Testpublic void test() {System.out.println(李四的订单start);OrderStatusMachine m1 new OrderStatusMachine();// 1.李四(付款 - 仓库发货 - 确认收货 - 评价)m1.clickPay();m1.submitDelivery();m1.confirmReceive();m1.reviewGoods();System.out.println(李四的订单end);System.out.println(王五的订单start);OrderStatusMachine m2 new OrderStatusMachine();// 2.王五(付款 - 退货退款)m2.clickPay();m2.waitDeliverApplyRefund();System.out.println(王五的订单end);System.out.println(赵六的订单start);OrderStatusMachine m3 new OrderStatusMachine();// 3.赵六(付款 - 仓库发货 - 退货退款)m3.clickPay();m3.submitDelivery();m3.waitReceiveApplyRefund();System.out.println(赵六的订单end);}
}
3、总结
3.1 状态机中 public void cancelPay() {((WaitPay) this.orderStatus).cancelPay(this);}如果不这样写那么对于原状态为其他的比如待发货状态不会有任何Event事件更不会有Action动作但是发货状态类也需要定义WaitPay方法只不过方法内没东西
public class WaitDeliver implements OrderStatus{private static final WaitDeliver instance new WaitDeliver();private WaitDeliver(){}public static WaitDeliver getInstance() {return instance;}// 因为对于WaitDeliver代发货状态而言他没有取消付款事件所以定义个空方法。// 同理他也没有评价事件。public void cancelPay() {}/*** 事件Event用户点击了申请退货退款按钮*/public void waitDeliverApplyRefund(OrderStatusMachine machine) {System.out.println(Event:用户申请了退货退款);// 状态Status: 待发货 - 退货退款machine.setOrderStatus(Refund.getInstance());// 2.动作System.out.println(1、更新订单状态为退货退款);System.out.println(2、支付宝将钱原路退回给用户);}/*** 事件Event仓库提交发货*/public void submitDelivery(OrderStatusMachine machine) {System.out.println(Event:仓库提交了发货);// 状态Status: 待发货 - 待收货machine.setOrderStatus(WaitReceive.getInstance());// 2.动作System.out.println(1、更细订单状态为待收货);System.out.println(2、发送信息给用户您的包裹正在快马加鞭的赶来);}
}这样写的好处是只有原状态为待付款状态才会有Event取消付款才会有后续Action动作。其他状态类不需要关注内部也不需要定义此方法
3.2 单例
private static final WaitDeliver instance new WaitDeliver();防止反复创建
3.3 扩展
对于像WaitPay状态类当用户付款了后状态变为待发货。然后有好几个总做要去做 /*** 事件Event用户付款了*/public void clickPay(OrderStatusMachine machine) {System.out.println(Event:用户付款了);// 状态Status: 待付款 - 待发货machine.setOrderStatus(WaitDeliver.getInstance());// 动作ActionSystem.out.println(1、更细订单状态为待发货);System.out.println(2、将钱存入支付宝);System.out.println(3、淘宝信息提醒您的地址信息为xxx请核对);}当然这3个动作都可以在clickPay方法中定义但是如果后续用户付款后新增加动作或者原动作不执行了。则需要改clickPay中代码违背了开闭原则。
解决责任链模式
参考责任链模式中场景3自定义责任链。可以根据动作的执行先后对应处理器的先后执行。
这样不同的动作都实现了抽象动作这样新增、删除等直接增加对应的动作 - 处理器即可满足开闭原则。
补充前提是clickPay事件Event触发后动作确认存在频繁变动的场景
3.3.7 解释器模式
定义
描述如何 构建一个简单的语言解释器
作用
解释自定义“语言”
实战
背景
开发一个监控业务系统。当每分钟接口出错数目超过10或者每分钟API的调用总数大于10w则触发告警。当然具体的告警可以是短信、电话等
分析
我们可以把自定义的告警规则当做一种“语言”的语法规则然后实现一个解释器即可。
针对用户的输入判断是否触发告警
实现
举例
String exp “key1 100 key2 100000 || key3 404”;
表达式含义
key1即每分钟的错误数大于100 同时key2即每分钟的接口调用量大于10w
或者即key3接口返回404
上述场景则返回true需要告警
使用 Testpublic void test() {String exp key1 100 key2 100000 || key3 404;Expression expression RuleExpressionFactory.getExpression(exp);MapString, Long map new HashMap();map.put(key1, 200L);map.put(key2, 1000L);map.put(key3, 404L);boolean intercept expression.intercept(map);System.out.println(intercept);}解释器接口
public interface Expression {boolean intercept(MapString , Long map);
}大于解释器
public class GreaterExpression implements Expression{private String key;private Long value;public GreaterExpression(String strExpression) {String[] elements strExpression.trim().split(\\s);this.key elements[0].trim();this.value NumberUtils.toLong(elements[2].trim());}public GreaterExpression(String key, Long value) {this.key key;this.value value;}Overridepublic boolean intercept(MapString, Long map) {if (!map.containsKey(key)) {return false;}Long val map.get(key);return val value;}
}小于解释器
public class LessExpression implements Expression{private String key;private Long value;public LessExpression(String strExpression) {String[] elements strExpression.trim().split(\\s);this.key elements[0].trim();this.value NumberUtils.toLong(elements[2].trim());}public LessExpression(String key, Long value) {this.key key;this.value value;}Overridepublic boolean intercept(MapString, Long map) {if (!map.containsKey(key)) {return false;}Long val map.get(key);return val value;}
}等于解释器
public class EqualsExpression implements Expression{private String key;private Long value;public EqualsExpression(String strExpression) {String[] elements strExpression.trim().split(\\s);this.key elements[0].trim();this.value NumberUtils.toLong(elements[2].trim());}public EqualsExpression(String key, Long value) {this.key key;this.value value;}Overridepublic boolean intercept(MapString, Long map) {if (!map.containsKey(key)) {return false;}Long val map.get(key);return Objects.equals(val, value);}
}解释器
public class AndExpression implements Expression{private ListExpression 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(ListExpression expressionList) {this.expressionList.addAll(expressionList);}Overridepublic boolean intercept(MapString, Long map) {for (Expression expression : expressionList) {if (expression.intercept(map)) {System.out.println(expression 符合表达式);} else {return false;}}return true;}
}||解释器
public class OrExpression implements Expression{private ListExpression 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(ListExpression expressionList) {this.expressionList.addAll(expressionList);}Overridepublic boolean intercept(MapString, Long map) {for (Expression expression : expressionList) {if (expression.intercept(map)) {return true;}}return false;}
}解释器工厂类
UtilityClass
public class RuleExpressionFactory {public Expression getExpression(String exp) {if (exp.contains()) {return new AndExpression(exp);} else if (exp.contains(||)) {return new OrExpression(exp);} else if (exp.contains()) {return new GreaterExpression(exp);} else if (exp.contains()) {return new LessExpression(exp);} else if (exp.contains()) {return new EqualsExpression(exp);}throw new RuntimeException();}
}场景
编译器、规则引擎这里举例阿里的规则引擎QLExpress、正则表达式
1、地址https://github.com/alibaba/QLExpress dependencygroupIdcom.alibaba/groupIdartifactIdQLExpress/artifactIdversion3.3.2/version/dependency2、 eg1 Testpublic void t() throws Exception {ExpressRunner runner new ExpressRunner();DefaultContextString, 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 allos;//false
// String express allo||s; //trueObject res runner.execute(express, context, null, true, false);System.out.println(res);}eg2 Testpublic void t() throws Exception {DefaultContextString, 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;DefaultContextString, Object computeContext new DefaultContext();for (Map.EntryString, MetaRuleResult entry : context.entrySet()) {computeContext.put(entry.getKey(), entry.getValue().getResult());}String ruleExpress olswlollo;result runner.execute(ruleExpress, computeContext, null, true, false);Boolean bResult (Boolean) result;System.out.println(bResult);//falseString failReason buildFailureReason(ruleExpress, context);System.out.println(failReason);//售罄预警且锁库存不可更改且锁库且修改值小于等于OR值可以更改}private String buildFailureReason(String ruleExpress, DefaultContextString, MetaRuleResult context) {StringBuilder sb new StringBuilder();sb.append(修改失败原因如下);context.forEach((rule, meta) - {if (! meta.getResult()) {sb.append(meta.getFailureReason() ; );}});return sb.toString();}context key为规则rule内容eg”a“允许、llo锁库且小于OR允许、s即20点后修改值小于可履约库存允许修改 value为rpc查询依赖的各个数据判断当前sku是否满足这个规则满足为true不满足为false egkey “s”此时为20点后修改最大售卖量想把最大售卖量从50 - 30计算发现可履约库存为4030 40则允许修改即MetaRuleResult的result值为true express 表达式为修改规则的组合 db中规则为allo、llost等等 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中只写CRUDRepository中通过构建example调用Mapper的CRUD Service中对查询出的PO进行Convert成BO|DTO 接口的TReq不要渗透到底层 每一层只提供最基本的查询。至于查询的结处理成什么样由上一层调用方自己决定。这样每一层的方法复用性才更好。 网关层只写rpc查询。并发查询业务侧自己封装Repository只写查询出的List list。Service自己对list进行convert2DTO或别的 数据体 PO - BO|DTO - VOPO(id、name、age、sex、pwd、edu) - BO(name, age, sex, edu) - DTO(“mjp”, 1, “man”, “大学”) - VO(“mjp”, “青年”, “男士”, “本科”)BO 和 DTO基本等效 数据需要返回给前端且大量字段需要后端优化展示内容才需要VO正常DTO即可
4.2 重构
4.2.1what
在保持功能不变的前提下利用设计原则、设计模式等理论来修改设计上的不足 和 提高代码质量
4.1.2why
避免一开始的过度设计运用模式
初级在原有框架下写
高级从零开始设计代码结构搭建代码框架
资深发觉代码、框架等存在的问题重构
4.1.3 context
1、大型重构 涉及到的面 架构商品、物流、模块触发、计算、合单下发、代码结构单据和单据明细、类之间的交互同步、异步 how如何大型重构 使用设计思想、原则 和模式。常见手段有 模块化计划内部划分为3个模块解耦计划内部通过MQ解耦抽象可复用单据下发抽象成接口分层Controller、Service、Dao 解耦手段封装、抽象、模块化、中间层 作用哪些代码需要解耦如何解耦 列出计划分阶段重构 每个阶段完成一小部分代码的重构然后UT再继续下一阶段的重构让代码始终可运行每个阶段要控制重构影响的代码范围考虑好新老兼容。切记不可无计划重构
2、小型重构
涉及到的面 类单一职责、提取公共 代码函数里氏替换原则Repository中方法只写查询具体查询到的结果交于上层自己去convert这样方法复用性更好变量等代码规范
4.1.4 when
新需求涉及对老代码的改动。如果时间充足 老代码设计或质量存在问题则重构重构意识把重构作为开发的一部分成为一种习惯。对自己和代码都好
4.1.5 如何避免重构出问题
UT
本身也是一次CR代码的可测试性从侧面也反映出代码的设计是否合理
ecute(express, context, null, true, false);System.out.println(res);}4.2 重构
4.2.1what
在保持功能不变的前提下利用设计原则、设计模式等理论来修改设计上的不足 和 提高代码质量
4.2.2why
避免一开始的过度设计运用模式
初级在原有框架下写
高级从零开始设计代码结构搭建代码框架
资深发觉代码、框架等存在的问题重构
4.2.3 context
1、大型重构 涉及到的面 架构逆向链路商品、物流、模块触发、计算、合单下发、代码结构单据和单据明细、类之间的交互同步、异步 how如何大型重构 使用设计思想、原则 和模式。常见手段有 模块化逆向计划内部划分为3个模块解耦逆向计划内部通过MQ解耦抽象可复用单据下发抽象成接口分层Controller、Service、Dao 解耦手段封装、抽象、模块化、中间层 作用哪些代码需要解耦如何解耦 列出计划分阶段重构 每个阶段完成一小部分代码的重构然后UT再继续下一阶段的重构让代码始终可运行每个阶段要控制重构影响的代码范围考虑好新老兼容。切记不可无计划重构
2、小型重构
涉及到的面 类单一职责、提取公共 代码函数里氏替换原则Repository中方法只写查询具体查询到的结果交于上层自己去convert这样方法复用性更好变量等代码规范
4.2.4 when
新需求涉及对老代码的改动。如果时间充足 老代码设计或质量存在问题则重构重构意识把重构作为开发的一部分成为一种习惯。对自己和代码都好
4.2.5 如何避免重构出问题
UT
本身也是一次CR代码的可测试性从侧面也反映出代码的设计是否合理