专栏名称: 算法爱好者
算法是程序员的内功!伯乐在线旗下账号「算法爱好者」专注分享算法相关文章、工具资源和算法题,帮程序员修炼内功。
目录
相关文章推荐
南都周刊  ·  复播被封禁!被质疑吃相难看 ·  10 小时前  
人民日报  ·  【夜读】为人父母,一定要跟孩子说的7句话 ·  10 小时前  
新华社  ·  夜读|最舒服的关系莫过于此 ·  4 天前  
51好读  ›  专栏  ›  算法爱好者

优化重复冗余代码的 8 种方式!

算法爱好者  · 公众号  ·  · 2023-10-04 09:36

正文

日常开发中,我们经常会遇到 一些重复冗余的代码 。大家都知道 重复代码不好 ,它主要有这些缺点: 可维护性差、可读性差、增加错误风险 等等。

最近,我优化了一些系统中的重复代码,用了好几种的方式,感觉挺有用的。所以本文主要讲讲优化重复冗余代码的几种方式。

  • 抽取公用方法
  • 抽工具类
  • 反射
  • 泛型
  • 继承和多态
  • 使用设计模式
  • 自定义注解( AOP面向切面
  • 函数式接口和 Lambda表达式

1. 抽取公用方法

抽取公用方法 ,是最常用的 代码去重方式

比如下面这个例子,分别遍历 names 列表,然后各自转化为大写和小写打印出来:

public class TianLuoExample {

    public static void main(String[] args) {
        List names = Arrays.asList("Alice""Bob""Charlie""David""TianLuo");

        System.out.println("Uppercase Names:");
        for (String name : names) {
            String uppercaseName = name.toUpperCase();
            System.out.println(uppercaseName);
        }

        System.out.println("Lowercase Names:");
        for (String name : names) {
            String lowercaseName = name.toLowerCase();
            System.out.println(lowercaseName);
        }
    }
}

显然,都是遍历 names 过程,代码是重复冗余的, 只不过转化大小写不一样而已 。我们可以抽个 公用方法 processNames ,优化成这样:

public class TianLuoExample {

    public static void processNames(List names, Function nameProcessor, String processType) {
        System.out.println(processType + " Names:");
        for (String name : names) {
            String processedName = nameProcessor.apply(name);
            System.out.println(processedName);
        }
    }

    public static void main(String[] args) {
        List names = Arrays.asList("Alice""Bob""Charlie""David""TianLuo");

        processNames(names, String::toUpperCase, "Uppercase");
        processNames(names, String::toLowerCase, "Lowercase");
    }
}

2. 抽工具类

我们优化重复代码,抽一个公用方法后,如果发现这个方法有更多共性,就可以把 公用方法升级为一个工具类

比如这样的业务场景:注册、修改邮箱、重置密码等,都需要校验邮箱。

实现注册功能时,用户会填邮箱, 需要验证邮箱格式

public class RegisterServiceImpl implements RegisterService{
    private static final String EMAIL_REGEX =
        "^[A-Za-z0-9+_.-]+@(.+)$";

    public boolean registerUser(UserInfoReq userInfo) {
        String email = userInfo.getEmail();
        Pattern pattern = Pattern.compile(EMAIL_REGEX);
        Matcher emailMatcher = pattern.matcher(email);
        if (!emailMatcher.matches()) {
            System.out.println("Invalid email address.");
            return false;
        }

        // 进行其他用户注册逻辑,比如保存用户信息到数据库等
        // 返回注册结果
        return true;
    }
}

密码重置 流程中,通常会向用户提供一个链接或验证码,并且需要发送到用户的电子邮件地址。在这种情况下,也需要 验证邮箱格式合法性

public class PasswordServiceImpl implements PasswordService{

    private static final String EMAIL_REGEX =
        "^[A-Za-z0-9+_.-]+@(.+)$";

    public void resetPassword(PasswordInfo passwordInfo) {
        Pattern pattern = Pattern.compile(EMAIL_REGEX);
        Matcher emailMatcher = pattern.matcher(passwordInfo.getEmail());
        if (!emailMatcher.matches()) {
            System.out.println("Invalid email address.");
            return false;
        }
        //发送通知修改密码
        sendReSetPasswordNotify();
    }
}

我们可以 抽取个校验邮箱的方法 出来,又因为校验邮箱的功能在不同的类中,因此,我们可以抽个 校验邮箱的工具类

public class EmailValidatorUtil {
    private static final String EMAIL_REGEX =
        "^[A-Za-z0-9+_.-]+@(.+)$";

    private static final Pattern pattern = Pattern.compile(EMAIL_REGEX);

    public static boolean isValid(String email) {
        Matcher matcher = pattern.matcher(email);
        return matcher.matches();
    }
}

//注册的代码可以简化为这样啦
public class RegisterServiceImpl implements RegisterService{
    
    public boolean registerUser(UserInfoReq userInfo) {
        if (!EmailValidatorUtil.isValid(userInfo.getEmail())) {
            System.out.println("Invalid email address.");
            return false;
        }

        // 进行其他用户注册逻辑,比如保存用户信息到数据库等
        // 返回注册结果
        return true;
    }
}

3. 反射

我们日常开发中,经常需要进行PO、DTO和VO的转化。所以大家经常看到类似的代码:

    //DTO 转VO
    public UserInfoVO convert(UserInfoDTO userInfoDTO) {
        UserInfoVO userInfoVO = new UserInfoVO();
        userInfoVO.setUserName(userInfoDTO.getUserName());
        userInfoVO.setAge(userInfoDTO.getAge());
        return userInfoVO;
    }
      //PO 转DTO
    public UserInfoDTO convert(UserInfoPO userInfoPO) {
        UserInfoDTO userInfoDTO = new UserInfoDTO();
        userInfoDTO.setUserName(userInfoPO.getUserName());
        userInfoDTO.setAge(userInfoPO.getAge());
        return userInfoDTO;
    }

我们可以使用 BeanUtils.copyProperties() 去除 重复代码 BeanUtils.copyProperties() 底层就是使用了 反射

    public UserInfoVO convert(UserInfoDTO userInfoDTO) {
        UserInfoVO userInfoVO = new UserInfoVO();
        BeanUtils.copyProperties(userInfoDTO, userInfoVO);
        return userInfoVO;
    }

    public UserInfoDTO convert(UserInfoPO userInfoPO) {
        UserInfoDTO userInfoDTO = new UserInfoDTO();
        BeanUtils.copyProperties(userInfoPO,userInfoDTO);
        return userInfoDTO;
    }

4.泛型

泛型是如何去除重复代码的呢?

给大家看个例子,比如有个 转账明细和转账余额 对比的业务需求,有两个类似这样的方法:

private void getAndUpdateBalanceResultMap(String key, Map> compareResultListMap,
List balanceDTOs) {
    List tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());
    tempList.addAll(balanceDTOs);
    compareResultListMap.put(key, tempList);
}

