嗨,大家好,好久没有更新了,最近忙活了一段时间,这段时间里我一边开发一边思考怎么让代码看起来扩展性好且看起来优雅,于是尝试着去学习设计模式,在学习完之后加入自己的理解并应用到实际的业务场景中,发现设计模式真的好优雅,重点是符合设计原则,新的逻辑对代码的影响降到了最低。
业务场景
我的这个场景是新建立了一个发送录取offer的项目,整个项目都是新逻辑,采用了DDD架构+cola框架进行开发,在我们的理念设计里,offer域包含了所有形式的录取offer发放,当然包含平台合作、正式、等等等等,对于不同形式的offer,所发出的offer结构也不一样,但是公司的结构经常会发生变化,我想对发送offer进行一个抽象,希望它扩展性强(公司有了新的结构我可以很方便的去扩张,且对我新逻辑不影响主干)、健壮性好(新的结构对其他结构没有影响且方便阅读)。
思考过程
针对上述情况我的想法就是对我的业务按照类别找出相同点和不同点,在本子上写写画画之后发现每种结构的offer发放,大致流程如下:
从数据库以及薪资库查询数据到offer域,之后组装offer实体信息,根据apollo配置构建模版所需要的信息,信息填充到模版之后发送邮件、短信。
在所有结构的offer中,只有前三步的步骤不一样,需要结合对应结构构件对应的offer,4、5步可以是所有结构公用的,所以我第一时间想到了模版模式(1.一次性实现算法的不变部分,并将可变的行为留给子类来实现2.各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复),之后按照这个思考先把模版框架写了出来如下图:
- 模版模式
public abstract class AbstractSendCandidate {
/**
* 构建模版所需要参数
*
* @param offer
* @return
*/
public abstract String builderParam(OfferEntity offer);
/**
* 获取对应模版
*
* @param type
* @return
*/
public abstract void getOfferTemplate(String type);
/**
* 信息填充
*
* @param type
* @return
*/
public final void convertDataToTemplate(String type) {
}
/**
* 发送短信
*
* @param type
* @return
*/
public final Boolean sendSms(String type) {
return true;
}
/**
* 发送邮件
*
* @param type
* @return
*/
public final Boolean sendEmail(String type) {
return true;
}
}
模版实现类之一灵活用工:
@Component
public class FlexibleSendCandidateCmdExe extends AbstractSendCandidate<OfferEntity> {
@Override
public String builderParam(OfferEntity offer) {
return null;
}
@Override
public void getOfferTemplate(String type) {
}
}
外层的代码最初设想就是通过swatch来判断,去发送对应结构offer(很简单的if这里就不给大家写了)
大致看了一下,由于公司的复杂性,大致会有七种结构,且每种结构的实现逻辑都不太一样,这样就导致代码有很长的一段判断,每新加一种结构就需要在if里添加一次,于是乎我在想,有没有什么方法,可以不修改主干代码,且可以实现我要的功能,符合开闭原则,结合自己之前看过的设计模式,很快就想到了策略模式(准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性)。
优化
策略模式,我们需要定义三个角色:
- 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。 在本次的业务场景下,结合已有的模板模型,我先定义一个接口作为抽象策略类
public interface ISendOfferExe {
// 只有一个发送offer的方法
Boolean sendOffer(OfferEntity offer);
}
因为我们已经有提前定义好的模版类,所以先用模版类实现策略
public abstract class AbstractSendCandidate implements ISendOfferExe {
/**
* 获取offer类型
* @return
*/
public abstract String getOfferType();
/**
* 构建模版所需要参数
* @param offer
* @return
*/
public abstract OfferEntity builderParam(OfferEntity offer);
/**
* 获取对应模版
* @param type
* @return
*/
public abstract void getOfferTemplate(String type);
/**
* 信息填充
* @param type
* @return
*/
public final void convertDataToTemplate(String type) {
}
/**
* 发送短信
* @param type
* @return
*/
public final Boolean sendSms(String type) {
return true;
}
/**
* 发送邮件
* @param type
* @return
*/
public final Boolean sendEmail(String type) {
return true;
}
@Override
public Boolean sendOffer(OfferEntity offer){
OfferEntity offer = builderParam(offer);
sendSms("");
sendEmail("");
... ...
return true;
}
我的发送offer都是在offer域下完成的,我固定定义为了offer实体,所以这里不再抽象范型,大家结合自己场景,getOfferType方法则是决定具体使用哪种策略。 接下来看我的策略模版实现类
@Component
public class FlexibleSendCandidateCmdExe extends AbstractSendCandidate {
@Override
public String getOfferType() {
return OfferTypeEnum.FLEXIBLE.getCode();
}
@Override
public OfferEntity builderParam(OfferEntity offer) {
return new OfferEntity();
}
@Override
public void getOfferTemplate(String type) {
}
}
@Component
public class RegularSendCandidateCmdExe extends AbstractSendCandidate {
@Override
public String getOfferType() {
return OfferTypeEnum.REGULAR.getCode();
}
@Override
public OfferEntity builderParam(OfferEntity offer) {
return new OfferEntity();
}
@Override
public void getOfferTemplate(String type) {
}
}
他俩区别就在于,getOfferType中返回对应的一个枚举类型,作为区分策略的标记,其余的builderParam和getOfferTemplate则是实现各自类型的业务逻辑。 现在抽象策略类和实现策略类都有了,接下来就是整理我们的策略环境类,也可以理解为上下文
@Slf4j
@Component
public class SendOfferCandidateStrategyExe implements InitializingBean, ApplicationContextAware {
/**
* 策略上下文 <策略类型, 策略实现>
*/
private static Map<String, ISendOfferExe> sendCandidateContext = new HashMap<>();
private ApplicationContext applicationContext;
public Boolean execute(OfferEntity offerEntity) {
ISendOfferExe sendOfferExeI = sendCandidateContext.get(offerentity.getofferType);
return sendOfferExeI.sendOffer(offerEntity);
}
@Override
public void afterPropertiesSet() {
// 获取实现发送信息采集类的抽象类
Map<String, AbstractSendCandidate> beanMap = applicationContext.getBeansOfType(AbstractSendCandidate.class);
if (CollectionUtil.isEmpty(beanMap)) {
return;
}
// 收集上下文
beanMap.values().forEach(bean -> {
sendCandidateContext.putIfAbsent(bean.getOfferType(), bean);
});
}
// 设置spring上下文
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
在各个bean属性设置完成后,我们在afterPropertiesSet方法里通过spring的上下文把策略环境搭建了起来,这样上下文环境就完成了,我们在使用的时候直接根据offer的类型从上下文中获取对应的策略并执行就OK了,等产品新增、删除offer类型的时候,我们只需要对代码就行扩展,无需担心影响其他的offer构建。
这个链路其实很长为了写博客简化了很多,因为涉及公司的offer发放所以只是搭了空架子,写的博客不是很好,主要是为了和大家分享这个思想,在你用开第一个设计模式对代码进行思考之后就会发现可以更好,自己做完之后也会觉得很开心,有成长,写作不易,欢迎大家点赞、提问、交流~