優(yōu)化重復冗余代碼的8種方式!
前言
如果想學Java項目的,強烈推薦我的??項目消息推送平臺Austin(8K stars),可以用作畢業(yè)設計,可以用作校招,可以看看生產環(huán)境是怎么推送消息的。
倉庫地址(可點擊閱讀原文跳轉):https://gitee.com/zhongfucheng/austin
日常開發(fā)中,我們經常會遇到一些重復冗余的代碼。大家都知道重復代碼不好,它主要有這些缺點:可維護性差、可讀性差、增加錯誤風險等等。最近呢,我優(yōu)化了一些系統中的重復代碼,用了好幾種的方式,感覺挺有用的。所以本文給大家講講優(yōu)化重復冗余代碼的幾種方式~
-
抽取公用方法 -
抽個工具類 -
反射 -
泛型 -
繼承和多態(tài) -
設計模式 -
函數式Lambda -
AOP切面
1. 抽取公用方法
抽取公用方法,是最常用的代碼去重方式~
比如這個例子,分別遍歷names列表,然后各自轉化為大寫和小寫打印出來:
public class TianLuoExample {
public static void main(String[] args) {
List<String> 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,優(yōu)化成這樣:
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");
}
}
2. 抽工具類
我們優(yōu)化重復代碼,抽一個公用方法后,如果發(fā)現這個方法有更多共性,就可以把公用方法升級為一個工具類。比如這樣的業(yè)務場景:注冊,修改郵箱,重置密碼等,都需要校驗郵箱
實現注冊功能時,用戶會填郵箱,需要驗證郵箱格式,
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;
}
}
在密碼重置流程中,通常會向用戶提供一個鏈接或驗證碼,并且需要發(fā)送到用戶的電子郵件地址。在這種情況下,也需要驗證郵箱格式合法性:
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;
}
//發(fā)送通知修改密碼
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. 反射
我們日常開發(fā)中,經常需要進行PO、DTO和VO的轉化。所以大家經??吹筋愃频拇a:
//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.泛型
泛型是如何去除重復代碼的呢?給大家看個例子,我有個轉賬明細和轉賬余額對比的業(yè)務需求,有兩個類似這樣的方法:
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);
}
這兩塊代碼,流程功能看著很像,但是就是不能直接合并抽取一個公用方法,因為類型不一致。單純類型不一樣的話,我們可以結合泛型處理,因為泛型的本質就是參數化類型.優(yōu)化為這樣:
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);
}
5. 繼承與多態(tài)
假設你正在開發(fā)一個電子商務平臺,需要處理不同類型的訂單,例如普通訂單和折扣訂單。每種訂單都有一些共同的屬性(如訂單號、購買商品列表)和方法(如計算總價、生成訂單報告),但折扣訂單還有特定的屬性和方法。
在沒有使用繼承和多態(tài)的話,會寫出類似這樣的代碼:
//普通訂單
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 {
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()則是有一點點區(qū)別,但也大相徑庭。
我們可以使用繼承和多態(tài)去除重復代碼,讓DiscountOrder去繼承Order,代碼如下:
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;
}
@Override
public double calculateTotalPrice() {
double total = super.calculateTotalPrice();
return total - (total * discountPercentage / 100);
}
}
6.使用設計模式
很多設計模式可以減少重復代碼、提高代碼的可讀性、可擴展性.比如:
-
工廠模式: 通過工廠模式,你可以將對象的創(chuàng)建和使用分開,從而減少重復的創(chuàng)建代碼。 -
策略模式: 策略模式定義了一族算法,將它們封裝成獨立的類,并使它們可以互相替換。通過使用策略模式,你可以減少在代碼中重復使用相同的邏輯。 -
模板方法模式:模板方法模式定義了一個算法的骨架,將一些步驟延遲到子類中實現。這有助于避免在不同類中重復編寫相似的代碼。
我給大家舉個例子,模板方法是如何去除重復代碼的吧,業(yè)務場景:
假設你正在開發(fā)一個咖啡和茶的制作流程,制作過程中的熱水和添加物質的步驟是相同的,但是具體的飲品制作步驟是不同的。
如果沒有使用模板方法模式,實現是醬紫的:
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");
}
}
這個代碼例子,我們可以發(fā)現,燒水和倒入杯子的步驟代碼,在Coffee和Tea類中是重復的。
使用模板方法模式,代碼可以優(yōu)化成這樣:
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");
}
}
在這個例子中,我們創(chuàng)建了一個抽象類Beverage,其中定義了制作飲品的模板方法 prepareBeverage()。這個方法包含了燒水、倒入杯子等共同的步驟,而將制作過程中的特定步驟 brew() 和 addCondiments() 延遲到子類中實現。這樣,我們避免了在每個具體的飲品類中重復編寫相同的燒水和倒入杯子的代碼,提高了代碼的可維護性和重用性。
7.自定義注解(或者說AOP面向切面)
使用 AOP框架可以在不同地方插入通用的邏輯,從而減少代碼重復。
業(yè)務場景:
假設你正在開發(fā)一個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
@Component
public 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表達式
業(yè)務場景:
假設你正在開發(fā)一個應用程序,需要根據不同的條件來過濾一組數據。每次過濾的邏輯都可能會有些微的不同,但基本的流程是相似的。
沒有使用函數式接口和Lambda表達式的情況:
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;
}
}
在這個例子中,我們有兩個不同的方法來過濾一組數據,但是基本的循環(huán)和條件判斷邏輯是重復的,我們可以使用使用函數式接口和Lambda表達式,去除重復代碼,如下:
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表達式來傳遞具體的條件,這樣最終也達到去除重復代碼的效果啦.
我開通了項目股東服務,已經有不少消息推送平臺項目股東拿了阿里/vivo等大廠offer了。我是沒找到網上有跟我提供相同的服務,價格還比我低的。
??一對一周到的服務:有很多人的自學能力和基礎確實不太行,不知道怎么開始學習,從哪開始看起,學習項目的過程中會走很多彎路,很容易就迷茫了。付費最跟自學最主要的區(qū)別就是我的服務會更周到。我會告訴你怎么開始學這個開源項目,哪些是重點需要掌握的,如何利用最短的時間把握整個系統架構和編碼的設計,把時間節(jié)省下來去做其他事情。學習經驗/路線/簡歷編寫/面試經驗知無不言
??本地直連遠程服務:生產環(huán)境的應用系統肯定會依賴各種中間件,我專門買了兩臺服務器已經搭建好必要的環(huán)境??,在本地就可以直接啟動運行體驗和學習,無須花額外的時間自行搭建調試。
??細致的文檔&視頻:巨細致的語雀文檔11W+ 字,共106個文檔,項目視頻還在持續(xù)制作更新中(20個),不怕你學不會。
??付費社群:優(yōu)質的社群里需篩選過濾,學習氛圍是很重要的,多跟同輩或前輩聊聊,會少走很多彎路??
??清爽干練commit:專屬股東倉庫,一步一步從零復現austin,每個commit都帶著文檔&視頻學習。
如果想獲取上面的權益,可以看看??Java項目訓練營
