23種設(shè)計(jì)模式之七大設(shè)計(jì)原則

23種設(shè)計(jì)模式之七大設(shè)計(jì)原則

一、前言

1.1 產(chǎn)生背景

設(shè)計(jì)模式”這個(gè)術(shù)語(yǔ)最初并不是出現(xiàn)在軟件設(shè)計(jì)中,而是被用于建筑領(lǐng)域的設(shè)計(jì)中。

1995 年,艾瑞克·伽馬(ErichGamma)、理査德·海爾姆(Richard Helm)、拉爾夫·約翰森(Ralph Johnson)、約翰·威利斯迪斯(John Vlissides)等 4 位作者合作出版了《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》(Design Patterns: Elements of Reusable Object-Oriented Software)一書(shū),在本教程中收錄了 23 個(gè)設(shè)計(jì)模式,這是設(shè)計(jì)模式領(lǐng)域里程碑的事件,導(dǎo)致了軟件設(shè)計(jì)模式的突破。這 4 位作者在軟件開(kāi)發(fā)領(lǐng)域里也以他們的“四人組”(Gang of Four,GoF)匿名著稱。

直到今天,狹義的設(shè)計(jì)模式還是本教程中所介紹的 23 種經(jīng)典設(shè)計(jì)模式。

1.2 軟件設(shè)計(jì)模式的概念

軟件設(shè)計(jì)模式(Software Design Pattern),又稱設(shè)計(jì)模式,是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過(guò)分類(lèi)編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。它描述了在軟件設(shè)計(jì)過(guò)程中的一些不斷重復(fù)發(fā)生的問(wèn)題,以及該問(wèn)題的解決方案。也就是說(shuō),它是解決特定問(wèn)題的一系列套路,是前輩們的代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié),具有一定的普遍性,可以反復(fù)使用。其目的是為了提高代碼的可重用性、代碼的可讀性和代碼的可靠性。

1.3 學(xué)習(xí)設(shè)計(jì)模式的意義

設(shè)計(jì)模式的本質(zhì)是面向?qū)ο笤O(shè)計(jì)原則的實(shí)際運(yùn)用,是對(duì)類(lèi)的封裝性、繼承性和多態(tài)性以及類(lèi)的關(guān)聯(lián)關(guān)系和組合關(guān)系的充分理解。正確使用設(shè)計(jì)模式具有以下優(yōu)點(diǎn)。

1.4 軟件設(shè)計(jì)模式的基本要素

軟件設(shè)計(jì)模式使人們可以更加簡(jiǎn)單方便地復(fù)用成功的設(shè)計(jì)和體系結(jié)構(gòu),它通常包含以下幾個(gè)基本要素:模式名稱、別名、動(dòng)機(jī)、問(wèn)題、解決方案、效果、結(jié)構(gòu)、模式角色、合作關(guān)系、實(shí)現(xiàn)方法、適用性、已知應(yīng)用、例程、模式擴(kuò)展和相關(guān)模式等,其中最關(guān)鍵的元素包括以下 4 個(gè)主要部分。

  1. 模式名稱:每一個(gè)模式都有自己的名字,通常用一兩個(gè)詞來(lái)描述,可以根據(jù)模式的問(wèn)題、特點(diǎn)、解決方案、功能和效果來(lái)命名。模式名稱(PatternName)有助于我們理解和記憶該模式,也方便我們來(lái)討論自己的設(shè)計(jì)。

  2. 問(wèn)題:?jiǎn)栴}(Problem)描述了該模式的應(yīng)用環(huán)境,即何時(shí)使用該模式。它解釋了設(shè)計(jì)問(wèn)題和問(wèn)題存在的前因后果,以及必須滿足的一系列先決條件。

  3. 解決方案:模式問(wèn)題的解決方案(Solution)包括設(shè)計(jì)的組成成分、它們之間的相互關(guān)系及各自的職責(zé)和協(xié)作方式。因?yàn)槟J骄拖褚粋€(gè)模板,可應(yīng)用于多種不同場(chǎng)合,所以解決方案并不描述一個(gè)特定而具體的設(shè)計(jì)或?qū)崿F(xiàn),而是提供設(shè)計(jì)問(wèn)題的抽象描述和怎樣用一個(gè)具有一般意義的元素組合(類(lèi)或?qū)ο蟮?組合)來(lái)解決這個(gè)問(wèn)題。

  4. 效果:描述了模式的應(yīng)用效果以及使用該模式應(yīng)該權(quán)衡的問(wèn)題,即模式的優(yōu)缺點(diǎn)。主要是對(duì)時(shí)間和空間的衡量,以及該模式對(duì)系統(tǒng)的靈活性、擴(kuò)充性、可移植性的影響,也考慮其實(shí)現(xiàn)問(wèn)題。顯式地列出這些效果(Consequence)對(duì)理解和評(píng)價(jià)這些模式有很大的幫助。

1.5 分類(lèi)

設(shè)計(jì)模式有兩種分類(lèi)方法,即根據(jù)模式的目的來(lái)分和根據(jù)模式的作用的范圍來(lái)分。

1.5.1 根據(jù)目的來(lái)分

根據(jù)模式是用來(lái)完成什么工作來(lái)劃分,這種方式可分為創(chuàng)建型模式、結(jié)構(gòu)型模式和行為型模式 3 種。