private void getAndUpdateDetailResultMap(String key, Map> compareResultListMap,
                                          List detailDTOS) {
    List tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());
    tempList.addAll(detailDTOS);
    compareResultListMap.put(key, tempList);
}

这两块代码,流程功能看着很像,但是就是 不能直接合并抽取一个公用方法,因为类型不一致 。单纯类型不一样的话,我们可以结合 泛型 处理,因为泛型的本质就是参数化类型。优化为这样:

private  void getAndUpdateResultMap(String key, Map> compareResultListMap, List accountingDTOS) {
List tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());
tempList.addAll(accountingDTOS);
compareResultListMap.put(key, tempList);
}

5. 继承和多态

假设要开发一个电子商务平台,需要 处理不同类型的订单 ,例如普通订单和折扣订单。每种订单都有一些 共同的属性 (如订单号、购买商品列表)和方法(如计算总价、生成订单报告), 但折扣订单还有特定的属性和方法

在没有使用继承和多态的话,会写出类似这样的代码:

//普通订单
public class Order {
    private String orderNumber;
    private List products;

    public Order(String orderNumber, List products) {
        this.orderNumber = orderNumber;
        this.products = products;
    }

    public double calculateTotalPrice() {
        double total = 0;
        for (Product product : products) {
            total += product.getPrice();
        }
        return total;
    }
    
      public String generateOrderReport() {
        return "Order Report for " + orderNumber + ": Total Price = $" + calculateTotalPrice();
    }
}

//折扣订单
public class DiscountOrder {
    private String orderNumber;
    private List products;
    private double discountPercentage;

    public DiscountOrder(String orderNumber, List products, double discountPercentage) {
        this.orderNumber = orderNumber;
        this.products = products;
        this.discountPercentage = discountPercentage;
    }

    public double calculateTotalPrice() {
        double total = 0;
        for (Product product : products) {
            total += product.getPrice();
        }
        return total - (total * discountPercentage / 100);
    }
      public String generateOrderReport() {
        return "Order Report for " + orderNumber + ": Total Price = $" + calculateTotalPrice();
    }
}

显然,看到在 Order DiscountOrder 类中, generateOrderReport() 方法的代码是完全相同的。 calculateTotalPrice() 则是有一点点区别,但也大相径庭。

我们可以使用继承和多态去除重复代码,让 DiscountOrder 去继承 Order ,代码如下:

public class Order {
    private String orderNumber;
    private List products;

