SOLID原則詳解:提升軟件設計質量的關鍵

前言

關于設計原則SOLID具體指的是什么,怎么理解這些設計原則,我覺得有必要記錄一筆,畢竟這個設計原則確實經常在關鍵技術文檔中提及,在編程思想中提及,在日常的開發中使用,但是對我來說,似乎知道但又不那么明確,我希望自己對設計原則的思想有一個更加準確和全面的理解,也想明確如果沒有這個設計原則會如何?此設計原則的亮點和優勢是什么?我在日常開發中怎么使用到這些設計原則的?
本文就是基于以上問題的總結歸納,方便自己日后復盤。
說明:匯總風格和內容借助AI工具

一、什么是SOLID?

SOLID是面向對象編程和軟件設計的五個基本原則的首字母縮寫,這些原則幫助我們編寫更易于維護、擴展和理解的代碼。

  1. S - 單一職責原則 (Single Responsibility Principle)
  2. O - 開閉原則 (Open/Closed Principle)
  3. L - 里氏替換原則 (Liskov Substitution Principle)
  4. I - 接口隔離原則 (Interface Segregation Principle)
  5. D - 依賴倒置原則 (Dependency Inversion Principle)

1. 單一職責原則(SRP)

  • 核心:一個類應該只有一個引起它變化的原因(即只有一個職責)。
  • 關鍵點
    • 方法層面:一個方法只做一件事(如saveStudent()不應同時包含驗證和存儲邏輯)。
    • 類層面:Student類管理學生屬性,若需日志記錄,應拆分出StudentLogger類。
  • 優勢:降低復雜度、提高可維護性,修改一個功能時不會意外影響其他功能。
  • 現實類比:就像餐廳里廚師負責烹飪,服務員負責上菜,收銀員負責結賬,各司其職,而不是一個人做所有事情。

日常開發中的問題:忽視SRP會導致"上帝類"(God Class),修改一處可能影響多處功能,測試困難,代碼難以復用。

  • 反例:
    class Student {void saveToDatabase() { /* 數據庫操作 */ }void generateReport() { /* 生成PDF */ } // 違反SRP
    }
    

2. 開閉原則(OCP)

  • 核心:通過擴展(繼承/組合)添加新功能,而非修改已有代碼。
  • 關鍵點
    • 多態是手段之一,但OCP更強調抽象(接口/抽象類)的設計。
    • 示例:支付系統支持新支付方式時,應實現Payment接口,而非修改原有代碼。
    interface Payment { void pay(); }
    class CreditCard implements Payment { /* 無需修改現有類 */ }
    
  • 優勢:減少回歸測試風險,提高系統可擴展性。
  • 現實類比:USB接口設計 - 你可以插入各種設備(擴展開放),而不需要修改電腦的USB接口本身(修改關閉)。

日常開發中的問題:忽視OCP會導致每次需求變更都要修改核心類,增加回歸測試負擔,引入新bug的風險高。

  • 反例:
class Shape {private String type;public double calculateArea() {if (type.equals("circle")) {// 計算圓形面積} else if (type.equals("rectangle")) {// 計算矩形面積}// 每添加一個新形狀都要修改這個方法}
}

3. 里氏替換原則(LSP)

  • 核心:子類必須能夠替換父類而不破壞程序邏輯(行為一致性)。
  • 關鍵點:
    • 子類可擴展父類功能,但不能改變父類的契約(如輸入/輸出約束)。
  • 優勢:保證繼承體系的健壯性,避免運行時意外錯誤。
  • 現實類比:正方形是長方形的特例,但如果長方形有設置不同長寬的方法,正方形繼承長方形就會有問題,因為正方形長寬必須相同。

日常開發中的問題:忽視LSP會導致在使用多態時出現意外行為,子類無法真正替代父類,增加了代碼的脆弱性。

