编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

若依二开:重构验证码模块(带你了解什么是高内聚低耦合)

wxchong 2024-10-08 20:04:12 开源技术 12 ℃ 0 评论


前言

在软件领域,我们经常会听到高内聚低耦合这两个词,特别是在一些优秀的框架或者软件架构对这种原则遵循得很到位。高内聚低耦合是软件设计中的两个重要原则,高内聚指的是软件模块或类内部的代码或元素之间紧密相关,共同完成某一特定功能或任务,这样的模块具有较好的功能独立性和重用性。低耦合则是指软件的不同模块或类之间的相互依赖程度较低,模块间的接口简单清晰,一个模块的变更不会导致其他模块的大规模修改。低耦合有助于提高系统的灵活性和可维护性。

高内聚

体现在一个模块或者一个类亦或者一个方法内部尽量的职责单一,能不对外的接口尽量不对外。内部元素之间紧密相关完成同一个功能。具体例子,我们个人计算机的磁盘,它主要功能功能是读写数据,内部的电路设计极其复杂,但是这些复杂度都是全部封装起来,从外部看磁盘的功能非常的简单,就是写入、读取数据。磁盘内部实现肯定通过不同部件和电路共同完成,但是这种复杂度都是被封装在局部,对外是看不到这些复杂度的。所以,高内聚有助于独立性重用性

低耦合

体现在多个模块之间依据接口建立联系,不要依据具体实现,也就是我们经常说的面向接口编程。当一个模块发生了变化也不会影响到其他模块大范围的修改。还是拿磁盘为例子来说明低耦合。上面说过磁盘内部的复杂度封装是高内聚的表现,那么磁盘和其他计算机部件的对接能做到足够简单甚至可替换,那就是低耦合。比如磁盘和主板直接的接口,磁盘和CPU直接的接口,当磁盘有故障换一个新的一样可以使用,不影响主板和CUP对磁盘的供电和读写数据。因此,低耦合有助于提高系统灵活性和可维护性

示例

下面我通过一个具体的例子,和大家分享在实际开发中如何遵循高内聚,低耦合的设计原则让你的系统更具有重用性和灵活性。为了方便大家理解,我这里继续拿若依框架来举例说明。用过若依框架的伙伴们,肯定对若依的验证码这个模块非常的熟悉,在登录界面就用到这个验证码,截图如下:

这个验证码在若依中有两种配置方式,一个是计算方式,一个是字符形式,可以在属性文件配置指定验证码生成方式,如图:

我们先看一下Controller中的实现:

具体的这两个验证码管理对象在spring中配置在ruoyi-framework模块config 目录下的 CaptchaConfig.java这个配置类中,代码截图如下:

这就是框架中对验证码如何生成的具体实现过程。从功能角度看这个还是比较灵活的,但是从设计角度看我觉得还有提升空间:

  1. 在controller中需要通过if else 多分支方式去实现,要是后续再新增一种验证码实现方式那 这个业务代码得修改。
  2. 新增一种验证实现,controller中就需要autowrite注入一个bean,如果验证方式越多,这里就要注入越多对象,但是一个系统一般只使用一种验证码方式
  3. 业务代码和非业务代码混合,导致非业务代码新增或者修改影响到业务代码
  4. 那两个验证码管理对象其实没有重用性,因为在整个业务中登录才用到验证码,感觉没必要让spring容器去管理,而且即使不需要也要实例化DefaultKaptcha对象

这里说的业务代码是指controller,因为controller就是直接面对前端的,如果后端还继续分前台和后台的话,那么controller就是前台代码,像service,mapper,utils其他代码都是后台代码。在设计中,前台代码尽可能的简单,保证业务流程的正确性和稳定性,后台代码可以相应复杂,但是要保证和前台代码低耦合。那么笔者今天就按照自己的思路做一个简单的改造,让设计更符合高内聚,低耦合的设计原则。整体思路是这样的:

  1. 因为验证码最终需要返回一个字符串的code和BufferedImage类型的image给前端,所以需要先定义一个包装类CaptchaReult,包含这两个参数。
  2. 重新定义一个CaptchaHandler接口,这个接口只有一个方法,负责创建CaptchaReult
  3. 分别定义两个具体实现类,实现CaptchaHandler接口
  4. 在spring中配置 CaptchaHandler这个Bean,根据类型具体实例化第三步的具体实现类,这里是典型的策略模式
  5. 在controller只依赖注入CaptchaHandler,调用创建验证码方法即可

下面是具体的代码实现:


//用来给前端返回code和image
public class CaptchaReult {
    public CaptchaReult(String code, BufferedImage image) {
        this.code = code;
        this.image = image;
    }

    private BufferedImage image;
    private String code;

