请稍等 ...
×

采纳答案成功!

向帮助你的同学说点啥吧!感谢那些助人为乐的人

工作中用过的接口和抽象类

  一哥你好,之前有在群里和前几节课听到你说的一些关于接口的应用,我自己十分有感触,所以在这里分享自己在开发中的经验,第一次写分享,格式和措辞可能很差,就当做抛砖引玉了吧。
  先说一下自己的背景吧,我是18年年初入职一家创业公司,公司只有一个开发经理,项目小而且多,所以对于我这菜鸟的要求就是项目能跑就行,而且想用什么技术都由自己决定。
1、项目开发脚手架
  开发了两三个月左右,我对枯燥的crud十分头疼,代码里总是千篇一律的entity-repository-service-controller。就拿controller中普通的列表来说吧,参数传进来,对所有的参数进行判空,拼接条件,然后返回分页数据。而添加数据则是对入参进行各种判断,然后保存。当时就想着是不是有什么办法可以免去这些重复的工作,但由于刚入职,也不是很敢去尝试。直到我接手了一个二开项目,很吃力的阅读了三个礼拜左右,发现它的开发模式竟然和我之前的想法不谋而合,而且写得相当漂亮。于是我立刻着手去做我们公司第一版开发脚手架。
  首先定义了含有基本属性的entity超类,包含id,name,createTime等属性,repository接口则采用泛型
<T extends MyEntity>作为entity的声明,并继承了jpa的JpaRepository,service的超类则是手动写了一些基础的crud,而controller就比较麻烦了。就像我之前说的,如何在超类就解决列表-添加数据-修改数据之前的操作呢。
  首先是列表,动态条件的构建首先让我想到的是反射,通过制定condition规范(入参参数名必须与实体类参数名一致),将入参的参数名与实体类的参数名进行对应,动态构建JPA的specification。果然和预料的一样,之后再定义condition的超类(包含页码、页面大小、排序字段等),通用列表功能就解决了。当然此处有个缺点,这只能运用于equal的操作,我如何对比较,like进行精确定位?大概是框架运行了大半年,我用注解解决了这一问题。
  其次是添加,添加的痛点在于各种入参判断,如何动态判断各个参数?这时候spring-boot validation就帮上忙了,只要在入参中添加注解,并在实体类中添加@valid注解就能动态判断了。
  最后是修改(删除的通用操作难度不大),采用了Hutool的BeanCopy工具,然后用validation主动校验的工具进行参数校验。也算是完成了。
  最后实现的效果就是,一个需求进来,我只需要定义实体类,然后repository,service,controller一路继承,只要不是特别复杂的需求,这些通用方法都能应付,甚至连表都是jpa创建的。
  这是我职业生涯中对超类的第一次运用,只是普通的继承,相当粗浅,不过认为自己造了有用的轮子还是很有成就感的。当然实现起来肯定不像我说的这些这么简单,也是踩了相当多的坑。
2、自制validation框架
  脚手架用了半年多,还是发现了很多问题。但我认为最有意义的一个就是发现了validation校验框架有时候会重复判断多次,影响到了当时的某个业务。我曾多次静下心跟着断点去追踪源码,但奈何水平不够,也不够耐心,没发现问题所在,索性就自己做一个吧。
  我观察了validation的使用,其中包含了声明注解(我乱起的)@valid,各种类型的判断注解@NotNull等,以及相对应的这些注解的判断算法。思索了一两天后,框架的大概轮廓就出来了。
  @valid是指明该参数需要校验,这个通过百度,查到了参数拦截器(也是乱起的)HandlerMethodArgumentResolver这么个东西。在个这方法下,通过反射我能获取到标记了@valid注解实体类的参数名、值、以及注解。那么关键的问题来了,知道了具体的校验注解我如何根据注解实现校验?
  第一个想到的方法就是if-else,然后我就给了自己一嘴巴子,那也太low太麻烦了。略微参考了下源码(虽然还是没看懂),发现了这些校验算法都指向了一个接口ConstraintValidator。然后就联想到初学java继承时父和子的例子:

Father f = new Son();
f.method();//此处调用的是子类实现

  这里情况略微不同,ConstraintValidator是接口,其实很好理解,父类并不关心方法是如何实现的,只要指定这个方法的规则就行,子类爱咋实现无所谓。于是我的抽象思维就从这里开始了。
  定义一个校验超类接口,校验的行为就是校验参数,由各个子类进行实现,而子类注解和子类算法需要进行绑定,也就是在知道某个注解的情况下能够精确地去调用这个注解的算法,如下图。

