設計模式之單例模式(二): 心得體會

設計模式之單例模式(一)-CSDN博客

目錄

1.背景

2.分析

2.1.違背面向對象設計原則,導致職責混亂

2.2.全局狀態泛濫,引發依賴與耦合災難

2.3.多線程場景下風險放大,性能與穩定性受損

2.4.測試與維護難度指數級上升

2.5.違背 “最小知識原則”,代碼可重用性極低

3.總結


1.背景

最新在做一個老項目的維護,里面對類的調用全部都是用的單例模式,我抽取了其中某一個類CTargetShowWidget,它的單實例模型關鍵部分代碼如下:

//TargetShowWidget.h
class CTargetShowWidget : public QWidget
{
private:CTargetShowWidget(QWidget *parent = nullptr);public:~CTargetShowWidget();static  CTargetShowWidget*  getInstance();private:static CTargetShowWidget* m_pTargetShowWidget;
};//TargetShowWidget.cpp
CTargetShowWidget* CTargetShowWidget::m_pTargetShowWidget = NULL;CTargetShowWidget*  CTargetShowWidget::getInstance()
{if (!m_pTargetShowWidget){m_pTargetShowWidget = new CTargetShowWidget(NULL);}return m_pTargetShowWidget;
}

初看好像沒有什么問題,邏輯都是對的;但是細細品味,里面有幾個關鍵的問題我們來一一說明。

2.分析

在程序中所有類都使用單實例模式(單例模式)會帶來一系列設計和工程上的問題,違背面向對象設計的核心原則,嚴重影響代碼的可維護性、擴展性和健壯性。

2.1.違背面向對象設計原則,導致職責混亂

1.單一職責原則被破壞

????????單例模式的核心是 “管理實例生命周期”+“實現業務邏輯”,二者強耦合在一個類中。當所有類都采用單例時,每個類不僅要處理自身業務,還要承擔 “全局唯一實例” 的管理邏輯(如線程安全、實例銷毀等),導致類的職責膨脹,代碼復雜度飆升。

  • 例:一個負責 “日志記錄” 的單例類,本應專注于日志寫入,卻需要額外處理實例創建的線程同步邏輯,代碼可讀性和維護性下降。

2.開閉原則(擴展性)受損

????????單例模式通常通過 “私有化構造函數” 限制實例創建,這使得類難以被繼承(子類無法調用父類私有構造函數),也無法在不修改原代碼的前提下擴展功能(如創建單例的子類實例)。當所有類都被單例 “鎖定” 時,后續需求變更(如需要多實例、子類擴展)會被迫修改底層代碼,違反 “對擴展開放,對修改關閉” 的原則。

2.2.全局狀態泛濫,引發依賴與耦合災難

1.強耦合形成 “全局依賴網”

????????單例本質是 “全局變量的封裝”,所有類的實例都是全局可訪問的(通過靜態接口獲取),這會導致程序中充滿隱性依賴—— 類 A 直接調用類 B 的單例實例,而類 B 可能又依賴類 C 的單例,形成復雜的 “全局依賴網”。

  • 問題:依賴關系難以梳理,修改某個單例的接口或生命周期(如延遲初始化改為餓漢式),可能引發整個系統的連鎖反應,調試時難以定位依賴源頭。

2.內存與資源浪費,生命周期不可控

????????單例的實例通常在程序啟動后長期存在(除非程序退出),即使某些類的功能在特定場景下才會使用。當所有類都是單例時,即使程序只用到 10% 的功能,也需要提前創建或保持 100% 的單例實例,導致內存占用過高,尤其對資源敏感的場景(如嵌入式、移動端)影響顯著。

  • 例:一個僅在用戶點擊 “設置” 時才用到的 “配置管理單例”,卻在程序啟動時就被創建,閑置內存直到程序結束。

3.全局狀態破壞 “數據封裝”

????????面向對象的核心是 “封裝數據,暴露接口”,但單例的全局實例允許任何模塊直接調用其方法、修改其狀態,導致數據一致性難以保證(如多個模塊同時修改單例的成員變量,引發競態條件)。這種 “無邊界的訪問” 讓程序變成 “不可控的全局狀態機”,調試時難以追蹤狀態變化的源頭。

4.內存泄露