    public Order(String orderNumber, List products) {
        this.orderNumber = orderNumber;
        this.products = products;
    }

    public double calculateTotalPrice() {
        double total = 0;
        for (Product product : products) {
            total += product.getPrice();
        }
        return total;
    }

    public String generateOrderReport() {
        return "Order Report for " + orderNumber + ": Total Price = $" + calculateTotalPrice();
    }
}

public class DiscountOrder extends Order {
    private double discountPercentage;

    public DiscountOrder(String orderNumber, List products, double discountPercentage) {
        super(orderNumber, products);
        this.discountPercentage = discountPercentage;
    }

    @Override
    public double calculateTotalPrice() {
        double total = super.calculateTotalPrice();
        return total - (total * discountPercentage / 100);
    }
}

6.使用设计模式

很多设计模式可以 减少重复代码、提高代码的可读性、可扩展性 。比如:

  • 工厂模式 :通过工厂模式,可以将对象的创建和使用分开,从而 减少重复创建代码
  • 策略模式 :策略模式定义了一族算法,将它们封装成独立的类,并使它们可以互相替换。通过使用策略模式, 可以减少在代码中重复使用相同的逻辑
  • 模板方法模式 :模板方法模式定义了一个算法的骨架,将一些步骤延迟到子类中实现。 这有助于避免在不同类中重复编写相似的代码

下面举个例子,看看模板方法是 如何去除重复代码的。

业务场景:

假设要开发一个 咖啡和茶 的制作流程, 制作过程中的热水和添加物质的步骤是相同的 ,但是 具体的饮品制作步骤是不同的

如果没有使用模板方法模式,实现是这样子的:

public class Coffee {
    public void prepareCoffee() {
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addCondiments();
    }

    private void boilWater() {
        System.out.println("Boiling water");
    }

    private void brewCoffeeGrinds() {
        System.out.println("Brewing coffee grinds");
    }

    private void pourInCup() {
        System.out.println("Pouring into cup");
    }

    private void addCondiments() {
        System.out.println("Adding sugar and milk");
    }
}

public class Tea {
    public void prepareTea() {
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

    private void boilWater() {
        System.out.println("Boiling water");
    }

    private void steepTeaBag() {
        System.out.println("Steeping the tea bag");
    }

    private void pourInCup() {
        System.out.println("Pouring into cup");
    }

    private void addLemon() {
        System.out.println("Adding lemon");
    }
}

这个代码例子,我们可以发现, 烧水和倒入杯子的步骤代码 Coffee Tea 类中是重复的。

使用模板方法模式,代码可以优化成这样:

abstract class Beverage {
    public final void prepareBeverage() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    private void boilWater() {
        System.out.println("Boiling water");
    }

    abstract void brew();

    private void pourInCup() {
        System.out.println("Pouring into cup");
    }

    abstract void addCondiments();
}

class Coffee extends Beverage {
    @Override
    void brew() {
        System.out.println("Brewing coffee grinds");
    }

    @Override
    void addCondiments() {
        System.out.println("Adding sugar and milk");
    }
}

class Tea extends Beverage {
    @Override
    void brew() {
        System.out.println("Steeping the tea bag");
    }

    @Override
    void addCondiments() {
        System.out.println("Adding lemon");
    }
}

在这个例子中,我们创建了一个抽象类 Beverage ,其中定义了制作饮品的模板方法 prepareBeverage() 。这个方法包含了 烧水、倒入杯子 等共同的步骤,而将制作过程中的特定步骤 brew() 和 addCondiments() 延迟到子类中实现。这样,我们避免了在每个具体的饮品类中 重复编写相同的烧水和倒入杯子的代码 ,提高了代码的可维护性和重用性。

7.自定义注解(AOP面向切面)

使用 AOP 框架可以在不同地方插入通用的逻辑,从而减少代码重复。

业务场景:

假设要开发一个 Web 应用程序,需要对不同的 Controller 方法进行权限检查。每个 Controller 方法都需要进行类似的权限验证,但是 重复的代码会导致代码的冗余和维护困难

public class MyController {
    public void viewData() {
        if (!User.hasPermission("read")) {
            throw new SecurityException("Insufficient permission to access this resource.");
        }
        // Method implementation
    }

    public void modifyData() {
        if (!User.hasPermission("write")) {
            throw new SecurityException("Insufficient permission to access this resource.");
        }
        // Method implementation
    }
}

可以看到,在 每个需要权限校验的方法中都需要重复编写相同的权限校验逻辑 ,即出现了 重复代码 。我们使用 自定义注解的方式 能够将权限校验逻辑集中管理,通过 切面来处理,消除重复代码 。如下:

@Aspect






请到「今天看啥」查看全文