自己动手做一个开源工具

自己动手做一个开源工具


1. 为什么想自己做一个开源工具


  • 原因1:在前不久学完设计模式的情况下,急切的想试一试设计模式带来的美感。
  • 原因2:崇拜,向往开源的世界。当自己做的东西被千千万万人使用的时候,那种感觉对痴迷编程的程序员来说不是金钱和权利可以来衡量的。
  • 原因3:应适应景,根据现在和曾经的公司,以及以往所看到和所写的代码所出现的隐藏问题。想制造出一套能通用而且方便和可读性强的开源工具。让用到的人已最快的理解速度和上手速度,是我追求的目标。
  • 原因4:因为 项目开发慢,spring 诞生了。因为访问 jdbc 封装对象麻烦,mybatis 诞生了。因为 servlet 开发效率太低, spring mvc 诞生了。因为参数繁琐且臃肿,所以自己就做了一个简单的工具。

特再此说明,本人只是一个刚毕业的大学生。只是对开源和编程很是痴迷。可能所写的东西根本不止一题,但对于本人来说是一种提升。我所想表达的是设计模式的力量和希望能做出一套开源工具的向往。希望各位大神和牛人勿喷。可能外面已经有实现以下功能更好的 jar 工具。但是在我没有接触过任何类似的工具时,只是凭着自己的一丝灵感和一腔热血所创造出来的一个非常简单但对于本人意义重大的工具。

2.功能介绍

非常简单的参数验证问题,但是为了统一管理和快速参数验证所用设计模式封装的”密不透风”;

3.需解决的问题

3.1 版本问题 (小问题)

我们经常会用到字符串的非空判断,但是工具类的繁多导致开发中他用的这个版本,我用的哪个版本,虽然实现的方式差不多但是有时候还是会出现问题的(亲身经历)。例如:

        // com.alibaba.dubbo.common.utils;
        if (StringUtils.isEmpty(weChatId)) {}
        // com.alibaba.druid.util.
        if (StringUtils.isEmpty(weChatId)) {}
        // com.mysql.jdbc
        if (StringUtils.isEmpty(weChatId)) {}
        // org.apache.commons.lang
        if (StringUtils.isBlank(weChatId)) {}

我们可以看到,仅是一个非空判断就有那么多工具类(其实还有好多),但是我们的项目都用到了这些 jar 包,比如 dubbo,druid,jdbc,commons。我们不可能因为这个原因而不用这些 jar。当我们在特殊情况下需要把一段代码复制到其他项目的代码里,因为这个原因我们的代码将会编程下面这个样子。

if (org.apache.commons.lang3.StringUtils.isEmpty(weChatId)) {}

非常丑陋的代码。这只是其中一个小问题而已,让我们往下看。

3.2 代码过长问题 (中等问题)

当我们需要验证一个参数,需要很多次判断时,代码会闲的很臃肿。例如:

       // 这只是个简单的例子,不要钻牛角尖。可能验证逻辑很复杂。
        if (StringUtils.isEmpty(weChatId)) { // 判断是否为空
            if (weChatId.length() > 10) {   // 判断是否长度大于10
                if (weChatId.startsWith("a")) { // 判断是否是 'a' 开头
                }
            }
        }

或许我们还可以写成这样,变成一行代码:

if (StringUtils.isEmpty(weChatId) && weChatId.length() > 0 && weChatId.startsWith("a")) {}

这只是个简单的参数验证,如果参数验证中又有 “!” 号 (!true),又有括号包起来的问题。那不仅长,而且可读性太差。

3.3 代码冗余问题 (大等问题)

我们写接口时经常会在每个接口访问前加参数验证。但是多个接口可能需要相同的验证,这样代码冗余的问题就出现了,如果附带上 3.2 的问题。每个方法访问前都需要这么复杂的验证那要累死的,并且还很丑。

这时可能又会有人说,可以把这种又臭又长的参数验证封装成一个静态方法放在工具类中。

但这只是治标不治本,如果一个方法的参数需要验证,A,B,C 三次才可以访问接口,另一个方法的参数需要 A,C,D 三次才可以,种种交叉验证。那我们的工具类就变得非常长,非常冗余了,当以后想新增和维护的时候,会越来越难。

4.工具介绍

4.1 使用的设计模式

本工具只是用了一种设计模式 - -。装饰者模式。来看下 UML 图。
这里写图片描述

来看看我的 Component 抽象类。

public abstract class ParamComponent {

    /**
     * 装饰方法处理参数
     * @param param 需要效验的值
     * @return
     */
    public abstract String operation(String param) throws MyException;

    /**
     * 赋值装饰类
     * @param paramComponent
     */
    public abstract void setParamComponent(ParamComponent paramComponent);

}

在来看看我的 ConcreteComponent 被封装类:

public class ParamConcreteComponent extends ParamComponent {

    /**
     * 校验类
     */
    private ParamComponent paramComponent;