? ? ? ? ?如上面的代碼肯定會出現內存泄露的。尤其是在動態庫(如 Linux 下的 .so、Windows 下的 .dll)中寫這樣的類,因為動態庫可以動態加載。當用dlopen加載動態庫,然后調用CTargetShowWidget::getInstance()獲取指針,用 dlclose卸載動態庫時,CTargetShowWidget::getInstance()中new的內存是不會自動釋放的,如果程序中不停的加載和卸載此動態庫,加載過程中一直不停的分配內存,而卸載時候沒有釋放,這些內存會一直被進程占用,導致泄漏。

2.3.多線程場景下風險放大,性能與穩定性受損

1.線程安全成本激增

????????為保證單例在多線程環境下的唯一性,通常需要加鎖(如 C++ 的std::mutex、Java 的synchronized)或使用原子操作。當所有類都是單例時,每個單例都可能成為線程競爭的 “鎖熱點”—— 尤其是頻繁被調用的單例,加鎖 / 解鎖操作會帶來顯著的性能損耗,甚至引發死鎖(若多個單例的鎖順序不一致)。

  • 對比:非單例的普通類可通過 “局部變量” 或 “依賴注入” 在線程內獨立使用,避免全局鎖競爭。

2.銷毀順序與資源釋放問題

????????單例的生命周期與程序一致,但其成員變量(如動態分配的內存、文件句柄、網絡連接等)需要在程序退出時正確釋放。當存在多個相互依賴的單例時,它們的銷毀順序無法保證(如單例 A 依賴單例 B 的數據,但若 B 先被銷毀,A 銷毀時可能訪問到無效數據),導致崩潰或資源泄漏。

這種問題在 C++ 等沒有自動垃圾回收的語言中尤為突出,即使在 Java 中,靜態單例的銷毀順序也難以控制。

2.4.測試與維護難度指數級上升

1.單元測試無法隔離,結果不可靠

單例的全局狀態會導致測試用例之間互相污染 —— 例如:

  • 測試用例 1 修改了單例 A 的狀態,測試用例 2 執行時依賴 A 的 “初始狀態”,卻拿到了被修改后的狀態,導致測試失敗。
  • 無法模擬 “不同場景下的實例狀態”,因為單例只有一個實例,難以注入測試數據(如模擬異常狀態的單例)。
    解決方式通常需要 “重置單例狀態” 的額外接口,但這會進一步破壞封裝性,且增加代碼復雜度。

2.依賴注入失效,代碼靈活性喪失

????????現代開發中,依賴注入(DI)是解耦的核心手段(如通過構造函數注入依賴的實例),但單例模式通過 “靜態方法” 自我創建,無法被外部依賴替換(如單元測試時用 mock 對象替代真實單例)。當所有類都是單例時,程序完全喪失 “依賴替換” 的能力,只能硬編碼依賴關系,難以適應需求變化(如切換底層實現、對接不同接口)。

2.5.違背 “最小知識原則”,代碼可重用性極低

????????單例模式的 “全局訪問” 特性,使得類與 “全局上下文” 強綁定 —— 一個單例類無法在另一個程序或模塊中復用,除非接受其 “全局唯一” 的約束。而面向對象設計的目標之一是 “高內聚、低耦合”,讓類可以像 “積木” 一樣被復用,單例的全局化設計徹底破壞了這一點。

  • 例:一個用于 “網絡請求” 的單例類,若被設計為依賴全局的 “配置單例”,則無法在不包含該配置單例的項目中獨立使用。

3.總結

????????單例模式本身是一種 “慎用的設計模式”,僅適用于明確需要全局唯一實例、且生命周期與程序一致的場景(如日志管理器、配置管理器)。當所有類都使用單例時,會將單例的缺點(全局狀態、耦合、測試困難等)放大到極致,導致程序退化為 “面向過程的全局變量堆砌”,違背面向對象設計的核心思想。

? ? ? ? 優化建議:

1.優先使用 “普通類 + 依賴注入”:通過函數參數、上下文對象(如容器)傳遞實例,讓依賴關系顯性化,而非依賴全局訪問;

2.僅在必要時使用單例:嚴格限制單例的適用場景(如真正需要 “全局唯一且生命周期與程序一致” 的場景,如日志器、全局配置),且每個系統中單例數量應控制在個位數;

