【Java】那几种很不错的代码抽象设计
创始人
2024-05-25 05:26:08
0

代码这种东西,机器不管你写的怎么样,肯定能识别, 人就不一样了,前人拉屎后人踩屎,一坨认栽,n坨就有点过分了哈
一般写代码也不用太高大上,很多接口还是很简单的,如果把各种设计模式搞上来那叫过度设计,叫逼王;就在多渠道,多河流的时候才能当逼王

看到过几位写的不错的同事搞的设计,和大家简单介绍一下

1. 注解式
2. 拼接式
3. 实现扩展接口式

下面介绍下这三种方式

注解式

核心用到的方式:单例模式+注解+反射+策略模式

直接上代码了哈,注释搞在代码中,大家直接体验

既然是注解,那就先定义一个注解吧

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface BizCode {String value() default "";}

然后定义一个执行接口

/*** 扩展点执行引擎* 真正的扩展点在ProcessExtension接口,实现此接口并注解即可,在外部接口使用过程中自然根据BizCode* 索引到类* 单例模式+注解+反射+策略模式*/
public interface Engine {String process(String bizCode, String params);}

然后定义一个实现类,大家可以看到ExtensionEngine的构造方法中有个单例的写法,为什么写在构造方法中,是为了在启动时就会执行并加载到一个全局变量中,等会我们往下看就知道了,当然了,此处用其他方式也行,能在启动时加载就好