創(chuàng)建型模式:用于描述“怎樣創(chuàng)建對(duì)象”,它的主要特點(diǎn)是“將對(duì)象的創(chuàng)建與使用分離”。GoF 中提供了單例、原型、工廠方法、抽象工廠、建造者等 5 種創(chuàng)建型模式。

結(jié)構(gòu)型模式:用于描述如何將類(lèi)或?qū)ο蟀茨撤N布局組成更大的結(jié)構(gòu),GoF 中提供了代理、適配器、橋接、裝飾、外觀、享元、組合等 7 種結(jié)構(gòu)型模式。

行為型模式:用于描述類(lèi)或?qū)ο笾g怎樣相互協(xié)作共同完成單個(gè)對(duì)象都無(wú)法單獨(dú)完成的任務(wù),以及怎樣分配職責(zé)。GoF 中提供了模板方法、策略、命令、職責(zé)鏈、狀態(tài)、觀察者、中介者、迭代器、訪問(wèn)者、備忘錄、解釋器等 11 種行為型模式。

1.5.2 根據(jù)作用范圍來(lái)分

根據(jù)模式是主要用于類(lèi)上還是主要用于對(duì)象上來(lái)分,這種方式可分為類(lèi)模式和對(duì)象模式兩種。

類(lèi)模式:用于處理類(lèi)與子類(lèi)之間的關(guān)系,這些關(guān)系通過(guò)繼承來(lái)建立,是靜態(tài)的,在編譯時(shí)刻便確定下來(lái)了。GoF中的工廠方法、(類(lèi))適配器、模板方法、解釋器屬于該模式。

對(duì)象模式:用于處理對(duì)象之間的關(guān)系,這些關(guān)系可以通過(guò)組合或聚合來(lái)實(shí)現(xiàn),在運(yùn)行時(shí)刻是可以變化的,更具動(dòng)態(tài)性。GoF 中除了以上 4 種,其他的都是對(duì)象模式。

表1 GoF 的 23 種設(shè)計(jì)模式的分類(lèi)表

范圍\目的 創(chuàng)建型模式 結(jié)構(gòu)型模式 行為型模式
類(lèi)模式 工廠方法 (類(lèi))適配器 模板方法、解釋器
對(duì)象模式 單例 原型 抽象工廠 建造者 代理 (對(duì)象)適配器 橋接 裝飾 外觀 享元 組合 策略 命令 職責(zé)鏈 狀態(tài) 觀察者 中介者 迭代器 訪問(wèn)者 備忘錄

1.6 GoF的23種設(shè)計(jì)模式的功能

前面說(shuō)明了 GoF 的 23 種設(shè)計(jì)模式的分類(lèi),現(xiàn)在對(duì)各個(gè)模式的功能進(jìn)行介紹。 單例(Singleton)模式:某個(gè)類(lèi)只能生成一個(gè)實(shí)例,該類(lèi)提供了一個(gè)全局訪問(wèn)點(diǎn)供外部獲取該實(shí)例,其拓展是有限多例模式。 原型(Prototype)模式:將一個(gè)對(duì)象作為原型,通過(guò)對(duì)其進(jìn)行復(fù)制而克隆出多個(gè)和原型類(lèi)似的新實(shí)例。 工廠方法(Factory Method)模式:定義一個(gè)用于創(chuàng)建產(chǎn)品的接口,由子類(lèi)決定生產(chǎn)什么產(chǎn)品。 抽象工廠(AbstractFactory)模式:提供一個(gè)創(chuàng)建產(chǎn)品族的接口,其每個(gè)子類(lèi)可以生產(chǎn)一系列相關(guān)的產(chǎn)品。 建造者(Builder)模式:將一個(gè)復(fù)雜對(duì)象分解成多個(gè)相對(duì)簡(jiǎn)單的部分,然后根據(jù)不同需要分別創(chuàng)建它們,最后構(gòu)建成該復(fù)雜對(duì)象。 代理(Proxy)模式:為某對(duì)象提供一種代理以控制對(duì)該對(duì)象的訪問(wèn)。即客戶端通過(guò)代理間接地訪問(wèn)該對(duì)象,從而限制、增強(qiáng)或修改該對(duì)象的一些特性。 適配器(Adapter)模式:將一個(gè)類(lèi)的接口轉(zhuǎn)換成客戶希望的另外一個(gè)接口,使得原本由于接口不兼容而不能一起工作的那些類(lèi)能一起工作。 橋接(Bridge)模式:將抽象與實(shí)現(xiàn)分離,使它們可以獨(dú)立變化。它是用組合關(guān)系代替繼承關(guān)系來(lái)實(shí)現(xiàn),從而降低了抽象和實(shí)現(xiàn)這兩個(gè)可變維度的耦合度。 裝飾(Decorator)模式:動(dòng)態(tài)的給對(duì)象增加一些職責(zé),即增加其額外的功能。 外觀(Facade)模式:為多個(gè)復(fù)雜的子系統(tǒng)提供一個(gè)一致的接口,使這些子系統(tǒng)更加容易被訪問(wèn)。 享元(Flyweight)模式:運(yùn)用共享技術(shù)來(lái)有效地支持大量細(xì)粒度對(duì)象的復(fù)用。 組合(Composite)模式:將對(duì)象組合成樹(shù)狀層次結(jié)構(gòu),使用戶對(duì)單個(gè)對(duì)象和組合對(duì)象具有一致的訪問(wèn)性。 模板方法(TemplateMethod)模式:定義一個(gè)操作中的算法骨架,而將算法的一些步驟延遲到子類(lèi)中,使得子類(lèi)可以不改變?cè)撍惴ńY(jié)構(gòu)的情況下重定義該算法的某些特定步驟。 策略(Strategy)模式:定義了一系列算法,并將每個(gè)算法封裝起來(lái),使它們可以相互替換,且算法的改變不會(huì)影響使用算法的客戶。 命令(Command)模式:將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,使發(fā)出請(qǐng)求的責(zé)任和執(zhí)行請(qǐng)求的責(zé)任分割開(kāi)。 職責(zé)鏈(Chain of Responsibility)模式:把請(qǐng)求從鏈中的一個(gè)對(duì)象傳到下一個(gè)對(duì)象,直到請(qǐng)求被響應(yīng)為止。通過(guò)這種方式去除對(duì)象之間的耦合。 狀態(tài)(State)模式:允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)發(fā)生改變時(shí)改變其行為能力。 觀察者(Observer)模式:多個(gè)對(duì)象間存在一對(duì)多關(guān)系,當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí),把這種改變通知給其他多個(gè)對(duì)象,從而影響其他對(duì)象的行為。 中介者(Mediator)模式:定義一個(gè)中介對(duì)象來(lái)簡(jiǎn)化原有對(duì)象之間的交互關(guān)系,降低系統(tǒng)中對(duì)象間的耦合度,使原有對(duì)象之間不必相互了解。 迭代器(Iterator)模式:提供一種方法來(lái)順序訪問(wèn)聚合對(duì)象中的一系列數(shù)據(jù),而不暴露聚合對(duì)象的內(nèi)部表示。 訪問(wèn)者(Visitor)模式:在不改變集合元素的前提下,為一個(gè)集合中的每個(gè)元素提供多種訪問(wèn)方式,即每個(gè)元素有多個(gè)訪問(wèn)者對(duì)象訪問(wèn)。 備忘錄(Memento)模式:在不破壞封裝性的前提下,獲取并保存一個(gè)對(duì)象的內(nèi)部狀態(tài),以便以后恢復(fù)它。 解釋器(Interpreter)模式:提供如何定義語(yǔ)言的文法,以及對(duì)語(yǔ)言句子的解釋方法,即解釋器。