    public BufferedImage getImage() {
        return image;
    }
    public String getCode() {
        return code;
    }
}
//创建验证码接口
public interface CaptchaHandler {
    public CaptchaReult createCaptcha();
}
// 字符方式验证码实现
public class CharCaptchaHandler implements CaptchaHandler {
    private final Producer producer;

    public CharCaptchaHandler() {
        this.producer = initProducer();
    }

    @Override
    public CaptchaReult createCaptcha() {
        String capStr = producer.createText();
        BufferedImage image = producer.createImage(capStr);
        return new CaptchaReult(capStr, image);
    }
    // 这里使用私有方法 创建具体的验证码生成对象
    private final DefaultKaptcha initProducer() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 是否有边框 默认为true 我们可以自己设置yes,no
        properties.setProperty(KAPTCHA_BORDER, "yes");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black");
        // 验证码图片宽度 默认为200
        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
        // 验证码图片高度 默认为50
        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38");
        // KAPTCHA_SESSION_KEY
        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode");
        // 验证码文本字符长度 默认为5
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple
        // 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy
        // 阴影com.google.code.kaptcha.impl.ShadowGimpy
        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, ShadowGimpy.class.getName());
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}
// 计算模式生成验证码
public class MachCaptchaHandler implements CaptchaHandler {
    private final Producer producer;

    public MachCaptchaHandler() {
        this.producer = initProducer();
    }

    @Override
    public CaptchaReult createCaptcha() {
        String capText = producer.createText();
        String capStr = capText.substring(0, capText.lastIndexOf("@"));
        String code = capText.substring(capText.lastIndexOf("@") + 1);
        BufferedImage image = producer.createImage(capStr);
        return new CaptchaReult(code, image);
    }

      // 这里使用私有方法 创建具体的验证码生成对象
    private final DefaultKaptcha initProducer() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 是否有边框 默认为true 我们可以自己设置yes,no
        properties.setProperty(KAPTCHA_BORDER, "yes");
        // 边框颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90");
        // 验证码文本字符颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
        // 验证码图片宽度 默认为200
        properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160");
        // 验证码图片高度 默认为50
        properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60");
        // 验证码文本字符大小 默认为40
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35");
        // KAPTCHA_SESSION_KEY
        properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath");
        // 验证码文本生成器
        properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, KaptchaTextCreator.class.getName());
        // 验证码文本字符间距 默认为2
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3");
        // 验证码文本字符长度 默认为5
        properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6");
        // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
        properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
        // 验证码噪点颜色 默认为Color.BLACK
        properties.setProperty(KAPTCHA_NOISE_COLOR, "white");
        // 干扰实现类
        properties.setProperty(KAPTCHA_NOISE_IMPL, NoNoise.class.getName());
        // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple
        // 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy
        // 阴影com.google.code.kaptcha.impl.ShadowGimpy
        properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, ShadowGimpy.class.getName());
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}
@Configuration
public class CaptchaConfig {
    @Autowired
    private RuoYiConfig config;

    // 配置验证码生成工厂bean,controller中自动注入这个bean
    @Bean
    public CaptchaHandler captchaHandler() {
        switch (config.getCaptchaType()) {
            case "math":
                return new MachCaptchaHandler();
            case "char":
                return new CharCaptchaHandler();
            // 后续有新增的类型,这里可以继续添加
            default:
                return null;
        }
    }
}

可以看到,最终一个目录下存放了有关验证码的生成的相关接口和实现,如下图所示:

最后在验证码生成的controller中改造如下:

箭头部分是改造后的代码,注释部分是之前的代码。可以看到controller中只需要注入一个 CaptchaHandler,并且调用其createCaptcha方法即可,无需关注CaptchaHandler具体是 char或者是mach类型,也不需要通过if else这样的方式去生成验证码了。未来要是想新增一种验证码生成方式,只需要新增一个具体的类去实现CaptchaHandler接口,并在CaptchaConfig下添加一个策略,controller中的代码不需要修改,如图所示:

这里的MachCaptchaHandler和CharCaptchaHandler的具体实现就是高内聚的体现,里面的构造器和createCaptcha()方法封装了验证码的具体实现方式,而DefaultKaptcha对象的获取定义为类私有,因为这个对象只为一个类服务,不需要像若依原来的实现一样把它直接暴露到容器中。最后只需要返回CaptchaReult这个对象,调用方不需要关注验证码是如何生成的。而controller中的 CaptchaReult captchaReult = captchaHandler.createCaptcha() 这一句就是改造后低耦合的体现,验证码生成方式和具体业务代码是通过接口建立联系的,未来验证码创建方式变更或者新增实现方式,这一句代码都不需要改动,这正是低耦合所遵循的灵活性和可维护性。

以上这种改进,其实在设计模式里面有一个专有的名字叫策略模式,策略模式的主要核心就是把算法封装起来,客户端可以依赖接口灵活的替换这些算法。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表