  • 反例:
    父類Birdfly()方法,子類Penguin重寫為空方法——違反LSP。
class Bird {public void fly() {System.out.println("Flying");}
}class Ostrich extends Bird {@Overridepublic void fly() {throw new UnsupportedOperationException("鴕鳥不會飛!");}
}public class Main {public static void makeBirdFly(Bird bird) {bird.fly();  // 對于鴕鳥,這會拋出異常}
}

4. 接口隔離原則(ISP)

  • 核心:客戶端不應被迫依賴它不需要的接口方法。
  • 關鍵點
    • 將龐大接口拆分為更小、更具體的接口(如PrinterScanner分開,而非合并為MultiFunctionDevice)。
    • 示例:
      interface Printable { void print(); }
      interface Scannable { void scan(); }
      class SimplePrinter implements Printable { ... } // 無需實現scan()
      
  • 優勢:減少接口污染,降低依賴耦合。
  • 現實類比:多功能工具 vs 專用工具 ,你不會用瑞士軍刀上的剪刀功能來剪頭發(雖然可以,但不合適)。

日常開發中的問題:忽視ISP會導致"胖接口",實現類被迫提供空實現或拋出異常,接口變得難以理解和維護。

  • 反例:
interface Worker {void work();void eat();void sleep();
}class HumanWorker implements Worker {// 實現所有方法
}class RobotWorker implements Worker {public void work() {// 機器人可以工作}public void eat() {throw new UnsupportedOperationException("機器人不需要吃飯");}public void sleep() {throw new UnsupportedOperationException("機器人不需要睡覺");}
}

5. 依賴倒置原則(DIP)

  • 核心
    高層模塊不應直接依賴低層模塊,二者都應依賴抽象(接口或抽象類)。
    抽象不應依賴細節(具體實現),細節應依賴抽象。
  • 關鍵點
    “反轉”傳統的依賴關系方向,使得軟件的設計更加靈活、可復用,并且更容易應對變化。
  • 現實類比:電源插座提供標準接口(抽象),各種電器(具體實現)只要符合接口標準就能使用,插座不需要知道具體是什么電器。

日常開發中的問題:忽視DIP會導致高層模塊與低層模塊緊耦合,難以替換實現,單元測試困難(因為難以mock依賴)。

  • 反例:
class LightBulb {public void turnOn() {// 開燈}public void turnOff() {// 關燈}
}class Switch {private LightBulb bulb;public Switch(LightBulb bulb) {this.bulb = bulb;}public void operate() {// 直接依賴具體實現bulb.turnOn();}
}

二、SpringBoot+MyBatis后臺系統中的SOLID原則實踐

1. 單一職責原則(SRP)在SpringBoot中的體現

反面案例(違反SRP)

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;// 用戶CRUDpublic User getUserById(Long id) { /*...*/ }public void saveUser(User user) { /*...*/ }// 密碼加密public String encryptPassword(String raw) { /*...*/ }// 權限檢查public boolean checkPermission(User user) { /*...*/ }// 日志記錄public void writeLog(User user, String action) { /*...*/ }
}

問題:這個Service類做了太多事情,違反了SRP。如果密碼加密算法或日志記錄方式需要修改,都要改這個類。

正面案例(遵循SRP)

// 用戶CRUD服務
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate PermissionChecker permissionChecker;@Autowiredprivate UserActionLogger actionLogger;public User getUserById(Long id) { /*...*/ }public void saveUser(User user) { user.setPassword(passwordEncoder.encode(user.getPassword()));userMapper.insert(user);actionLogger.log(user, "CREATE");}
}// 密碼加密組件
@Component
public class BCryptPasswordEncoder implements PasswordEncoder {public String encode(String raw) { /* 使用BCrypt加密 */ }
}// 權限檢查組件
@Component
public class PermissionChecker {public boolean check(User user) { /*...*/ }
}// 日志記錄組件
@Component
public class UserActionLogger {public void log(User user, String action) { /*...*/ }
}

SpringBoot中的體現

  • Controller只處理HTTP請求和響應
  • Service只處理業務邏輯
  • Mapper只負責數據庫操作
  • 各種Util/Helper類各司其職

2. 開閉原則(OCP)在MyBatis中的體現

場景:我們需要支持多種數據庫查詢方式(ID查詢、姓名查詢、條件組合查詢)

反面案例(違反OCP)

@Mapper
public interface UserMapper {@Select("SELECT * FROM user WHERE ${condition}") List<User> findByCondition(String condition); // 危險!SQL注入風險// 每新增一種查詢方式都要添加新方法
}

正面案例(遵循OCP)

使用MyBatis-Plus,它的Wrapper設計就符合OCP:

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;// 使用條件構造器,不需要修改原有代碼就能擴展新查詢方式public List<User> findUsers(String name, Integer age) {QueryWrapper<User> wrapper = new QueryWrapper<>();if (name != null) {wrapper.like("name", name);}if (age != null) {wrapper.eq("age", age);}return userMapper.selectList(wrapper);}
}

MP的設計

  • 通過Wrapper可以靈活組合查詢條件
  • 新增查詢條件不需要修改Mapper接口
  • 符合"對擴展開放,對修改關閉"

3. 里氏替換原則(LSP)在權限系統中的應用

場景:我們有普通用戶和管理員用戶

反面案例(違反LSP)

class User {public void deletePost(Post post) {// 基礎權限檢查}
}class Admin extends User {@Overridepublic void deletePost(Post post) {throw new UnsupportedOperationException("管理員應該用adminDeletePost方法");}public void adminDeletePost(Post post) {// 跳過權限檢查}
}

問題:Admin無法替換User,因為重寫的方法拋出了異常。

正面案例(遵循LSP)

interface PostDeleter {void deletePost(Post post);
}class UserPostDeleter implements PostDeleter {public void deletePost(Post post) {// 基礎權限檢查}
}class AdminPostDeleter implements PostDeleter {public void deletePost(Post post) {// 管理員有特殊處理,但不拋出異常}
}// 使用時
@Autowired
private Map<String, PostDeleter> deleterMap; // Spring會自動注入所有實現public void deletePost(Post post, String userType) {PostDeleter deleter = deleterMap.get(userType + "PostDeleter");deleter.deletePost(post); // 無論什么用戶類型都能安全調用
}

4. 接口隔離原則(ISP)在Service層設計中的應用

場景:用戶操作有讀操作和寫操作,有些客戶端只需要讀功能

反面案例(違反ISP)

public interface UserService {User getById(Long id);List<User> findAll();void save(User user);void delete(Long id);void resetPassword(Long id);// 很多方法...
}// 報表系統只需要讀功能,但被迫實現所有方法

正面案例(遵循ISP)

// 拆分接口
public interface UserReadService {User getById(Long id);List<User> findAll();
}public interface UserWriteService {void save(User user);void delete(Long id);void resetPassword(Long id);
}@Service
public class UserServiceImpl implements UserReadService, UserWriteService {// 實現所有方法
}// 報表系統只需要注入UserReadService
@Autowired
private UserReadService userReadService;

5. 依賴倒置原則(DIP)在SpringBoot中的體現

場景:用戶數據存儲可能使用MySQL或Redis

反面案例(違反DIP)

@Service
public class UserService {// 直接依賴具體實現private UserMySQLRepository userRepository = new UserMySQLRepository();// 如果改用Redis需要修改代碼
}

正面案例(遵循DIP)

// 定義抽象接口
public interface UserRepository {User findById(Long id);void save(User user);
}// MySQL實現
@Repository
public class UserMySQLRepository implements UserRepository {// 實現方法
}// Redis實現
@Repository
public class UserRedisRepository implements UserRepository {// 實現方法
}@Service
public class UserService {@Autowiredprivate UserRepository userRepository; // 依賴抽象// 可以通過@Qualifier或Profile決定注入哪個實現
}

SpringBoot天生支持DIP

  • 通過@Autowired注入接口
  • 具體實現由Spring容器管理
  • 輕松替換實現而不修改業務代碼

三、實際應用建議

(1)實際應用

  • Spring框架:依賴注入(DI)是DIP的典型實現。
  • Java集合框架List接口(抽象)與ArrayList/LinkedList(實現)遵循DIP和OCP。
  • 日志庫:SLF4J是抽象,Logback/Log4j是具體實現,符合DIP。

(2)實際編程中的選擇

  • 寫業務代碼時:優先用 SRPDIP(拆分職責+依賴接口)。
  • 設計架構時:重點考慮 OCPISP(方便擴展+接口精簡)。
  • review代碼時:檢查 LSP(子類是否破壞父類行為)。

后記

SOLID不是教條,而是幫助寫出更健壯代碼的工具。在SpringBoot項目中,很多設計已經遵循了這些原則,我們只需要有意識地應用它們。

參考鏈接

SOLID,面向對象設計五大基本原則

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/78957.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/78957.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/78957.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何使用 ONLYOFFICE 恢復之前的文件版本?

如何使用 ONLYOFFICE 恢復之前的文件版本&#xff1f; https://www.onlyoffice.com/blog/zh-hans/2023/04/how-to-use-version-history

簡簡單單實現一個Python+Selenium的自動化測試框架

什么是Selenium&#xff1f; Selenium是一個基于瀏覽器的自動化測試工具&#xff0c;它提供了一種跨平臺、跨瀏覽器的端到端的web自動化解決方案。Selenium主要包括三部分&#xff1a;Selenium IDE、Selenium WebDriver 和Selenium Grid。 Selenium IDE&#xff1a;Firefox的…

Java設計模式之中介者模式:從入門到架構級實踐

一、什么是中介者模式&#xff1f; 中介者模式&#xff08;Mediator Pattern&#xff09;是一種行為型設計模式&#xff0c;其核心思想是通過引入一個中介對象來封裝多個對象之間的交互關系。這種模式將原本復雜的網狀通信結構轉換為星型結構&#xff0c;類似于現實生活中的機…

Trinity三位一體開源程序是可解釋的 AI 分析工具和 3D 可視化

一、軟件介紹 文末提供源碼和程序下載學習 Trinity三位一體開源程序是可解釋的 AI 分析工具和 3D 可視化。Trinity 提供性能分析和 XAI 工具&#xff0c;非常適合深度學習系統或其他執行復雜分類或解碼的模型。 二、軟件作用和特征 Trinity 通過結合具有超維感知能力的不同交…

LeetCode 熱題 100_單詞拆分(86_139_中等_C++)(動態規劃)

LeetCode 熱題 100_單詞拆分&#xff08;86_139&#xff09; 題目描述&#xff1a;輸入輸出樣例&#xff1a;題解&#xff1a;解題思路&#xff1a;思路一&#xff08;動態規劃&#xff09;&#xff1a; 代碼實現代碼實現&#xff08;思路一&#xff08;動態規劃&#xff09;&a…

VM虛擬機安裝及Ubuntu安裝配置

VM虛擬機安裝及Ubuntu安裝配置 1、VM虛擬機安裝2、創建虛擬機3、Ubuntu系統安裝4、編譯環境配置4.1 、Ubuntu和 Windows文件互傳 文件互傳4.1.1、 開啟Ubunt下的FTP服務 4.2、 Ubuntu下NFS和SSH服務開啟4.2.1、 NFS服務開啟4.2.2、 SSH服務開啟 4.3、 交叉編譯器安裝4.3.1 安裝…

【KWDB 創作者計劃】_產品技術解讀_1

【KWDB 創作者計劃】_產品技術解讀_1 一、存儲引擎:高性能混合存儲架構1. 存儲模型設計2. 存儲壓縮與編碼3. 持久化策略二、KWDB 組件源碼解析1. 核心模塊分層架構2. 關鍵組件源碼剖析三、KWDB 特性代碼通讀1. 實時分析能力(Real-Time OLAP)2. 混合負載隔離(HTAP)3. 智能索…

高速電路中的電阻、電容的選型及應用

2.1 電阻的應用 2.1.1 與電阻相關的經典案例 如果說芯片是電路的骨架&#xff0c;那么電阻就是在芯片之間起連接作用的關節。電阻的阻值、布放位置等&#xff0c;對設計的成功起著至關重要的作用。 【案例2.1】串聯電阻過大&#xff0c;導致板間告警失敗 某產品由業務板和主…

springBoot接入文心一言

文章目錄 效果接入步驟項目接入配置類&#xff1a;WenXinYiYan前端vue代碼js代碼 后端mapper層service層controller層 測試代碼 效果 先來看一下最后實現的效果 &#xff08;1&#xff09;未點擊前的功能頁面 &#xff08;2&#xff09;點擊后的頁面 &#xff08;3&#xff…

css解決邊框四個角有顏色

效果 html <div class"gradient-corner">2021年</div>css background:/* 左上角橫線 */linear-gradient(90deg, rgb(5, 150, 247) 9px, transparent 0) 0 0,/* 左上角豎線 */linear-gradient(0deg, rgb(5, 150, 247) 9px, transparent 0) 0 0,/* 右上…

自動化三維掃描:CASAIM外觀尺寸智能檢測

制造業向智能化、數字化加速轉型&#xff0c;傳統檢測方式因效率低、精度差、數據斷層等問題&#xff0c;已難以滿足現代工業對精密測量與實時質控的需求。CASAIM依托前沿技術實力&#xff0c;以自動化三維掃描為核心&#xff0c;為工業檢測提供了從數據采集到智能分析的全流程…

突破亞馬遜壁壘,Web Unlocker API 助您輕松獲取數據

目錄 一、Web Unlocker API簡介二、開始使用Web Unlocker API1、首先進入控制臺頁面&#xff0c;點擊左側第一個tab鍵“代理 & 抓取基礎設施”&#xff0c;找到“網頁解鎖器”&#xff0c;開始使用。2、進入網頁解鎖器頁面后&#xff0c;填寫通道名稱&#xff0c;添加簡短描…

【力扣05】最長回文子串

0. 引言 ●子串(substring&#xff09;&#xff1a;原始字符串的一個連續子集; ●子序列&#xff08;subsequence&#xff09;&#xff1a;原始字符串的一個子集。 1. 什么叫回文串&#xff1f; 如果一個字符串正著讀和反著讀是一樣的&#xff0c;那它就是回文串。[1] 例如&…

統計銷量前十的訂單

傳入參數&#xff1a; 傳入begin和end兩個時間 返回參數 返回nameList和numberList兩個String類型的列表 controller層 GetMapping("/top10")public Result<SalesTop10ReportVO> top10(DateTimeFormat(pattern "yyyy-MM-dd") LocalDate begin,Dat…

【HDFS入門】HDFS核心組件Secondary NameNode角色職責與運行機制解析

目錄 1 Secondary NameNode的角色定位與常見誤解 2 核心職責詳解 2.1 核心功能職責 2.2 與NameNode的協作關系 3 運行機制深度剖析 3.1 檢查點觸發機制 3.2 元數據合并流程 4 與Hadoop 2.0 HA架構的對比 5 配置調優指南 5.1 關鍵配置參數 5.2 性能優化建議 6 實踐應…

MySQL存儲引擎:存儲什么意思?引擎什么意思?存儲引擎是什么?在MySQL中有什么作用?

MySQL存儲引擎詳解 一、術語解析 “存儲”與“引擎”的漢語詞典解釋 1. 存儲&#xff08;chǔ cn&#xff09; 漢語詞典釋義&#xff1a; ? 動詞&#xff1a; ? 存放、保存&#xff08;將物品或信息放置在特定地方&#xff0c;以便后續使用&#xff09;。 ? 例&#xff…

測試第三課-------自動化測試相關

作者前言 &#x1f382; ??????&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ?&#x1f382; 作者介紹&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

Hive null safe的用法

總結: null safe 是用<> 代表比較&#xff0c;而不是用 。null <> null 返回 true&#xff0c; 而 null null 代表 false。 NULL 和任意字符比較都返回 NULL&#xff0c;而不是 true 或者 false。如 SELECT 1 1, NULL NULL, 1 NULL;輸出 true NULL NULL如果我…

LINUX基礎 [四] - Linux工具

目錄 軟件包管理器yum Linux開發工具vim vim的基本概念 vim的三種常用模式 vim的簡單配置 vim常用模式的基本操作 命令模式 底行模式 處理vim打開文件報錯的問題 Linux編譯器-gcc/g使用 為什么我們可以用C/C做開發呢&#xff1f; 預處理&#xff08;進行宏替換&#x…

RocketMQ 03

今天是2025/04/14 21:58 day 20 總路線請移步主頁Java大綱相關文章 今天進行RocketMQ 6,7,8 個模塊的歸納 最近在忙畢設&#xff0c;更新有點慢&#xff0c;見諒 首先是RocketMQ 的相關內容概括的思維導圖 6. 安全機制 6.1 ACL 訪問控制 核心功能 權限分級&#xff1a;通過…