1.7 如何正確使用設(shè)計(jì)模式

設(shè)計(jì)模式要活學(xué)活用,不要生搬硬套。想要游刃有余地使用設(shè)計(jì)模式,需要打下牢固的程序設(shè)計(jì)語(yǔ)言基礎(chǔ)、夯實(shí)自己的編程思想、積累大量的時(shí)間經(jīng)驗(yàn)、提高開(kāi)發(fā)能力。目的都是讓程序低耦合,高復(fù)用,高內(nèi)聚,易擴(kuò)展,易維護(hù)。使用設(shè)計(jì)模式可以從以下幾點(diǎn)出發(fā)或?qū)W習(xí):

1.7.1. 需求驅(qū)動(dòng)

不僅僅是功能性需求,需求驅(qū)動(dòng)還包括性能和運(yùn)行時(shí)的需求,如軟件的可維護(hù)性和可復(fù)用性等方面。設(shè)計(jì)模式是針對(duì)軟件設(shè)計(jì)的,而軟件設(shè)計(jì)是針對(duì)需求的,一定不要為了使用設(shè)計(jì)模式而使用設(shè)計(jì)模式,否則可能會(huì)使設(shè)計(jì)變得復(fù)雜,使軟件難以調(diào)試和維護(hù)。

1.7.2. 分析成功的模式應(yīng)用項(xiàng)目

對(duì)現(xiàn)有的應(yīng)用實(shí)例進(jìn)行分析是一個(gè)很好的學(xué)習(xí)途徑,應(yīng)當(dāng)注意學(xué)習(xí)已有的項(xiàng)目,而不僅是學(xué)習(xí)設(shè)計(jì)模式如何實(shí)現(xiàn),更重要的是注意在什么場(chǎng)合使用設(shè)計(jì)模式。

1.7.3. 充分了解所使用的開(kāi)發(fā)平臺(tái)

設(shè)計(jì)模式大部分都是針對(duì)面向?qū)ο蟮能浖O(shè)計(jì),因此在理論上適合任何面向?qū)ο蟮恼Z(yǔ)言,但隨著技術(shù)的發(fā)展和編程環(huán)境的改善,設(shè)計(jì)模式的實(shí)現(xiàn)方式會(huì)有很大的差別。在一些平臺(tái)下,某些設(shè)計(jì)模式是自然實(shí)現(xiàn)的。

不僅指編程語(yǔ)言,平臺(tái)還包括平臺(tái)引入的技術(shù)。例如,Java EE 引入了反射機(jī)制和依賴注入,這些技術(shù)的使用使設(shè)計(jì)模式的實(shí)現(xiàn)方式產(chǎn)生了改變。