3.用 “作用域唯一性” 替代 “全局唯一性”:若僅需在某個作用域(如線程內、函數內)保證唯一,可通過局部靜態變量、線程本地存儲(TLS)等更輕量的方式實現,避免全局化。

????????總之,設計模式是解決特定問題的工具,而非通用方案 —— 濫用單例模式的本質,是用 “便捷性” 犧牲 “可維護性”,最終會讓程序付出更高的技術債務。

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

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

相關文章

windows10 php報錯

參考這個, 實際解決了問題, 主要是repair c 然后重啟 【BUG】PHP Warning: ‘C:\\WINDOWS\\SYSTEM32\\VCRUNTIME140.dll‘ 14.0 is not compatible with this PHP bu_php warning: vcruntime140.dll 14.0 is not compat-CSDN博客

GPU顯存的作用和如何選擇

核心定義與作用 首先,顯存的全稱是顯示內存,英文是Video RAM或VRAM,是顯卡上的專用內存。 顯存的主要作用是用來存儲圖形處理單元(GPU)需要處理的數據,比如紋理、頂點數據、幀緩沖區等。 數據中轉站 GPU…

從零開始:用Tkinter打造你的第一個Python桌面應用

目錄 一、界面搭建:像搭積木一樣組合控件 二、菜單系統:給應用裝上“控制中樞” 三、事件驅動:讓界面“活”起來 四、進階技巧:打造專業級體驗 五、部署發布:讓作品觸手可及 六、學習路徑建議 在Python生態中,Tkinter就像一把瑞士軍刀,它沒有花哨的特效,卻能快速…

Unity基礎-Mathf相關

Unity基礎-Mathf相關 一、Mathf數學工具 概述 Mathf是Unity中封裝好用于數學計算的工具結構體,提供了豐富的數學計算方法,特別適用于游戲開發場景。它是Unity開發中最常用的數學工具之一,能夠幫助我們處理各種數學計算和插值運算。 Mathf…

Android Studio 之基礎代碼解析

1、 onCreate 在 Android 開發中,MainActivity 作為應用的入口 Activity,其 onCreate() 方法是生命周期中第一個且最重要的回調方法,負責初始化核心組件和界面。以下是其核心要點: 一、基本定義與作用 調用時機 當 Activity 首次…

AIGC圖像去噪:核心原理、算法實現與深度學習模型詳解

1. 背景概述 1.1 目標與范疇 在AIGC(人工智能生成內容) 的技術生態系統中,圖像生成模型(如生成對抗網絡GAN、擴散模型Diffusion Model)所產出的視覺內容,其質量常因訓練數據中的固有瑕疵、生成過程中的隨機擾動或數據傳輸期間的信號衰減而呈現出不同程度的退化。因此,…

電路圖識圖基礎知識-自耦變壓器降壓啟動電動機控制電路(十六)

自耦變壓器降壓啟動電動機控制電路 自耦變壓器降壓啟動電動機控制電路是將自耦變壓器的原邊繞組接于電源側,副邊繞組接 于電機側。電動機定子繞組啟動時的電壓為自耦變壓器降壓后得到的電壓,這樣可以減少電動 機的啟動電流和啟動力矩,當電動…

Life:Internship finding

1. 前言 fishwheel writes this Blog to 記錄自分自身在研二下找實習的經歷。When 寫這篇 Blog 的時候我的最后一搏也掛掉了,只能啟用保底方案了。When I 打開我的郵箱時,發現里面有 nearly 100 多封與之相關的郵件,頓時感到有些心涼&#x…

Redis 常用數據類型和命令使用

目錄 1 string 2 hash 3 list 4 set集合 5 zset有序集合 1 string 值可以是字符串、數字和二進制的value&#xff0c;值最大不能超過512MB 應用場景&#xff1a; 應用程序緩存 計數器 web共享session 限速 1.1 設置單個鍵值 set <key> value [EX seconds|PX…

Spring Boot緩存組件Ehcache、Caffeine、Redis、Hazelcast

一、Spring Boot緩存架構核心 Spring Boot通過spring-boot-starter-cache提供統一的緩存抽象層&#xff1a; #mermaid-svg-PW9nciqD2RyVrZcZ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-PW9nciqD2RyVrZcZ .erro…

【photoshop】專色濃度和專色密度

