1、写出好的代码,个人认为依次重要程度为:
健壮性
个人理解为最重要的之一,好的代码,首先是无bug代码
代码中,常见可能引起问题的点(重要程度不分先后):
性能:并发会不会存在问题;并发度大小(大了可能对下游压力过大,提前对其好SLA);是否需要异步处理(写OP日志、推送消息等)
降级、兜底:下游接口拿不到数据或不可用,产品侧是否有兜底数据、技术侧是否有兜底方案
限流:对上游是否需要限流,保护我们的服务
数据量评估:数据量增大,甚至极端场景下,会不会有慢查询,索引是否合理
幂等:消息是否可能重复
一致性:上下游的状态是否一致(eg:逆向计划无可用库存,将任务状态置为终态,如何上游有动作依赖我们的状态,那我们也要告诉上游任务终态了。否则上游发现下游一直未终态,他们可能会重试等)
主从延时:写完读
接口异常:是否强依赖、重试等
中间件异常:redis异常,是否强依赖、leaf异常,是否有备用方案生成单据号等
边界条件: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:skuId变为skuIdList,一般是新增字段,然后做好上线过度)
2、设计原则、设计模式等,目的都是为了写“好”代码
1、看似面向对象,实则面向过程的做法
滥用get、set方法,违反了面向对象的特征:封装。除非需要否则,不要给属性定义setter
Constants常量类:不要单独设计此常量类。好的 做法:哪个类用的用到了某个常量,在此类汇总定义即可
否则,不易维护:改一个常量,影响太多地方,不能轻易修改;不易复用:要在另一个项目中复用本项目的某类,此类中又依赖Constants,相当于把整个Constants都一并引入了
2、面向对象编程步骤
以:对接口进行鉴权为例
分析实现步骤
划分职责,识别出有哪些类
如果是大需求,涉及多个模块,则需要先把需求按照模块划分。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:定义属性和方法:
ApiUrl
3:buildUrl()
4:getTokenFromUrl(String url)
:getUseIdFromUrl(String url)
:getTsFromUrl(String url)
定义类和类之间的交互关系(继承、实现、聚合、组合、依赖等)
思考:
我理解的面向对象编程,就好比要外出旅游,将这个需求分为:衣食住行四个模块
就是在未出发之前,衣食住行模块都想好,方法也想好(先公交、再火车),类之间如何衔接(对应类之间的关系)。然后按照这些去旅游。
面向过程编程,则是准备去旅游。
类似这种,我理解为面向过程。
1、什么时候使用接口
2、要用接口和抽象类时,选择哪个
3、基于接口编程注意事项
eg:sku查询算法值(不同的sku对应的供货链路不同,不同的供货链路,对应查询不同的算法类型值),则queryAlQty(Integer supplyType,Long skuId)不如
queryAlQty(Long skuId):内部封装了查询供货链路。
(设计模式本身的原则)
1、概念:一个类只负责一项职责。如果负责了多个,就需要拆分成多个类
2、举例:OrderRepository中不要涉及对SkuDO的CRUD
3、作用:
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方法
类已经找不到合适的词来形容了,职责定义已经不清晰了,可拆
类中大量的方法都在对某几个属性进行操作,则可以考虑将这几个属性抽取出来单独成一个类
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);
1、概念:高层模块(调用者)不要依赖低层模块(被调用者),二者应该通过抽象(接口)互相依赖
eg:Tomcat:高层模块,编程的Web应用程序(低层模块)只需要部署在Tomcat容器下,便可以被Tomcat调用运行。
Tomcat不依赖Web应用程序,只要Web应用程序满足Servlet接口规范,那么无论你是啥Web应用程序,都可以在Tomcat上运行。
Tomcat和Web应用程序通过Servlet接口互相依赖
2、作用:通用性好、扩展性好
3、控制翻转IOC
定义:原本是程序员自己控制整个程序的执行,使用框架之后,框架来控制程序流程。流程的控制权从程序员反转到了框架
举例:
程序员控制程序执行
public class UserServiceTest {
public static boolean needTest() {
return true;
}
public static void main(String[] args) {
if (needTest()) {
System.out.println("do test");
} else {
System.out.println("not do test");
}
}
}
public class SkuServiceTest {
public static boolean needTest() {
return false;
}
public static void main(String[] args) {
if (needTest()) {
System.out.println("do test");
} else {
System.out.println("not do test");
}
}
}
框架控制程序执行
//这里类似模板设计
public abstract class BaseTest {
public boolean needTest();//预留扩展点
public void run() {
if (needTest()) {
System.out.println("do test");
} else {
System.out.println("not do test");
}
}
}
public class UserServiceTest extends BaseTest{
@Override
public static boolean needTest() {
return true;
}
}
public class SkuServiceTest extends BaseTest{
@Override
public static boolean needTest() {
return false;
}
}
public class ApplicationLoader {
public static void main(String[] args) {
SpringApplication.run(ApplicationLoader.class, args);
private static final List<BaseTest> LIST = new ArrayList<>();
for (BaseTest test : LIST) {
test.run()
}
}
public void register(BaseTest test) {
LIST.add(test);
}
}
ApplicationLoader.register(new UserServiceTest());
ApplicationLoader.register(new UserServiceTest());
1、在BaseTest预约扩展点
2、不同的Test类,实现自己业务相关的功能(是否needTest),不需要再写用于执行流程的main函数了
3、将不同的Test类,添加到ApplicationLoader
4、在ApplicationLoader启动的时候执行main函数,会遍历执行所有Test的run方法
程序的执行(main函数执行),由程序员控制(写在不同Test中),反转到框架控制(统一register到Application,它启动的时候,会执行所有Test的run方法)
4、依赖注入DI
定义:
A类中使用B类,不同new B()的方法创建b,而是将B在外部创建好后,通过new A(b)构造函数、函数参数func(B b)、set属性等方式传递(注入)给A类使用
和控制反转的关系:
控制反转不是具体的实现技巧,而是一种用于指导框架设计的思想。而DI则是具体的编码技巧,是IOC的具体实现
依赖注入 和 非依赖注入
Notification类负责将商品的促销、验证码消息等给用户。它依赖MessageProductor生产者类发送消息
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");
依赖注入
public interface MessgaeProductor {
public boolean send(String msg);
}
// B1:短信生产类
public class SmsProductor implements MessgaeProductor{
@Override
public boolean send(String msg) {
//发送短信
}
}
// B2:大象消息生产类
public class DaxiangProductor implements MessgaeProductor{
@Override
public boolean send(String msg) {
//发送大象信息
}
}
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();//创建对象b
Notification notification = new Notification(messgaeProductor);//通过构造函数,将b依赖注入A类中
notification.sendMessage("msg");
}
}
5、依赖注入框架
产生背景
常见的依赖注入框架:Spring、Google的Guice
作用
举例
public class A{
@Resource
private B b;
public static void main(String[] args) {
b.send("msg");
}
}
通过Spring框架提供的扩展点-后置处理器,@Resource private B b,就可以实现B的创建和生命周期的管理,同时后置处理器通过set的将b注入A类中
1、概念: 子类对象能够替换程序中父类对象出现的任何地方,并且保证原来的逻辑行为不变且正确性不被破坏。
一句话:子类重写父类的方法,不要改变原有方法的逻辑(方法声明、输入、输出、对异常的处理等约定)
2、作用:指导子类如何设计,不改变父类的逻辑
3、子类重写父类方法时,常见的违背里氏替换原则的场景有
违背父类的输入
父类输入Integer是整数,子类输入Integer要求是正整数
违背父类的输出
父类catch代码块中return的是空集合,子类重写方法中catch块中return的是null
违背父类方法的声明
父类sortBySkuId,查询结果按照skuId排序。子类sortBySkuId查询结果按照实时销量排序了
违背父类异常的处理
父类valid参数时,不满足时,抛出的是ArguementNullException。子类抛出的是illeagalException
4、思考:
1、概念:一个模块、类、方法对修改关闭,对扩展开放。添加一个新功能,应该是新增代码,而非 修改代码。
补充:有的时候新增功能,是改变了类,对于类而言是被改变了,但是对于方法来说没有改变,也满足开闭。
2、作用:提升代码的扩展性,23种设计模式的目的都是为了满足开闭原则。尽量让修改更为集中、给小、更上层。核心的、复杂的逻辑尽量不修改少修改
3、编码:可以参考RuleExpressHelper,通过遍历规则枚举类,将枚举类code、desc提前put至Map
4、如何做到满足开闭原则
业务层面:扩展意识、抽象意识很重要
多想下,这块逻辑,后续会有哪些需求变更,设计代码结构的时候,可以提前预留好扩展点,以便将来改动小,新的代码可灵活的插入
eg1:规则中心,现有n个规则,如果后续新增规则,是不是不改变现有代码逻辑实现,仅仅通过新增枚举类就可以实现页面的CRUD。
eg2:退供下发执行,后续会不会有逆向调拨下发执行。可以提前设计下发逻辑,抽象出接口。
但是对于未来不确定的功能点,当下没必要过度设计,后续持续重构即可
技术层面:提升代码的可扩展性即基于接口编程、设计原则、设计模式(策略、模板、状态、装饰着、职责链等)
感触最深的就是策略模式,定义行为(接口方法),新增功能,只需要新增对应的新实现,不需要改动原本的行为实现
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的发送
复用意识:
1、定义:一个对象应该对其他对象有最少的了解,即最小知道。或只是直接的朋友交流
2、作用:低耦合、高内聚
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;
}
符合设计
/**
* 公司
*/
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;
}
单例、工厂、建造者
一个类只允许创建唯一一个对象。这里唯一性作用的范围是进程
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的唯一性、线程安全性
表示全局唯一类
处理共享资源访问冲突(写日志、共享数据库连接池等)
eg
解决:线程1和2使用单例模式创建FileWriter,FileWriter本身是线程安全的,其内部实现了对象级别的锁即相同的FileWriter实例,在写操作是线程安全的,不会被覆盖。
public void write(String str, int off, int len) throws IOException {
synchronized (lock) {
char cbuf[];
if (len <= WRITE_BUFFER_SIZE) {
if (writeBuffer == null) {
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
cbuf = writeBuffer;
} else { // Don't permanently allocate very large buffers.
cbuf = new char[len];
}
str.getChars(off, (off + len), cbuf, 0);
write(cbuf, 0, len);
}
}
public class IDGenerator {
private static final AtomicLong id = new AtomicLong(0);
private static final ThreadLocal<IDGenerator> tl = new ThreadLocal<>();
public IDGenerator getInstance() {
tl.set(new IDGenerator());
return tl.get();
}
public Long getId() {
return id.incrementAndGet();
}
}
为了保证全局唯一,除了单例外,我们还可以使用工厂模式来实现
将不同后缀的配置文件解析成类
根据文件路径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;
}
}
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;
}
}
上述代码每次createConfigParser都会new一个新的Configparser对象。我们可以提前将Configparser对象创建好放到map中缓存起来,当调用createConfigParser方法时,直接从缓存中拿去。
public class ConfigParserFactory {
private static final Map<String, Configparser> map = new HashMap<>();
static {
map.put("xml", new XmlConfigparser());
map.put("yaml", new YamlConfigparser());
map.put("properties", new PropertiesConfigparser());
}
// 这里Configparser是接口,XmlConfigparser是接口实现类
public Configparser createConfigParser(String fileSuffix) {
return map.get(fileSuffix);
}
}
1、创建名称特点:create、getInstance、newInstance、valueOf、of、as
2、优点:当新增了YmlConfigparser解析类,只需要实现Configparser接口重写parse方法即可,然后将其添加到map中。满足开闭原则
规则中心定义卡控最大售卖量规则,规则解析
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、使用场景
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));
}
}
优点:
StringBuilder sb = new StringBuilder();
if(Objects.equals(rule,"a")) {
sb.append("允许修改")
} else if(Objects.equals(rule,"n")) {
sb.append("且");
sb,append("不允许修改")
} else if () {
}
1、创建Calendar实例
Calendar instance = Calendar.getInstance();
2、简答工厂模式
private static Calendar createCalendar(TimeZone zone,Locale aLocale){//这里zone和aLocale(zh_CN)都是默认值
Calendar cal = null;
//根据地区的语言和国家来判断日历类型
if (cal == null) {
if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
cal = new BuddhistCalendar(zone, aLocale);
} else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
&& aLocale.getCountry() == "JP") {
cal = new JapaneseImperialCalendar(zone, aLocale);
} else {
// 其他情况一律返回公历
cal = new GregorianCalendar(zone, aLocale);
}
}
return cal;
}
为什么说这是一种简单工厂模式呢?
因为静态createCalendar() 创建实例时,根据该方法传入的参数来返回对应的 Calendar 实现类,符合工厂模式的思想(类似场景1)
当代码中存在大量if - else,根据A获取|创建B的场景,则可以考虑使用简单工厂模式
相当于一个大型工厂,负责在程序启动时,根据各种配置信息,创建对象。因为它持有一堆对象,所以又叫容器
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放入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);
}
}
@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;
}
}
<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.mjp.lean.RateLimiter">
<constructor-arg ref="redis">constructor-arg>
bean>
<bean id="redis" class="com.mjp.lean.Redis">
<constructor-arg type="java.lang.String" value="127.0.0.1"/>
<constructor-arg type="int" value="6001"/>
bean>
beans>
@NoArgsConstructor
@AllArgsConstructor
@Data
public class RateLimiter {
private Redis redis;
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Redis {
private String ipAddress;
private int port;
}
主要就是将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);
}
}
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;
}
}
比如 initmethod=loadProperties(),在创建好对象后,会主动调用 init-method属性指定的方法来初始化对象。
destroy-method=updateConfigFile(),在对象被最终销毁之前,会主动调用 destroy-method 属性指定的方法来做一些清理工作(释放数据库连接池、关闭文件)。
1、构造器的缺点
如果类中有很多的属性,则new X(太多的属性,容易赋值错)
2、set方法的缺点
当要求对象一旦被new其属性值就不允许被修改,则不能对外暴露set
3、建造者的缺点:
建造者内部类中也需要再定义一遍和外部类中一样的属性
1、private 构造器
2、只提供get方法,不提供set
3、定义成员内部类Builder类
/**
* Author:majinpeng
* Date: 2023/08/26 23:07
*/
@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);
}
}
}
@Data
@Accessors(chain = true)
public class UserDemo {
private String name;
private Integer age;
}
UserDemo m = new UserDemo().setName("m").setAge(18);
工厂模式是创建一系列相同类型的对象
建造者模式是创建一个复杂属性的对象
代理、装饰者、适配器、享元
在不改变原有类的情况下,引入代理类来给原始类附加功能
日志打印、权限校验、Rhino限流、事务、Swan最大努力重试
为给个接口方法的执行,计算花费的时间
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{
@Override
public void eat() {
System.out.println("eat");
}
}
StopWatchProxy stopWatchProxy = new StopWatchProxy();
User user = (User) stopWatchProxy.creatProxy(new UserImpl());
user.eat();
给原始类添加增强功能
代理模式中代理类附加的是跟原始类无关的功能(日志、权限校验等);装饰器类附加的是跟原始类相关的增强功能(原始类是直接读、装饰类增加的功能是缓存读)
可以对A嵌套使用多个装饰器类
public interface IA {
void f();
}
@Service
public class A implements IA{
@Override
public void f() {
System.out.println("f");
}
}
@Service
public class ADecorator implements IA{
@Resource
private A a;
@Override
public void f() {
// 增强
System.out.println("增强1");
a.f();
// 增强
System.out.println("增强2");
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
public class SpringTest {
@Resource
private ADecorator aDecorator;
@Test
public void test() {
aDecorator.f();
}
}
抽象类:InputStream
A:FileInputStream
ADecorator:BufferedInputStream、DateInputStream
FileInputStream fis = new FileInputStream(new File("xxx.txt"));
BufferedInputStream bis = new BufferedInputStream(fis);
bis.read();
3.1 使用装饰者增强后的read
@Test
public 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的read
fis.read(null, 1, 1);
// 增强
return 1;
}
}
@Data
@AllArgsConstructor
public class FatherFilterInputStream extends InputStream {
protected FileInputStream fis;
@Override
public int read() throws IOException {
return fis.read();
}
}
这样当执行SonBufferedInputStream bis = new SonBufferedInputStream(fis)时
4.1 抽象类InputStream-read()
4.2 A : read(是个nativate方法)
public class FileInputStream extends InputStream{
private native int read() throws IOException;
}
4.3 ADecorator(BufferedInputStream)-read
这里的BufferedInputStream bis = new BufferedInputStream(fis);
=》
public BufferedInputStream(InputStream in, int size) {
super(in);//super(fis)
buf = new byte[size];
}
=>
public class FilterInputStream extends InputStream {
//即ADecorator中组合了A(fis)
protected volatile InputStream in;//fis
protected FilterInputStream(InputStream in) {
this.in = in;//this.fis = fis
}
}
==》等效
public class FilterInputStream extends InputStream {
protected volatile FileInputStream fis;
protected FilterInputStream(FisleInputStream fis) {
this.fis = fis;
}
}
这样一来,就相当于子类ADecorator(Buffered)中通过继承父类也具有属性A(FileInputStream)
4.4 bis.read()
public synchronized int read() throws IOException {
fill();//实现
return getBufIfOpen()[pos++] & 0xff;
}
private void fill() throws IOException {
// 增强功能缓存
byte[] buffer = getBufIfOpen();
// 调用属性a 的方法
int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
}
private InputStream getInIfOpen() throws IOException {
InputStream input = in;//fis
return input;
}
getInIfOpen().read(buffer, pos, buffer.length - pos) ==相当于使用fis.read(buffer, pos, buffer.length - pos)
public class FileInputStream{
public int read(byte b[], int off, int len) throws IOException {
return readBytes(b, off, len);
}
private native int readBytes(byte b[], int off, int len) throws IOException;
}
这样一来,ADcorator中使用了buffer增强了fis的read
4.5 父类作用
完全可以直接将属性A a放入ADecorator中
public class BufferedInputStream extends InputStream {
private FileInputStream fis;
public int read(){
//
}
}
为什么采用,将A a 放入父类中,然后子类继承父类属性的方式从而子类ADecorator也具有了属性A。
父类作用:
让自子类Buffered、Data只需要关注A(Fis)中需要增强的方法,比如A中的read方法的Buffered为其增强为具有缓存功能的字节流读。
A类的其它不需要增强的方法都交给父类FilterInputStream的去关注去实现,这样众多装饰者子类就无需重写
将不兼容的接口转为可兼容的接口,让原本因为接口不兼容无法在一起工作的类可以一起工作
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{
@Override
public void queryNew() {
super.query();
}
@Override
public 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 {
@Resource
private Target target;
@Test
public void test() throws IOException {
target.queryNew();
target.add();
target.deleteNew();
}
}
query
add
delete new
目标Target和原Adaptee中大部分方法都一样,没有那么多方法需要适配的时候,使用类继承,这样很多方法(比如像add)都无需重写。
@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();
}
}
}
目标Target和原Adaptee中大部分方法都不一样即定义不同
原本查询黑名单接口BlackListService#queryBlackList(Long poiId)根据仓id查询仓下的所有很名单。
本次需求查询黑名单时,除了需要仓id外,还需要businessType
实现方式1
直接修改BlackListService#queryBlackList(Long poiId,Integer businessType)方法声明和逻辑
优点:无需重构
缺点:风险大,线上很多业务使用到这个方法,一旦方法有问题相当于全量了,风险不可控
实现方式2:适配器模式
@Service
public class BlackListAdaptee {
public List<Object> queryBlackList(Long poiId){
return Lists.newArrayList();
}
}
public interface BlackListServiceTarget {
List<Object> queryBlackListNew(Long poiId, Integer businessType);
}
@Service
public class BlackListServiceAdaptor extends BlackListAdaptee implements BlackListServiceTarget{
@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());
}
}
}
@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
// 改为:
List<Object> result = blackListServiceAdaptor.queryBlackListNew(323L, 1);
}
}
敏感词过滤。
引入第三方过滤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,"???");//使用???代替敏感词
}
}
当需要环境污染过滤C时,这个时候RishManagement会违背开闭原则
A依赖提供的方法-单个入参:使用默认实现使用xxx代替敏感词
B依赖提供的方法-两个入参,第二个入参是replace:使用String replace 代替敏感词
这样接口的调用也不统一,还需要人为指定replace
解决:使用对象适配器模式统一接口设计
public interface SensitiveWordsFilterTarget {
String filter(String text);
}
@Service
public class SexyWordsFilterAdapter implements SensitiveWordsFilterTarget{
@Resource
private AFilterAdaptee aFilterAdaptee;
@Override
public String filter(String text) {
return aFilterAdaptee.filterSexyWords(text);
}
}
@Service
public class PoliticalWordsFilterAdapter implements SensitiveWordsFilterTarget{
@Resource
private BFilterAdaptee bFilterAdaptee;
@Override
public String filter(String text) {
return bFilterAdaptee.filterPoliticalWords(text, "???");
}
}
@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;
}
}
这样当需要过滤:环境污染相关关键词引入C时,不需要修改RiskManager,只需要创建C-EnvironmentWordsFilterAdapter即可
被共享的单元(比如类的属性,当这些属性是通用且不可变时,可以组成元,让系统共享使用)
系统复用不可变队形-享元,节省内存
背景
建设一个象棋棋牌室游戏,同时在线1w个房间,每个房间是一盘对局(棋局类),对局中需要棋子(棋子类)
@Data
@AllArgsConstructor
public class ChessPiece {
/**
* 棋子编号1-32(红黑各16)
*/
private Integer id;
private Color color;
/**
* 将、士、车、马---
*/
private String name;
/**
* 棋子在棋局上的位置
*/
private Integer x;
private Integer y;
public enum Color{
RED,BLACk;
}
}
public class ChessBoard {
private Map<Integer, ChessPiece> pieceIdMap = new HashMap<>();
public ChessBoard() {
init();
}
private void init() {
pieceIdMap.put(1, new ChessPiece(1, ChessPiece.Color.RED, "车", 0 , 1));
pieceIdMap.put(2, new ChessPiece(2, ChessPiece.Color.BLACk, "跑", 7 , 4));
// 剩下30个棋子
}
}
问题
如果游戏有1w个房间,则有1w个棋局,再创建每个棋局的时候,都需要创建32个棋子对象。所以需要创建32w个棋子对象。占用很大内存。
我们发现棋子属性只有x、y坐标属性,对于不同房间的棋局棋子的坐标是不同的
id、颜色、名称,对于不同棋局来说都是相同的属性,这些属性都是不可变的可以共享,可以抽取为享元类
享元类
@Data
@AllArgsConstructor
public class ChessPieceUnit {
/**
* 棋子编号1-32(红黑各16)
*/
private Integer id;
private ChessPiece.Color color;
/**
* 将、士、车、马---
*/
private String name;
public enum Color{
RED,BLACk;
}
}
棋子类
@Data
@AllArgsConstructor
public class ChessPiece {
/**
* 享元类(id、color、name)
*/
private ChessPieceUnit chessPieceUnit;
/**
* 棋子在棋局上的位置
*/
private Integer x;
private Integer y;
}
享元工厂类:存取享元类
public class ChessPieceUnitFactory {
private static final Map<Integer, ChessPieceUnit> pieceIdMap = new HashMap<>();
static {
pieceIdMap.put(1, new ChessPieceUnit(1, ChessPieceUnit.Color.RED, "车"));
pieceIdMap.put(2, new ChessPieceUnit(2, ChessPieceUnit.Color.BLACk, "跑"));
// 剩下30个棋子
}
private ChessPieceUnit getUnitByChessPieceId(Integer id) {
return pieceIdMap.get(id);
}
}
棋局类
public class ChessBoard {
private static final Map<Integer, ChessPiece> pieceIdAndPieceMap = new HashMap<>();
public ChessBoard() {
init();
}
private void init() {
pieceIdAndPieceMap.put(1, new ChessPiece(ChessPieceUnitFactory.pieceIdMap.get(1), 0 , 1));
pieceIdAndPieceMap.put(2, new ChessPiece(ChessPieceUnitFactory.pieceIdMap.get(2), 0 , 1));
// 剩下30个棋子
}
}
棋局类在put棋子的时候,棋子的享元部分属性是通过享元工厂获取的。
棋局A:在put棋子的时候,享元类属性是通过享元类工厂缓存map获取的,map只需要第一次创建时new一个享元类
棋局B:后续棋局B使用享元类时,直接存缓存map获取即可,无需再创建。
即1w棋局,需要创建32w个棋子类,但是棋子类中大量的属性即享元属性只需要创建1次,这样无疑大大减少内存的占用。(1w房间,1w棋局类、32w棋子类、32个享元属性)
1、背景
Integer i1 = new Integer(123);
Integer i2 = 123;//等价Integer i3 = Integer.valueOf(123);
2、原理
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
low:-128
high:127
当i的值在 -128-127之间,则从IntegerCache.cache缓存数组中获取,这个数据大小是256
[-128,-127,—0,1,—,127]
不在这个区间,则new一个新的对象
Integer.valueOf(1)即cache[1-(-128)] = cache[129] = 1
new Integer(123),不使用IntegerCache.cache缓存,直接创建对象
3、作用
若需要创建1w个-128-127的数字。方式1:new1w个对象
方式2|3则只需要new 256个对象
4、其他
String字符串常量池同理
将共享的,再创建完成后,使用缓存(数组、map)存储起来。后续直接从缓存中取
观察者、模板、策略、职责链、迭代器、状态模式
对象之间定义一个1 vs n的依赖,当1的状态改变时,所有依赖于它的n对象,都会接收到通知并更新
被观察者:subject
观察者: observe
将被观察者和观察者解耦
1、生产消费模式:
2、观察者模式
1、订阅-发布模式
被观察者需要维护观察者们的信息
1、抽象观察者
public interface Observe {
void update();
}
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更新");
}
}
3、被观察
@Service
public class Subject {
@Resource
private List<Observe> observes;
public void notice() {
for (Observe observe : observes) {
observe.update();
}
}
}
使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class SpringTest {
@Resource
private Subject subject;
@Test
public void test(){
subject.notice();
}
}
观察者1更新
观察者2更新
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);
}
}
}
@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{
@Override
public void update(Long iphone) {
System.out.println("发送新人优惠券");
}
}
@Service
public class SendMsgObserve implements RegisterSSuccessObserver{
@Override
public void update(Long iphone) {
System.out.println("发送注册成功短信");
}
}
@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);
}
}
}
}
这样后续,发送新人券改为发送礼品卡,也只需要新增具体观察者即可。对于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);
}
}
}
public interface RegisterSSuccessObserver {
void receive(Long iphone);
}
@Service
public class SendMsgObserve implements RegisterSSuccessObserver{
@Override
@Subscribe
public void receive(Long iphone) {
System.out.println("发送注册成功短信");
}
}
@Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{
@Override
@Subscribe
public void receive(Long iphone) {
System.out.println("发送新人优惠券");
}
}
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ApplicationLoader.class)
@Slf4j
public class SpringTest {
@Resource
private UserAppControllerSubject userAppControllerSubject;
@Test
public void test(){
userAppControllerSubject.register(186L, "123mjp");
}
}
发送注册成功短信
发送新人优惠券
如果IssueNewConsumerCouponObserve观察者的receive方法内部流程较长,执行的慢。可能会影响整体的性能,则需要非阻塞模式,即其receive方法异步执行
@Service
public class IssueNewConsumerCouponObserve implements RegisterSSuccessObserver{
private ExecutorService myThreadPool = Executors.newFixedThreadPool(2);
@Override
@Subscribe
@Async("myThreadPool")
public void receive(Long iphone) {
try {
TimeUnit.SECONDS.sleep(5L);
} catch (Exception e) {
}
System.out.println("发送新人优惠券");
}
}
启动类@EnableAsync
@EnableAsync
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class ApplicationLoader {
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(ApplicationLoader.class);
springApplication.run(args);
}
}
在抽象类中定义一个逻辑框架由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{
@Override
protected void m1() {
}
@Override
protected void m2() {
}
}
B类同理
1.1 io
public abstract class InputStream implements Closeable {
// 父类定义一个方法框架
public int read(byte b[], int off, int len) throws IOException {
// m1();
read();
// m3();
}
// 其中m2()即read方法,是抽象方法,由不同子类自己去实现
public abstract int read() throws IOException;
}
eg:ByteArrayInputStream会重写read方法
1.2 AbstractList
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
boolean modified = false;
for (E e : c) {
add(index++, e);
modified = true;
}
return modified;
}
其中add方法等效abstract类型方法,原因如下
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
即AbstractList中的add()直接抛出异常,即如果其子类不重写add方法,那么就会调用父类AbstractList的add,直接抛异常。这就和abstract关键字一样,强制要求子类重写add方法
2.1 背景
Web项目中的SpringMvc中的XxxController#xxxFunc。请求url对应类上注解 + 方法上注解,流量就能打到
这是SpringMvc封装了Servlet实现。自己也可以通过Servlet的扩展功能实现上述功能
2.2 扩展点
Servlet通过模板模式,留下了doGet、doPost等扩展点。让用户再不修改框架的情况下,通过继承HttpServlet重写扩展点方法,将用户自己的业务代码嵌入整个框架中
2.3 源码解析
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);
}
}
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、实现
func(){
// 计算周长
double perimerter = perimerter();
// 计算面积
double area = area();
m3(perimerter, area);
}
A类中的a调用B类b方法时,B类的b方法会反过来调用A类中注册给它的f方法
复用
由于和模板方法的复用功能一样,所以很多回调方式直接叫XxxTemplate
普通版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;
}
}
如果想执行update语句,则需要定义updateById(Long id)则整个流程还需要再走一遍。毫无复用性而言
抽取版JDBCUtils
driver = com.mysql.jdbc.Driver
url = "jdbc:mysql://127.0.0.1:3306/day21"
user = root
password = root
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);
JDBCTemplate版本
public class DataSourceDemo {
private static DataSource ds;
private static Properties properties;
static {
ClassLoader classLoader = DataSourceDemo.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("D:\\CodeBetter\\src\\main\\resources\\jdbc.properties");
properties = new Properties();
try {
// 这里会把properties中所有属性都读取到
properties.load(is);
} catch (Exception e) {
}
}
public static DataSource getDataSource() {
try {
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
return ds;
}
}
JdbcTemplate jtl = new JdbcTemplate(DataSourceDemo.getDataSource());
// 1.查询
String sql = "select * from table_user where id = " + 1;
Map<String, Object> map = jtl.queryForMap(sql, 1);
// 2.更新
String updateSql = "update from table_user set name = mjp where id = 1";
int result = jtl.update(updateSql, 1);
@FunctionalInterface
public interface StatementCallback<T> {
T doInStatement(Statement stm) throws SQLException, DataAccessException;
}
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执行对应的语句
@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;
});
}
}
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");
回调函数定义: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方法
调用了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中使用的匿名内部类作为对象,这里是使用的内部类作为的对象。无论使用哪种方式,入参对象都有需要重写的方法即回调方法
JDBCTemplate#query -->> JDBCTemplate#execute -->> 执行步骤123 -->> 步骤4QueryStatementCallback#doInStatement#executeQuery -->> 步骤56
1、背景
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public 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线程执行start
for (Thread hook : threads) {
hook.start();
}
}
执行顺序
Runtime#addShutdownHook() -->> static{} -->> runHooks() -->> hook.start() -->> 执行hook线程的回调方法run方法
业务任务有1、2、3、6、7、8 六种状态。其中6和8是成功状态。剩余任务状态都是失败状态。现在想提供一个接口可以根据任务号,查询某个失败任务失败的原因。
接口入参为任务号 + 失败任务的状态
public interface Task {
TaskStatusEnum getTaskStatus();
String queryTaskStatus(Integer status);
}
@Slf4j
@Service
public class FailedTask implements Task {
@Override
public TaskStatusEnum getTaskStatus() {
return TaskStatusEnum.FAILED;
}
@Override
public String queryTaskStatus(Integer status) {
return "网路原因计算失败";
}
}
@Getter
@RequiredArgsConstructor
public enum TaskStatusEnum {
INIT(1,"初始化"),
FAILED(2,"失败"),
SUCCESS(3,"成功");
private final Integer value;
private final String desc;
}
@Resource
private List<Task> tasks;
private Map<TaskStatusEnum, Task> map;
@PostConstruct
private void initMap() {
map = tasks.stream().collect(Collectors.toMap(Task::getTaskStatus, Function.identity()));
}
@Test
public void t() {
TaskStatusEnum status = TaskStatusEnum.FAILED;
Task task = map.get(status);
System.out.println(task.queryTaskStatus(1));
}
这里是使用枚举 和 实现类一一对应的方式,达到set效果。
1、背景
通过n分钟内业务告警m次来定级业务失败的严重程度。不同程度的告警 有不同的处理方式
3min内触发2次 ==》严重P1 ==》 电话告警
3min内触发1次 ==> 紧急P2 ==》 短信告警
10min内触发2次 ==> 正常P3 ==》 大象告警
2、思路
规则引擎 + 策略模式
通过规则引擎来判定出严重程度
根据不同的严重程度,使用策略模式,做不同的处理
一个请求经过A处理器处理 -->> 然后再把请求传递给B处理器处理 -->> 再传给C处理器。以此类推,形成一个链条
复用、扩展
解释:有一个处理器可以处理此请求,则结束整个职责链。后续的处理器不会再被调用
public interface Handle {
boolean handle();
}
@Service
public class HandleA implements Handle{
@Override
public boolean handle() {
// A处理器无法处理此请求
boolean handled = false;
// 自己的业务
return handled;
}
}
public class HandleChain {
@Resource
private List handleList;
public void doXxx() {
for (Handle handle : handleList) {
boolean canDeal = handle.handle();
if (canDeal) {
break;
}
}
}
}
解释:职责链上的所有处理器都会依次处理此请求
public interface Handle {
void handle();
}
@Service
public class HandleA implements Handle{
@Override
public void handle() {
// 处理请求
}
}
public class HandleChain {
@Resource
private List<Handle> handleList;
public void doXxx() {
for (Handle handle : handleList) {
handle.handle();
}
}
}
1、背景
在文本发布时,如果text中如果含有性、政治、广告相关的关键字,则会被处理
处理方式一:直接禁止本次文本发布
处理方式二:过滤关键字违规词后,再发布
2、处理方式一:终止型责任链模式
public interface SensitiveWordFilterHandle {
boolean doFilter(String text);
}
@Service
public class SexyWordFilterHandle implements SensitiveWordFilterHandle{
@Override
public boolean doFilter(String text) {
// 如果text中含有x、x、x词,则任务含有了性相关的敏感词。则会终止职责链
if (true) {
return true;
}
return false;
}
}
其他处理器类似
public class SensitiveFilterHandleChain {
@Resource
List<SensitiveWordFilterHandle> sensitiveWordFilterHandles;
public void legalText(String text) {
for (SensitiveWordFilterHandle handle : sensitiveWordFilterHandles) {
boolean legal = handle.doFilter(text);
if (legal) {
// 允许发布
} else {
// 禁止
}
}
}
}
3、处理方式二:无终止型责任链模式
public interface SensitiveWordFilterHandle {
String doFilter(String text);
}
@Service
public class SexyWordFilterHandle implements SensitiveWordFilterHandle{
@Override
public String doFilter(String text) {
// 如果text包含了a、b、c等性相关词,将这些词替换成xxx
if (true) {
return text.replace(abc, "xxx");
}
return text;
}
}
其他处理器类似
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;
}
}
可以实现对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 {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 处理req
// 业务
chain.doFilter(request, response);
// 处理resp
}
}
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) {
}
}
MVC框架的一部分
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 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
}
}
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;
}
}
public interface DefinedHandler {
void doHandle(xxxReq req);
void preHandle(Object req);
// 执行顺序,值越小,优先级越高
default Integer executeOrder() {
return Integer.MIN_VALUE;
}
}
@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;
}
}
@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;
}
}
@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;
}
}
@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;
}
}
@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());
});
}
}
传统的职责链,例如Filter和HandlerInterceptor都是使用的数组存储的。然后从数组中按照处理器存储的index先后顺序一个一个取,直到全部处理器都执行完毕。
区别:这里的职责链不是数组[]存储,然后遍历index获取处理器。而是通过在抽象处理器中定义executeOrder值,每个具体的处理器,自己根据业务优先级自定义自己的处理顺序。优先级高的处理器先执行。相同优先级并行执行
状态机由3部分组成
1、状态Status
2、事件Event
3、动作Action
其中事件Event也被称为状态转移条件。一旦触发了事件,则一定伴随着状态改变Status A -> B,可能会有相应的动作Action产生
1、不建议使用场景
对象的状态,单行道变换。举例:逆向任务的状态,初始化 -> 已落表 -> 已计算 -> 成功|部分成功|失败
2、建议使用场景
当一个对象的状态<=5,而且状态之间可以相互转换,当达到不同的状态会产生不同的动作时,建议使用状态模式
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());
// 动作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请核对");
}
}
/**
* 取消状态类
* 此状态为终态,不会再转移:
*/
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 {
@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==================");
}
}
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());
// 动作Action
System.out.println("1、更细订单状态为:待发货");
System.out.println("2、将钱存入支付宝");
System.out.println("3、淘宝信息提醒:您的地址信息为xxx请核对");
}
当然这3个动作都可以在clickPay方法中定义,但是如果后续,用户付款后,新增加动作,或者原动作不执行了。则需要改clickPay中代码,违背了开闭原则。
参考责任链模式中场景3:自定义责任链。可以根据动作的执行先后,对应处理器的先后执行。
这样不同的动作,都实现了抽象动作,这样新增、删除等,直接增加对应的动作 - 处理器即可,满足开闭原则。
补充:前提是,clickPay事件Event触发后,动作确认存在频繁变动的场景
描述如何 构建一个简单的"语言"解释器
解释自定义“语言”
开发一个监控业务系统。当每分钟接口出错数目超过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);
}
public interface Expression {
boolean intercept(Map<String , Long> map);
}
public class GreaterExpression implements Expression{
private String key;
private Long value;
public GreaterExpression(String strExpression) {
String[] elements = strExpression.trim().split("\\s+");
this.key = elements[0].trim();
this.value = NumberUtils.toLong(elements[2].trim());
}
public GreaterExpression(String key, Long value) {
this.key = key;
this.value = value;
}
@Override
public boolean intercept(Map<String, Long> map) {
if (!map.containsKey(key)) {
return false;
}
Long val = map.get(key);
return val > value;
}
}
public class LessExpression implements Expression{
private String key;
private Long value;
public LessExpression(String strExpression) {
String[] elements = strExpression.trim().split("\\s+");
this.key = elements[0].trim();
this.value = NumberUtils.toLong(elements[2].trim());
}
public LessExpression(String key, Long value) {
this.key = key;
this.value = value;
}
@Override
public boolean intercept(Map<String, Long> map) {
if (!map.containsKey(key)) {
return false;
}
Long val = map.get(key);
return val < value;
}
}
public class EqualsExpression implements Expression{
private String key;
private Long value;
public EqualsExpression(String strExpression) {
String[] elements = strExpression.trim().split("\\s+");
this.key = elements[0].trim();
this.value = NumberUtils.toLong(elements[2].trim());
}
public EqualsExpression(String key, Long value) {
this.key = key;
this.value = value;
}
@Override
public boolean intercept(Map<String, Long> map) {
if (!map.containsKey(key)) {
return false;
}
Long val = map.get(key);
return Objects.equals(val, value);
}
}
public class AndExpression implements Expression{
private List<Expression> expressionList = new ArrayList<>();
public AndExpression(String expression) {
String[] elements = expression.split("&&");
for (String ele : elements) {
if (ele.contains("||")) {
expressionList.add(new OrExpression(ele));
} else if (ele.contains(">")) {
expressionList.add(new GreaterExpression(ele));
} else if (ele.contains("<")) {
expressionList.add(new LessExpression(ele));
} else if (ele.contains("==")) {
expressionList.add(new EqualsExpression(ele));
} else {
throw new RuntimeException("错误的表达式");
}
}
}
public AndExpression(List<Expression> expressionList) {
this.expressionList.addAll(expressionList);
}
@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;
}
}
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;
}
}
@UtilityClass
public class RuleExpressionFactory {
public Expression getExpression(String exp) {
if (exp.contains("&&")) {
return new AndExpression(exp);
} else if (exp.contains("||")) {
return new OrExpression(exp);
} else if (exp.contains(">")) {
return new GreaterExpression(exp);
} else if (exp.contains("<")) {
return new LessExpression(exp);
} else if (exp.contains("==")) {
return new EqualsExpression(exp);
}
throw new RuntimeException();
}
}
编译器、规则引擎(这里举例阿里的规则引擎QLExpress)、正则表达式
1、地址:https://github.com/alibaba/QLExpress
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>QLExpress</artifactId>
<version>3.3.2</version>
</dependency>
2、 eg1:
@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);
}
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();
}
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,不允许修改最大售卖量
然后将每个规则,不满足的原因都记录下来 ”且“,返回给档期展示即可。
定义一个中介对象,用其来封装一组对象之间的交互。
将这组对象之间的交互 =》 这组对象与中介的交互。
航空管制:参与者之间的交互关系错综复杂,维护成本很高。
为了让飞机在飞行的时候互相不干扰,每架飞机必须知道其他飞机每时每刻的位置,这样就需要飞机之间时刻通信。通信网络就会非常复杂
eg:逆向计划中触发模块、计算模块、合单下发模块。模块内部高内聚,模块之间MQ交互低耦合
如何判断模块的划分是否合理
如果某个功能的修改或添加,经常需要跨系统才能完成,说明模划分不合理,职责不清晰,耦合严重。
接口:设计原则 + 设计模式
业务逻辑:
Service中对查询出的PO,进行Convert成BO|DTO
接口的TReq不要渗透到底层
每一层,只提供最基本的查询。至于查询的结处理成什么样,由上一层调用方自己决定。这样,每一层的方法复用性才更好。
数据体
在保持功能不变的前提下,利用设计原则、设计模式等理论,来修改设计上的不足 和 提高代码质量
初级:在原有框架下写
高级:从零开始设计代码结构,搭建代码框架
资深:发觉代码、框架等存在的问题,重构
1、大型重构
涉及到的面
架构(逆向链路:商品、物流)、模块(触发、计算、合单下发)、代码结构(单据和单据明细)、类之间的交互(同步、异步)
how如何大型重构
2、小型重构
UT
ecute(express, context, null, true, false);
System.out.println(res);
}
eg2:
```java
@Test
public void t() throws Exception {
DefaultContext 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 computeContext = new DefaultContext<>();
for (Map.Entry 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 context) {
StringBuilder sb = new StringBuilder();
sb.append("修改失败原因如下:");
context.forEach((rule, meta) -> {
if (! meta.getResult()) {
sb.append(meta.getFailureReason() + "; ");
}
});
return sb.toString();
}
context
key为规则rule内容eg:”a“允许、"llo"锁库且小于OR允许、"s"即20点后修改值小于可履约库存允许修改,
value为,rpc查询依赖的各个数据,判断当前sku,是否满足这个规则,满足为true,不满足为false
eg:key = “s”,此时为20点后修改最大售卖量,想把最大售卖量从50 -> 30,计算发现可履约库存为40,30 < 40,则允许修改,即MetaRuleResult的result值为true
express
表达式为修改规则的组合;
db中规则为"a&&llo"、"llo&&s&&t"等等
execute执行
当满足所有的规则,全部为true,则本次此sku允许修改最大售卖量,一个规则不满足,最终结果res都会为false,不允许修改最大售卖量
然后将每个规则,不满足的原因都记录下来 ”且“,返回给档期展示即可。
定义一个中介对象,用其来封装一组对象之间的交互。
将这组对象之间的交互 =》 这组对象与中介的交互。
航空管制:参与者之间的交互关系错综复杂,维护成本很高。
为了让飞机在飞行的时候互相不干扰,每架飞机必须知道其他飞机每时每刻的位置,这样就需要飞机之间时刻通信。通信网络就会非常复杂
eg:逆向计划中触发模块、计算模块、合单下发模块。模块内部高内聚,模块之间MQ交互低耦合
如何判断模块的划分是否合理
如果某个功能的修改或添加,经常需要跨系统才能完成,说明模划分不合理,职责不清晰,耦合严重。
接口:设计原则 + 设计模式
业务逻辑:
Service中对查询出的PO,进行Convert成BO|DTO
接口的TReq不要渗透到底层
每一层,只提供最基本的查询。至于查询的结处理成什么样,由上一层调用方自己决定。这样,每一层的方法复用性才更好。
数据体
在保持功能不变的前提下,利用设计原则、设计模式等理论,来修改设计上的不足 和 提高代码质量
初级:在原有框架下写
高级:从零开始设计代码结构,搭建代码框架
资深:发觉代码、框架等存在的问题,重构
1、大型重构
涉及到的面
架构(逆向链路:商品、物流)、模块(触发、计算、合单下发)、代码结构(单据和单据明细)、类之间的交互(同步、异步)
how如何大型重构
2、小型重构
UT