    public ParamConcreteComponent(ParamComponent paramComponent) {
        this.paramComponent = paramComponent;
    }

    public ParamConcreteComponent() {}

    @Override
    public String operation(String param) throws MyException {
        return paramComponent.operation(param);
    }

    public ParamComponent getParamComponent() {
        return paramComponent;
    }

    @Override
    public void setParamComponent(ParamComponent paramComponent) {
        this.paramComponent = paramComponent;
    }
}

Decorator 抽象类:

public abstract class ParamDecorator extends ParamComponent {

    /**
     * 要装饰的类
     */
    protected ParamComponent paramComponent;

    public ParamDecorator(ParamComponent paramComponent) {
        this.paramComponent = paramComponent;
    }



    @Override
    public String operation(String param) throws MyException {
        return paramComponent == null ? param : paramComponent.operation(param);
    }

    @Override
    public void setParamComponent(ParamComponent paramComponent) {
        this.paramComponent = paramComponent;
    }
}

再来看看我的 Decorator 抽象类子类(只发出来一个,最后会附上源码地址):

public class ParamNotNullDecorator extends ParamDecorator {

    public ParamNotNullDecorator(ParamComponent paramComponent) {
        super(paramComponent);
    }

    @Override
    public String operation(String param) throws MyException {
        if (param != null && param.length() > 0) {
            return super.operation(param);
        }
        throw new MyException("40000",param,"param is null");
    }
}

或许看上去很简单,只是把一种验证封装成了一个对象而已。但是用这个模式不是只为了把他做成对象,而是更加方便的调用。
在加上我们的工具类试试:

public class ParamValidateUtil<T extends ParamValidateUtil> {

    /**
     * 被装饰的参数类
     */
    protected ParamConcreteComponent paramConcreteComponent;

    /**
     * 当前的验证类
     */
    protected ParamComponent thisParamComponent;

    protected ParamValidateUtil(ParamConcreteComponent paramConcreteComponent) {

        this.thisParamComponent = paramConcreteComponent;
        this.paramConcreteComponent = paramConcreteComponent;
    }

    /**
     * 赋值验证类
     */
    protected T assignmentDecorator(ParamComponent paramComponent) {
        thisParamComponent.setParamComponent(paramComponent);
        thisParamComponent = paramComponent;
        return (T) this;
    }

    /**
     * 获取实例
     *
     * @return
     */
    public static ParamValidateUtil getInstance() {
        return new ParamValidateUtil(new ParamConcreteComponent());
    }

    /**
     * 执行
     *
     * @return
     */
    public T execuet(String...params) throws MyException {
        for (String param : params) {
            paramConcreteComponent.operation(param);
        }
        return (T) this;
    }
    /**
     * 非空验证
     *
     * @return
     */
    public ParamValidateUtil notNull() {
        return assignmentDecorator(new ParamNotNullDecorator(null));
    }
    // ....其他验证。请在最下面的源码地址查看
}

我的工具类也很简单。但是就是一堆简单的代码产生的巨大的化学反应。让我们看看如何使用他把。

5. 如何使用

因为我们的验证失败都是以异常抛出来的。所以我们的代码都写在 try-chat 中,那原因是什么呢?

5.1 为什么我们要以异常的形式抛出来呢?
  1. 如果以返回值的性质发送出来我们还需要接收返回值,然后做返回值的判断,这样代码又会增加几行,这不是我想看到的。
  2. 我们写接口的时候经常遇到一些我们没有抓到的异常,或许是我们没有发现哪些地方会出现异常。但是抛出异常后接口异常终止,有时就会返回给用户错误页面,这也是我们不行看到的。我们通常都会在 Controller 层调用服务的时候 try-chat 一下。防止没有捕获的异常返回给用户不好的错误页面。

这时我们就可以把参数验证一起和调用服务时的 try-chat 写在一起。一举两得。如果参数验证错误了。会返回相应的参数验证错误信息。返还给调用者。

5.2 使用方法

