荔灣網(wǎng)站制作如何搜索網(wǎng)頁關(guān)鍵詞
?
? ? 如何同時提高一個軟件系統(tǒng)的可維護性 和 可復(fù)用性是面向?qū)ο髮ο笠鉀Q的核心問題。
? ? 通過學習和應(yīng)用設(shè)計模式,可以更加深入地理解面向?qū)ο蟮脑O(shè)計理念,從而幫助設(shè)計師改善自己的系統(tǒng)設(shè)計。但是,設(shè)計模式并不能夠提供具有普遍性的設(shè)計指導(dǎo)原則。在經(jīng)過一段時間的對設(shè)計模式的學習和使用之后,就會覺得這些孤立的設(shè)計模式的背后應(yīng)當還有一些更為深層的、更具有普遍性的、共同的思想原則。
? ? 比如“開-閉”原則,這樣的面向?qū)ο笤O(shè)計原則就是這些在設(shè)計模式中不斷地顯現(xiàn)出來的共同思想原則,它們隱藏在設(shè)計模式背后的,比設(shè)計模式本身更加基本和單純的設(shè)計思想。
目錄
一、軟件與產(chǎn)品
1.1、可維護性
1.2、可復(fù)用性
1.3、可維護性與復(fù)用性的關(guān)系
二、接口
2.1 什么是接口
2.2 為什么使用接口
2.3 接口常見的用法
三、抽象類
3.1 什么是抽象類
3.2 為什么使用抽象類
3.2 抽象類常見的用法
3.3 哪些設(shè)計模式使用抽象類
四、軟件設(shè)計原則
4.1 單一職責原則(SRP)
4.1.1?如何做到單一職責原則
4.1.2?與其它設(shè)計模式的關(guān)系
4.1.3?示例
4.2 開閉原則(OCP)
4.2.1?如何做到開閉原則
4.2.2?與其它設(shè)計模式的關(guān)系
?4.2.3 示例
4.3 里氏代替原則(LSP)
?4.3.1 如何做到里氏代替原則
4.3.2?與其它設(shè)計模式的關(guān)系
4.3.3 示例
4.4 依賴倒轉(zhuǎn)原則(DIP)
?4.4.1 如何做到依賴倒轉(zhuǎn)原則
?4.4.2 與其它設(shè)計模式的關(guān)系
?4.4.3 示例
4.5 接口隔離原則(ISP)
?4.5.1 如何做到接口隔離原則
?4.5.2 與其它設(shè)計模式的關(guān)系
?4.5.3 示例
4.6?合成/聚合復(fù)用原則(CARP)
?4.6.1 如何做到合成/聚合復(fù)用原則
?4.6.2 與其它設(shè)計模式的關(guān)系
?4.6.3 示例
4.7 迪米特法則(LoD)
?4.7.1 如何做到迪米特法則
?4.7.2 與其它設(shè)計模式的關(guān)系
?4.7.3 示例
一、軟件與產(chǎn)品
? 生命周期
- 軟件:分析、設(shè)計、開發(fā)、測試、部署、維護和退役。
- 產(chǎn)品:概念、產(chǎn)品研發(fā)、成長階段、成熟階段、衰退階段 和 終結(jié)階段。
? 關(guān)系
軟件作為產(chǎn)品的一部分:軟件可以作為產(chǎn)品的一部分進行開發(fā)。許多產(chǎn)品都需要軟件來實現(xiàn)其功能和特性。例如,智能手機的操作系統(tǒng)、汽車的車載娛樂系統(tǒng)、智能家居設(shè)備的控制應(yīng)用程序等都是軟件作為產(chǎn)品的一部分而開發(fā)的。在這種情況下,軟件的開發(fā)和產(chǎn)品的開發(fā)密不可分,軟件貢獻了產(chǎn)品的核心功能和用戶體驗。
軟件作為獨立產(chǎn)品:軟件本身也可以作為獨立的產(chǎn)品進行開發(fā)和銷售。這種情況下,軟件的開發(fā)和產(chǎn)品的開發(fā)是獨立的過程。例如,辦公軟件、圖像處理軟件、游戲等都是作為獨立的產(chǎn)品開發(fā)的。軟件產(chǎn)品可以通過直接銷售、訂閱模式、廣告等方式獲得盈利,并滿足用戶的特定需求。
軟件驅(qū)動和增強產(chǎn)品:軟件可以在產(chǎn)品中起到驅(qū)動和增強的作用。通過軟件的不斷升級和優(yōu)化,產(chǎn)品可以獲得新的功能、性能提升和改進的用戶體驗。例如,智能家居設(shè)備通過軟件更新獲得新的智能控制功能,汽車通過車載軟件升級獲得新的駕駛輔助功能。軟件的升級可以延長產(chǎn)品的壽命周期并提升產(chǎn)品的競爭力。
軟件支持和服務(wù):軟件在產(chǎn)品生命周期中通常需要提供支持和服務(wù)。這包括技術(shù)支持、軟件更新、bug修復(fù)、安全補丁等。產(chǎn)品的使用者需要軟件供應(yīng)商提供及時的支持和服務(wù)來確保軟件的正常運行。這些支持和服務(wù)對于產(chǎn)品的用戶滿意度和產(chǎn)品的市場聲譽至關(guān)重要。
? 共同點
階段劃分:軟件生命周期和產(chǎn)品生命周期都可以劃分為多個不同的階段。例如,軟件生命周期可以包括規(guī)劃、設(shè)計、開發(fā)、測試、部署和維護等階段;而產(chǎn)品生命周期可以包括市場導(dǎo)向、產(chǎn)品開發(fā)、增長、成熟和衰退等階段。這些階段的劃分都有助于管理和控制產(chǎn)品的不同階段的活動和目標。
目標導(dǎo)向:軟件生命周期和產(chǎn)品生命周期都以實現(xiàn)特定的目標為導(dǎo)向。在軟件生命周期中,目標可能包括開發(fā)高質(zhì)量、穩(wěn)定的軟件系統(tǒng),并在市場上取得成功;而在產(chǎn)品生命周期中,目標可能包括通過不同階段的活動實現(xiàn)產(chǎn)品的成功上市、增加市場份額并獲取利潤。
迭代與優(yōu)化:在軟件和產(chǎn)品的生命周期過程中,都存在迭代和優(yōu)化的機會。軟件開發(fā)團隊和產(chǎn)品管理團隊都會根據(jù)用戶反饋和市場需求進行持續(xù)改進和優(yōu)化。軟件開發(fā)過程中可能會有多個版本的發(fā)布和升級,以滿足不斷變化的用戶需求;產(chǎn)品生命周期中也可能會有產(chǎn)品的升級、特性增加和市場推廣等舉措,以適應(yīng)競爭壓力和市場變化。
維護與退役:軟件和產(chǎn)品都需要進行維護和最終退役。在軟件生命周期中,維護階段涉及缺陷修復(fù)、升級和性能優(yōu)化等工作;退役階段則包括軟件的退市、替代或停止支持。在產(chǎn)品生命周期中,維護階段可能包括產(chǎn)品技術(shù)支持、售后服務(wù)等;而退役階段則可能涉及產(chǎn)品的下架、關(guān)閉或轉(zhuǎn)型。
????????
1.1、可維護性
? ? 系統(tǒng)可維護性指的是一個系統(tǒng)在部署后,能夠便捷、有效地進行維護的能力。一個具有良好可維護性的系統(tǒng)能夠降低維護成本,提高系統(tǒng)的可靠性和可用性,同時保證系統(tǒng)能夠快速響應(yīng)和適應(yīng)后續(xù)的變化和需求。
??軟件退役根本原因
- 過于僵硬:很難在一個軟件系統(tǒng)里加入一個新的性能,哪怕是很小的都很難。這是因為加入一個新性能,不僅僅意味著建造一個獨立的新模塊,而且因為這個新性能會波及很多其他模塊,最后變成跨越幾個模塊的改動。使得一個起初只需要幾天的工作,最后需要幾個月才能完成。
- 過于脆弱:軟件系統(tǒng)在個性已有代碼時過于脆弱。對一個地方個性,往往會導(dǎo)致看上去沒有什么關(guān)系的另一個地方發(fā)生故障。盡管在個性之前,設(shè)計師們會竭盡所能預(yù)測可能故障點,但是在個性完成之前,系統(tǒng)的原始設(shè)計師們甚至都無法確切預(yù)測到可能會波及到的地方。一碰就碎的情況,造成軟件系統(tǒng)過于脆弱。
- 利用率低:復(fù)用指一個軟件的組成部分,可以在同一個項目的不同地方甚至另一個項目中重復(fù)使用。每當程序員發(fā)現(xiàn)一段代碼、函數(shù)、模塊所做的事情是可以在新的模塊、或者新系統(tǒng)中使用的時候,他們總是發(fā)現(xiàn),這些已有的代碼依賴于一大堆其他的東西,以至于很難將它們分開。最后,他們發(fā)現(xiàn)最好的辦法就是不去碰這些已有的東西,而是重新寫自己的代碼。他們可能會使用源代碼剪貼的辦法,以最原始的復(fù)用方法,節(jié)省一些時間。這樣的系統(tǒng)就有復(fù)用低的問題。
- 黏度過高:有時,一個改動呆以以保存原始設(shè)計意圖和原始設(shè)計框架的方式進行,也可以以破壞原始意圖和框架的方式進行。第一種辦法無疑會對系統(tǒng)的未來有利,第二種辦法是權(quán)宜之計,可以解決短期的問題,但是會犧牲中長期利益。第二種辦法比較容易,且快速。一個系統(tǒng)的設(shè)計,如果使用第二種辦法比第一種辦法很多的話,就叫做黏度過高。一個黏度過高的系統(tǒng)會誘使維護它的程序員采取錯誤的維護方案,并懲罰采取正確維護方案的程序。
??設(shè)計目的
? 一個軟件有良好的維護性,在軟件設(shè)計之初和實現(xiàn)過程中要做到如下幾個理念:
? ? 可擴展性
? ? ? ? 新的性能可以很容易地加入到系統(tǒng)中去,就是可擴展性。(與“過于僵硬”的屬性相反)
? ? 靈活性
? ? ? ??可以允許代碼個性平穩(wěn)地發(fā)生,而不會波及到很多其它的模塊,這就是靈活性。(與“過于脆弱”的屬性相反)
? ? 可插入性
? ? ? ? 可以很容易地將一個類抽出去,同時將另一個有同樣接口的類加入進來,這就是可插入性。(與“黏度過高”的屬性相反)
????????
? ? 通過考慮和提升系統(tǒng)的可維護性,可以減少系統(tǒng)維護過程中的風險和工作量,同時促進系統(tǒng)的持續(xù)演化和升級。在軟件開發(fā)過程中,考慮和優(yōu)化系統(tǒng)的可維護性是非常重要的,它有助于提高開發(fā)效率、降低維護成本,最終提升系統(tǒng)的整體質(zhì)量和用戶滿意度。
????????
1.2、可復(fù)用性
? ? 系統(tǒng)可復(fù)用性是指系統(tǒng)中的軟件組件、模塊或功能能夠在多個不同的場景或系統(tǒng)中被重復(fù)利用的能力。這種能力可以幫助開發(fā)人員在不同的項目中共享和重復(fù)利用已有的代碼和功能,從而提高開發(fā)效率、降低成本,并且可以帶來更高的質(zhì)量和一致性。
? 可復(fù)用性通常包括以下幾個方面
算法的復(fù)用:各種算法比如排序算法得到了大量的研究。現(xiàn)在幾乎不會有人在應(yīng)用程序編程時試圖建立自己的排序算法,通常的做法是在得到了很好的研究的各種算法中選擇一個。這就是算法的復(fù)用。
數(shù)據(jù)結(jié)構(gòu)復(fù)用:與算法的復(fù)用相對的,是數(shù)據(jù)結(jié)構(gòu)的復(fù)用。如隊、棧、隊列、列表等數(shù)據(jù)結(jié)構(gòu)得到了十分透徹的研究,所有的計算機教學都要詳細描述這些數(shù)據(jù)結(jié)構(gòu)。這就是數(shù)據(jù)結(jié)構(gòu)的復(fù)用。
模塊化設(shè)計:系統(tǒng)應(yīng)該具有明確定義的模塊化結(jié)構(gòu),不同的功能模塊之間通過清晰的接口進行連接。每個模塊應(yīng)該有自己獨立的功能和責任,以便于被其他系統(tǒng)或項目所復(fù)用。
標準化接口:系統(tǒng)中的模塊和組件應(yīng)該提供標準化的接口,以確保與其他組件的兼容性和可替換性。常見的接口標準包括 API、規(guī)范文件、數(shù)據(jù)格式等。
文檔和示例:系統(tǒng)中的復(fù)用組件應(yīng)該配備完善的文檔和示例,幫助其他開發(fā)人員理解和正確使用這些組件。文檔可以包括 API 文檔、使用指南、示例代碼等。
獨立性:可復(fù)用的組件應(yīng)當盡可能地與系統(tǒng)的其余部分解耦,降低對具體上下文的依賴性,以便于在不同的環(huán)境中被引入和使用。
兼容性:可復(fù)用組件應(yīng)當能夠在不同的系統(tǒng)環(huán)境中被無縫集成和使用,不論是技術(shù)架構(gòu)、操作系統(tǒng)還是開發(fā)語言都應(yīng)當盡量具有通用性。
????????
1.3、可維護性與復(fù)用性的關(guān)系
? 可維護性與可復(fù)用性之間存在密切的關(guān)系:
設(shè)計影響:軟件的設(shè)計對于可維護性和可復(fù)用性都有著重要的影響。一個良好的軟件設(shè)計不僅能夠提高軟件的可維護性,還能夠提高其可復(fù)用性。模塊化的設(shè)計、清晰的接口定義和低耦合的架構(gòu)都有利于提高軟件的可維護性和可復(fù)用性。
目標一致:在設(shè)計階段,提高軟件的可維護性和可復(fù)用性的目標是一致的。例如,通過提高模塊間的獨立性和接口的規(guī)范化,既可以使得軟件更易于維護,又可以使得模塊更容易被其他系統(tǒng)或項目復(fù)用。
共同影響因素:軟件的可維護性和可復(fù)用性都受到諸如模塊化、文檔化、標準化接口、獨立性等因素的影響。這些因素對于兩者的提升都是有益的。
適用范圍:可復(fù)用的組件通常具有良好的接口定義和獨立性,這也同時使得這些組件更容易維護。而在維護過程中發(fā)現(xiàn)的一些通用性問題和bug的修復(fù),也可以促進組件的可復(fù)用性。
? ? 軟件的可維護性和可復(fù)用性之間是相輔相成的關(guān)系。通過提高軟件的可維護性,可以使得軟件更易于維護和變更,從而增強了軟件的可復(fù)用性。而一個具有高可復(fù)用性的軟件組件也通常會具有良好的可維護性,因為它必須能夠在不同的環(huán)境和場景下被重復(fù)利用。因此,在軟件工程中,需要綜合考慮提高軟件的可維護性和可復(fù)用性,將有助于構(gòu)建高質(zhì)量、易維護和可持續(xù)演進的軟件系統(tǒng)。
?????????
二、接口
2.1 什么是接口
? ? 在Java編程語言中,接口(Interface)是一種特殊的引用類型,它是一組沒有方法體的抽象方法的集合。接口定義了一個規(guī)范,所有實現(xiàn)該接口的類都必須實現(xiàn)接口中定義的所有方法。接口在Java中扮演了重要的角色,用于定義類之間的契約,實現(xiàn)了面向?qū)ο缶幊讨械慕涌诟綦x原則和多態(tài)特性。
??特點
完全抽象:接口中的方法都是抽象方法,沒有方法體,只有方法簽名,沒有具體的實現(xiàn)。接口只定義了方法的規(guī)范,具體的實現(xiàn)由實現(xiàn)接口的類來完成。
多實現(xiàn):一個類可以實現(xiàn)多個接口,通過關(guān)鍵字?
implements
?來實現(xiàn)。這種多實現(xiàn)的特性使得Java支持了接口的多繼承,一個類可以具有多個接口的行為。接口間的繼承:接口也可以繼承其他接口,通過關(guān)鍵字?
extends
?來實現(xiàn)。這種接口間的繼承關(guān)系可以幫助組織和繼承接口的行為,形成更復(fù)雜的接口體系。規(guī)范契約:接口用于定義類之間的契約,描述了類應(yīng)該具有的行為和特征。實現(xiàn)接口的類必須提供接口中定義的所有方法的具體實現(xiàn)。
常量定義:接口中可以定義常量,常量默認為?
public static final
,表示一旦定義后不允許修改。默認方法:從Java 8開始,接口中可以定義默認方法,使用關(guān)鍵字?
default
?來標識。默認方法是一種帶有方法體的方法,可以在接口中提供一些默認的實現(xiàn),而不需要實現(xiàn)類重新實現(xiàn)。接口變量:變量可以聲明為接口類型,即接口變量。通過接口變量,可以引用實現(xiàn)了接口的類的對象,實現(xiàn)了接口的多態(tài)特性。
接口的應(yīng)用:接口在Java中廣泛應(yīng)用,用于實現(xiàn)回調(diào)函數(shù)、定義API規(guī)范、實現(xiàn)插件系統(tǒng)等各種場景。
????????
2.2 為什么使用接口
實現(xiàn)多態(tài):接口提供了多態(tài)性,允許使用接口類型的引用來引用實現(xiàn)了該接口的任何類的對象。這樣可以根據(jù)具體對象的類型來調(diào)用相應(yīng)的方法,從而實現(xiàn)多態(tài)性,提高代碼的靈活性和擴展性。
降低耦合:接口可以將實現(xiàn)類與接口的使用者解耦,實現(xiàn)了代碼的分離。實現(xiàn)類在實現(xiàn)接口的方法時,只需遵循接口規(guī)范,而不用關(guān)心具體的調(diào)用方,從而降低了模塊之間的依賴關(guān)系,提高了代碼的可維護性和靈活性。
定義規(guī)范:接口是對行為的規(guī)范化描述,定義了類應(yīng)該具有的行為。通過接口,可以清晰地定義出系統(tǒng)的接口或規(guī)范,使得系統(tǒng)更加清晰,方便團隊協(xié)作開發(fā)。
多繼承:接口可以被多個類實現(xiàn),因此接口在一定程度上彌補了Java單繼承的不足。一個類可以實現(xiàn)多個接口,從而獲得多個不同接口的特性,增強了類的靈活性。
實現(xiàn)回調(diào):接口常常用于實現(xiàn)回調(diào)機制,例如事件監(jiān)聽器、觀察者模式等。通過接口,可以定義回調(diào)方法,然后由其他類來實現(xiàn)這些接口,從而實現(xiàn)靈活的回調(diào)邏輯。
適用于插件開發(fā):接口可用于插件式開發(fā),即定義一個接口規(guī)范,然后由插件來實現(xiàn)這個接口,系統(tǒng)可以動態(tài)加載這些插件,實現(xiàn)了系統(tǒng)的可擴展性和靈活性。
? ? 接口是面向?qū)ο缶幊讨蟹浅V匾母拍?#xff0c;它具有多種優(yōu)點,包括提高代碼的靈活性和可維護性、降低耦合度、定義規(guī)范和契約、支持多繼承等。在軟件設(shè)計中,合理地使用接口可以提高代碼的質(zhì)量、可擴展性和復(fù)用性,是一種非常有用的工具和編程思想。
? 不足
不支持方法實現(xiàn):接口只能定義方法簽名,無法提供默認的方法實現(xiàn)。這意味著每個實現(xiàn)接口的類都必須自己實現(xiàn)接口中的所有方法,即使這些方法在多個實現(xiàn)類中代碼是相同的,也無法進行代碼復(fù)用。
局限于公共接口:接口的方法默認都是公共的,無法定義私有方法。這可能會導(dǎo)致對一些實現(xiàn)類不應(yīng)該暴露的方法,也需要在接口中進行定義。
無法包含狀態(tài)和變量:接口只能包含靜態(tài)常量,而無法包含實例變量和非靜態(tài)方法。這意味著無法在接口中保存狀態(tài),也無法定義實例方法來訪問或修改狀態(tài)。
不支持多繼承:與抽象類不同,接口可以實現(xiàn)多繼承,一個類可以實現(xiàn)多個接口。然而,這也可能導(dǎo)致類之間的接口過于復(fù)雜,代碼難以維護和理解。
接口版本兼容性問題:在接口中新增方法或修改方法簽名后,所有實現(xiàn)該接口的類都需要做相應(yīng)的改動以適應(yīng)新的接口。這可能導(dǎo)致對現(xiàn)有代碼的修改,破壞已有的穩(wěn)定狀態(tài)。
容易出現(xiàn)過多接口:接口的設(shè)計應(yīng)遵循高內(nèi)聚低耦合的原則,但有時為了滿足不同的需求,容易在系統(tǒng)中出現(xiàn)過多的接口,增加了代碼的復(fù)雜性和理解難度。
? ? 綜上所述,接口在軟件開發(fā)中具有許多優(yōu)點,但也有一些缺點和限制。在使用接口時應(yīng)權(quán)衡利弊,根據(jù)具體需求進行選擇,并結(jié)合抽象類或其他設(shè)計模式來實現(xiàn)代碼的靈活性和可維護性。
????????
2.3 接口常見的用法
? ? 接口是一種定義行為的方法,即定義了某個類或模塊需要實現(xiàn)的方法,而不需要關(guān)心具體的實現(xiàn)細節(jié)。
? 常見用法
定義 API 規(guī)范:接口通常被用來定義類之間的契約或規(guī)范,描述類應(yīng)該具有的行為和特征。通過定義接口,可以明確規(guī)定類應(yīng)該實現(xiàn)哪些方法,并提供給其他開發(fā)者使用,以便在不知道具體實現(xiàn)類的情況下,使用接口定義的方法進行編程。
實現(xiàn)回調(diào)函數(shù):接口經(jīng)常在實現(xiàn)回調(diào)機制時使用。定義一個接口,其中包含回調(diào)方法,然后其他類可以實現(xiàn)這個接口以提供具體的回調(diào)行為。通過回調(diào)函數(shù),實現(xiàn)類可以在特定的事件發(fā)生時通知調(diào)用方。
多態(tài)性:接口實現(xiàn)了多態(tài)性的特性,使得可以使用接口類型的引用來引用實現(xiàn)了該接口的任何類的對象。這樣可以根據(jù)具體對象的類型來調(diào)用相應(yīng)的方法,實現(xiàn)多態(tài)操作。
插件機制:接口可用于實現(xiàn)插件系統(tǒng),通過定義接口規(guī)范,不同的插件可以實現(xiàn)這個接口,并在系統(tǒng)中使用。這種插件式開發(fā)方式使得系統(tǒng)具有良好的擴展性和靈活性。
實現(xiàn)策略模式:接口可以用于實現(xiàn)策略模式,其中定義一個策略接口,多個具體策略實現(xiàn)這個接口,并被用來實現(xiàn)不同的算法或策略。在運行時,可以根據(jù)需要動態(tài)切換策略實現(xiàn)。
組織代碼:接口可以用于組織代碼,將具有相似功能的方法定義在一個接口中,然后不同的類實現(xiàn)這個接口來提供具體的功能。這樣可以更清晰地組織和管理代碼。
簡化單元測試:接口可以用于簡化單元測試。通過使用接口來定義類的依賴,可以更容易地創(chuàng)建模擬對象,進行單元測試,提高代碼的可測試性。
- 擴展性:接口具有良好的擴展性,即可以在不修改原有代碼的基礎(chǔ)上增加新的方法和屬性。這樣,當需要增加新的功能時,只需要實現(xiàn)新的接口,而不需要修改原有的代碼。
- 依賴注入:接口可以用于依賴注入的實現(xiàn)。通過接口,可以將不同的依賴注入到類或模塊中,使得代碼更加靈活和可測試。
? ??利用接口可以提高代碼的可擴展性、靈活性和可維護性,增強程序的可讀性和可測試性。合理地使用接口可以幫助我們更好地組織代碼,規(guī)范開發(fā)流程,并促進更好的代碼復(fù)用和擴展。
? 示例
? ? 當定義一個接口時,可以為接口中的方法提供一個約定,而具體的類可以根據(jù)需要來實現(xiàn)這些方法。以下是一個簡單的 Java 接口使用示例:
? 首先,定義一個接口?
Shape
:public interface Shape {double getArea(); // 計算圖形的面積double getPerimeter(); // 計算圖形的周長 }
? 然后,我們可以創(chuàng)建實現(xiàn)這個接口的具體類,比如?
Circle
?和?Rectangle
:public class Circle implements Shape {private double radius;public Circle(double radius) {this.radius = radius;}@Overridepublic double getArea() {return Math.PI * radius * radius;}@Overridepublic double getPerimeter() {return 2 * Math.PI * radius;} }public class Rectangle implements Shape {private double length;private double width;public Rectangle(double length, double width) {this.length = length;this.width = width;}@Overridepublic double getArea() {return length * width;}@Overridepublic double getPerimeter() {return 2 * (length + width);} }
? 接著,我們可以使用這些具體類來創(chuàng)建對象并調(diào)用接口中定義的方法:
public class Main {public static void main(String[] args) {Shape circle = new Circle(5.0);System.out.println("Circle Area: " + circle.getArea());System.out.println("Circle Perimeter: " + circle.getPerimeter());Shape rectangle = new Rectangle(4.0, 6.0);System.out.println("Rectangle Area: " + rectangle.getArea());System.out.println("Rectangle Perimeter: " + rectangle.getPerimeter());} }
? ? 在上面的示例中,我們定義了?
Shape
?接口,并且創(chuàng)建了兩個實現(xiàn)了?Shape
?接口的具體類?Circle
?和?Rectangle
。然后在?Main
?類中,我們創(chuàng)建了?Circle
?和?Rectangle
?的對象,并調(diào)用了接口中定義的方法。這樣,通過接口,我們可以對不同的形狀對象使用相同的方法進行操作,從而實現(xiàn)了接口的多態(tài)特性。? ? 這個簡單的示例展示了接口的使用,通過接口可以定義一組類的共同行為規(guī)范,并實現(xiàn)這些規(guī)范的具體方法,從而提高代碼的靈活性和可維護性。
????????
三、抽象類
3.1 什么是抽象類
? ? 抽象類是Java中一種特殊的類,它不能被實例化,只能被子類繼承。抽象類通過使用關(guān)鍵字?
abstract
?來聲明,并可以包含抽象方法和具體方法。
? 特點
不能被實例化:抽象類不能直接創(chuàng)建對象,只能被繼承后才能使用。由于抽象類包含抽象方法(沒有具體實現(xiàn)),因此無法直接實例化對象。
可以包含抽象方法和具體方法:抽象類可以包含抽象方法和具體方法。抽象方法是沒有實現(xiàn)體的方法,只有方法的聲明。具體方法是有具體實現(xiàn)的方法。子類繼承抽象類時,必須實現(xiàn)所有的抽象方法;而具體方法可以被繼承,也可以在子類中進行重寫。
用于定義規(guī)范和共享代碼:抽象類常常被用于定義類之間的共同行為和特征,作為一種規(guī)范或模板。它可以定義抽象方法作為規(guī)范,要求具體子類提供方法的實現(xiàn)。同時,抽象類中的具體方法可以被多個子類共享和復(fù)用。
支持單繼承:與接口不同,抽象類只能被一個類繼承,即Java只支持單繼承。一個類只能繼承一個抽象類,但可以實現(xiàn)多個接口。
具有繼承特性:抽象類可以被其他類繼承,子類繼承了抽象類后,可以繼承和訪問抽象類中的成員變量和方法。
????????
3.2 為什么使用抽象類
定義模板和規(guī)范:抽象類可以定義一些方法的框架結(jié)構(gòu),讓子類去實現(xiàn)具體的細節(jié)。這樣可以為子類提供一種模板和規(guī)范,確保子類都具備一定的基本行為,有助于統(tǒng)一代碼風格,提高代碼的一致性和可讀性。
提高代碼復(fù)用:抽象類可以包含一些通用的方法和屬性,這些通用內(nèi)容可以被多個子類繼承和復(fù)用,從而避免了重復(fù)編寫相同的代碼,提高了代碼的重用性。
封裝共同行為:抽象類可以封裝一些子類共同的行為,將公共的部分抽象到抽象類中,有助于降低代碼的耦合度,提高代碼的內(nèi)聚性,增強了系統(tǒng)的可維護性和擴展性。
定義共同接口:抽象類可以定義抽象方法,要求繼承它的子類必須實現(xiàn)這些方法,這樣可以定義一組類的共同接口,確保繼承類具備特定的行為和功能。
實現(xiàn)多態(tài)性:抽象類允許多態(tài)性的應(yīng)用,子類可以向上轉(zhuǎn)型為其抽象類類型的引用,從而通過統(tǒng)一的接口來處理不同的子類對象,提高了程序的靈活性和可擴展性。
方便后續(xù)擴展:通過抽象類可以很容易地向系統(tǒng)中添加新的功能和行為,通過新增具體子類來擴展系統(tǒng)的功能,不需要修改抽象類本身,符合開閉原則。
? ? 總的來說,抽象類提供了一種優(yōu)雅的方式來定義類之間的共同行為和關(guān)聯(lián),通過創(chuàng)建抽象類,可以簡化代碼的設(shè)計和維護,提高代碼的可讀性和可維護性,從而更好地滿足軟件開發(fā)中的需求。
? 不足
限制了單繼承:Java 只支持單繼承,因此如果一個類繼承了某個抽象類,就無法再繼承其他類。這會限制類的靈活性,特別是在需要使用或繼承多個類的情況下,可能會受到限制。
增加了類的耦合度:因為子類必須繼承抽象類,這意味著子類和抽象類之間存在一定的耦合關(guān)系,導(dǎo)致子類的實現(xiàn)與抽象類的定義緊密相連。這種緊耦合可能會增加代碼的復(fù)雜性并影響代碼的靈活性。
對新功能的擴展可能會受限:一旦一個抽象類被創(chuàng)建并投入使用,如果后續(xù)需要為其添加新的方法,將會影響所有的子類,因為它們都必須實現(xiàn)新添加的方法。這可能導(dǎo)致現(xiàn)有子類需要做修改,破壞已有的穩(wěn)定狀態(tài)。
難以對現(xiàn)有抽象類進行修改:在現(xiàn)有的抽象類中修改方法簽名或刪除已存在的方法,會對所有的子類造成影響,因為它們都必須相應(yīng)地修改。這可能導(dǎo)致修改波及現(xiàn)有代碼的大規(guī)模變更。
復(fù)雜性增加:隨著應(yīng)用的擴展和發(fā)展,抽象類體系可能變得越來越復(fù)雜。難以管理的復(fù)雜度可能導(dǎo)致代碼維護和理解的困難。
? ? 考慮到這些缺點,在使用抽象類時需要認真權(quán)衡利弊,根據(jù)具體需求來選擇是否使用抽象類,或者考慮使用其他實現(xiàn)接口等方式來彌補抽象類的缺陷。
????????
3.2 抽象類常見的用法
? 常見用法
定義規(guī)范和模板:抽象類可以定義一些方法的框架結(jié)構(gòu),讓子類去實現(xiàn)具體的細節(jié)。這種用法常見于設(shè)計模式中的模板方法模式,通過定義抽象類中的算法流程,然后讓子類根據(jù)實際情況進行具體實現(xiàn)。
封裝共同行為:抽象類可以封裝一組子類的共同行為,將公共的部分抽象到抽象類中。這樣可以避免重復(fù)編寫相同的代碼,提高代碼的復(fù)用性和可維護性。
定義共同接口:抽象類可以定義一組子類必須實現(xiàn)的方法,形成共同的接口。這種用法常見于設(shè)計模式中的策略模式,通過定義抽象類中的方法簽名,讓不同的子類提供不同的實現(xiàn),以實現(xiàn)不同的策略。
實現(xiàn)多態(tài)性:抽象類可以作為父類,允許子類向上轉(zhuǎn)型為其抽象類類型的引用,從而通過統(tǒng)一的接口來處理不同的子類對象。這種用法常見于多態(tài)性的應(yīng)用場景,可以提高程序的靈活性和可擴展性。
作為框架基類:抽象類常被用作框架的基類,提供框架的核心結(jié)構(gòu)和基本行為。子類可以繼承抽象類并進行擴展,以實現(xiàn)具體的業(yè)務(wù)邏輯。
為子類提供默認實現(xiàn):抽象類可以為一些方法提供默認實現(xiàn),這樣子類可以選擇性地覆蓋這些方法,而不是強制全部重新實現(xiàn)。這種用法可以減少子類的工作量,并提供一些通用的默認行為。
- 組織相關(guān)方法:當有一組相關(guān)的方法需要定義時,可以使用抽象類將這些方法組織在一起。這樣,其他類可以實現(xiàn)這個抽象類,并重寫這些方法以滿足自己的需求。這有助于提高代碼的組織性和可維護性。
- 代碼復(fù)用:通過使用抽象類,可以在不同的類之間共享一些通用的行為和屬性。子類可以繼承抽象類,并覆蓋抽象類中定義的方法來實現(xiàn)自己的行為。這樣可以避免代碼的重復(fù)編寫,提高代碼的復(fù)用性。
- 設(shè)計模式:在許多設(shè)計模式中,抽象類都起著重要的作用。例如,在工廠模式中,可以使用抽象類來定義一個通用的工廠接口,其他工廠類可以實現(xiàn)這個接口來創(chuàng)建不同的對象。這有助于提高代碼的可擴展性和可維護性。
? ? 總的來說,抽象類具有許多實用的用法,可以提供模板、規(guī)范、共同接口和共享代碼等功能。這些用法可以幫助開發(fā)者更好地組織和設(shè)計代碼,提高代碼的可讀性、可維護性和可擴展性。
? 示例
? ? 假設(shè)我們正在開發(fā)一個幾何圖形的應(yīng)用程序,其中有多種不同類型的幾何圖形,如圓形、矩形和三角形等。我們希望通過抽象類定義一個共享的接口,并在具體的子類中實現(xiàn)不同的幾何圖形。
// 定義抽象類Shape abstract class Shape {// 抽象方法,獲取圖形的面積public abstract double getArea();// 抽象方法,獲取圖形的周長public abstract double getPerimeter(); }// 具體的子類Circle class Circle extends Shape {private double radius;public Circle(double radius) {this.radius = radius;}public double getArea() {return Math.PI * radius * radius;}public double getPerimeter() {return 2 * Math.PI * radius;} }// 具體的子類Rectangle class Rectangle extends Shape {private double length;private double width;public Rectangle(double length, double width) {this.length = length;this.width = width;}public double getArea() {return length * width;}public double getPerimeter() {return 2 * (length + width);} }// 使用示例 public class Main {public static void main(String[] args) {Circle circle = new Circle(5);System.out.println("Circle Area: " + circle.getArea()); // 輸出圓形的面積Rectangle rectangle = new Rectangle(3, 4);System.out.println("Rectangle Perimeter: " + rectangle.getPerimeter()); // 輸出矩形的周長} }
? ? 在上述示例中,我們定義了一個抽象類
Shape
,其中包含了兩個抽象方法getArea()
和getPerimeter()
,用于獲取圖形的面積和周長。然后,我們通過具體的子類Circle
和Rectangle
來繼承抽象類,并實現(xiàn)這兩個抽象方法。? ? 在
Main
類中,我們創(chuàng)建了一個Circle
對象和一個Rectangle
對象,并分別調(diào)用它們的getArea()
和getPerimeter()
方法來獲取圓形的面積和矩形的周長,并打印輸出結(jié)果。? ? 通過使用抽象類,我們可以定義圖形對象的公共接口,并在具體的子類中實現(xiàn)不同的幾何圖形的操作。這樣,我們可以以統(tǒng)一的方式處理不同類型的幾何圖形對象,提高代碼的復(fù)用性和可擴展性。
????????
3.3 哪些設(shè)計模式使用抽象類
? 抽象類在設(shè)計模式中的作用
定義公共接口:抽象類用于定義一組共享的方法或?qū)傩?#xff0c;形成公共接口。這些方法或?qū)傩钥梢员蛔宇惱^承和實現(xiàn),確保在不同的子類中具有一致的行為和約束。
提供默認實現(xiàn):抽象類可以為一些方法提供默認的實現(xiàn),這樣子類可以選擇性地覆蓋它們,而不是強制全部重新實現(xiàn)。這樣可以減少子類的工作量,并提供一些通用的默認行為。
促進代碼復(fù)用:抽象類可以定義一些通用的實現(xiàn),供多個相關(guān)的子類共享。通過使用抽象類,可以減少代碼的重復(fù)編寫,提高代碼的復(fù)用性和維護性。
實現(xiàn)多態(tài)性:抽象類作為父類,允許子類向上轉(zhuǎn)型為其抽象類類型的引用,從而通過統(tǒng)一的接口來處理不同的子類對象。這種多態(tài)性的應(yīng)用可以提高程序的靈活性和可擴展性。
框架的基類:抽象類經(jīng)常被用作框架的基類,提供框架的核心結(jié)構(gòu)和基本行為。子類可以繼承抽象類并進行擴展,以實現(xiàn)具體的業(yè)務(wù)邏輯。
用作模板方法模式的骨架:抽象類可以作為模板方法模式的關(guān)鍵組成部分。抽象類中定義了一個算法的骨架,將具體的實現(xiàn)延遲到子類中,以實現(xiàn)算法的定制化。
? ??抽象類在設(shè)計模式中扮演著重要的角色,通過定義共享接口、提供默認實現(xiàn)、促進代碼復(fù)用、實現(xiàn)多態(tài)性等方式,幫助開發(fā)者設(shè)計和組織更加靈活、可維護和可擴展的代碼結(jié)構(gòu)。
??常見的設(shè)計模式
模板方法模式(Template Method Pattern):模板方法模式使用抽象類定義一個算法的骨架,將具體的實現(xiàn)延遲到子類中。抽象類中的模板方法定義了算法的流程,而具體實現(xiàn)則交給子類去實現(xiàn)。
工廠方法模式(Factory Method Pattern):工廠方法模式使用抽象類作為工廠的基類,定義一個創(chuàng)建對象的接口。每個具體的子類工廠繼承抽象類并實現(xiàn)工廠方法來創(chuàng)建不同的對象。
策略模式(Strategy Pattern):策略模式使用抽象類定義一組算法族,通過繼承抽象類并實現(xiàn)其中的方法來提供不同的具體算法??蛻舳烁鶕?jù)需要選擇不同的策略來完成任務(wù)。
狀態(tài)模式(State Pattern):狀態(tài)模式使用抽象類定義一組狀態(tài),并使用子類繼承并實現(xiàn)這些狀態(tài)。通過切換不同的狀態(tài)對象,可以改變對象的行為和狀態(tài)。
橋接模式(Bridge Pattern):橋接模式使用抽象類作為橋梁,將抽象類和實現(xiàn)類分離開來。抽象類定義了抽象方法,而實現(xiàn)類負責具體的實現(xiàn),通過組合的方式實現(xiàn)抽象類和實現(xiàn)類的解耦。
裝飾器模式(Decorator Pattern):裝飾器模式使用抽象類作為裝飾器的基類,通過繼承抽象類并進行裝飾來擴展對象的功能。裝飾器模式允許動態(tài)地給對象添加新的功能,而無需修改其原始類。
適配器模式(Adapter Pattern):適配器模式使用抽象類作為適配器的基類,通過繼承抽象類并實現(xiàn)適配器接口的方法來將不兼容的接口進行轉(zhuǎn)換。適配器模式將兩個不兼容的接口之間的轉(zhuǎn)換工作放在適配器類中。
? ??這些設(shè)計模式都是基于抽象類和繼承的概念,通過使用抽象類來定義接口、提供默認實現(xiàn),以及實現(xiàn)多態(tài)性和代碼復(fù)用。它們幫助開發(fā)者在設(shè)計和組織代碼時更加靈活、可維護和可擴展。
????????
四、軟件設(shè)計原則
? 設(shè)計原則
- 單一職則原則(Single Responsibility Principle,SRP)
- “開-閉”原則(Open-Closed Principle,OCP)
- 里氏替換原則(Liskov Substitution Principle,LSP)
- 依賴倒轉(zhuǎn)原則(Dependency Inversion Pronciple,DIP)
- 接口隔離原則(Interface Segregation Principle,ISP)
- 組合/聚合復(fù)用原則(Composition/Aggregation Principle,CARP)
- 迪米特法則(Law of Demeter,LoD)
4.1 單一職責原則(SRP)
? 定義
? ? 單一職責原則(Single Responsibility Principle,SRP)是面向?qū)ο笤O(shè)計中的重要原則之一,它指導(dǎo)著我們設(shè)計和組織類、模塊和函數(shù)。單一職責原則的核心思想是一個類或模塊應(yīng)該只有一個引起它變更的原因,即應(yīng)該只有一個職責。
????????
? ? 具體來說,單一職責原則要求一個類或模塊只負責一項特定的功能或職責,它應(yīng)該只有一個改變的原因。換句話說,一個類內(nèi)部的代碼應(yīng)該實現(xiàn)一種類型的責任,如果一個類承擔的職責過多,就等于給這個類增加了變更的原因,使得類變得復(fù)雜、難以維護和擴展。
? 優(yōu)點
降低類的復(fù)雜性:每個類只負責單一的功能,使得類的職責更加清晰明確,避免了類的職責過于復(fù)雜和混亂。
提高代碼的可讀性:類的職責單一使得類的結(jié)構(gòu)更加清晰,易于理解,提高了代碼的可讀性和可維護性。
提高代碼的可維護性:當類的職責單一時,當需求發(fā)生變化或 bug 需要修復(fù)時,代碼變更的影響范圍更加集中,維護和修改起來更加方便。
降低耦合度:類的職責單一減少了類與類之間的依賴關(guān)系,降低了代碼的耦合度,使得系統(tǒng)更加靈活,易于擴展和維護。
支持單元測試:類的職責單一易于編寫和執(zhí)行單元測試,因為每個類的功能更加明確,測試范圍更加集中和清晰。
? ? 通過遵循單一職責原則,可以設(shè)計出更加健壯、可維護和可擴展的系統(tǒng),有助于提高軟件質(zhì)量和開發(fā)效率。
??缺點和挑戰(zhàn)
類的數(shù)量增多:遵循單一職責原則可能會導(dǎo)致類的數(shù)量增多。如果每個功能都要創(chuàng)建一個獨立的類,可能會導(dǎo)致類的數(shù)量龐大,增加了系統(tǒng)的復(fù)雜性。
代碼重復(fù):將功能分離到不同的類中可能會導(dǎo)致代碼的重復(fù)。因為不同的類需要處理不同的職責,可能會有一些共享的代碼邏輯需要在多個類中重復(fù)編寫。
跨類協(xié)作復(fù)雜性增加:當類的職責被細分為多個單一職責時,不同的類之間需要進行協(xié)作。這可能導(dǎo)致類之間的交互復(fù)雜化,增加了設(shè)計和調(diào)試的難度。
可能引入額外的接口和依賴關(guān)系:為了實現(xiàn)單一職責原則,可能需要定義更多的接口和依賴關(guān)系。這可能增加了代碼的耦合度和維護的復(fù)雜性。
? ? 需要權(quán)衡單一職責原則的利弊,并結(jié)合具體的項目需求和設(shè)計目標來進行決策。在某些情況下,追求單一職責原則可能會使代碼更加清晰、可維護和靈活,但在其他情況下,過度分割職責可能會導(dǎo)致代碼冗余和復(fù)雜性增加。在實際應(yīng)用中,需要根據(jù)項目特點、團隊的技術(shù)水平和業(yè)務(wù)要求來平衡設(shè)計和代碼組織的復(fù)雜性。
????????
4.1.1?如何做到單一職責原則
明確定義類的職責:在設(shè)計類時,明確定義該類的職責范圍,確保每個類只負責一項特定的功能或職責。
關(guān)注領(lǐng)域內(nèi)的功能:確保類內(nèi)部的方法和屬性都與該類的職責相關(guān),避免將不相關(guān)的功能混合到同一個類中。
精簡類的函數(shù)和方法:每個函數(shù)和方法應(yīng)該只實現(xiàn)單一功能,避免函數(shù)和方法包含過多的業(yè)務(wù)邏輯。如果一個函數(shù)或方法需要實現(xiàn)多個功能,考慮將其拆分為多個單一職責的函數(shù)或方法。
避免跨界操作:避免類與類之間進行過多的交互,盡量保持類的獨立性,減少類之間的耦合。
應(yīng)用設(shè)計模式:在實際設(shè)計中,可以使用一些常見的設(shè)計模式,如工廠模式、策略模式、觀察者模式等,來幫助實現(xiàn)單一職責原則,將不同的職責分配到不同的類中,并通過接口抽象來降低類之間的依賴關(guān)系。
持續(xù)重構(gòu):在開發(fā)過程中,保持對代碼的持續(xù)審查和重構(gòu),確保每個類的職責都得到了恰當?shù)膭澐?#xff0c;避免職責蔓延和功能耦合。
遵循設(shè)計原則:除了單一職責原則,還需要結(jié)合其他設(shè)計原則,如開閉原則、依賴倒置原則等,來保持代碼的靈活性、可擴展性和可維護性。
? ? 通過遵循以上指導(dǎo),可以更好地實現(xiàn)單一職責原則,設(shè)計出清晰、靈活且易于維護的代碼結(jié)構(gòu)。
????????
4.1.2?與其它設(shè)計模式的關(guān)系
工廠模式(Factory Pattern):工廠模式可以幫助將對象的創(chuàng)建邏輯單獨封裝到工廠類中,實現(xiàn)了對象創(chuàng)建和具體職責的分離,符合單一職責原則。
策略模式(Strategy Pattern):策略模式定義了一系列算法,并將每個算法封裝到具有單一職責的類中。使用策略模式可以使得算法的變化獨立于使用算法的客戶,符合單一職責原則。
觀察者模式(Observer Pattern):觀察者模式定義了一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生變化時,所有依賴它的對象都得到通知并自動更新。觀察者模式中,觀察者和被觀察者各自有各自的職責,符合單一職責原則。
裝飾器模式(Decorator Pattern):裝飾器模式可以動態(tài)地為對象添加額外的職責,而且還能夠避免類的職責蔓延。裝飾器模式通過將職責分割到單一的裝飾類中,符合單一職責原則。
命令模式(Command Pattern):命令模式將請求封裝成對象,從而使你可以用不同的請求對客戶進行參數(shù)化,對請求排隊或記錄請求日志,以及支持可撤銷的操作。命令模式中,命令對象負責封裝命令執(zhí)行的操作,符合單一職責原則。
? ? 這些設(shè)計模式幫助將類的職責進行有效分離,使得每個類都具有清晰的單一職責,從而滿足了單一職責原則。使用這些設(shè)計模式有助于編寫結(jié)構(gòu)清晰、易于維護和擴展的代碼。
????????
4.1.3?示例
可以通過以下幾個步驟來實現(xiàn)單一職責原則:
明確類的職責:在設(shè)計類時,明確確定該類的職責范圍,并確保該類只負責一項特定的功能。
拆分多余職責:如果存在一個類包含多個不相關(guān)的職責,將其拆分為多個單一職責的類。每個類都負責一個具體的職責。
定義接口:定義接口來描述每個類的職責。接口可以幫助明確類的功能,并提供一致的契約。
封裝數(shù)據(jù)和行為:為每個類封裝相應(yīng)的數(shù)據(jù)和方法。確保類的屬性和方法與其職責相關(guān),并且不包含其他職責的邏輯。
下面是一個簡單的示例:
// 定義接口來描述職責 interface EmployeeManagerInterface {void addEmployee(Employee employee);void removeEmployee(Employee employee);void calculateSalary(Employee employee); }// 實現(xiàn)具體的職責 class EmployeeManager implements EmployeeManagerInterface {@Overridepublic void addEmployee(Employee employee) {// 添加員工的邏輯}@Overridepublic void removeEmployee(Employee employee) {// 移除員工的邏輯}@Overridepublic void calculateSalary(Employee employee) {// 計算員工薪水的邏輯} }// 定義員工類 class Employee {// 員工屬性和方法 }public class Main {public static void main(String[] args) {EmployeeManagerInterface manager = new EmployeeManager();Employee employee = new Employee();// 使用 manager 對象進行員工管理的操作manager.addEmployee(employee);manager.calculateSalary(employee);manager.removeEmployee(employee);} }
? ? 如上示例中,我們使用接口?
EmployeeManagerInterface
?描述了員工管理的職責,并提供了相應(yīng)的方法。具體的職責則由?EmployeeManager
?類來實現(xiàn),包括添加員工、移除員工和計算薪水等操作。同時,我們還定義了?Employee
?類來表示員工的屬性和方法。? ? 通過將不同的職責分離為不同的類和接口,我們實現(xiàn)了單一職責原則,使得每個類都具有明確的職責,便于代碼的維護和擴展。
????????
4.2 開閉原則(OCP)
? 定義
? ? 軟件設(shè)計的開閉原則(Open-Closed Principle,OCP)是面向?qū)ο笤O(shè)計中的重要原則之一,由勃蘭特·梅耶(Bertrand Meyer)提出。該原則的核心思想是“對擴展開放,對修改關(guān)閉”。
????????
? ? 具體來說,開閉原則要求一個軟件實體(類、模塊、函數(shù)等)應(yīng)該對擴展開放,即在不修改原有代碼的情況下,可以通過擴展來增加新功能或修改舊功能。同時,對修改關(guān)閉意味著一旦軟件實體的設(shè)計完成后,就不應(yīng)該再修改其源代碼,以避免對系統(tǒng)其他部分造成影響。
? 優(yōu)點
可擴展性:開閉原則要求系統(tǒng)對擴展是開放的,可以通過添加新的代碼來增加新的功能或模塊。這使得系統(tǒng)更容易適應(yīng)變化和支持新的需求,同時降低了向現(xiàn)有功能添加新功能時的風險。
可維護性:由于開閉原則鼓勵使用擴展而不是修改現(xiàn)有代碼,這使得維護工作更加簡化。通過增加新代碼來實現(xiàn)變化,可以避免對已有功能可能產(chǎn)生的潛在錯誤或?qū)е缕渌槐匾男薷摹?/span>
可測試性:遵守開閉原則使得代碼更具可測試性。由于不需要修改現(xiàn)有代碼來實現(xiàn)新功能,可以更方便地編寫針對新增代碼的單元測試,確保新功能的正確性和穩(wěn)定性,而不會對現(xiàn)有功能造成意外影響。
代碼復(fù)用性:開閉原則鼓勵通過擴展已有的抽象概念和接口來實現(xiàn)新功能,這樣可以使得代碼更具有復(fù)用性。當需要添加類似的功能時,可以直接使用已有的抽象類或接口,減少重復(fù)編寫代碼的工作量。
系統(tǒng)穩(wěn)定性:由于開閉原則要求對現(xiàn)有代碼的修改盡量降低,這有助于維持系統(tǒng)的穩(wěn)定性和正確性。通過擴展而不是修改現(xiàn)有代碼,可以減少引入錯誤的風險,降低系統(tǒng)出錯的概率。
? ? 總的來說,遵守開閉原則可以提高軟件系統(tǒng)的可擴展性、可維護性、可測試性,并提升代碼的復(fù)用性,同時也有助于保持系統(tǒng)的穩(wěn)定性。這些優(yōu)點使得開閉原則成為設(shè)計高質(zhì)量、可持續(xù)發(fā)展的軟件系統(tǒng)的重要指導(dǎo)原則。
??缺點和挑戰(zhàn)
抽象設(shè)計的復(fù)雜性:為了實現(xiàn)開閉原則,需要預(yù)先設(shè)計良好的抽象層次結(jié)構(gòu)和接口,這可能增加了系統(tǒng)的復(fù)雜性。正確地確定和定義抽象概念可能需要更多的時間和精力。
維護成本:在長期維護過程中,系統(tǒng)可能需要頻繁地進行擴展和變化。遵守開閉原則要求增加新功能而不是修改現(xiàn)有代碼,這可能導(dǎo)致系統(tǒng)的代碼量增加,增加了對代碼的維護成本。
非充分性:雖然開閉原則的目標是盡量避免修改現(xiàn)有代碼,但現(xiàn)實情況下可能會有一些特殊情況無法完全符合開閉原則。有時候,對現(xiàn)有代碼進行一些必要的修改可能是更有效的解決方案。
引入復(fù)雜性:為了遵守開閉原則,可能需要引入更多的抽象概念、接口和設(shè)計模式,從而增加了系統(tǒng)的復(fù)雜性。在某些情況下,這些復(fù)雜性可能會對開發(fā)人員的理解和維護造成困難。
對擴展的需求無法預(yù)見:在系統(tǒng)設(shè)計初期,很難準確地預(yù)見未來可能的擴展需求。過度地設(shè)計抽象層次結(jié)構(gòu)和接口可能會帶來不必要的復(fù)雜性和開銷。
? ??開閉原則是一種高層次的設(shè)計原則,它提供了更靈活和可擴展的系統(tǒng)設(shè)計方案。然而,遵守開閉原則可能需要權(quán)衡其他方面的考慮,并需要根據(jù)具體的項目需求和環(huán)境來判斷是否適用。在實踐中,需要綜合考慮開閉原則的優(yōu)點和缺點,合理地應(yīng)用和權(quán)衡,以求達到系統(tǒng)設(shè)計的最佳平衡點。
????????
4.2.1?如何做到開閉原則
? 采用以下設(shè)計方法
- 利用抽象類和接口:通過定義抽象類或接口,可以讓實現(xiàn)類對擴展開放,同時限制了對源代碼的修改。
- 使用多態(tài)性:通過多態(tài)性,可以在不修改原有代碼的情況下改變對象的行為。
- 使用設(shè)計模式:許多設(shè)計模式(如工廠模式、策略模式、觀察者模式等)都是為了幫助我們設(shè)計滿足開閉原則的系統(tǒng)結(jié)構(gòu)。這些設(shè)計模式可以幫助我們將系統(tǒng)的不同部分相互解耦,以便進行靈活的擴展。
參數(shù)化配置:將系統(tǒng)的行為參數(shù)化配置,使得系統(tǒng)的行為可以通過配置而不是修改代碼來改變。
代碼重構(gòu):當需要修改現(xiàn)有代碼時,可以考慮使用代碼重構(gòu)的方式,將代碼結(jié)構(gòu)重組或抽取出通用部分,從而使得系統(tǒng)更加符合開閉原則。
? ? 遵守開閉原則的關(guān)鍵在于設(shè)計具有良好擴展性和靈活性的系統(tǒng)架構(gòu),使得系統(tǒng)的不同部分可以相互獨立地擴展和變化,而不影響其他部分的穩(wěn)定性和正確性。
????????
4.2.2?與其它設(shè)計模式的關(guān)系
? 與開閉原則密切相關(guān)設(shè)計模式
工廠模式(Factory Pattern):工廠模式通過定義一個公共的接口和抽象類來創(chuàng)建對象,使得系統(tǒng)能夠面向接口編程而不是具體實現(xiàn)類。這樣,當需要添加新的產(chǎn)品時,只需擴展工廠類和產(chǎn)品類,而不需要修改現(xiàn)有的代碼。
策略模式(Strategy Pattern):策略模式通過將一組可替換的算法封裝成獨立的類,并通過一個公共接口進行調(diào)用。這樣,系統(tǒng)的行為可以在運行時動態(tài)地更改和選擇,實現(xiàn)了開閉原則的要求。
觀察者模式(Observer Pattern):觀察者模式定義了一種一對多的依賴關(guān)系,當一個對象狀態(tài)發(fā)生變化時,其他依賴的對象都能夠接收到通知并做出相應(yīng)響應(yīng)。這樣,可以通過添加/移除觀察者來擴展系統(tǒng)的功能,而不需要修改被觀察者的代碼。
裝飾器模式(Decorator Pattern):裝飾器模式通過包裝和增強已有對象的功能,而不需要修改原始對象的代碼,實現(xiàn)了開閉原則??梢酝ㄟ^添加裝飾器類來擴展對象的功能,同時保持現(xiàn)有代碼的穩(wěn)定性。
適配器模式(Adapter Pattern):適配器模式用于將一個類的接口轉(zhuǎn)換成客戶端所期望的接口。通過適配器,可以在不修改現(xiàn)有代碼的情況下將已有的類和新的接口進行適應(yīng),實現(xiàn)了開閉原則的要求。
? ??還有許多其他的設(shè)計模式,如模板方法模式、享元模式、狀態(tài)模式等,它們在不同的情況下都可以幫助我們實現(xiàn)開閉原則,并提高系統(tǒng)的靈活性和可擴展性。根據(jù)具體的需求和設(shè)計情境,選擇合適的設(shè)計模式可以更好地滿足開閉原則的要求。
????????
?4.2.3 示例
? ? 通過創(chuàng)建接口和抽象類來實現(xiàn)開閉原則,并且使用多態(tài)和依賴倒置原則來保持系統(tǒng)的靈活性。
? ? 假設(shè)有一個圖形繪制系統(tǒng),需要支持繪制不同形狀的圖形,比如圓形和矩形。我們通過接口和抽象類來實現(xiàn)開閉原則:
? 首先,定義一個抽象的圖形接口?
Shape
,并且定義一個抽象方法?draw
:public interface Shape {void draw(); }
? 然后,創(chuàng)建實現(xiàn)這一接口的具體圖形類,比如?
Circle
?和?Rectangle
:public class Circle implements Shape {@Overridepublic void draw() {System.out.println("繪制圓形");} }public class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("繪制矩形");} }
? 接下來,我們定義一個圖形繪制器?
ShapeDrawer
,它接收一個?Shape
?對象,并能夠繪制不同的圖形:public class ShapeDrawer {public void drawShape(Shape shape) {shape.draw();} }
? ? 現(xiàn)在,如果需要添加新的圖形,比如三角形,我們只需創(chuàng)建一個新的實現(xiàn)?
Shape
?接口的類,并實現(xiàn)?draw
?方法,而無需修改已有的代碼。這樣,系統(tǒng)保持了對修改關(guān)閉的同時對擴展開放,符合開閉原則。通過遵循開閉原則,我們可以更容易地擴展系統(tǒng)的功能,同時保持較低的修改成本和風險。
????????
4.3 里氏代替原則(LSP)
? 定義
? ? 里氏代換原則(Liskov Substitution Principle)是面向?qū)ο笤O(shè)計中的重要原則之一,由計算機科學家Barbara Liskov提出。
? ? 該原則是對繼承和子類型化的一個準則,它闡述了子類型(派生類或子類)應(yīng)當能夠替換其基類型(父類或超類)而不影響程序的正確性。換句話說,只要程序中使用基類的地方,都應(yīng)當能夠用其子類來替換而不產(chǎn)生任何錯誤或異常行為。
? 優(yōu)點
提高代碼的可復(fù)用性:遵循LSP原則可以使得基類和子類之間的關(guān)系更加清晰和穩(wěn)定,使得代碼更容易被復(fù)用。
降低系統(tǒng)的耦合度:通過LSP原則,子類可以替換父類而不影響系統(tǒng)的行為,從而減少了模塊間的耦合,使得各個模塊可以更加獨立地開發(fā)、測試和維護。
便于功能的擴展和修改:當需要引入新的子類時,遵循LSP原則可以更容易地進行系統(tǒng)擴展,不需要對已有的代碼做出修改,降低了引入新功能時的風險。
增強程序的健壯性和可靠性:符合LSP原則的代碼能夠更好地避免因為子類替換父類導(dǎo)致的意外錯誤,提高了軟件的健壯性和可靠性。
促進代碼的可理解性和可維護性:遵循LSP原則可以使得代碼結(jié)構(gòu)更加清晰和易于理解,也更容易進行維護和重構(gòu),從而降低了代碼的復(fù)雜性。
? ??遵循里氏代換原則可以幫助我們設(shè)計出更加穩(wěn)定、可靠、可維護和可擴展的軟件系統(tǒng),使得軟件設(shè)計更加符合面向?qū)ο笤O(shè)計的原則和思想。
? 缺點和挑戰(zhàn)
增加設(shè)計和開發(fā)的復(fù)雜性:嚴格的LSP要求子類能完全替換父類,這對繼承結(jié)構(gòu)和接口設(shè)計提出更高的要求,可能會增加設(shè)計和開發(fā)的復(fù)雜性。
可能會產(chǎn)生過度設(shè)計:為了滿足LSP,可能需要對類設(shè)計進行過度的抽象和泛化,導(dǎo)致出現(xiàn)過于復(fù)雜和冗余的設(shè)計,甚至在某些情況下會降低代碼的可讀性和可維護性。
可能會限制一些特定的優(yōu)化和實現(xiàn)方式:有時為了滿足LSP,可能會限制一些特定的優(yōu)化和實現(xiàn)方式,導(dǎo)致性能上的一些損失。
可能會引發(fā)“類爆炸”問題:為了滿足LSP原則,可能會產(chǎn)生大量微小的子類,導(dǎo)致繼承體系變得過于龐大和復(fù)雜。
可能會增加系統(tǒng)的耦合度:雖然LSP有助于降低模塊間的耦合度,但過于嚴格地依賴LSP也可能使得類與類之間的依賴關(guān)系過于緊密,導(dǎo)致了一定程度的耦合。
? ? 雖然里氏代替原則有利于提高系統(tǒng)的設(shè)計質(zhì)量和穩(wěn)定性,但在實際應(yīng)用中,需要權(quán)衡好遵循LSP原則和系統(tǒng)的復(fù)雜度、靈活性等因素,不應(yīng)盲目追求LSP而導(dǎo)致過度設(shè)計或者對系統(tǒng)性能造成不必要的影響。
????????
?4.3.1 如何做到里氏代替原則
? 采用以下設(shè)計方法
- 子類可以擴展父類的功能,但不應(yīng)該改變父類原有的功能,確保子類可以完全替代父類在所有的地方。
- 子類應(yīng)該在不改變父類原有行為的基礎(chǔ)上提供特定的功能擴展。
子類可以增加自己特有的方法,子類可以擁有自己的特有方法,但不能重寫父類的非抽象方法。
- 子類的方法重載父類的方法時,方法的入?yún)㈩愋?、出參類型和異常類型需要保持一致或者更寬松?/span>
- 盡量不要重寫父類的方法,而是通過子類擴展的方式來實現(xiàn)新的功能。
- 父類的抽象方法可以讓子類有多樣性的實現(xiàn),但子類不應(yīng)該覆蓋父類的非抽象方法。
? ? 通過遵循上述原則,可以確保子類能夠無縫地替代父類,并且在保持系統(tǒng)穩(wěn)定性的基礎(chǔ)上實現(xiàn)功能的擴展。
????????
4.3.2?與其它設(shè)計模式的關(guān)系
? 與里氏代替原則密切相關(guān)設(shè)計模式
模板方法模式(Template Method Pattern):模板方法模式通過定義一個算法的骨架,將具體步驟的實現(xiàn)延遲到子類中。里氏代替原則可以確保子類在實現(xiàn)具體步驟時不會改變骨架的邏輯,從而實現(xiàn)算法的復(fù)用和擴展。
工廠方法模式(Factory Method Pattern):工廠方法模式通過將對象的創(chuàng)建延遲到子類,以便子類可以決定實例化哪個具體類。里氏代替原則可以確保子類創(chuàng)建的對象能夠替代父類對象,保持系統(tǒng)的靈活性和可擴展性。
策略模式(Strategy Pattern):策略模式定義了一系列算法,將每個算法封裝起來,并使它們可以相互替換。里氏代替原則可以確保新的算法能夠替代現(xiàn)有的算法而不會影響系統(tǒng)的其他部分。
裝飾者模式(Decorator Pattern):裝飾者模式通過動態(tài)地給一個對象添加一些額外的職責,來擴展該對象的功能。里氏代替原則可以確保裝飾者子類可以替代被裝飾的對象,以便實現(xiàn)透明地擴展對象的功能。
? ? 總的來說,里氏代替原則與設(shè)計模式密切相關(guān),可以確保子類與父類之間的替代關(guān)系,從而幫助設(shè)計出靈活而穩(wěn)定的面向?qū)ο笙到y(tǒng)。
????????
4.3.3 示例
實現(xiàn)里氏代替原則需要遵循以下幾點:
- 子類必須保證可以替換父類并且不影響原有功能。
- 子類可以擴展父類的功能,但不能修改父類已有的功能。
- 父類中的抽象方法由子類去實現(xiàn),但實現(xiàn)過程中不應(yīng)該改變父類行為的基本邏輯。
? ? 假設(shè)有一個圖形類 Shape,包括獲取面積的方法 area(),然后有一個矩形類 Rectangle 繼承自 Shape 類。我們來看看如何保證里氏代替原則:
// 定義圖形類 public class Shape {public double area() {return 0.0;} }// 定義矩形類 public class Rectangle extends Shape {private double width;private double height;public Rectangle(double width, double height) {this.width = width;this.height = height;}@Overridepublic double area() {return width * height;} }
? ? 在這個示例中,Rectangle 類繼承自 Shape 類,并且重寫了父類的 area() 方法,但并沒有改變原有的功能,而是在原有的基礎(chǔ)上進行了擴展。
? ? 另外,可以結(jié)合接口來實現(xiàn)里氏代替原則,確保子類可以完全替代父類在所有的場景中。在軟件設(shè)計中,繼承并不是唯一的實現(xiàn)里氏代替原則的方式,還可以通過組合、接口實現(xiàn)等方式來達到同樣的效果。
????????
4.4 依賴倒轉(zhuǎn)原則(DIP)
? 定義
? ? 依賴倒轉(zhuǎn)原則(Dependency Inversion Principle,簡稱DIP)是面向?qū)ο笤O(shè)計的一條重要原則之一,它強調(diào)高層模塊不應(yīng)該依賴于低層模塊,而是應(yīng)該依賴于抽象。同時,抽象不應(yīng)該依賴于具體細節(jié),而是具體細節(jié)應(yīng)該依賴于抽象。
????????
? ? 換句話說,依賴倒轉(zhuǎn)原則主張要通過抽象來解耦程序的各個部分,高層模塊和低層模塊都應(yīng)該依賴于抽象的接口或者抽象類,而不是依賴于具體的實現(xiàn)。這樣可以提高系統(tǒng)的靈活性、可擴展性和可維護性。
? 優(yōu)點
降低耦合性:依賴倒轉(zhuǎn)原則可以減少高層模塊對低層模塊的依賴。高層模塊只需要依賴于抽象接口,而不需要依賴于具體實現(xiàn)類,從而降低了模塊之間的耦合性。
提高可擴展性:通過依賴倒轉(zhuǎn)原則,系統(tǒng)的各個模塊都依賴于抽象接口,當需要新增或者修改功能時,通過添加新的實現(xiàn)類或者修改現(xiàn)有的實現(xiàn)類,而不需要修改高層模塊的代碼,從而提高了系統(tǒng)的可擴展性。
增加靈活性:依賴倒轉(zhuǎn)原則可以使得系統(tǒng)更加靈活,可以方便地切換不同的具體實現(xiàn)類,滿足不同的業(yè)務(wù)需求,而不需要修改高層模塊的代碼。
提高可維護性:通過依賴倒轉(zhuǎn)原則,將模塊之間的依賴關(guān)系解耦,使系統(tǒng)的各個模塊獨立變化,當一個模塊需要修改時,不會對其他模塊造成影響,從而提高了系統(tǒng)的可維護性。
促進代碼重用:依賴倒轉(zhuǎn)原則通過抽象接口進行編程,可以使得模塊的功能更加獨立和可重用。不同的模塊可以共享同一個抽象接口,從而實現(xiàn)代碼的重用。
? ? 依賴倒轉(zhuǎn)原則可以提高系統(tǒng)的靈活性、可擴展性、可維護性,降低模塊之間的耦合度,從而提高整個系統(tǒng)的質(zhì)量和可靠性。它是面向?qū)ο笤O(shè)計中非常重要的原則之一。
? 缺點和挑戰(zhàn)
復(fù)雜性增加:引入依賴倒轉(zhuǎn)原則需要引入抽象接口和依賴注入等機制,這可能會使得代碼結(jié)構(gòu)變得更加復(fù)雜,增加了開發(fā)和維護的難度。
學習成本增加:對于團隊中沒有經(jīng)驗的開發(fā)人員來說,理解和應(yīng)用依賴倒轉(zhuǎn)原則可能需要較長的學習周期,增加了學習成本和上手難度。
過度設(shè)計:有時為了滿足依賴倒轉(zhuǎn)原則,可能會過度設(shè)計接口和抽象類,導(dǎo)致系統(tǒng)結(jié)構(gòu)變得過于復(fù)雜,甚至出現(xiàn)“設(shè)計模式過度使用”的問題。
性能開銷:依賴注入等方式會帶來額外的性能開銷,尤其是在一些性能要求較高的場景下,可能需要權(quán)衡性能和設(shè)計規(guī)范之間的關(guān)系。
增加代碼量:引入抽象接口和依賴注入可能會增加代碼量,使得代碼變得更加冗長,增加了閱讀和維護的復(fù)雜度。
? ? 雖然存在這些缺點,但在大多數(shù)情況下,依賴倒轉(zhuǎn)原則的優(yōu)點仍然能夠為軟件系統(tǒng)帶來更多的長期利益。在實際應(yīng)用中,需要根據(jù)具體情況和需求權(quán)衡利弊,合理應(yīng)用依賴倒轉(zhuǎn)原則,避免過度設(shè)計和過度使用。
????????
?4.4.1 如何做到依賴倒轉(zhuǎn)原則
識別依賴關(guān)系:首先,要明確識別出模塊之間的依賴關(guān)系,確定哪些模塊是高層模塊,哪些模塊是低層模塊。
創(chuàng)建抽象接口:將高層模塊所需的功能抽象成接口或者抽象類。這個接口應(yīng)該是相對穩(wěn)定、通用的,而且要符合單一職責原則。
低層模塊實現(xiàn)接口:低層模塊需要實現(xiàn)高層模塊所定義的抽象接口。這樣,高層模塊就可以通過接口來使用低層模塊,而不直接依賴于具體的實現(xiàn)類。
高層模塊依賴注入:在高層模塊中,通過依賴注入(Dependency Injection)將具體的實現(xiàn)類注入進來,而不是直接實例化具體的實現(xiàn)類。這樣可以動態(tài)地切換具體實現(xiàn)類,提高系統(tǒng)的靈活性。
使用依賴注入容器(可選):可以使用依賴注入容器來管理對象的創(chuàng)建和依賴注入。依賴注入容器可以幫助自動實現(xiàn)依賴注入,減少手動配置的工作。
遵循開閉原則:依賴倒轉(zhuǎn)原則和開閉原則(Open-Closed Principle)相輔相成。要嚴格遵循開閉原則,即模塊應(yīng)該對擴展開放,對修改關(guān)閉。當需要新增功能時,應(yīng)該通過添加新的實現(xiàn)類來擴展,而不是修改原有代碼。
? ? 通過以上步驟,可以實現(xiàn)依賴倒轉(zhuǎn)原則,將模塊之間的依賴關(guān)系解耦,提高系統(tǒng)的靈活性、可擴展性和可維護性。同時,在應(yīng)用過程中,也要根據(jù)具體情況合理應(yīng)用和調(diào)整依賴倒轉(zhuǎn)的策略,以滿足項目需求。
????????
?4.4.2 與其它設(shè)計模式的關(guān)系
工廠模式(Factory Pattern):工廠模式可以用來實現(xiàn)依賴倒轉(zhuǎn)原則。通過工廠模式,高層模塊可以依賴于抽象的工廠接口,由工廠負責創(chuàng)建具體的實例,從而將高層模塊與具體實現(xiàn)類解耦。
策略模式(Strategy Pattern):策略模式可以幫助實現(xiàn)依賴倒轉(zhuǎn)原則。通過定義一組策略接口,并將不同的策略實現(xiàn)注入到高層模塊中,高層模塊可以根據(jù)需要動態(tài)切換不同的策略實現(xiàn),實現(xiàn)了高層模塊與具體策略的解耦。
觀察者模式(Observer Pattern):觀察者模式也與依賴倒轉(zhuǎn)原則有關(guān)。通過定義抽象的觀察者接口和被觀察者接口,高層模塊可以依賴于抽象接口,具體的觀察者可以通過實現(xiàn)觀察者接口注入到被觀察者中,實現(xiàn)了高層模塊與具體實現(xiàn)類的解耦。
? ??除了以上幾種設(shè)計模式,還有其他一些設(shè)計模式也與依賴倒轉(zhuǎn)原則相關(guān),如策略工廠模式、代理模式等。這些設(shè)計模式都有助于實現(xiàn)依賴倒轉(zhuǎn)原則,提高系統(tǒng)的靈活性和可維護性,并支持解耦模塊之間的依賴關(guān)系。
????????
?4.4.3 示例
? 下面使用構(gòu)造函數(shù)注入來實現(xiàn)依賴倒轉(zhuǎn)原則。
? 假設(shè)有以下接口和實現(xiàn)類:
// 服務(wù)接口 public interface Service {void execute(); }// 具體服務(wù)實現(xiàn)類 public class ConcreteService implements Service {@Overridepublic void execute() {System.out.println("Executing ConcreteService");} }// 高層模塊 public class HighLevelModule {private final Service service;// 通過構(gòu)造函數(shù)注入public HighLevelModule(Service service) {this.service = service;}public void doWork() {// 使用注入的服務(wù)service.execute();} }
在這個示例中,通過構(gòu)造函數(shù)注入的方式將具體的服務(wù)實現(xiàn)類注入到高層模塊中,實現(xiàn)了高層模塊對具體實現(xiàn)類的解耦。
然后可以在應(yīng)用的入口處進行依賴的注入和組裝:
public class Application {public static void main(String[] args) {// 創(chuàng)建具體的服務(wù)實現(xiàn)類對象Service concreteService = new ConcreteService();// 將具體的服務(wù)實現(xiàn)類注入到高層模塊中HighLevelModule highLevelModule = new HighLevelModule(concreteService);// 執(zhí)行高層模塊的業(yè)務(wù)邏輯highLevelModule.doWork();} }
? ??通過這種方式,高層模塊依賴于抽象的服務(wù)接口,具體的服務(wù)實現(xiàn)類通過構(gòu)造函數(shù)注入的方式被注入到高層模塊中。這樣就實現(xiàn)了依賴倒轉(zhuǎn)原則,高層模塊不依賴于具體實現(xiàn)類,而是依賴于抽象接口,從而提高了系統(tǒng)的靈活性和可擴展性。
? ? 以上示例演示了如何在 Java 中通過構(gòu)造函數(shù)注入的方式實現(xiàn)依賴倒轉(zhuǎn)原則。當然,實際應(yīng)用中也可以使用其他依賴注入的方式,如方法注入、屬性注入,或者利用依賴注入容器(如Spring Framework)來管理對象的創(chuàng)建和依賴注入。
????????
4.5 接口隔離原則(ISP)
? 定義
? ? 接口隔離原則(Interface Segregation Principle,簡稱ISP)是面向?qū)ο笤O(shè)計中的一條重要原則,由羅伯特·C·馬丁在他的著作《敏捷軟件開發(fā):原則、模式與實踐》中提出。
????????
? ??該原則指導(dǎo)我們設(shè)計接口時應(yīng)該將龐大臃腫的接口拆分成更小、更具體的接口,以便客戶端只需要依賴于其需要使用的接口,而不用依賴于多余的接口。
??優(yōu)點
降低耦合性:遵循接口隔離原則可以將龐大的接口拆分成更小、更具體的接口,從而降低類與接口之間的耦合度??蛻舳酥恍枰蕾囉谧陨硇枰褂玫慕涌?#xff0c;而不用依賴于不必要的接口,降低了類之間的依賴關(guān)系,提高了系統(tǒng)的靈活性和可維護性。
促進代碼重用:當接口被精心設(shè)計并分離出單一的職責時,可以更容易地被其他模塊或服務(wù)所重用,這有利于系統(tǒng)的拓展和維護。
便于測試和調(diào)試:拆分成小而專一的接口意味著更小的單元測試范圍和更清晰的功能定義,方便進行測試和調(diào)試,提高了軟件質(zhì)量。
降低修改的風險:精簡的接口代表更小的變更范圍,若需要對某個功能進行修改,只需關(guān)注受影響的接口,而不會牽扯到其他無關(guān)的接口,降低了修改代碼帶來的風險。
符合單一職責原則:ISP 的遵循有利于類擁有單一的職責,一個類只需要實現(xiàn)與其業(yè)務(wù)邏輯相關(guān)的接口,不需要實現(xiàn)不相關(guān)的接口,符合單一職責原則。
? ??遵循接口隔離原則有助于提高系統(tǒng)的靈活性、可維護性和擴展性,降低代碼的復(fù)雜度和耦合性,并促進系統(tǒng)的健壯性和可測試性。
? 缺點和挑戰(zhàn)
接口的細粒度可能過分分散:遵循接口隔離原則會導(dǎo)致接口的細粒度變得更小,可能會造成接口數(shù)量的增加。如果接口過于細分,可能會增加代碼的復(fù)雜度和理解難度,增加維護成本。
引入更多的抽象層次:拆分接口可能需要引入更多的抽象層次,這會導(dǎo)致代碼結(jié)構(gòu)的復(fù)雜化。過多的抽象可能增加了學習成本和理解難度,尤其是對于新加入的團隊成員。
實現(xiàn)類的數(shù)量可能增加:拆分接口意味著某個功能可能需要由多個實現(xiàn)類來分別實現(xiàn),這會增加實現(xiàn)類的數(shù)量。如果實現(xiàn)類過多,可能會增加代碼的復(fù)雜性和系統(tǒng)的維護成本。
需要更多的接口管理和協(xié)調(diào):拆分接口后,需要更多的接口管理和協(xié)調(diào)工作,包括接口的版本管理、兼容性調(diào)整等,這可能會增加項目的開發(fā)和維護工作量。
? ? 盡管遵循接口隔離原則可能會帶來上述的一些缺點,但在大多數(shù)情況下,其優(yōu)點遠遠超過了缺點。需要權(quán)衡考慮業(yè)務(wù)需求、系統(tǒng)的復(fù)雜性和團隊的實際情況,以找到合適的劃分接口的方法。另外,合理的代碼結(jié)構(gòu)和良好的設(shè)計規(guī)范也可以幫助解決和緩解接口隔離原則可能帶來的一些缺點。
????????
?4.5.1 如何做到接口隔離原則
分析需求和功能:首先,仔細分析系統(tǒng)的需求和功能,了解各個模塊或類的職責和功能,明確它們之間的關(guān)系和依賴。
拆分龐大接口:根據(jù)需求和功能分析的結(jié)果,將龐大的接口拆分為更小、更具體的接口,每個接口只包含與其職責相關(guān)的方法。避免把不相關(guān)的功能強制放在一個接口中。
接口要小而精,不要臃腫:接口應(yīng)該是精簡的,不應(yīng)該包含太多的方法。一個接口應(yīng)該滿足一個單一職責,不將過多的職責塞入一個接口中。
精心設(shè)計接口:設(shè)計拆分后的接口時,應(yīng)該關(guān)注接口的單一職責,確保每個接口都只包含一組高內(nèi)聚的方法。接口命名要具有清晰和準確的表達力。
避免接口的冗余和重復(fù):仔細審查已有的接口,避免接口之間的重復(fù)和冗余。確保每個接口只包含必要的方法,避免無效的方法聲明。
使用抽象類和接口繼承:根據(jù)接口隔離原則,可以使用抽象類和接口繼承來幫助實現(xiàn)合理的接口設(shè)計。抽象類和接口的使用可以對相似的接口進行抽象和分類,以提高代碼的復(fù)用性和靈活性。
評估接口設(shè)計的靈活性和可擴展性:在設(shè)計接口時,要考慮系統(tǒng)的靈活性和可擴展性,預(yù)留必要的擴展點,避免頻繁修改接口導(dǎo)致的影響范圍擴大。
測試和驗證接口設(shè)計:在設(shè)計完接口后,進行充分的測試和驗證,確保接口設(shè)計與實際需求一致,并符合接口隔離原則。
客戶端不應(yīng)該被迫依賴于它不使用的接口:一個類對另外一個類的依賴應(yīng)該建立在最小的接口上,這樣就降低了客戶端與之耦合的可能性。
接口設(shè)計優(yōu)于繼承:ISP原則也暗示了接口設(shè)計要優(yōu)于繼承,因為接口設(shè)計更加靈活,能夠更好地滿足不同類的實際需求。
? ? 通過遵循接口隔離原則,可以使接口設(shè)計更加靈活和可維護,降低類之間的耦合性,提高系統(tǒng)的可擴展性和可維護性,從而更好地滿足軟件設(shè)計的開閉原則和單一職責原則。
????????
?4.5.2 與其它設(shè)計模式的關(guān)系
適配器模式(Adapter Pattern):適配器模式可以幫助遵循ISP,因為它允許客戶端通過特定接口與適配器進行交互,而無需了解適配器是如何與其他類進行交互的。
橋接模式(Bridge Pattern):橋接模式將抽象部分與它的實現(xiàn)部分分離,這符合ISP的理念,可以幫助避免龐大接口的出現(xiàn),同時有助于避免不相關(guān)的接口方法導(dǎo)致耦合性的增加。
觀察者模式(Observer Pattern):觀察者模式允許主題對象與觀察者對象之間的松耦合聯(lián)系,觀察者只需要實現(xiàn)一個接口,這符合ISP的原則,每個觀察者只需要關(guān)注自己感興趣的事件。
策略模式(Strategy Pattern):策略模式定義了一系列算法,將每個算法封裝到具有共同接口的獨立類中。這有利于遵循ISP,因為它可以確保每個算法只關(guān)注自己的功能,比如一個計算稅金的算法不需要關(guān)心其他算法。
? ? 這些設(shè)計模式可以幫助支持接口隔離原則的實施,并在實際編程中有助于解決ISP所涉及的設(shè)計挑戰(zhàn)。結(jié)合這些設(shè)計模式,可以更好地設(shè)計出遵循ISP的接口,并確保系統(tǒng)具有良好的靈活性、可維護性和擴展性。
????????
?4.5.3 示例
實現(xiàn)接口隔離原則(ISP)涉及以下幾個方面:
定義接口:根據(jù)ISP的原則,需要定義符合單一職責的接口。在Java中,可以使用
interface
關(guān)鍵字來定義接口,例如:public interface Animal {void eat(); }public interface Flyable {void fly(); }
實現(xiàn)接口:根據(jù)實際需求,創(chuàng)建實現(xiàn)接口的類。每個類只需實現(xiàn)與自己相關(guān)的接口方法,而不需要實現(xiàn)不相關(guān)的方法。例如:
public class Bird implements Animal, Flyable {@Overridepublic void eat() {System.out.println("Bird is eating");}@Overridepublic void fly() {System.out.println("Bird is flying");} }public class Dog implements Animal {@Overridepublic void eat() {System.out.println("Dog is eating");} }
使用接口:在使用對象時,盡可能使用接口類型引用對象,而不是具體類的引用。這樣做可以降低代碼的耦合度,增加靈活性。例如:
public class Main {public static void main(String[] args) {Animal bird = new Bird();bird.eat();if (bird instanceof Flyable) {((Flyable) bird).fly();}Animal dog = new Dog();dog.eat();} }
? ? 通過以上方式,在Java中可以實現(xiàn)接口隔離原則,確保接口的單一職責和高內(nèi)聚性。這樣的設(shè)計使得系統(tǒng)更具靈活性和可維護性,能夠更好地應(yīng)對變化和擴展。
????????
4.6?合成/聚合復(fù)用原則(CARP)
? 定義
? ? 合成/聚合復(fù)用原則(Composite/Aggregate Reuse Principle,CARP)也被稱為合成/聚合原則,它是面向?qū)ο笤O(shè)計中的一個重要原則,強調(diào)應(yīng)該盡量使用合成/聚合關(guān)系(Composition/Aggregation)而不是繼承來實現(xiàn)代碼的復(fù)用。
????????
? ? CARP 的主要思想是,更傾向于通過將對象組合成更大的對象來達到復(fù)用的目的,而不是依賴于繼承的層次結(jié)構(gòu)。通過合成/聚合關(guān)系,一個對象可以包含其他對象作為其組成部分或成員,而不是通過繼承來繼承其行為。
? 優(yōu)點
靈活性:使用合成/聚合關(guān)系可以更靈活地組合和替換對象,因為對象之間的耦合度更低。這使得系統(tǒng)更容易應(yīng)對變化和擴展,更具靈活性。
可維護性:合成/聚合關(guān)系使得代碼更具模塊化,易于理解和維護。每個獨立的對象都只負責自己的職責,使得在對系統(tǒng)進行修改和維護時更加容易。
松耦合:由于不依賴于繼承,組成對象之間的耦合度更低。這意味著各個對象可以更獨立地進行開發(fā)、測試和維護,而不會因為一個對象的改動而影響其他對象。
復(fù)用性:通過合成/聚合關(guān)系,可以更靈活地復(fù)用對象,因為對象的組合可以根據(jù)需求進行調(diào)整,而不會受到繼承層次結(jié)構(gòu)的限制。
降低繼承的缺點:繼承層次結(jié)構(gòu)會引入一些固有的問題,如“脆弱基類”問題等。合成/聚合復(fù)用原則通過減少對繼承的依賴,有助于避免這些問題的產(chǎn)生。
? ? 合成/聚合復(fù)用原則有利于增加系統(tǒng)的靈活性、可維護性和復(fù)用性,幫助設(shè)計出模塊化、低耦合度的系統(tǒng)。這些優(yōu)點使得合成/聚合復(fù)用原則成為面向?qū)ο笤O(shè)計中的重要原則之一。
缺點和挑戰(zhàn)
增加設(shè)計復(fù)雜性:使用合成/聚合關(guān)系可能會增加設(shè)計的復(fù)雜性。在組合對象時需要考慮對象之間的關(guān)系和交互,這可能需要更多的代碼和設(shè)計。
增加代碼量:使用合成/聚合關(guān)系可能會增加代碼量。相比于簡單的繼承關(guān)系,合成/聚合關(guān)系需要創(chuàng)建和維護更多的對象,并且涉及更多的交互和調(diào)用。
引入間接性:使用合成/聚合關(guān)系引入了更多的間接性。當一個對象使用其他對象的方法時,需要通過組合對象進行間接調(diào)用,可能導(dǎo)致額外的開銷和復(fù)雜性。
難以確定組成對象的生命周期:合成/聚合關(guān)系下,對象的生命周期管理變得更復(fù)雜。如果一個對象包含其他對象作為組成部分,需要確保正確處理其生命周期,避免可能出現(xiàn)的對象泄露或內(nèi)存泄漏問題。
可能降低性能:使用合成/聚合關(guān)系可能會導(dǎo)致一定的性能損失。相比于直接繼承,對象之間的交互和間接調(diào)用可能會引入額外的開銷,導(dǎo)致性能下降。
? ? 盡管存在這些缺點,但在面對應(yīng)對變化、增強靈活性和可維護性的需求時,合成/聚合復(fù)用原則仍然是一種有價值的設(shè)計原則。它可以幫助設(shè)計出更模塊化、可復(fù)用和易于維護的系統(tǒng)。在實際應(yīng)用中,需要根據(jù)具體情況權(quán)衡使用合成/聚合關(guān)系的利弊,找到合適的設(shè)計方案。
????????
?4.6.1 如何做到合成/聚合復(fù)用原則
Identify對象的組成關(guān)系:首先需要識別出對象之間的組成關(guān)系。確定哪些對象是整體對象的組成部分,以及它們之間的關(guān)系是合成(強關(guān)聯(lián))還是聚合(弱關(guān)聯(lián))關(guān)系。
封裝對象的組成關(guān)系:使用合成/聚合關(guān)系時,需要將對象之間的關(guān)系封裝起來。這意味著在整體對象中包含成員對象,并且對外部提供統(tǒng)一的訪問接口,隱藏內(nèi)部組成結(jié)構(gòu)的具體細節(jié)。
注重對象間的交互:在設(shè)計時需要思考對象之間的交互方式。合成/聚合關(guān)系下,整體對象與其部分對象之間可能會有交互和協(xié)作,需要合理設(shè)計對象之間的消息傳遞和調(diào)用方式。
管理對象的生命周期:特別要注意管理組成對象的生命周期,確保整體對象的創(chuàng)建和銷毀不會導(dǎo)致組成對象的不當操作或泄漏。合成對象通常應(yīng)該負責管理其組成對象的生命周期。
強調(diào)接口而非實現(xiàn):在整體對象與其部分對象之間的交互時,應(yīng)該強調(diào)接口而不是具體的實現(xiàn)。通過定義清晰的接口,可以降低對象之間的耦合度,增強靈活性。
靈活地調(diào)整組成關(guān)系:合成/聚合關(guān)系下,對象之間的組成關(guān)系應(yīng)該是靈活可調(diào)的。需要設(shè)計出可以輕松替換、增加或移除組成部分對象的結(jié)構(gòu)。
避免過度設(shè)計:在應(yīng)用合成/聚合復(fù)用原則時,需要避免過度設(shè)計。根據(jù)實際需求和場景來靈活運用合成/聚合關(guān)系,避免引入不必要的復(fù)雜性。
? ??通過遵循以上指導(dǎo)原則,可以幫助設(shè)計出符合合成/聚合復(fù)用原則的對象組成結(jié)構(gòu),從而實現(xiàn)代碼的高復(fù)用性、可維護性和靈活性。
????????
?4.6.2 與其它設(shè)計模式的關(guān)系
組合模式(Composite Pattern):組合模式使用合成/聚合復(fù)用原則,通過將對象組織成樹形結(jié)構(gòu)來表示部分-整體關(guān)系。這種關(guān)系允許客戶端統(tǒng)一處理單個對象和對象組合,從而實現(xiàn)了透明地處理對象的組合。
橋接模式(Bridge Pattern):橋接模式也使用了合成/聚合復(fù)用原則,通過將抽象部分與其實現(xiàn)部分分離,使它們可以獨立地變化。這樣的設(shè)計遵循了合成/聚合復(fù)用原則的思想,通過組合關(guān)系將抽象部分和實現(xiàn)部分進行解耦。
裝飾者模式(Decorator Pattern):裝飾者模式利用合成/聚合復(fù)用原則,以動態(tài)、透明的方式給單個對象添加功能。通過將對象進行包裝和組合,裝飾者模式可以靈活地擴展對象的功能,同時保持對象的接口不變。
策略模式(Strategy Pattern):策略模式利用合成/聚合復(fù)用原則,定義一系列算法家族,分別封裝起來,并使它們可以互相替換。這樣的設(shè)計使得算法的變化獨立于使用算法的客戶,符合合成/聚合復(fù)用原則的靈活組合和替換特性。
適配器模式(Adapter Pattern):適配器模式使用合成/聚合復(fù)用原則,通過組合關(guān)系將不兼容的接口轉(zhuǎn)換為可兼容的接口。這樣的設(shè)計保持了兩個接口的獨立性,遵循了合成/聚合復(fù)用原則的思想。
? ? 這些設(shè)計模式與合成/聚合復(fù)用原則緊密相關(guān),都是為了提高代碼的靈活性、可維護性和可復(fù)用性而設(shè)計的。通過結(jié)合設(shè)計模式和合成/聚合復(fù)用原則,可以更好地應(yīng)對軟件開發(fā)中的變化和需求,幫助構(gòu)建出更加模塊化、低耦合度的系統(tǒng)。
????????
?4.6.3 示例
實現(xiàn)合成/聚合復(fù)用原則可以通過以下方式進行:
組合關(guān)系的實現(xiàn):
在 Java 中,實現(xiàn)合成/聚合復(fù)用原則的一種簡單方式就是通過組合關(guān)系來包含其他對象。這通常涉及將一個類的實例作為另一個類的成員變量。例如:
public class Engine {// 引擎的相關(guān)屬性和方法 }public class Car {private Engine engine;public Car() {this.engine = new Engine(); // 組合關(guān)系} }
在這個例子中,Car 類包含一個 Engine 對象作為其成員,實現(xiàn)了組合關(guān)系。
構(gòu)造函數(shù)注入實現(xiàn)聚合關(guān)系:
通過在類的構(gòu)造函數(shù)中接受其他對象作為參數(shù),實現(xiàn)聚合關(guān)系,讓外部對象可以注入一個對象到另一個對象中。例如:
public class Car {private Engine engine;public Car(Engine engine) {this.engine = engine; // 聚合關(guān)系} }
接口的應(yīng)用:
使用接口定義對象之間的交互,而不是依賴于具體的實現(xiàn)類,可以幫助降低對象之間的耦合度。例如:
public interface Engine {void start();void stop(); }public class GasEngine implements Engine {// GasEngine 的實現(xiàn) }public class ElectricEngine implements Engine {// ElectricEngine 的實現(xiàn) }public class Car {private Engine engine;public Car(Engine engine) {this.engine = engine; // 聚合關(guān)系}public void startEngine() {engine.start(); // 通過接口進行交互} }
? ? 通過上述方式,可以在 Java 中實現(xiàn)合成/聚合復(fù)用原則,提高代碼的靈活性和可維護性。這些技術(shù)可以幫助將對象組織成更靈活、可復(fù)用的結(jié)構(gòu),符合軟件設(shè)計的良好實踐。
????????
4.7 迪米特法則(LoD)
? 定義
? ? 迪米特法則(Law of Demeter,LoD)又稱最少知識原則(Principle of Least Knowledge),是面向?qū)ο笤O(shè)計中的一個重要原則。迪米特法則指導(dǎo)著如何降低系統(tǒng)中各對象之間的耦合度,以提高代碼的靈活性、可維護性和復(fù)用性。
????????
? ? 迪米特法則的核心思想可以總結(jié)為:一個對象應(yīng)該對其他對象有盡可能少的了解,即一個對象應(yīng)該對其它對象盡可能少的暴露接口。
? 優(yōu)點
降低耦合度:迪米特法則通過限制對象之間的交互,降低了對象之間的直接依賴關(guān)系。這樣可以減少對象間的耦合,使得系統(tǒng)的各個模塊之間更加獨立,修改一個模塊不會對其他模塊產(chǎn)生太大的影響。
增強模塊的獨立性:迪米特法則使得對象只需與直接朋友進行通信,對象不需要了解它們之間的詳細實現(xiàn),從而提高了模塊的獨立性。當一個模塊變化時,只需關(guān)注與之直接交流的模塊,而不需要關(guān)注其他模塊的變化,降低了系統(tǒng)的復(fù)雜度。
提高代碼的可維護性:迪米特法則提倡減少對象之間的依賴,降低了對象之間的關(guān)聯(lián),從而使得代碼更加清晰和簡潔。當系統(tǒng)需要進行維護時,定位問題和修改代碼將會更加容易。
促進代碼的復(fù)用性:通過減少對象之間的耦合,迪米特法則可以提高代碼的復(fù)用性。當某個對象需要替換或者重用時,由于對象之間關(guān)聯(lián)的減少,它的影響范圍會更小,因此代碼的復(fù)用性也會更高。
提升系統(tǒng)的擴展性:迪米特法則降低了模塊之間的依賴關(guān)系,使得系統(tǒng)更加靈活和可擴展。當需要增加新的功能或模塊時,迪米特法則使得代碼的修改范圍變小,減少了對原有代碼的影響,提高了系統(tǒng)的擴展性。
? ? 迪米特法則通過限制對象之間的耦合度,增強模塊的獨立性,提高代碼的可維護性和復(fù)用性,同時促進系統(tǒng)的擴展性。這些優(yōu)點使得應(yīng)用迪米特法則的代碼更加靈活、可維護和可擴展,有助于構(gòu)建高質(zhì)量的軟件系統(tǒng)。
????????
? 缺點和挑戰(zhàn)
可能導(dǎo)致過多的中間類:嚴格遵循迪米特法則可能導(dǎo)致需要引入大量的中間類來傳遞消息,使得系統(tǒng)中存在過多的簡單委托方法。這可能增加類的數(shù)量,使得系統(tǒng)變得復(fù)雜,同時也增加了維護成本。
可能引入不必要的間接性:迪米特法則要求對象對于所調(diào)用的對象的朋友只能進行有限的了解,這可能導(dǎo)致需要引入一層層的間接方法調(diào)用,從而引入不必要的間接性。這樣導(dǎo)致了代碼的冗余和復(fù)雜性,可能影響系統(tǒng)的性能。
可能增加代碼組織的難度:嚴格遵循迪米特法則可能會導(dǎo)致需要更多的類和接口,使得代碼組織和管理變得更加困難。特別是在大型項目中,過多的中間類和接口可能增加了項目的復(fù)雜度,使得代碼的理解和維護變得更加困難。
可能導(dǎo)致過度設(shè)計:迪米特法則的嚴格遵循可能導(dǎo)致過度設(shè)計的問題,過分將注意力放在降低對象之間的直接交流上,而忽視了系統(tǒng)的實際需求和功能。這可能會增加開發(fā)和維護的成本,同時也使得系統(tǒng)的設(shè)計變得過于復(fù)雜。
可能犧牲了效率:嚴格遵循迪米特法則可能會導(dǎo)致需要增加額外的方法調(diào)用和對象之間的交互,從而犧牲了一定的效率。特別是在對性能要求較高的系統(tǒng)中,過度遵循迪米特法則可能會帶來一定的性能損失。
? ? 雖然迪米特法則有一些潛在的缺點,但在實際應(yīng)用中,需要根據(jù)具體的業(yè)務(wù)需求和系統(tǒng)情況來權(quán)衡,避免盲目地追求原則而導(dǎo)致過度設(shè)計或者犧牲效率和可理解性。合理地應(yīng)用迪米特法則,有助于降低系統(tǒng)的耦合度,提高代碼的靈活性和可維護性。
????????
?4.7.1 如何做到迪米特法則
封裝對象的內(nèi)部細節(jié):對象應(yīng)該盡量隱藏其內(nèi)部的實現(xiàn)細節(jié),只暴露必要的公共方法。通過封裝,可以減少對象之間的直接依賴關(guān)系,降低耦合度。
限制對象間的交互:對象之間的交互應(yīng)該通過少量的“朋友”來進行,對象只與自身直接關(guān)聯(lián)的對象通信。不直接與陌生對象進行交互,避免鏈式調(diào)用。
使用中介者或者外觀模式:中介者模式或者外觀模式可以作為對象之間通信的橋梁,在中介者或外觀對象的中部分隔對象之間的直接聯(lián)系,降低耦合度。
遵循依賴倒置原則:依賴倒置原則要求針對抽象進行編程,而不是針對具體實現(xiàn)。通過面向接口編程,可以減少對具體類的依賴,降低耦合度。
合理劃分模塊和責任:合理劃分模塊和責任,盡量使得每個模塊獨立、高內(nèi)聚低耦合。每個模塊只負責自己的業(yè)務(wù)邏輯實現(xiàn),并盡量減少模塊之間的直接交互。
避免暴露不必要的信息:對于對象的方法參數(shù)和返回值,應(yīng)該盡量只傳遞和返回必要的信息,避免暴露對象的內(nèi)部狀態(tài)和實現(xiàn)細節(jié)。
注意設(shè)計和代碼的簡潔性:在遵循迪米特法則的前提下,盡量保持設(shè)計和代碼的簡潔性。避免過度設(shè)計,關(guān)注系統(tǒng)的功能和實際需求。
? ? 遵循迪米特法則需要注意對象之間的交互、信息封裝和模塊劃分等方面。合理應(yīng)用迪米特法則可以降低系統(tǒng)的耦合度,提高代碼的可維護性、復(fù)用性和擴展性。
????????
?4.7.2 與其它設(shè)計模式的關(guān)系
中介者模式(Mediator Pattern):中介者模式通過引入一個中介者對象,將對象之間的復(fù)雜交互轉(zhuǎn)移到中介者對象中進行管理。中介者模式符合迪米特法則的原則,將對象之間的依賴關(guān)系限制在中介者對象上,降低了對象之間的耦合度。
觀察者模式(Observer Pattern):觀察者模式是一種對象間的一對多依賴關(guān)系,當被觀察對象狀態(tài)發(fā)生改變時,會自動通知依賴于它的觀察者對象。觀察者模式符合迪米特法則的原則,被觀察對象只需要與觀察者對象進行通信,而不需要了解觀察者對象的具體實現(xiàn)。
外觀模式(Facade Pattern):外觀模式提供了一個統(tǒng)一的接口,將子系統(tǒng)的復(fù)雜性進行封裝,使得客戶端只需要與外觀對象進行交互。外觀模式符合迪米特法則的原則,將客戶端與子系統(tǒng)的耦合度降低,客戶端只需與外觀對象進行通信。
適配器模式(Adapter Pattern):適配器模式將一個類的接口轉(zhuǎn)換成客戶端所期望的接口,使得原本不兼容的類能夠協(xié)同工作。適配器模式符合迪米特法則的原則,適配器作為兩個不相關(guān)的對象之間的中間類,限制了對象之間的直接交互。
? ? 這些設(shè)計模式在實現(xiàn)過程中都注重降低對象之間的耦合度,符合迪米特法則的原則。它們通過引入中間對象、定義統(tǒng)一接口、限制對象之間的交互等方式,實現(xiàn)了系統(tǒng)的解耦和模塊的獨立性,提高了系統(tǒng)的靈活性、可維護性和擴展性。
????????
?4.7.3 示例
? ? 迪米特法則要求一個對象應(yīng)該對其他對象有盡可能少的了解,也就是說,一個對象不應(yīng)直接調(diào)用其他對象的內(nèi)部方法,而應(yīng)通過以下方式來實現(xiàn):
只與直接的朋友通信:一個對象的直接朋友包括成員變量、方法參數(shù)、方法返回值等。根據(jù)迪米特法則,一個對象應(yīng)當只與其直接的朋友通信,不應(yīng)該與間接朋友(朋友的朋友)通信。
盡量不在方法內(nèi)部調(diào)用其他對象的方法:一個對象的方法中不應(yīng)該直接調(diào)用其他對象的方法,而是應(yīng)當通過參數(shù)、返回值等間接途徑。
? 實現(xiàn)迪米特法則的簡單示例:
// 課程類 class Course {private String name;public Course(String name) {this.name = name;}public String getName() {return name;} }// 學生類 class Student {private String name;private Course course;public Student(String name, Course course) {this.name = name;this.course = course;}public String getName() {return name;}// 課程信息通過方法參數(shù)傳遞public void studyCourse(Course course) {System.out.println(name + " is studying " + course.getName());} }// 學校類 class School {private String name;public School(String name) {this.name = name;}// 學生信息通過方法參數(shù)傳遞public void registerCourse(Student student, Course course) {System.out.println(student.getName() + " registered at " + name + " for " + course.getName());// 學校不直接調(diào)用課程的方法} }public class Main {public static void main(String[] args) {Course math = new Course("Math");Student student = new Student("Alice", math);School school = new School("ABC School");school.registerCourse(student, math);} }
? ??上面的示例中,通過將課程信息通過方法參數(shù)傳遞的方式實現(xiàn)了迪米特法則。學生對象不直接調(diào)用課程對象的方法,而是通過參數(shù)傳遞的方式與間接的朋友通信。這樣設(shè)計遵循了迪米特法則,減少了對象之間的耦合,提高了代碼的靈活性和可維護性。