//省略部分声明注解
@Constraint(validatedBy = NotBlankValidate.class)//此处进行绑定算法
public @interface NotBlank {
    String message();
    Class<?>[] groups() default {};
    boolean isTrim() default false;
}

  最后实现的步骤就很清晰了,参数拦截器捕获带有@valid实体,通过反射分解实体中每一个参数的参数名、值以及最重要的校验注解。获取到这些后,通过校验注解获取到校验算法,再调用算法中的校验方法,结束。

//获取校验算法类
Class validateClass = constraint.validatedBy();
//初始化校验算法类(以接口形式)
//此处可以一开始就初始化所有算法存在容器中去获取,效率会更高
AbstractValidate abstractValidate = (AbstractValidate)ReflectUtil.newInstance(validateClass);
//调用校验方法
boolean flag =abstractValidate.isValid();

  这次自己造轮子狠狠叩开了我对抽象认识的大门,虽然造出来的是已有的轮子,而且效率方面可能比我好的多。
3、策略模式在业务中的运用
  如果说自制框架是对我最有意义的一次coding,那么这一次策略模式的应用一定是最令我满意的一次。
  背景是这样的,公司在做一款对接各个第三方通道的支付产品,一开始只有一两家第三方接口,所以if-else用起来简直不要太酸爽。但随着对接的第三方增多,我逐渐感受到了以前crud时带给我的烦躁情绪。我该如何摆脱对接完接口还要去给那一坨看着就引起不适的if-else再加一行else if()?检验框架那会的开发经验给我某种直觉,一定是有某种方法能够解决我目前的困境,让我在写完对接接口方法后就能直接让系统自己去精准识别和调用。写了几份demo后总算是有了大概的思路。
  首先定义一个DefinedInterface的接口,里边就一个方法,Integer defined()。相当于给所有子类打上了一个标签。

public interface DefinedInterface {
	//此处EnumInterface是我自己定义的一个枚举的抽象,是之后优化的,一开始直接采用Integer粗暴指定
    EnumInterface defined();
}

  然后是定义支付方法抽象并继承之前的DefinedInterface接口

public interface PayInterface extends DefinedInterface {
    // 通用主扫支付
    WebPayVo webPay(CommonPayParam commonPayParam) throws Exception;

    // 通用条码支付
    MicroPayVo microPay(CommonPayParam commonPayParam) throws Exception;

    // 通用支付查询
    PayRes payQuery(CommonQueryParam commonQueryParam);

    // 通用支付退款
    PayRes payRefund(CommonRefundParam commonRefundParam);

    // 通用微信小程序支付
    default WebPayVo appletPay(CommonPayParam commonPayParam) throws Exception {
        return new WebPayVo();
    }
}

  再来就是以各个第三方支付公司为单位,继承PayInterface ,按照父类指定的方法去逐个实现
  最后就是调用,这个子类其实都是打上@service注解的,也就是说是能够通过spring去获取到实现这个父类的子类集合的。

Map<String, PayInterface> map = ApplicationContext.getBeanOfType(PayInterface.class);

  根据前端传上来的标识以及之前defined()方法,很容易知道该调用哪个子类

//此处可以一开始就初始化所有算法存在容器中去获取,效率会更高(抄上边的)
for (PayInterface payInterface : payInterfaceMap.values()) {
                if (order.getPayChannel().equals(payInterface.defined().getCode())) {
                    webPayVo = payInterface.appletPay(commonPayParam);
                }
   }

  这样就达到的效果就是,一个第三方公司的接口过来,我只要根据支付父类实现子类,对接好接口,就算完成了一次功能的对接。实际开发会更加复杂,因为不仅仅是多个第三方公司接口的存在,还有支付宝、微信、银行卡等支付类型,所以这里进行了简化,对这些支付类型不做讨论。
  经历过这次开发以后,偶然的一次机会叕翻开了设计模式中的策略模式,意外的发现和我现在的思路是如此相似。其实设计模式以前也看过好几遍,不过基本上是看完就忘了,你用不到的知识在脑中根本不会停留太久。以前看到这个设计模式,我认为这一定是个哪个傻*为了显示自己高级而弄出来没用的东西,我都知道该用哪种实现,都new出来了,还继承个什么劲?结果现实给了我一嘴巴子,只要解决了如何定位哪个子类的使用,那么这种模式对开发而言是极为便利的。我也明白了为什么博客、论坛、技术群里都会说,你不经意用到了设计模式的时候,你才真正懂了设计模式。