5.2.1. 普通使用

        try {
            /** 判断是否为空,判断是否是手机号,判断长度是否大于等于1,小于12。
             * 虽然手机号就是11位数。我这里还做了长度验证,只是为了增加一个参数验证的过程而已。
             * 这种参数验证 看到方法就能快速的看出我们做了哪些验证。
             */ 不论是再加一种还是再减一种都很方便
            ParamValidateUtil.getInstance()
                    .notNull()
                    .phone()
                    .size(1, 12).execuet(phone);
            // 调用 service 如果有未捕获到的异常也可以顺便捕获
            userService.getByPhone(phone);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

5.2.2. 双参数使用

        try {
            // 这样同一种验证我们只需要写一次验证过程,传入两个参数即可
            ParamValidateUtil.getInstance()
                    .notNull()
                    .phone()
                    .size(1, 12).execuet(newPhone, oldPhone);
            userService.updatePhone(newPhone, oldPhone);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

5.2.3 参数验证复用

        try {
            // 注意 : 下面的都只是为了演示,userId并不一定就是长度 112。只是为了和手机号公用一种验证方式。
            //        以下和以上的演示,只是为了演示思路和使用方法,不要太在意使用了哪些验证
            // userId 验证是否不为 null 且长度为 112 
            // phone 验证是否不为 null 且长度为 112 且符合正则手机号的标准
            ParamValidateUtil.getInstance()
                    .notNull()
                    .size(1,12).execuet(userId)
                    .phone().execuet(phone);
             userService.updatePhoneByUserId(userId, phone);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

6. 扩展性

如果本工具只是一个 jar 。当里面的验证方法不完全满足你的验证过程,比如你想增加一个邮箱验证,但是你并不能打开 jar,来修改源码。来让我们看看如何扩展自己的验证参数。

6.1 首先我们需要创建一个 ParamEmailDecorator 装饰类。

该装饰类继承 ParamDecorator 并重写 operation 方法。

public class ParamEmailDecorator extends ParamDecorator {

    public ParamEmailDecorator(ParamComponent paramComponent) {
        // 传入到父类的赋值 paramComponent 属性
        super(paramComponent);
    }

    @Override
    public String operation(String param) throws MyException {
        /** PatternUtil 是正则匹配的验证工具,当然你也可以写在类里面方便修改
         * 这里我们只是封装调用类 PatternUtil.matcherEmail 的方法。
         * 为了更好的灵活使用邮箱验证
         * 重点: 如果自己实现复杂点的验证,一定要有下面的要求.
         */ 验证成功就调用 super.operation(param) 。失败就抛出异常
        if (PatternUtil.matcherEmail(param)) {
            return super.operation(param);
        }
        // 如果参数验证错误,产出相应的错误信息
        throw new MyException("40002", param, "email mismatch");
    }

}
6.2 扩展 ParamValidateUtil 工具类。

该工具类继承 ParamValidateUtil 工具类。 并添加一个参数的构造器和重写 getInstance() 方法

public class CustomParamUtil extends ParamValidateUtil<CustomParamUtil> {


    protected CustomParamUtil(ParamConcreteComponent paramConcreteComponent) {
        super(paramConcreteComponent);
    }
    /**
     * 获取实例
     *
     * @return
     */
    public static CustomParamUtil getInstance() {
        return new CustomParamUtil(new ParamConcreteComponent());
    }

    /**
     * 邮箱验证
     *
     * @return
     */
    public CustomParamUtil email() {
        return assignmentDecorator(new ParamEmailDecorator(null));
    }
}

以后我们新增参数验证,只需要在自己参数验证工具类里面增加一个对应的方法。如 email()。
如果再增加参数验证方法时,只需要复制 email 方法。将方法名改为可读性高的名字,和修改assignmentDecorator 方法参数的 new ParamEmailDecorator(null) 这个参数即可。如:

    /**
     * 其他验证
     *
     * @return
     */
    public CustomParamUtil XXXX() {
        return assignmentDecorator(new XXXXXXX(null));
    }

将代码中的 xxxx 改为你所需要的就可以。注意 new XXXXXXX(null) 是你实现的参数验证的装饰者类。

工具还有很多可以更改的地方,如 new ParamEmailDecorator(null) 其实可以不用传 null 。后期会修改。

6.3 使用自己的参数验证工具
        try {
            // 使用自己的参数验证工具
            CustomParamUtil.getInstance()
                    .notNull()
                    .size(1, 12).execuet(userId)
                    .email().execuet(email);
            userService.updateEmailByUserId(userId, email);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

这样我们扩展的自己的参数工具类就完成了。非常快捷方便。

统一管理起来的参数验证问题,可以省很多事情,比如某个程序员验证某个邮箱必须是 qq 邮箱,写好的验证方法,加入到工具类中,所有人都可以使用了。而且非常灵活。可以在邮箱验证前面增加非空验证,也可以在后面增加长度验证等等。

不需要在用一长串臃肿的 if else 语句做参数验证。
还可以根据捕获的验证错误状态码做相应的处理。

再次强调,本人制作出来的工具只是在生活中发现的一些隐性问题并且灵感一来而创作的。可能对于一部分人来多不怎么样,或者有自己更好的解决方案。 本人也是根据自己的想法做出来的一套工具而已(可能都不算一套工具,太小了。)。这次的博客只是为了分享以下我的想法和思路,也希望有大牛能做出一定的评价。毕竟本人才疏学浅,见识也不高,只能做出这种层次的东西。勿喷。。。

源代码地址:https://github.com/zcsh0823/common
装饰者类在 com.zsh.common.validate.param
参数工具在 com.zsh.common.util


若博客中有错误或者说的不好,请勿介意,仅代表个人想法。
csdn博客:https://blog.csdn.net/LNView
本文地址:https://blog.csdn.net/LNView/article/details/80675860

有问题或者喜欢的欢迎评论。

转载请注明出处!!!!!!

猜你喜欢

转载自blog.csdn.net/lnview/article/details/80675860