/*** 执行引擎*/
@Component
public class ExtensionEngine implements Engine {public ExtensionEngine() {// 注册扩展 在启动时就被加载进来ExtensionBuilder.getInstance().build();}@Overridepublic String process(String bizCode, String params) {StringBuilder processRecord = new StringBuilder();//拿到对应的实例ProcessExtension processExtension = ExtensionBuilder.getInstance().getExt(bizCode);// 1、前置处理processExtension.beforeProcess(params, processRecord);// 2、统一处理System.out.println("统一处理流程");processRecord.append("统一处理 - ");// 3、后置处理processExtension.afterProcess(params, processRecord);return processRecord.toString();}
}

我们看到如果拿到了实例就会进行前中后的调用处理,其实就是多条分支策略的处理,定义一个接口

public interface ProcessExtension {/*** 前置处理* @param params 参数* @param processRecord 处理对象传递*/void beforeProcess(String params, StringBuilder processRecord);/*** 后置处理* @param params 参数* @param processRecord 处理对象传递*/void afterProcess(String params, StringBuilder processRecord);// 其他模板动作接口
}

定义两三个实现类,你说的算,这就用到我们上面定义的注解了,value用常量定义就行,后来我也试过用枚举,只不过只能用枚举本身,不能枚举.getValue,都可以

@BizCode(value = "jd")
public class JdProcessExtension implements ProcessExtension {@Overridepublic void beforeProcess(String params, StringBuilder processRecord) {System.out.println("京东活动前置处理流程");processRecord.append("京东活动前置处理流程 - ");}@Overridepublic void afterProcess(String params, StringBuilder processRecord) {System.out.println("京东活动后置处理流程");processRecord.append("京东活动后置处理流程");}
}
@BizCode(value = "tb")
public class TaobaoProcessExtension implements ProcessExtension {@Overridepublic void beforeProcess(String params, StringBuilder processRecord) {System.out.println("淘宝活动前置处理流程");processRecord.append("淘宝活动前置处理流程 - ");}@Overridepublic void afterProcess(String params, StringBuilder processRecord) {System.out.println("淘宝活动后置处理流程");processRecord.append("淘宝活动后置处理流程");}
}

下面我们就看下那个单例是如何注册的吧,这个比较关键了!标准单例写法啊,静态内部类的姿势。

可以看到事实上就是吧所有的BizCode注解通过反射识别并注入到了Map中,即Map<枚举或String都可以, 接口> ,然后再定义一个根据Key获取Value的就行了

/*** 业务注册处理类* 单例模式*/
@Slf4j
public class ExtensionBuilder {private ExtensionBuilder() {}public static ExtensionBuilder getInstance() {return ExtensionBuilderInstance.INSTANCE;}/*** 静态内部类方式*/private static class ExtensionBuilderInstance {private static final ExtensionBuilder INSTANCE = new ExtensionBuilder();}/*** bizcode => 业务扩展类实例*/private Map extMap = new ConcurrentHashMap<>();/*** 注册业务扩展类实例*/public void build() {try {log.debug("找出所有注解了BizCode的类并注册");// 找出所有注解了BizCode的类Reflections extReflections = new Reflections("com.mtgg.laoxiang");Set> extClasses = extReflections.getTypesAnnotatedWith(BizCode.class);for (Class extClass : extClasses) {BizCode[] annotationsByType = extClass.getAnnotationsByType(BizCode.class);if (annotationsByType != null && annotationsByType.length > 0) {BizCode bizCode = annotationsByType[0];// 构建 bizcode => 业务扩展类实例extMap.put(bizCode.value(), (ProcessExtension) extClass.newInstance());}}log.debug("ExtensionBuilder加载扩展实例:{}", extMap);} catch (Exception e) {e.printStackTrace();}}/*** 根据业务编码,获取对应的业务扩展类实例** @param bizCode* @return*/public ProcessExtension getExt(String bizCode) {return extMap.get(bizCode);}
}

调用的时候就直接注入Engine,然后调用process方法就好了,只要指定bizCode就没毛病

下面介绍第二种哈

拼接式

从代码多少上面看这个还真容易一点,优劣等会再说

话说在启动后Spring已经帮我们把该注册的都已经注册成Bean Map了,key就是类名(首字母小写),value就是这个类的实例,我们只要用就好了,来我们直接用

以注解式jd,taobao两个实现类以及其实现的接口ProcessExtension为例,不需要自定义注解了

@Component
public class JdProcessExtension implements ProcessExtension {
}
@Component
public class TaobaoProcessExtension implements ProcessExtension {
}

直接注入使用,下面这个map就已经有数据了,

@Autowired
private Map map;

这个map是什么形式呢,我直接演示下

jdProcessExtension JdProcessExtension

taobaoProcessExtension TaobaoProcessExtension

好了我们只要拿到key获取到value–>ProcessExtension,调用相关接口就能自己往下流了,所以说我们用到了拼接

可以定义一个常量"%sProcessExtension"; 就是%s+实现类统一后缀;然后传入bizCode用String.format替换一下%s就好了,比如我传个jd,得到了jdProcessExtension ,用map.get()拿到实例后调用接口,完毕!

下面介绍第三种

扩展接口式

说到底这个就是利用Spring的扩展接口或预加载注解,或构造方法来实现启动时将实例加载到一个Map中的,和第一个结构有点相似,有一个调用层处理。上菜,以支付为例,先定义一个Service

public interface PayCoreService {/*** @Author: 老乡* @Date: 2020/6/13 21:50* @Describe: 统一下单* 接入新的支付方式只需要增加枚举类,继承PayStrategyContent抽象类,重写支付等功能接口*/Result toPayCore(PayCoreDTO payCoreDTO);

再定义一个抽象类实现Service,既然叫抽象类那就要有抽象方法,将payCore需要子类自行实现的封装为抽象方法即可(下面会定义两个子类继承它),然后大家可以看到这里也同样是定义了ConcurrentHashMap用来全局保存实例的,key为枚举,value为Service实例

/*** @Author: 老乡* @Describe: 当增加一个功能的时候可以在抽象类中实现接口,通过抽象方法让子类去实现* 如果有的子类没有用到这个功能也可以不重写,将类改为抽象类即可;* 不过一般抽象出来的功能都是共用的行为,所以即使暂时不用也可以先重写后放在那里* 

* 此抽象类完成了对无法选择的延迟到子类实现,实现了PayStrategy接口,转化找到*/ @Slf4j public abstract class PayCoreStrategyAbstract implements PayCoreService {/*** 各种支付策略实例预加载*/public static Map strategyMap = new ConcurrentHashMap<>();/*** @Author: 老乡* @Describe: 只在启动时加载一次,加载类别在子类中控制* this为当前PayStrategy,因为调用的时候取到具体的实例如支付宝实例,返回的是接口PayStrategy*/@PostConstructpublic void init() {Map integerPayStrategyMap = channelInit();for (Map.Entry m : integerPayStrategyMap.entrySet()) {strategyMap.put(m.getKey(), m.getValue());}log.info("所有支付方式初始化完成");}/*** @Author: 老乡* @Date: 2020/6/13 21:51* @Describe: 支付实现类,具体支付类别由子类实现,消除if else分支,接入其他支付时不违反开闭原则*/@Overridepublic Result toPayCore(PayCoreDTO payCoreDTO) {return payStrategy(payCoreDTO);}public abstract Result payStrategy(PayCoreDTO payCoreDTO);//获取初始化渠道keypublic abstract Map channelInit(); }

定义两个子类继承它,key就用枚举会优雅一些

@Slf4j
@Service
public class AliPayCoreStrategy extends PayCoreStrategyAbstract {@Overridepublic Result payStrategy(PayCoreDTO payCoreDTO) {//阿里支付业务return Result.errorMessage("支付异常");}//此处因为可能有多种支付方式,所以返回Map,如果只有一种就返回一个key就可以@Overridepublic Map channelInit() {Map map = new HashMap<>();map.put(PayChannelEnum.ALI_APP.getChannel(), this);map.put(PayChannelEnum.ALI_H5.getChannel(), this);return map;}
}

一个Service,一个abstract,两个继承类都定义好了,如何调用呢?

调用层定义一个Manager和实现类

public interface PayCoreManager {/*** @Author: 老乡* @Describe: pay*/Result payChoose(PayCoreDTO payCoreDTO);
@Service
public class PayCoreManagerImpl implements PayCoreManager {@Overridepublic Result payChoose(PayCoreDTO payCoreDTO) {//从静态map支付类别中取出对应的支付类别,对应的实例PayStrategy,若没有应检查是否对应//不同的类别获取到不同的支付实例PayCoreService p = PayCoreStrategyAbstract.strategyMap.get(payCoreDTO.getPayChannel());if (p == null) {return Result.errorMessage("payType is not valid");}//调用支付封装接口return p.toPayCore(payCoreDTO);}

可以看到传入payChannel,直接通过抽象类获取静态变量Map.get取到实例进行调用即可

对比

方式优势劣势
注解分层,逼王,用到多种设计模式,反射等每一个业务都要定义自己的注解,注解上无法用枚举.getValue来指定
拼接式简单和子类名耦合,需要自己按类名拼接,不够优雅
实现接口式分层,优雅调用链可能稍长一点

推荐使用2和3,当然了,只要你开心

以上就是如何抽象扩展的代码设计示例,希望能够误导大家哈哈

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...