1.7.4. 在編程中領(lǐng)悟模式

軟件開(kāi)發(fā)是一項(xiàng)實(shí)踐工作,最直接的方法就是編程。沒(méi)有從來(lái)不下棋卻熟悉定式的圍棋高手,也沒(méi)有不會(huì)編程就能成為架構(gòu)設(shè)計(jì)師的先例。掌握設(shè)計(jì)模式是水到渠成的事情,除了理論只是和實(shí)踐積累,可能會(huì)“漸悟”或者“頓悟”。

1.7.5.避免設(shè)計(jì)過(guò)度

設(shè)計(jì)模式解決的是設(shè)計(jì)不足的問(wèn)題,但同時(shí)也要避免設(shè)計(jì)過(guò)度。一定要牢記簡(jiǎn)潔原則,要知道設(shè)計(jì)模式是為了使設(shè)計(jì)簡(jiǎn)單,而不是更復(fù)雜。如果引入設(shè)計(jì)模式使得設(shè)計(jì)變得復(fù)雜,只能說(shuō)我們把簡(jiǎn)單問(wèn)題復(fù)雜化了,問(wèn)題本身不需要設(shè)計(jì)模式。

這里需要把握的是需求變化的程度,一定要區(qū)分需求的穩(wěn)定部分和可變部分。一個(gè)軟件必然有穩(wěn)定部分,這個(gè)部分就是核心業(yè)務(wù)邏輯。如果核心業(yè)務(wù)邏輯發(fā)生變化,軟件就沒(méi)有存在的必要,核心業(yè)務(wù)邏輯是我們需要固化的。對(duì)于可變的部分,需要判斷可能發(fā)生變化的程度來(lái)確定設(shè)計(jì)策略和設(shè)計(jì)風(fēng)險(xiǎn)。要知道,設(shè)計(jì)過(guò)度與設(shè)計(jì)不足同樣對(duì)項(xiàng)目有害。

1.8 設(shè)計(jì)原則的好處

在軟件開(kāi)發(fā)中,為了提高軟件系統(tǒng)的可維護(hù)性和可復(fù)用性,增加軟件的可擴(kuò)展性和靈活性,程序員要盡量根據(jù) 7 條原則來(lái)開(kāi)發(fā)程序,從而提高軟件開(kāi)發(fā)效率、節(jié)約軟件開(kāi)發(fā)成本和維護(hù)成本。

二、開(kāi)閉原則

2.1 定義

開(kāi)閉原則(Open Closed Principle,OCP)由勃蘭特·梅耶(Bertrand Meyer)提出,他在 1988 年的著作《面向?qū)ο筌浖?gòu)造》(Object Oriented Software Construction)中提出:軟件實(shí)體應(yīng)當(dāng)對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉(Software entities should be open for extension,but closed for modification),這就是開(kāi)閉原則的經(jīng)典定義。

這里的軟件實(shí)體包括以下幾個(gè)部分:

  1. 項(xiàng)目中劃分出的模塊

  2. 類(lèi)與接口

  3. 方法

開(kāi)閉原則的含義是:當(dāng)應(yīng)用的需求改變時(shí),在不修改軟件實(shí)體的源代碼或者二進(jìn)制代碼的前提下,可以擴(kuò)展模塊的功能,使其滿足新的需求。

2.2 作用

開(kāi)閉原則是面向?qū)ο蟪绦蛟O(shè)計(jì)的終極目標(biāo),它使軟件實(shí)體擁有一定的適應(yīng)性和靈活性的同時(shí)具備穩(wěn)定性和延續(xù)性。具體來(lái)說(shuō),其作用如下。

2.2 .1. 對(duì)軟件測(cè)試的影響

軟件遵守開(kāi)閉原則的話,軟件測(cè)試時(shí)只需要對(duì)擴(kuò)展的代碼進(jìn)行測(cè)試就可以了,因?yàn)樵械臏y(cè)試代碼仍然能夠正常運(yùn)行。

2.2 .2. 可以提高代碼的可復(fù)用性

粒度越小,被復(fù)用的可能性就越大;在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,根據(jù)原子和抽象編程可以提高代碼的可復(fù)用性。

2.2 .3. 可以提高軟件的可維護(hù)性

遵守開(kāi)閉原則的軟件,其穩(wěn)定性高和延續(xù)性強(qiáng),從而易于擴(kuò)展和維護(hù)。

2.3 開(kāi)閉原則的實(shí)現(xiàn)方法

可以通過(guò)“抽象約束、封裝變化”來(lái)實(shí)現(xiàn)開(kāi)閉原則,即通過(guò)接口或者抽象類(lèi)為軟件實(shí)體定義一個(gè)相對(duì)穩(wěn)定的抽象層,而將相同的可變因素封裝在相同的具體實(shí)現(xiàn)類(lèi)中。