1.1 專色濃度 是圖層填充到專色前&#xff0c;設置的前景色CMYK的K值。填充到專色后&#xff0c;可以查看到專色中圖層的k值。 ps前景色填充快捷鍵 1.Windows 系統&#xff1a;Alt Delete&#xff1b;2.Mac 系統&#xff1a;Option Delete。 1.2專色密度 專色的屬性&…

用電腦控制keysight示波器

KEYSIGHT示波器HD304MSO性能 亮點&#xff1a; 體驗 200 MHz 至 1 GHz 的帶寬和 4 個模擬通道。與 12 位 ADC 相比&#xff0c;使用 14 位模數轉換器 &#xff08;ADC&#xff09; 將垂直分辨率提高四倍。使用 10.1 英寸電容式觸摸屏輕松查看和分析您的信號。捕獲 50 μVRMS …

leetcode hot100刷題日記——33.二叉樹的層序遍歷

解題總結二維vector的初始化方法 題目描述情況1&#xff1a;不確定行數和列數情況2&#xff1a;已知行數和列數情況3&#xff1a;已知行數但不知道列數情況4&#xff1a;已知列數但不知道行數 題目描述 解答&#xff1a;用隊列 思路都差不多&#xff0c;我覺得對于我自己來說&a…

微服務面試資料1

在當今快速發展的技術領域&#xff0c;微服務架構已經成為構建復雜系統的重要方式之一。本文將圍繞微服務的核心概念、技術棧、分布式事務處理、微服務拆分與設計&#xff0c;以及敏捷開發實踐等關鍵問題展開深入探討&#xff0c;旨在為準備面試的 Java 開發者提供一份全面的復…

【設計模式-4.8】行為型——中介者模式

說明&#xff1a;本文介紹行為型設計模式之一的中介者模式 定義 中介者模式&#xff08;Mediator Pattern&#xff09;又叫作調節者模式或調停者模式。用一個中介對象封裝一系列對象交互&#xff0c;中介者使各對象不需要顯式地互相作用&#xff0c;從而使其耦合松散&#xf…

Oracle 的 SEC_CASE_SENSITIVE_LOGON 參數

Oracle 的SEC_CASE_SENSITIVE_LOGON 參數 關鍵版本信息 SEC_CASE_SENSITIVE_LOGON 參數在以下版本中被棄用&#xff1a; Oracle 12c Release 1 (12.1)&#xff1a; 該參數首次被標記為"過時"(obsolete)但依然保持功能有效 Oracle 18c/19c 及更高版本&#xff1a; …

《圖解技術體系》How Redis Architecture Evolves?

Redis架構的演進經歷了多個關鍵階段&#xff0c;從最初的內存數據庫發展為支持分布式、多模型和持久化的高性能系統。以下為具體演進路徑&#xff1a; 單線程模型與基礎數據結構 Redis最初采用單線程架構&#xff0c;利用高效的I/O多路復用&#xff08;如epoll&#xff09;處…

【電賽培訓課】測量與信號類賽題分析

一、賽題基本情況及硬件電路準備 &#xff08;一&#xff09;賽題基本情況 1.測量與信號類賽題統計 2.測量與信號類賽題特點 &#xff08;二&#xff09;硬件電路準備 綜測環節不允許帶入電腦和手機&#xff0c;需要自己根據題目要求和芯片參數指標進行設計和計算&#xff0c…

移動AI神器GPT Mobile:多模型自由切換

GPT Mobile是什么 GPT Mobile是一款開源的本地移動部署AI工具,主要用于安卓設備。以下是其相關介紹: 功能特點 多模型交互:支持與多個大型語言模型(LLM)同時進行對話,用戶導入相應的API密鑰,就可連接OpenAI、Anthropic、Google、Ollama等平臺,還能根據需求自由切換不同…

AirSim/Cosys-AirSim 游戲開發(二)使用自定義場景

在實際的開發過程中很少會只用 AirSim 自帶的 Blocks 場景&#xff0c;通常需要用到自定義的一些環境和模型&#xff0c;依托于強大的 UE 引擎可以較為逼真地完成場景渲染。這篇博客記錄了如何從頭開始導入一個自定義場景并加載 AirSim 插件。 【Note】&#xff1a;由于 UE Ed…