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