因?yàn)槌橄箪`活性好,適應(yīng)性廣,只要抽象的合理,可以基本保持軟件架構(gòu)的穩(wěn)定。而軟件中易變的細(xì)節(jié)可以從抽象派生來(lái)的實(shí)現(xiàn)類(lèi)來(lái)進(jìn)行擴(kuò)展,當(dāng)軟件需要發(fā)生變化時(shí),只需要根據(jù)需求重新派生一個(gè)實(shí)現(xiàn)類(lèi)來(lái)擴(kuò)展就可以了。

三、里氏替換原則

3.1 定義

里氏替換原則主要闡述了有關(guān)繼承的一些原則,也就是什么時(shí)候應(yīng)該使用繼承,什么時(shí)候不應(yīng)該使用繼承,以及其中蘊(yùn)含的原理。里氏替換原是繼承復(fù)用的基礎(chǔ),它反映了基類(lèi)與子類(lèi)之間的關(guān)系,是對(duì)開(kāi)閉原則的補(bǔ)充,是對(duì)實(shí)現(xiàn)抽象化的具體步驟的規(guī)范。

3.2 作用

里氏替換原則的主要作用如下。

3.2.1 里氏替換原則是實(shí)現(xiàn)開(kāi)閉原則的重要方式之一;

3.2.2 它克服了繼承中重寫(xiě)父類(lèi)造成的可復(fù)用性變差的缺點(diǎn);

3.2.3 它是動(dòng)作正確性的保證。即類(lèi)的擴(kuò)展不會(huì)給已有的系統(tǒng)引入新的錯(cuò)誤,降低了代碼出錯(cuò)的可能性;

3.2.4 加強(qiáng)程序的健壯性,同時(shí)變更時(shí)可以做到非常好的兼容性,提高程序的維護(hù)性、可擴(kuò)展性,降低需求變更時(shí)引入的風(fēng)險(xiǎn)。

3.3 實(shí)現(xiàn)方法

里氏替換原則通俗來(lái)講就是:子類(lèi)可以擴(kuò)展父類(lèi)的功能,但不能改變父類(lèi)原有的功能。也就是說(shuō):子類(lèi)繼承父類(lèi)時(shí),除添加新的方法完成新增功能外,盡量不要重寫(xiě)父類(lèi)的方法。

根據(jù)上述理解,對(duì)里氏替換原則的定義可以總結(jié)如下:

  • 子類(lèi)可以實(shí)現(xiàn)父類(lèi)的抽象方法,但不能覆蓋父類(lèi)的非抽象方法

  • 子類(lèi)中可以增加自己特有的方法

  • 當(dāng)子類(lèi)的方法重載父類(lèi)的方法時(shí),方法的前置條件(即方法的輸入?yún)?shù))要比父類(lèi)的方法更寬松

  • 當(dāng)子類(lèi)的方法實(shí)現(xiàn)父類(lèi)的方法時(shí)(重寫(xiě)/重載或?qū)崿F(xiàn)抽象方法),方法的后置條件(即方法的的輸出/返回值)要比父類(lèi)的方法更嚴(yán)格或相等

通過(guò)重寫(xiě)父類(lèi)的方法來(lái)完成新的功能寫(xiě)起來(lái)雖然簡(jiǎn)單,但是整個(gè)繼承體系的可復(fù)用性會(huì)比較差,特別是運(yùn)用多態(tài)比較頻繁時(shí),程序運(yùn)行出錯(cuò)的概率會(huì)非常大。

如果程序違背了里氏替換原則,則繼承類(lèi)的對(duì)象在基類(lèi)出現(xiàn)的地方會(huì)出現(xiàn)運(yùn)行錯(cuò)誤。這時(shí)其修正方法是:取消原來(lái)的繼承關(guān)系,重新設(shè)計(jì)它們之間的關(guān)系。

四、依賴倒置原則

4.1 定義

依賴倒置原則(Dependence Inversion Principle,DIP)是 Object Mentor 公司總裁羅伯特·馬?。≧obert C.Martin)于 1996 年在 C++ Report 上發(fā)表的文章。

依賴倒置原則的原始定義為:高層模塊不應(yīng)該依賴低層模塊,兩者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象(High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions)。其核心思想是:要面向接口編程,不要面向?qū)崿F(xiàn)編程。

依賴倒置原則是實(shí)現(xiàn)開(kāi)閉原則的重要途徑之一,它降低了客戶與實(shí)現(xiàn)模塊之間的耦合。

由于在軟件設(shè)計(jì)中,細(xì)節(jié)具有多變性,而抽象層則相對(duì)穩(wěn)定,因此以抽象為基礎(chǔ)搭建起來(lái)的架構(gòu)要比以細(xì)節(jié)為基礎(chǔ)搭建起來(lái)的架構(gòu)要穩(wěn)定得多。這里的抽象指的是接口或者抽象類(lèi),而細(xì)節(jié)是指具體的實(shí)現(xiàn)類(lèi)。

使用接口或者抽象類(lèi)的目的是制定好規(guī)范和契約,而不去涉及任何具體的操作,把展現(xiàn)細(xì)節(jié)的任務(wù)交給它們的實(shí)現(xiàn)類(lèi)去完成。

4.2 作用

  • 依賴倒置原則可以降低類(lèi)間的耦合性。

  • 依賴倒置原則可以提高系統(tǒng)的穩(wěn)定性。

  • 依賴倒置原則可以減少并行開(kāi)發(fā)引起的風(fēng)險(xiǎn)。

  • 依賴倒置原則可以提高代碼的可讀性和可維護(hù)性。

4.3 實(shí)現(xiàn)方法

依賴倒置原則的目的是通過(guò)要面向接口的編程來(lái)降低類(lèi)間的耦合性,所以我們?cè)趯?shí)際編程中只要遵循以下4點(diǎn),就能在項(xiàng)目中滿足這個(gè)規(guī)則。

  1. 每個(gè)類(lèi)盡量提供接口或抽象類(lèi),或者兩者都具備。

  2. 變量的聲明類(lèi)型盡量是接口或者是抽象類(lèi)。

  3. 任何類(lèi)都不應(yīng)該從具體類(lèi)派生。

  4. 使用繼承時(shí)盡量遵循里氏替換原則。

五、單一職責(zé)原則

5.1 定義

單一職責(zé)原則(Single Responsibility Principle,SRP)又稱單一功能原則,由羅伯特·C.馬?。≧obert C. Martin)于《敏捷軟件開(kāi)發(fā):原則、模式和實(shí)踐》一書(shū)中提出的。這里的職責(zé)是指類(lèi)變化的原因,單一職責(zé)原則規(guī)定一個(gè)類(lèi)應(yīng)該有且僅有一個(gè)引起它變化的原因,否則類(lèi)應(yīng)該被拆分(There should never be more than one reason for a class to change)。

該原則提出對(duì)象不應(yīng)該承擔(dān)太多職責(zé),如果一個(gè)對(duì)象承擔(dān)了太多的職責(zé),至少存在以下兩個(gè)缺點(diǎn):

  1. 一個(gè)職責(zé)的變化可能會(huì)削弱或者抑制這個(gè)類(lèi)實(shí)現(xiàn)其他職責(zé)的能力;

  2. 當(dāng)客戶端需要該對(duì)象的某一個(gè)職責(zé)時(shí),不得不將其他不需要的職責(zé)全都包含進(jìn)來(lái),從而造成冗余代碼或代碼的浪費(fèi)。

5.2 單一職責(zé)原則的優(yōu)點(diǎn)

單一職責(zé)原則的核心就是控制類(lèi)的粒度大小、將對(duì)象解耦、提高其內(nèi)聚性。如果遵循單一職責(zé)原則將有以下優(yōu)點(diǎn)。

  • 降低類(lèi)的復(fù)雜度。一個(gè)類(lèi)只負(fù)責(zé)一項(xiàng)職責(zé),其邏輯肯定要比負(fù)責(zé)多項(xiàng)職責(zé)簡(jiǎn)單得多。

  • 提高類(lèi)的可讀性。復(fù)雜性降低,自然其可讀性會(huì)提高。

  • 提高系統(tǒng)的可維護(hù)性??勺x性提高,那自然更容易維護(hù)了。

  • 變更引起的風(fēng)險(xiǎn)降低。變更是必然的,如果單一職責(zé)原則遵守得好,當(dāng)修改一個(gè)功能時(shí),可以顯著降低對(duì)其他功能的影響。

5.3 單一職責(zé)原則的實(shí)現(xiàn)方法

單一職責(zé)原則是最簡(jiǎn)單但又最難運(yùn)用的原則,需要設(shè)計(jì)人員發(fā)現(xiàn)類(lèi)的不同職責(zé)并將其分離,再封裝到不同的類(lèi)或模塊中。而發(fā)現(xiàn)類(lèi)的多重職責(zé)需要設(shè)計(jì)人員具有較強(qiáng)的分析設(shè)計(jì)能力和相關(guān)重構(gòu)經(jīng)驗(yàn)

六、接口隔離原則

6.1 定義

接口隔離原則(Interface Segregation Principle,ISP)要求程序員盡量將臃腫龐大的接口拆分成更小的和更具體的接口,讓接口中只包含客戶感興趣的方法。

2002 年羅伯特·C.馬丁給“接口隔離原則”的定義是:客戶端不應(yīng)該被迫依賴于它不使用的方法(Clients should not be forced to depend on methods they do not use)。該原則還有另外一個(gè)定義:一個(gè)類(lèi)對(duì)另一個(gè)類(lèi)的依賴應(yīng)該建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)。

以上兩個(gè)定義的含義是:要為各個(gè)類(lèi)建立它們需要的專(zhuān)用接口,而不要試圖去建立一個(gè)很龐大的接口供所有依賴它的類(lèi)去調(diào)用。

接口隔離原則和單一職責(zé)都是為了提高類(lèi)的內(nèi)聚性、降低它們之間的耦合性,體現(xiàn)了封裝的思想,但兩者是不同的:

  • 單一職責(zé)原則注重的是職責(zé),而接口隔離原則注重的是對(duì)接口依賴的隔離。

  • 單一職責(zé)原則主要是約束類(lèi),它針對(duì)的是程序中的實(shí)現(xiàn)和細(xì)節(jié);接口隔離原則主要約束接口,主要針對(duì)抽象和程序整體框架的構(gòu)建。

6.2 優(yōu)點(diǎn)

接口隔離原則是為了約束接口、降低類(lèi)對(duì)接口的依賴性,遵循接口隔離原則有以下 5 個(gè)優(yōu)點(diǎn)。

  • 將臃腫龐大的接口分解為多個(gè)粒度小的接口,可以預(yù)防外來(lái)變更的擴(kuò)散,提高系統(tǒng)的靈活性和可維護(hù)性。

  • 接口隔離提高了系統(tǒng)的內(nèi)聚性,減少了對(duì)外交互,降低了系統(tǒng)的耦合性。

  • 如果接口的粒度大小定義合理,能夠保證系統(tǒng)的穩(wěn)定性;但是,如果定義過(guò)小,則會(huì)造成接口數(shù)量過(guò)多,使設(shè)計(jì)復(fù)雜化;如果定義太大,靈活性降低,無(wú)法提供定制服務(wù),給整體項(xiàng)目帶來(lái)無(wú)法預(yù)料的風(fēng)險(xiǎn)。

  • 使用多個(gè)專(zhuān)門(mén)的接口還能夠體現(xiàn)對(duì)象的層次,因?yàn)榭梢酝ㄟ^(guò)接口的繼承,實(shí)現(xiàn)對(duì)總接口的定義。

  • 能減少項(xiàng)目工程中的代碼冗余。過(guò)大的大接口里面通常放置許多不用的方法,當(dāng)實(shí)現(xiàn)這個(gè)接口的時(shí)候,被迫設(shè)計(jì)冗余的代碼。

6.3 接口隔離原則的實(shí)現(xiàn)方法

  • 接口盡量小,但是要有限度。一個(gè)接口只服務(wù)于一個(gè)子模塊或業(yè)務(wù)邏輯。

  • 為依賴接口的類(lèi)定制服務(wù)。只提供調(diào)用者需要的方法,屏蔽不需要的方法。

  • 了解環(huán)境,拒絕盲從。每個(gè)項(xiàng)目或產(chǎn)品都有選定的環(huán)境因素,環(huán)境不同,接口拆分的標(biāo)準(zhǔn)就不同深入了解業(yè)務(wù)邏輯。

  • 提高內(nèi)聚,減少對(duì)外交互。使接口用最少的方法去完成最多的事情。

七、迪米特法則

7.1 定義

迪米特法則(Law of Demeter,LoD)又叫作最少知識(shí)原則(Least Knowledge Principle,LKP),產(chǎn)生于 1987 年美國(guó)東北大學(xué)(Northeastern University)的一個(gè)名為迪米特(Demeter)的研究項(xiàng)目,由伊恩·荷蘭(Ian Holland)提出,被 UML 創(chuàng)始者之一的布奇(Booch)普及,后來(lái)又因?yàn)樵诮?jīng)典著作《程序員修煉之道》(The Pragmatic Programmer)提及而廣為人知。

迪米特法則的定義是:只與你的直接朋友交談,不跟“陌生人”說(shuō)話(Talk only to your immediate friends and not to strangers)。其含義是:如果兩個(gè)軟件實(shí)體無(wú)須直接通信,那么就不應(yīng)當(dāng)發(fā)生直接的相互調(diào)用,可以通過(guò)第三方轉(zhuǎn)發(fā)該調(diào)用。其目的是降低類(lèi)之間的耦合度,提高模塊的相對(duì)獨(dú)立性。

迪米特法則中的“朋友”是指:當(dāng)前對(duì)象本身、當(dāng)前對(duì)象的成員對(duì)象、當(dāng)前對(duì)象所創(chuàng)建的對(duì)象、當(dāng)前對(duì)象的方法參數(shù)等,這些對(duì)象同當(dāng)前對(duì)象存在關(guān)聯(lián)、聚合或組合關(guān)系,可以直接訪問(wèn)這些對(duì)象的方法。

7.2 優(yōu)點(diǎn)

  1. 降低了類(lèi)之間的耦合度,提高了模塊的相對(duì)獨(dú)立性。

  2. 由于親合度降低,從而提高了類(lèi)的可復(fù)用率和系統(tǒng)的擴(kuò)展性。

7.3 實(shí)現(xiàn)方法

從迪米特法則的定義和特點(diǎn)可知,它強(qiáng)調(diào)以下兩點(diǎn):

  1. 從依賴者的角度來(lái)說(shuō),只依賴應(yīng)該依賴的對(duì)象。

  2. 從被依賴者的角度說(shuō),只暴露應(yīng)該暴露的方法。

所以,在運(yùn)用迪米特法則時(shí)要注意以下 6 點(diǎn)。

  1. 在類(lèi)的劃分上,應(yīng)該創(chuàng)建弱耦合的類(lèi)。類(lèi)與類(lèi)之間的耦合越弱,就越有利于實(shí)現(xiàn)可復(fù)用的目標(biāo)。

  2. 在類(lèi)的結(jié)構(gòu)設(shè)計(jì)上,盡量降低類(lèi)成員的訪問(wèn)權(quán)限。

  3. 在類(lèi)的設(shè)計(jì)上,優(yōu)先考慮將一個(gè)類(lèi)設(shè)置成不變類(lèi)。

  4. 在對(duì)其他類(lèi)的引用上,將引用其他對(duì)象的次數(shù)降到最低。

  5. 不暴露類(lèi)的屬性成員,而應(yīng)該提供相應(yīng)的訪問(wèn)器(set 和 get 方法)。

  6. 謹(jǐn)慎使用序列化(Serializable)功能。

八、合成復(fù)用原則

8.1 定義

合成復(fù)用原則(Composite Reuse Principle,CRP)又叫組合/聚合復(fù)用原則(Composition/Aggregate Reuse Principle,CARP)。它要求在軟件復(fù)用時(shí),要盡量先使用組合或者聚合等關(guān)聯(lián)關(guān)系來(lái)實(shí)現(xiàn),其次才考慮使用繼承關(guān)系來(lái)實(shí)現(xiàn)。

如果要使用繼承關(guān)系,則必須嚴(yán)格遵循里氏替換原則。合成復(fù)用原則同里氏替換原則相輔相成的,兩者都是開(kāi)閉原則的具體實(shí)現(xiàn)規(guī)范。

8.2 重要性

通常類(lèi)的復(fù)用分為繼承復(fù)用和合成復(fù)用兩種,繼承復(fù)用雖然有簡(jiǎn)單和易實(shí)現(xiàn)的優(yōu)點(diǎn),但它也存在以下缺點(diǎn)。

  1. 繼承復(fù)用破壞了類(lèi)的封裝性。因?yàn)槔^承會(huì)將父類(lèi)的實(shí)現(xiàn)細(xì)節(jié)暴露給子類(lèi),父類(lèi)對(duì)子類(lèi)是透明的,所以這種復(fù)用又稱為“白箱”復(fù)用。

  2. 子類(lèi)與父類(lèi)的耦合度高。父類(lèi)的實(shí)現(xiàn)的任何改變都會(huì)導(dǎo)致子類(lèi)的實(shí)現(xiàn)發(fā)生變化,這不利于類(lèi)的擴(kuò)展與維護(hù)。

  3. 它限制了復(fù)用的靈活性。從父類(lèi)繼承而來(lái)的實(shí)現(xiàn)是靜態(tài)的,在編譯時(shí)已經(jīng)定義,所以在運(yùn)行時(shí)不可能發(fā)生變化。

采用組合或聚合復(fù)用時(shí),可以將已有對(duì)象納入新對(duì)象中,使之成為新對(duì)象的一部分,新對(duì)象可以調(diào)用已有對(duì)象的功能,它有以下優(yōu)點(diǎn)。

  1. 它維持了類(lèi)的封裝性。因?yàn)槌煞謱?duì)象的內(nèi)部細(xì)節(jié)是新對(duì)象看不見(jiàn)的,所以這種復(fù)用又稱為“黑箱”復(fù)用。

  2. 新舊類(lèi)之間的耦合度低。這種復(fù)用所需的依賴較少,新對(duì)象存取成分對(duì)象的唯一方法是通過(guò)成分對(duì)象的接口。

  3. 復(fù)用的靈活性高。這種復(fù)用可以在運(yùn)行時(shí)動(dòng)態(tài)進(jìn)行,新對(duì)象可以動(dòng)態(tài)地引用與成分對(duì)象類(lèi)型相同的對(duì)象。

8.3 實(shí)現(xiàn)方法

合成復(fù)用原則是通過(guò)將已有的對(duì)象納入新對(duì)象中,作為新對(duì)象的成員對(duì)象來(lái)實(shí)現(xiàn)的,新對(duì)象可以調(diào)用已有對(duì)象的功能,從而達(dá)到復(fù)用。

九、總結(jié)

這 7 種設(shè)計(jì)原則是軟件設(shè)計(jì)模式必須盡量遵循的原則,是設(shè)計(jì)模式的基礎(chǔ)。在實(shí)際開(kāi)發(fā)過(guò)程中,并不是一定要求所有代碼都遵循設(shè)計(jì)原則,而是要綜合考慮人力、時(shí)間、成本、質(zhì)量,不刻意追求完美,要在適當(dāng)?shù)膱?chǎng)景遵循設(shè)計(jì)原則。這體現(xiàn)的是一種平衡取舍,可以幫助我們?cè)O(shè)計(jì)出更加優(yōu)雅的代碼結(jié)構(gòu)。

設(shè)計(jì)原則 一句話歸納 目的
開(kāi)閉原則 對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉 降低維護(hù)帶來(lái)的新風(fēng)險(xiǎn)
依賴倒置原則 高層不應(yīng)該依賴低層,要面向接口編程 更利于代碼結(jié)構(gòu)的升級(jí)擴(kuò)展
單一職責(zé)原則 一個(gè)類(lèi)只干一件事,實(shí)現(xiàn)類(lèi)要單一 便于理解,提高代碼的可讀性
接口隔離原則 一個(gè)接口只干一件事,接口要精簡(jiǎn)單一 功能解耦,高聚合、低耦合
迪米特法則 不該知道的不要知道,一個(gè)類(lèi)應(yīng)該保持對(duì)其它對(duì)象最少的了解,降低耦合度 只和朋友交流,不和陌生人說(shuō)話,減少代碼臃腫
里氏替換原則 不要破壞繼承體系,子類(lèi)重寫(xiě)方法功能發(fā)生改變,不應(yīng)該影響父類(lèi)方法的含義 防止繼承泛濫
合成復(fù)用原則 盡量使用組合或者聚合關(guān)系實(shí)現(xiàn)代碼復(fù)用,少使用繼承 降低代碼耦合

實(shí)際上,這些原則的目的只有一個(gè):降低對(duì)象之間的耦合,增加程序的可復(fù)用性、可擴(kuò)展性和可維護(hù)性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容