zoukankan      html  css  js  c++  java
  • 设计模式之美学习-DRY原则(十)

    什么是DRY

    Don’t Repeat Yourself   不要定义重复代码

    实现逻辑重复

    public class UserAuthenticator {
      public void authenticate(String username, String password) {
        if (!isValidUsername(username)) {
          // ...throw InvalidUsernameException...
        }
        if (!isValidPassword(password)) {
          // ...throw InvalidPasswordException...
        }
        //...省略其他代码...
      }
    
      private boolean isValidUsername(String username) {
        // check not null, not empty
        if (StringUtils.isBlank(username)) {
          return false;
        }
        // check length: 4~64
        int length = username.length();
        if (length < 4 || length > 64) {
          return false;
        }
        // contains only lowcase characters
        if (!StringUtils.isAllLowerCase(username)) {
          return false;
        }
        // contains only a~z,0~9,dot
        for (int i = 0; i < length; ++i) {
          char c = username.charAt(i);
          if (!(c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.') {
            return false;
          }
        }
        return true;
      }
    
      private boolean isValidPassword(String password) {
        // check not null, not empty
        if (StringUtils.isBlank(password)) {
          return false;
        }
        // check length: 4~64
        int length = password.length();
        if (length < 4 || length > 64) {
          return false;
        }
        // contains only lowcase characters
        if (!StringUtils.isAllLowerCase(password)) {
          return false;
        }
        // contains only a~z,0~9,dot
        for (int i = 0; i < length; ++i) {
          char c = password.charAt(i);
          if (!(c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.') {
            return false;
          }
        }
        return true;
      }
    }

    上面验证用户名和密码2个方法都是一模一样,看起来像是重复代码 违反了DRY原则 下面重构一下

    public class UserAuthenticatorV2 {
    
      public void authenticate(String userName, String password) {
        if (!isValidUsernameOrPassword(userName)) {
          // ...throw InvalidUsernameException...
        }
    
        if (!isValidUsernameOrPassword(password)) {
          // ...throw InvalidPasswordException...
        }
      }
    
      private boolean isValidUsernameOrPassword(String usernameOrPassword) {
        //省略实现逻辑
        //跟原来的isValidUsername()或isValidPassword()的实现逻辑一样...
        return true;
      }
    }

    合并之后 代码简单了 也没有重复代码,实际上合并之后依然后问题 违反了“单一职责原则”和“接口隔离原则”。

    因为 isValidUserName() 和 isValidPassword() 两个函数,虽然从代码实现逻辑上看起来是重复的,但是从语义上并不重复。所谓“语义不重复”指的是:从功能上来看,这两个函数干的是完全不重复的两件事情,一个是校验用户名,另一个是校验密码。尽管在目前的设计中,两个校验逻辑是完全一样的,但如果按照第二种写法,将两个函数的合并,那就会存在潜在的问题。在未来的某一天,如果我们修改了密码的校验逻辑,比如,允许密码包含大写字符,允许密码的长度为 8 到 64 个字符,那这个时候,isValidUserName() 和 isValidPassword() 的实现逻辑就会不相同。我们就要把合并后的函数,重新拆成合并前的那两个函数。尽管代码的实现逻辑是相同的,但语义不同,我们判定它并不违反 DRY 原则。对于包含重复代码的问题,我们可以通过抽象成更细粒度函数的方式来解决。比如将校验只包含 a~z、0~9、dot 的逻辑封装成 boolean onlyContains(String str, String charlist); 函数。

    功能语义重复

    public boolean isValidIp(String ipAddress) {
      if (StringUtils.isBlank(ipAddress)) return false;
      String regex = "^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\."
              + "(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\."
              + "(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\."
              + "(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)$";
      return ipAddress.matches(regex);
    }
    
    public boolean checkIfIpValid(String ipAddress) {
      if (StringUtils.isBlank(ipAddress)) return false;
      String[] ipUnits = StringUtils.split(ipAddress, '.');
      if (ipUnits.length != 4) {
        return false;
      }
      for (int i = 0; i < 4; ++i) {
        int ipUnitIntValue;
        try {
          ipUnitIntValue = Integer.parseInt(ipUnits[i]);
        } catch (NumberFormatException e) {
          return false;
        }
        if (ipUnitIntValue < 0 || ipUnitIntValue > 255) {
          return false;
        }
        if (i == 0 && ipUnitIntValue == 0) {
          return false;
        }
      }
      return true;
    }

    上面的代码 在有验证ip验证的情况下 又重复定义了一个验证ip 虽然实现逻辑不重复但是语义重复了

    尽管两段代码的实现逻辑不重复,但语义重复,也就是功能重复,我们认为它违反了 DRY 原则。我们应该在项目中,统一一种实现思路,所有用到判断 IP 地址是否合法的地方,都统一调用同一个函数。

    会出现2套逻辑 不同的地方在用 如果哪一天 ip验证验证规则要修改.如果漏改了其中一个就会有问题.

    在实际项目中也遇到有这样的问题。比如优惠券发卷  存在 单个发卷 批量发卷 等几个方法  不同的地方调用 发卷更改数量改为异步 有一次就漏掉了 批量发卷  就是违反了此原则

    代码执行重复

    public class UserService {
      private UserRepo userRepo;//通过依赖注入或者IOC框架注入
    
      public User login(String email, String password) {
        boolean existed = userRepo.checkIfUserExisted(email, password);
        if (!existed) {
          // ... throw AuthenticationFailureException...
        }
        User user = userRepo.getUserByEmail(email);
        return user;
      }
    }
    
    public class UserRepo {
      public boolean checkIfUserExisted(String email, String password) {
        if (!EmailValidation.validate(email)) {
          // ... throw InvalidEmailException...
        }
    
        if (!PasswordValidation.validate(password)) {
          // ... throw InvalidPasswordException...
        }
    
        //...query db to check if email&password exists...
      }
    
      public User getUserByEmail(String email) {
        if (!EmailValidation.validate(email)) {
          // ... throw InvalidEmailException...
        }
        //...query db to get user by email...
      }
    }

    getUserByEmail和checkIfUserExisted验证email重复了 ,还有查询了2次db 执行重复了

    优化后

    public class UserService {
      private UserRepo userRepo;//通过依赖注入或者IOC框架注入
    
      public User login(String email, String password) {
        if (!EmailValidation.validate(email)) {
          // ... throw InvalidEmailException...
        }
        if (!PasswordValidation.validate(password)) {
          // ... throw InvalidPasswordException...
        }
        User user = userRepo.getUserByEmail(email);
        if (user == null || !password.equals(user.getPassword()) {
          // ... throw AuthenticationFailureException...
        }
        return user;
      }
    }
    
    public class UserRepo {
      public boolean checkIfUserExisted(String email, String password) {
        //...query db to check if email&password exists
      }
    
      public User getUserByEmail(String email) {
        //...query db to get user by email...
      }
    }

    不过我感觉这个例子不太好 我个人理解getUserByEmail checkIfUserExisted 的校验逻辑是应该存在的,因为这2个方法可能不止这一个地方调用,方法保证自身逻辑正常执行的校验是应该的。

  • 相关阅读:
    nodejs + typescirpt + vs code
    NodeJs使用nodejs-websocket + protobuf
    Windows10环境下使用VisualSVN server搭建SVN服务器
    微信小游戏下socket.io的使用
    JS中实现种子随机数
    帧同步和状态同步
    EgretPaper学习笔记一 (安装环境,新建项目)
    反编译微信小游戏
    微信小游戏 小程序跳转修改 不支持动态更新,只能在发布时修改
    HTML5实现本地JSON文件的读写
  • 原文地址:https://www.cnblogs.com/LQBlog/p/12143267.html
Copyright © 2011-2022 走看看