写点什么

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

作者:java易二三
  • 2023-08-23
    湖南
  • 本文字数:6789 字

    阅读完需:约 22 分钟

日常开发中,我们经常会遇到一些重复代码。大家都知道重复代码不好,它主要有这些缺点:可维护性差、可读性差、增加错误风险等等。最近呢,我优化了一些系统中的重复代码,用了好几种的方式。感觉挺有用的,所以本文给大家讲讲优化重复代码的几种方式。


抽取公用方法


抽个工具类


反射


泛型


继承和多态


设计模式


函数式


AOP


公众号:捡田螺的小男孩 (有田螺精心原创的面试 PDF)


github 地址,感谢每颗 star:github


  1. 抽取公用方法抽取公用方法,是最常用的代码去重方法~比如这个例子,分别遍历 names 列表,然后各自转化为大写和小写打印出来:typescript 复制代码 public class TianLuoExample {

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

  3. }}


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


public static void processNames(List<String> names, Function<String, String> 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<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "TianLuo");
processNames(names, String::toUpperCase, "Uppercase"); processNames(names, String::toLowerCase, "Lowercase");}
复制代码


}


  1. 抽工具类我们优化重复代码,抽一个公用方法后,如果发现这个方法有更多共性,就可以把公用方法升级为一个工具类。比如这样的业务场景:我们注册的时候,修改邮箱,重置密码等,都需要校验邮箱实现注册功能时,用户会填邮箱,需要验证邮箱格式,java 复制代码 public class RegisterServiceImpl implements RegisterService{private static final String EMAIL_REGEX ="^[A-Za-z0-9+_.-]+@(.+)$";

  2. 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;}

  3. }}


在密码重置流程中,通常会向用户提供一个链接或验证码,并且需要发送到用户的电子邮件地址。在这种情况下,也需要验证邮箱格式合法性:java 复制代码 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();}
复制代码


}


我们可以抽取个校验邮箱的方法出来,又因为校验邮箱的功能在不同的类中,因此,我们可以抽个校验邮箱的工具类:java 复制代码 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;}
复制代码


}


  1. 反射我们日常开发中,经常需要进行 PO、DTO 和 VO 的转化。所以大家经常看到类似的代码:scss 复制代码 //DTO 转 VOpublic UserInfoVO convert(UserInfoDTO userInfoDTO) {UserInfoVO userInfoVO = new UserInfoVO();userInfoVO.setUserName(userInfoDTO.getUserName());userInfoVO.setAge(userInfoDTO.getAge());return userInfoVO;}//PO 转 DTOpublic UserInfoDTO convert(UserInfoPO userInfoPO) {UserInfoDTO userInfoDTO = new UserInfoDTO();userInfoDTO.setUserName(userInfoPO.getUserName());userInfoDTO.setAge(userInfoPO.getAge());return userInfoDTO;}


我们可以使用 BeanUtils.copyProperties() 去除重复代码,BeanUtils.copyProperties()底层就是使用了反射:ini 复制代码 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.泛型泛型是如何去除重复代码的呢?给大家看个例子,我有个转账明细和转账余额对比的业务需求,有两个类似这样的方法:typescript 复制代码 private void getAndUpdateBalanceResultMap(String key, Map<String, List<TransferBalanceDTO>> compareResultListMap,List<TransferBalanceDTO> balanceDTOs) {List<TransferBalanceDTO> tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());tempList.addAll(balanceDTOs);compareResultListMap.put(key, tempList);}


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


这两块代码,流程功能看着很像,但是就是不能直接合并抽取一个公用方法,因为类型不一致。单纯类型不一样的话,我们可以结合泛型处理,因为泛型的本质就是参数化类型.优化为这样:typescript 复制代码 private <T> void getAndUpdateResultMap(String key, Map<String, List<T>> compareResultListMap, List<T> accountingDTOS) {List<T> tempList = compareResultListMap.getOrDefault(key, new ArrayList<>());tempList.addAll(accountingDTOS);compareResultListMap.put(key, tempList);}


  1. 继承与多态假设你正在开发一个电子商务平台,需要处理不同类型的订单,例如普通订单和折扣订单。每种订单都有一些共同的属性(如订单号、购买商品列表)和方法(如计算总价、生成订单报告),但折扣订单还有特定的属性和方法。在没有使用继承和多态的话,会写出类似这样的代码:arduino 复制代码//普通订单 public class Order {private String orderNumber;private List<Product> products;

  2. public Order(String orderNumber, List<Product> products) {this.orderNumber = orderNumber;this.products = products;}

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

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


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


