175人参与 • 2024-08-06 • 其它脚本
在日常开发过程中,经常遇到“根据不同场景,做不同的处理”。如:对领红包而言,不满足领红包资格的用户返回“不满足资格”文案。有资格而未领取的用户,需要领红包、纪录、发对账消息等操作。已经领过红包的用户,执行xxx的操作。
为了避免流水账的代码,会使用各种模式,建立抽象类、具体实现类、工厂类等去解决此问题。使得增加新场景,也具有更好的代码的扩展性,如:领取红包的用户,新增是否过期、是否使用的场景。
更进一步,开发了场景执行工具,简化开发流程。
优点:
用户只需要撰写具体的实现类,其余接口、工厂类都不需要考虑。
简化开发流程,但上面提到的扩展性、可读性仍然存在。

类图
状态模型 + 工厂模式

代码详解
现在有一个需求,用户要开通xx服务,返回开通结果消息。开通结果消息有很多状态:开通成功、解约成功、冻结等等。需要根据这个消息的状态去,做不同的行为。
很容易想到状态模式,先定义一个通用的状态处理接口,再根据开通状态去创建各种具体的状态实现类,实现docallback方法。如此,我们写出来的代码具有很好的扩展性和可读性。
场景处理接口:
public interface scenehandlebase<a,t,g> {
/**
* 执行方法
*/
g docallback(t params) ;
}
2. 场景抽象类:在场景处理接口基础之上,又封装了一层,主要用于打异常日志和监控
4j
public abstract class abstractscenehandlebase<a, t, g> implements scenehandlebase<a, t, g> {
/**
* 场景实现类的执行方法
*
* @param params
* @return
* @throws exception
*/
public abstract g execute(t params);
/**
* 打印执行异常日志,并监控异常
*/
public g docallback(t params) {
try {
return execute(params);
} catch (exception e) {
log.error("{}, |statushandlebase_docallback|error|,classname:{}, docallback, params:{}, msg:{}", eagleeye.gettraceid(), this.getclass().getsimplename(), json.tojsonstring(params), e.getmessage(), e);
throw e;
}
}
}
3. 具体场景实现类:
开通成功,执行写入签约表
4j
public class contractstartedscenehandleimpl extends abstractscenehandlebase<user, contractevent, tresult<boolean>> {
private purchasepaylaterbizservice purchasepaylaterbizservice;
public boolean judge(user params) {
return true;
}
public tresult<boolean> execute(contractevent contractevent) {
usersignresult usersignresult = purchasepaylaterbizservice.buildusersignresult(contractevent, signstatus.sign_success);
return purchasepaylaterbizservice.updatesignstatus(usersignresult);
}
}
解约成功,执行写入签约表
4j
public class contractclosedscenehandleimpl extends abstractscenehandlebase<user, contractevent, tresult<boolean>> {
private purchasepaylaterbizservice purchasepaylaterbizservice;
public boolean judge(user params) {
return true;
}
public tresult<boolean> execute(contractevent contractevent) {
usersignresult usersignresult = purchasepaylaterbizservice.buildusersignresult(contractevent, signstatus.sign_fail);
return purchasepaylaterbizservice.updatesignstatus(usersignresult);
}
}
等等具体实现类......
这样写出来的代码,虽然简化了不少。我们仍然需要判断消息的状态,去执行不同具体实现类,在代码中还要写if/else。状态执行如:
if(contractstatusenum.valueof(contractevent.getstatus())==contractstatusenum.started){
contractstartedscenehandleimpl.execute("x");
}else if(contractstatusenum.valueof(contractevent.getstatus())==contractstatusenum.close){
contractclosedscenehandleimpl.execute("x");
}
......
更进一步优化,很容易想到用工厂模式来管理这些实现类。
场景注解
/**
* @author shaojie
* @date 2024/07/10
* 注解时使用枚举值地址,如:*.contractstatusenum.closed
*/
public scenehandletype {
string value();
}
在具体实现类上添加注解@scenehandletype("closed")
如此就可以通过工厂类,获取到实现类
public class scenehandlefactory<a> implements applicationcontextaware, initializingbean {
private final map<string, list<scenehandlebase>> statusmap = new hashmap<>();
private applicationcontext applicationcontext;
public void setapplicationcontext(applicationcontext applicationcontext) throws beansexception {
this.applicationcontext = applicationcontext;
}
/**
* 工厂类初始化后,执行该方法
* 获取statushandlebase所有的实现类
* @throws exception
*/
public void afterpropertiesset() throws exception {
map<string, scenehandlebase> recommendstrategymap = applicationcontext.getbeansoftype(scenehandlebase.class);
if (maputils.isempty(recommendstrategymap)) {
return;
}
for (scenehandlebase strategy : recommendstrategymap.values()) {
scenehandletype statustype = strategy.getclass().getannotation(scenehandletype.class);
if (null == statustype) {
continue;
}
string statusvalue = statustype.value();
if (null == statusvalue) {
continue;
}
statusmap.put(statusvalue, strategy);
}
}
/**
* 获取具体场景处理类
*
* @param status
* @return
*/
public scenehandlebase getscenehandle(string status) {
return statusmap.get(statustype);
}
}
状态执行就变成:
statushandle = statusfactory.getscenehandle(contractevent.getstatus();
tresult<boolean> res = statushandle.docallback(contractevent);
到了这里,整个执行方法就变的非常简洁,以后新增了一个开通状态,也只需要加一个实现类,代码的扩展性也得到了保证。
但类似的情况非常多,如开头讲到的领红包情况,每遇到一次都要写一堆代码,去实现工厂类,注解,抽象接口等等,将会浪费很多时间。
故更进一步,如果能抽出来一个通用的场景执行器,用户只需要考虑实现类,其余的接口、工厂类都给实现了,那岂不是大大提高效率。
public interface sceneenumbase {
/**
* 获取实现枚举类的属性名称
* @return
*/
string getscenename();
}
开通状态枚举类:
“具体场景实现类”上的注解@scenehandletype值就是对应的枚举值全地址。
枚举值是什么?
这里的枚举值是if(x){y}中的x,将条件映射为一个枚举值。
如果“未领取红包”,用户“去领红包”。那么这个枚举值就是“未领取红包”,“去领取红包”是具体执行类中的execute。
也可以是开通结果枚举的一个状态值。
public enum contractstatusenum implements sceneenumbase {
/**
* 生效中
*/
started,
/**
* 冻结
*/
frozen,
/**
* 退出
*/
closed,
/**
* 没有开过
*/
no_entry;
/**
* 获取实现枚举类的属性名称
* @return
*/
public string getscenename() {
return this.name();
}
}
具体场景实现类
注解是枚举值全地址
@scenehandletype("*.contractstatusenum.closed")
@component
@slf4j
public class contractclosedscenehandleimpl extends abstractscenehandlebase<user, contractevent, tresult<boolean>> {
......
状态处理工厂类
获取具体实现类的方法
public class scenehandlefactory<a> implements applicationcontextaware, initializingbean {
......
public scenehandlebase getscenehandle(sceneenumbase status) {
string statustype = string.join(".", status.getclass().getname(), status.getscenename());//拼接出枚举全地址
return statusmap.get(statustype);
}
}
对于同一个场景枚举值,可能不止一个行为。如:用户已领取红包,这个红包已使用,执行a行为,红包未使用执行b行为。
此时,就用上了场景抽象接口的judgeconditions方法,此方法会在工厂类获取bean的方法中,选择合适的场景实现类。
public interface scenehandlebase<a,t,g> {
/**
* 同一个枚举值若有多个场景实现类,可通过此方法判断使用哪个场景实现类
*/
boolean judgeconditions(a params);
/**
* 执行方法
*/
g docallback(t params) ;
}
工厂类
public class scenehandlefactory<a> implements applicationcontextaware, initializingbean {
private final map<string, list<scenehandlebase>> statusmap = new hashmap<>();
private applicationcontext applicationcontext;
public void setapplicationcontext(applicationcontext applicationcontext) throws beansexception {
this.applicationcontext = applicationcontext;
}
/**
* 工厂类初始化后,执行该方法
* 获取statushandlebase所有的实现类
* @throws exception
*/
public void afterpropertiesset() throws exception {
map<string, scenehandlebase> recommendstrategymap = applicationcontext.getbeansoftype(scenehandlebase.class);
if (maputils.isempty(recommendstrategymap)) {
return;
}
for (scenehandlebase strategy : recommendstrategymap.values()) {
scenehandletype statustype = strategy.getclass().getannotation(scenehandletype.class);
if (null == statustype) {
continue;
}
string statusvalue = statustype.value();
if (null == statusvalue) {
continue;
}
list<scenehandlebase> list = statusmap.getordefault(statusvalue, new arraylist<>());//同一个注解值,可以对应多个场景实现类
list.add(strategy);
statusmap.put(statusvalue, list);
}
}
/**
* 根据条件判断,获取具体场景处理类
*
* @param status
* @return
*/
public scenehandlebase getscenehandle(sceneenumbase status, a params) {
string statustype = string.join(".", status.getclass().getname(), status.getscenename());
list<scenehandlebase> bases = statusmap.get(statustype);
if (collectionutils.isnotempty(bases)) {
for (scenehandlebase base : bases) {
if (base.judgeconditions(params)) {//筛选场景实现类
return base;
}
}
}
return null;
}
}
使用
添加依赖
<dependency>
<groupid>*</groupid>
<artifactid>*</artifactid>
<version>*</version>
</dependency>
注入工厂bean
public scenehandlefactory getstatushandlefactory() {
return new scenehandlefactory();
}
根据条件定义场景枚举,并implements sceneenumbase,getscenename方法直接copy
public enum contractstatusenum implements sceneenumbase {
/**
* 生效中
*/
started,
/**
* 冻结
*/
frozen,
/**
* 退出
*/
closed,
/**
* 没有开过
*/
no_entry;
/**
* 获取实现枚举类的属性名称
* @return
*/
public string getscenename() {
return this.name();
}
}
场景实现类:
1. 添加注解
@component
@statushandletype("*.cjcreditcontractstatusenum.closed")
引号中是枚举全地址+枚举值
2. 实现两个方法
judge():同一个枚举值可以设置多个实现类,工厂类获取具体实现类时,根据此方法获取此枚举值的实现类
execute():具体实现类的实现方法
"*.contractstatusenum.closed") (
4j
public class contractclosedscenehandleimpl extends abstractscenehandlebase<user, contractevent, tresult<boolean>> {
private purchasepaylaterbizservice purchasepaylaterbizservice;
public boolean judge(user params) {
return true;
}
public tresult<boolean> execute(contractevent contractevent) {
usersignresult usersignresult = purchasepaylaterbizservice.buildusersignresult(contractevent, signstatus.sign_fail);
return purchasepaylaterbizservice.updatesignstatus(usersignresult);
}
}
执行
scenehandlebase<user, contractevent, tresult<boolean>> statushandle = statusfactory.getscenehandle(contractstatusenum.valueof(contractevent.getstatus()),null);
tresult<boolean> res = statushandle.docallback(contractevent);
本文通过逐步深入的实践案例,阐述了从原始的条件分支逻辑到运用设计模式优化,最终实现高度抽象化的场景执行工具的全过程。这一过程不仅展示了技术深度,更重要的是体现了面向对象设计原则的应用价值,即通过高内聚低耦合的设计提升软件系统的灵活性与可扩展性。场景执行工具的提出,极大地减轻了开发者在面对多变业务场景时的编码负担,允许他们更加专注于业务逻辑的实现,而非繁琐的架构搭建,使得整个解决方案既强大又易于集成。
团队介绍
我们是淘天集团-丰富性行业技术团队。团队业务覆盖全淘宝天猫。我们深入不同垂直行业内部,以技术引领和促进行业变革。围绕商品、商家等要素,在商品的生产、流通、表达全链路上拥有领先于业界的技术积累。我们用技术帮助全平台商家和开发者,带来全平台业务的高效运营,并始终为消费者带来优质体验。
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论