4、总结
  从事开发到现在也有将近三年的时间,抽象的思维从校验框架启蒙后一直萦绕在我脑海中,一旦遇上相似的业务或者重复的代码,我的第一反应就是能不能抽象。这种思维也确实在我的工作中替我省去了很多麻烦事。抽象,配合反射、注解能够处理很多不必要的重复劳动,让代码真正“智能”起来。但是对于性能有极致需求的话可能不适合用太多反射,毕竟我现在所处的环境对性能要求并没有那么高。
  写到这里发现似乎有些啰嗦和跑题,一哥见谅

正在回答

2回答

张勤一 2020-09-14 12:52:05

ZK 你好:

    非常非常感谢你的分享(我这两天在忙家里的事,回复你有些晚,还请见谅!),非常精彩,也是我们学习的榜样。我读了两遍你所写的分享,我的感慨也是颇深的:

    (1)你的项目经历和项目经验都是非常丰富的,这从你的描述中可以看出来。另外,你只工作了三年,有如此多的项目经历非常难得(公司大小其实并不是很重要,重要的还是看你能做什么事,能接触什么事);

    (2)你勤于思考并动手实践的习惯或者说做事风格,是我们学习的方向。确实有很多很多的人,说的东西都是高大上(大概率是听别人说或者从网络上看到的),但是,真正能实现或者落地的情况少之又少。这无非就是执行力和思考的能力不能匹配(但是我从你的描述中能够看出,你能很好的平衡执行力和想法,非常棒)

    (3)对自己未来的规划非常明确,这一点也是我需要向你学习的,这点很难得。我在工作三年的时候,其实非常的迷茫和迷惑,不清楚自己能在未来做什么,这让我踩了很多坑。总结下来,就是缺少缜密的思考,把现实想的太过于简单或者是对自己的认识不清

    (4)年轻的时候,技术确实是非常重要的,毕竟,在可以预见的未来几年,我们依然需要靠技术吃饭。后面的,更在乎的是对自己的职业规划和思想,这远远大于技术本身

    加油,我们一起加油,并肩前行,找到属于自己的路,有自己的家!


    我是勤一,致力于将这门课程的问答区打造为 Java 知识体系知识库,Java 知识体系 BBS!共同建造、维护这门课程,我需要每一个你!

2 回复 有任何疑惑可以回复我~
  • 提问者 zk0405 #1
    谢谢一哥的肯定。
    其实我也很迷茫,迷茫的是自己做的成果有没有人肯定。由于公司的原因,很多项目上的事情都是我自己一个人拍拍脑门就决定的,没有技术大牛指引。虽然都是有经过考虑,但也是有失误的时候。
    去年微服务大热,在一个小项目中我就引入了springcloud。不过用力过猛把模块拆的太细了,结果被甲方一个懂技术的一顿狂怼,最终还是该成了单体应用。这次经历也让我明白为了用技术而去用技术的后果就是徒增项目复杂度,虽然项目介绍可以写的很漂亮,但是运维和开发成本也是同样上升的。
    最后再次谢谢一哥的肯定,我也会将自己视为这个社区的一份子,多多分享自己的开发经历。
    回复 有任何疑惑可以回复我~ 2020-09-14 16:37:13
Jacquie 2020-09-14 18:23:40

同学你好,打扰了。同学写的真的很诚恳也很详细。这个内容太精彩了,请问可以截图转载到手记中分享吗?作为吸引更多小伙伴关注我们的课的途径之一。当然我会将你的头像和id等信息模糊处理的。看你的经历和与老师的交流真的很感动,期待着这份感动和暖心可以再多暖几个人。祝学习愉快~

0 回复 有任何疑惑可以回复我~
  • 提问者 zk0405 #1
    可以的
    回复 有任何疑惑可以回复我~ 2020-09-14 22:21:24
  • Jacquie #2
    好的,谢谢~
    回复 有任何疑惑可以回复我~ 2020-09-15 09:08:26
问题已解决,确定采纳
还有疑问,暂不采纳
意见反馈 帮助中心 APP下载
官方微信