public DiscountOrder(String orderNumber, List<Product> 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,代码如下:arduino 复制代码 public class Order {private String orderNumber;private List<Product> products;


public Order(String orderNumber, List<Product> 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<Product> products, double discountPercentage) {    super(orderNumber, products);    this.discountPercentage = discountPercentage;}
@Overridepublic double calculateTotalPrice() { double total = super.calculateTotalPrice(); return total - (total * discountPercentage / 100);}
复制代码


}


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


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


我给大家举个例子,模板方法是如何去除重复代码的吧,业务场景:


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


如果没有使用模板方法模式,实现是酱紫的:csharp 复制代码 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 类中是重复的。使用模板方法模式,代码可以优化成这样:csharp 复制代码 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 {@Overridevoid brew() {System.out.println("Brewing coffee grinds");}


@Overridevoid addCondiments() {    System.out.println("Adding sugar and milk");}
复制代码


}


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


@Overridevoid addCondiments() {    System.out.println("Adding lemon");}
复制代码


}


在这个例子中,我们创建了一个抽象类 Beverage,其中定义了制作饮品的模板方法 prepareBeverage()。这个方法包含了烧水、倒入杯子等共同的步骤,而将制作过程中的特定步骤 brew() 和 addCondiments() 延迟到子类中实现。这样,我们避免了在每个具体的饮品类中重复编写相同的烧水和倒入杯子的代码,提高了代码的可维护性和重用性。7.自定义注解(或者说 AOP 面向切面)使用 AOP 框架可以在不同地方插入通用的逻辑,从而减少代码重复。业务场景:假设你正在开发一个 Web 应用程序,需要对不同的 Controller 方法进行权限检查。每个 Controller 方法都需要进行类似的权限验证,但是重复的代码会导致代码的冗余和维护困难。csharp 复制代码 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}
复制代码


}


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


@Before("@annotation(requiresPermission)")public void checkPermission(RequiresPermission requiresPermission) {    String permission = requiresPermission.value();        if (!User.hasPermission(permission)) {        throw new SecurityException("Insufficient permission to access this resource.");    }}
复制代码


}


public class MyController {@RequiresPermission("read")public void viewData() {// Method implementation}


@RequiresPermission("write")public void modifyData() {    // Method implementation}
复制代码


}


就这样,不管多少个 Controller 方法需要进行权限检查,你只需在方法上添加相应的注解即可。权限检查的逻辑在切面中集中管理,避免了在每个 Controller 方法中重复编写相同的权限验证代码。这大大提高了代码的可读性、可维护性,并避免了代码冗余。8.函数式接口和 Lambda 表达式业务场景:


假设你正在开发一个应用程序,需要根据不同的条件来过滤一组数据。每次过滤的逻辑都可能会有些微的不同,但基本的流程是相似的。


没有使用函数式接口和 Lambda 表达式的情况:typescript 复制代码 public class DataFilter {public List<Integer> filterPositiveNumbers(List<Integer> numbers) {List<Integer> result = new ArrayList<>();for (Integer number : numbers) {if (number > 0) {result.add(number);}}return result;}


public List<Integer> filterEvenNumbers(List<Integer> numbers) {    List<Integer> result = new ArrayList<>();    for (Integer number : numbers) {        if (number % 2 == 0) {            result.add(number);        }    }    return result;}
复制代码


}


在这个例子中,我们有两个不同的方法来过滤一组数据,但是基本的循环和条件判断逻辑是重复的,我们可以使用使用函数式接口和 Lambda 表达式,去除重复代码,如下:typescript 复制代码 public class DataFilter {public List<Integer> filterNumbers(List<Integer> numbers, Predicate<Integer> predicate) {List<Integer> result = new ArrayList<>();for (Integer number : numbers) {if (predicate.test(number)) {result.add(number);}}return result;}}


我们将过滤的核心逻辑抽象出来。该方法接受一个 Predicate 函数式接口作为参数,以便根据不同的条件来过滤数据。然后,我们可以使用 Lambda 表达式来传递具体的条件,这样最终也达到去除重复代码的效果啦.

用户头像

java易二三

关注

还未添加个人签名 2021-11-23 加入

还未添加个人简介

评论

发布
暂无评论
优化重复冗余代码的8种方式_Java_java易二三_InfoQ写作社区