設(shè)計(jì)模式的六大原則:1、單一職責(zé)原則,其核心就是控制類(lèi)的粒度大小、將對(duì)象解耦、提高其內(nèi)聚性;2、開(kāi)閉原則,可以通過(guò)“抽象約束、封裝變化”來(lái)實(shí)現(xiàn);3、里氏替換原則,主要闡述了有關(guān)繼承的一些原則;4、依賴(lài)倒置原則,降低了客戶與實(shí)現(xiàn)模塊之間的耦合;5、接口隔離原則,是為了約束接口、降低類(lèi)對(duì)接口的依賴(lài)性;6、迪米特法則,要求限制軟件實(shí)體之間通信的寬度和深度。
本教程操作環(huán)境:windows7系統(tǒng)、java8版、DELL G3電腦。
對(duì)于設(shè)計(jì)模式,自己很早之前就看了好多本設(shè)計(jì)模式書(shū)籍,其中一些還看了好幾遍,也一直希望自己能在編碼的時(shí)候把這些設(shè)計(jì)模式用上去??墒?,在日常的打碼中,用的做多的就是單例,其次是觀察者和建造者模式 ( builder ) 用得比較多,其他的基本很少用到。
用不到的原因是還是不能夠理解設(shè)計(jì)模式的思想,無(wú)法將這些設(shè)計(jì)模式和編碼遇到的問(wèn)題聯(lián)系起來(lái),從而用不到設(shè)計(jì)模式。
其實(shí)設(shè)計(jì)模式的提出都是為了解決一個(gè)常見(jiàn)的問(wèn)題而總結(jié)出來(lái)的辦法。所以當(dāng)你思考采用何種設(shè)計(jì)模式的時(shí)候,你應(yīng)該先問(wèn)問(wèn)自己當(dāng)前問(wèn)題的是什么?根據(jù)問(wèn)題去選取合適的設(shè)計(jì)模式。
等你熟悉了設(shè)計(jì)模式的以后,你會(huì)發(fā)現(xiàn)部分設(shè)計(jì)模式之間存在包含關(guān)系,甚至很相像,但是不同的設(shè)計(jì)模式解決的問(wèn)題是不一樣的。
當(dāng)我們?cè)谠O(shè)計(jì)一個(gè)模塊的時(shí)候可以從以下幾個(gè)角度去考慮:
-
這個(gè)模塊與其他模塊的關(guān)系是什么樣的?
-
模塊中哪些部分是不變的,哪些部分是在不斷變化的,是如何變化的?
-
類(lèi)與類(lèi)之間的關(guān)系是怎么樣的,為什么需要依賴(lài),怎么可以不依賴(lài)?
-
要不要加一個(gè)接口?接口的存在是為了解決什么問(wèn)題?
當(dāng)然,本文并不是教你是如何使用設(shè)計(jì)模式。而是講解設(shè)計(jì)模式的設(shè)計(jì)原則。設(shè)計(jì)模式在被設(shè)計(jì)出來(lái)的時(shí)候,也是遵循一些規(guī)則的。
設(shè)計(jì)模式六大原則,具體如下:
-
單一職責(zé)原則(類(lèi)和方法,接口)
-
開(kāi)閉原則 (擴(kuò)展開(kāi)放,修改關(guān)閉)
-
里氏替換原則(基類(lèi)和子類(lèi)之間的關(guān)系)
-
依賴(lài)倒置原則(依賴(lài)抽象接口,而不是具體對(duì)象)
-
接口隔離原則(接口按照功能細(xì)分)
-
迪米特法則 (類(lèi)與類(lèi)之間的親疏關(guān)系)
每一個(gè)設(shè)計(jì)原則旁邊都有個(gè)括號(hào),是用來(lái)解釋?zhuān)蛘呙枋鰬?yīng)用范圍的。下面將詳細(xì)介紹每一個(gè)原則。
單一職責(zé)原則的定義(類(lèi)、方法、接口)
單一職責(zé)原則(Single Responsibility Principle,SRP)又稱(chēng)單一功能原則。這里的職責(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):
-
一個(gè)職責(zé)的變化可能會(huì)削弱或者抑制這個(gè)類(lèi)實(shí)現(xiàn)其他職責(zé)的能力;
-
當(dāng)客戶端需要該對(duì)象的某一個(gè)職責(zé)時(shí),不得不將其他不需要的職責(zé)全都包含進(jìn)來(lái),從而造成冗余代碼或代碼的浪費(fèi)。
單一職責(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ù)性。可讀性提高,那自然更容易維護(hù)了。
-
變更引起的風(fēng)險(xiǎn)降低。變更是必然的,如果單一職責(zé)原則遵守得好,當(dāng)修改一個(gè)功能時(shí),可以顯著降低對(duì)其他功能的影響。
單一職責(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)。
示例
public interface UserService { public void login(String username, String password); public void register(String email, String username, String password); public void logError(String msg); public void sendEmail(String email); }
登錄后復(fù)制
登錄后復(fù)制
這段代碼很顯然存在很大的問(wèn)題,UserService 既要負(fù)責(zé)用戶的注冊(cè)和登錄,還要負(fù)責(zé)日志的記錄和郵件的發(fā)送,并且后者的行為明顯區(qū)別于前者。
假設(shè)我要修改發(fā)送郵件的邏輯就得修改這個(gè)類(lèi),這時(shí)候 qa 還得回歸登錄注冊(cè)邏輯,這樣明顯不合理。
因此我們需要進(jìn)行拆分,根據(jù)具體的職能可將其具體拆分如下:
UserService:只負(fù)責(zé)登錄注冊(cè)
public interface UserService { public void login(String username, String password); public void register(String email, String username, String password); }
登錄后復(fù)制
LogService :只負(fù)責(zé)日志
public interface LogService { public void logError(String msg); }
登錄后復(fù)制
EmailService: 只負(fù)責(zé)發(fā)送郵件
public interface EmailService { public void sendEmail(String email); }
登錄后復(fù)制
這時(shí)候,咱們?cè)偃セ仡櫱懊嫣岬降膬?yōu)點(diǎn),就能深深體會(huì)了。
這里只是講了接口,其實(shí)對(duì)類(lèi)也一樣,甚至方法也是一樣的。
對(duì)于類(lèi)來(lái)說(shuō),根據(jù)類(lèi)名,確保里面提供的方法都是屬于這個(gè)類(lèi)的。
對(duì)于方法,不要把不相關(guān)的對(duì)象實(shí)例作為參數(shù)傳進(jìn)來(lái)。如果你發(fā)現(xiàn)某個(gè)方法依賴(lài)某個(gè)不相關(guān)的對(duì)象,那么這個(gè)方法的實(shí)現(xiàn)可能就存在問(wèn)題。
比如 android 中圖片下載后顯示到 imageView 中,我提供如下的方法:
loadImage(String url, ImageView view) { // 下載圖片,展示圖片 }
登錄后復(fù)制
對(duì)于 loadImage 這個(gè)方法,參數(shù) url 是ok 的,但是參數(shù) ImageView 卻是不合理的。因?yàn)檫@里做了兩個(gè)操作,下載圖片,展示圖片。應(yīng)該將這個(gè)方法在進(jìn)行拆分:
// 下載圖片 loadImage(String url) { } // 顯示圖片 displayImage(String url, ImageView view) { // 調(diào)用 getBitmap (url) 獲取圖片 // 獲取圖片后將其設(shè)置到 view 中。 } // 根據(jù) url 獲取圖片, getBitmap(String url) { }
登錄后復(fù)制
這樣整個(gè)邏輯就很清晰。后續(xù)需要修改下載邏輯,也不會(huì)影響到展示邏輯。當(dāng)然其實(shí)還有個(gè)問(wèn)題是,這兩個(gè)方法要不要放在一個(gè)類(lèi)里面?
開(kāi)閉原則
開(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ò)展就可以了。
示例
// 矩形 public class Rectangle { public double getWidth() { return width; } public double getHeight() { return height; } }
登錄后復(fù)制
需要計(jì)算矩形的面積
// 面積計(jì)算器 public class AreaCalculator { public double area(Rectangle shape){ return shape.getWidth() * shape.getHeight(); } }
登錄后復(fù)制
假設(shè)這時(shí)候,又多了一個(gè)圓形類(lèi)
// 圓形 public class Circular { public double getRadius(){ return radius; } }
登錄后復(fù)制
同樣也需要計(jì)算他的面積,這時(shí)候就會(huì)變成下面這樣子:
public class AreaCalculator { public double area(Object shape){ if(shape instanceof Rectangle) { Rectangle rectangle = (Rectangle) shape; return rectangle.getWidth() * rectangle.getHeight(); } else if (shape instanceof Circular) { Circular circular = (Circular) shape; return circular.getRadius() * circular.getRadius() * Math.PI; } else { throw new RuntimeException("There is no such type."); } } }
登錄后復(fù)制
這么更改完成,完全沒(méi)有問(wèn)題。但是在真實(shí)的生產(chǎn)環(huán)境中,情況更為復(fù)雜,更改涉及的部分較多,那樣就可能導(dǎo)致?tīng)恳话l(fā)動(dòng)全身。并且,以前編寫(xiě)的經(jīng)過(guò)測(cè)試的一些功能需要重新測(cè)試,甚至導(dǎo)致某些功能不可用。
改進(jìn)版,把計(jì)算面積這個(gè)公有邏輯變成一個(gè)接口:
public interface Shape { public double getArea(); } public class Rectangle implements Shape{ public double getWidth() { return width; } public double getHeight() { return height; } public double getArea() { return getWidth() * getHeight(); } }
登錄后復(fù)制
這樣,當(dāng)需求變更,需要計(jì)算圓形面積的時(shí)候,我們只需創(chuàng)建一個(gè)圓形的類(lèi),并實(shí)現(xiàn) Shape 接口即可:
public class Circular implements Shape { public double getRadius(){ return radius; } public double getArea() { return getRadius() * getRadius() * Math.PI; } }
登錄后復(fù)制
計(jì)算三角形面積、四邊形面積... 的時(shí)候,我們只需讓它們?nèi)?shí)現(xiàn) Shape 接口即可,無(wú)需修改源代碼。
里氏替換原則
里氏替換原則主要闡述了有關(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ī)范。
里氏替換原則的作用
里氏替換原則的主要作用如下。
-
里氏替換原則是實(shí)現(xiàn)開(kāi)閉原則的重要方式之一。
-
它克服了繼承中重寫(xiě)父類(lèi)造成的可復(fù)用性變差的缺點(diǎn)。
-
它是動(dòng)作正確性的保證。即類(lèi)的擴(kuò)展不會(huì)給已有的系統(tǒng)引入新的錯(cuò)誤,降低了代碼出錯(cuò)的可能性。
-
加強(qiáng)程序的健壯性,同時(shí)變更時(shí)可以做到非常好的兼容性,提高程序的維護(hù)性、可擴(kuò)展性,降低需求變更時(shí)引入的風(fēng)險(xiǎn)。
里氏替換原則的實(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)系。
關(guān)于里氏替換原則的例子,最有名的是“正方形不是長(zhǎng)方形”。當(dāng)然,生活中也有很多類(lèi)似的例子,例如,企鵝、鴕鳥(niǎo)和幾維鳥(niǎo)從生物學(xué)的角度來(lái)劃分,它們屬于鳥(niǎo)類(lèi);但從類(lèi)的繼承關(guān)系來(lái)看,由于它們不能繼承“鳥(niǎo)”會(huì)飛的功能,所以它們不能定義成“鳥(niǎo)”的子類(lèi)。同樣,由于“氣球魚(yú)”不會(huì)游泳,所以不能定義成“魚(yú)”的子類(lèi);“玩具炮”炸不了敵人,所以不能定義成“炮”的子類(lèi)等。
對(duì)于正方形和長(zhǎng)方形最好的做法是再添加一個(gè)父類(lèi),他們同時(shí)繼承自這個(gè)父類(lèi)。
依賴(lài)倒置(抽線細(xì)節(jié))
依賴(lài)倒置原則是實(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)去完成。
依賴(lài)、倒置原則的作用
依賴(lài)倒置原則的主要作用如下。
-
依賴(lài)倒置原則可以降低類(lèi)間的耦合性。
-
依賴(lài)倒置原則可以提高系統(tǒng)的穩(wěn)定性。
-
依賴(lài)倒置原則可以減少并行開(kāi)發(fā)引起的風(fēng)險(xiǎn)。
-
依賴(lài)倒置原則可以提高代碼的可讀性和可維護(hù)性。
依賴(lài)倒置原則的實(shí)現(xiàn)方法
依賴(lài)倒置原則的目的是通過(guò)要面向接口的編程來(lái)降低類(lèi)間的耦合性,所以我們?cè)趯?shí)際編程中只要遵循以下4點(diǎn),就能在項(xiàng)目中滿足這個(gè)規(guī)則。
-
每個(gè)類(lèi)盡量提供接口或抽象類(lèi),或者兩者都具備。
-
變量的聲明類(lèi)型盡量是接口或者是抽象類(lèi)。
-
任何類(lèi)都不應(yīng)該從具體類(lèi)派生。
-
使用繼承時(shí)盡量遵循里氏替換原則。
依賴(lài)倒置原則在“顧客購(gòu)物程序”中的應(yīng)用。
分析:本程序反映了 “顧客類(lèi)”與“商店類(lèi)”的關(guān)系。商店類(lèi)中有 sell() 方法,顧客類(lèi)通過(guò)該方法購(gòu)物以下代碼定義了顧客類(lèi)通過(guò)韶關(guān)網(wǎng)店 ShaoguanShop 購(gòu)物
class Customer { public void shopping(ShaoguanShop shop) { //購(gòu)物 System.out.println(shop.sell()); } }
登錄后復(fù)制
但是,這種設(shè)計(jì)存在缺點(diǎn),如果該顧客想從另外一家商店(如婺源網(wǎng)店 WuyuanShop)購(gòu)物,就要將該顧客的代碼修改如下:
class Customer { public void shopping(WuyuanShop shop) { //購(gòu)物 System.out.println(shop.sell()); } }
登錄后復(fù)制
顧客每更換一家商店,都要修改一次代碼,這明顯違背了開(kāi)閉原則。
存在以上缺點(diǎn)的原因是:顧客類(lèi)設(shè)計(jì)時(shí)同具體的商店類(lèi)綁定了,這違背了依賴(lài)倒置原則。
解決方法是:定義“婺源網(wǎng)店”和“韶關(guān)網(wǎng)店”的共同接口 Shop,顧客類(lèi)面向該接口編程,其代碼修改如下:
class Customer { public void shopping(Shop shop) { //購(gòu)物 System.out.println(shop.sell()); } } class Customer { public void shopping(Shop shop) { //購(gòu)物 System.out.println(shop.sell()); } }
登錄后復(fù)制
這樣,不管顧客類(lèi) Customer 訪問(wèn)什么商店,或者增加新的商店,都不需要修改原有代碼了,其類(lèi)如下圖所示:
程序代碼如下:
package principle; public class DIPtest { public static void main(String[] args) { Customer wang=new Customer(); System.out.println("顧客購(gòu)買(mǎi)以下商品:"); wang.shopping(new ShaoguanShop()); wang.shopping(new WuyuanShop()); } } //商店 interface Shop { public String sell(); //賣(mài) } //韶關(guān)網(wǎng)店 class ShaoguanShop implements Shop { public String sell() { return "韶關(guān)土特產(chǎn):香菇、木耳……"; } } //婺源網(wǎng)店 class WuyuanShop implements Shop { public String sell() { return "婺源土特產(chǎn):綠茶、酒糟魚(yú)……"; } } //顧客 class Customer { public void shopping(Shop shop) { //購(gòu)物 System.out.println(shop.sell()); } }
登錄后復(fù)制
程序的運(yùn)行結(jié)果如下:
顧客購(gòu)買(mǎi)以下商品: 韶關(guān)土特產(chǎn):香菇、木耳…… 婺源土特產(chǎn):綠茶、酒糟魚(yú)……
登錄后復(fù)制
接口隔離原則(接口)
接口隔離原則(Interface Segregation Principle,ISP)要求程序員盡量將臃腫龐大的接口拆分成更小的和更具體的接口,讓接口中只包含客戶感興趣的方法。
2002 年羅伯特·C.馬丁給“接口隔離原則”的定義是:客戶端不應(yīng)該被迫依賴(lài)于它不使用的方法(Clients should not be forced to depend on methods they do not use)。該原則還有另外一個(gè)定義:一個(gè)類(lèi)對(duì)另一個(gè)類(lèi)的依賴(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)它的類(lèi)去調(diào)用。
接口隔離原則和單一職責(zé)都是為了提高類(lèi)的內(nèi)聚性、降低它們之間的耦合性,體現(xiàn)了封裝的思想,但兩者是不同的:
-
單一職責(zé)原則注重的是職責(zé),而接口隔離原則注重的是對(duì)接口依賴(lài)的隔離。
-
單一職責(zé)原則主要是約束類(lèi),它針對(duì)的是程序中的實(shí)現(xiàn)和細(xì)節(jié);接口隔離原則主要約束接口,主要針對(duì)抽象和程序整體框架的構(gòu)建。
接口隔離原則的優(yōu)點(diǎn)
接口隔離原則是為了約束接口、降低類(lèi)對(duì)接口的依賴(lài)性,遵循接口隔離原則有以下 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ì)冗余的代碼。
接口隔離原則的實(shí)現(xiàn)方法
在具體應(yīng)用接口隔離原則時(shí),應(yīng)該根據(jù)以下幾個(gè)規(guī)則來(lái)衡量。
-
接口盡量小,但是要有限度。一個(gè)接口只服務(wù)于一個(gè)子模塊或業(yè)務(wù)邏輯。
-
為依賴(lài)接口的類(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ì)外交互。使接口用最少的方法去完成最多的事情。
對(duì)于接口隔離,大家還是可以參考單一職責(zé)提到的示例:
public interface UserService { public void login(String username, String password); public void register(String email, String username, String password); public void logError(String msg); public void sendEmail(String email); }
登錄后復(fù)制
登錄后復(fù)制
這時(shí)候,應(yīng)該就能理解拆分的好處了。
迪米特法則 (類(lèi)與類(lèi)之間的關(guān)系)
迪米特法則(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ì)象的方法。
迪米特法則的優(yōu)點(diǎn)
迪米特法則要求限制軟件實(shí)體之間通信的寬度和深度,正確使用迪米特法則將有以下兩個(gè)優(yōu)點(diǎn)。
-
降低了類(lèi)之間的耦合度,提高了模塊的相對(duì)獨(dú)立性。
-
由于親合度降低,從而提高了類(lèi)的可復(fù)用率和系統(tǒng)的擴(kuò)展性。
但是,過(guò)度使用迪米特法則會(huì)使系統(tǒng)產(chǎn)生大量的中介類(lèi),從而增加系統(tǒng)的復(fù)雜性,使模塊之間的通信效率降低。所以,在釆用迪米特法則時(shí)需要反復(fù)權(quán)衡,確保高內(nèi)聚和低耦合的同時(shí),保證系統(tǒng)的結(jié)構(gòu)清晰。
迪米特法則的實(shí)現(xiàn)方法
從迪米特法則的定義和特點(diǎn)可知,它強(qiáng)調(diào)以下兩點(diǎn):
-
從依賴(lài)者的角度來(lái)說(shuō),只依賴(lài)應(yīng)該依賴(lài)的對(duì)象。
-
從被依賴(lài)者的角度說(shuō),只暴露應(yīng)該暴露的方法。
所以,在運(yùn)用迪米特法則時(shí)要注意以下 6 點(diǎn)。
-
在類(lèi)的劃分上,應(yīng)該創(chuàng)建弱耦合的類(lèi)。類(lèi)與類(lèi)之間的耦合越弱,就越有利于實(shí)現(xiàn)可復(fù)用的目標(biāo)。
-
在類(lèi)的結(jié)構(gòu)設(shè)計(jì)上,盡量降低類(lèi)成員的訪問(wèn)權(quán)限。
-
在類(lèi)的設(shè)計(jì)上,優(yōu)先考慮將一個(gè)類(lèi)設(shè)置成不變類(lèi)。
-
在對(duì)其他類(lèi)的引用上,將引用其他對(duì)象的次數(shù)降到最低。
-
不暴露類(lèi)的屬性成員,而應(yīng)該提供相應(yīng)的訪問(wèn)器(set 和 get 方法)。
-
謹(jǐn)慎使用序列化(Serializable)功能
明星與經(jīng)紀(jì)人的關(guān)系實(shí)例。
分析:明星由于全身心投入藝術(shù),所以許多日常事務(wù)由經(jīng)紀(jì)人負(fù)責(zé)處理,如與粉絲的見(jiàn)面會(huì),與媒體公司的業(yè)務(wù)洽淡等。這里的經(jīng)紀(jì)人是明星的朋友,而粉絲和媒體公司是陌生人,所以適合使用迪米特法則,其類(lèi)圖如下圖所示。
代碼如下:
package principle; public class LoDtest { public static void main(String[] args) { Agent agent=new Agent(); agent.setStar(new Star("林心如")); agent.setFans(new Fans("粉絲韓丞")); agent.setCompany(new Company("中國(guó)傳媒有限公司")); agent.meeting(); agent.business(); } } //經(jīng)紀(jì)人 class Agent { private Star myStar; private Fans myFans; private Company myCompany; public void setStar(Star myStar) { this.myStar=myStar; } public void setFans(Fans myFans) { this.myFans=myFans; } public void setCompany(Company myCompany) { this.myCompany=myCompany; } public void meeting() { System.out.println(myFans.getName()+"與明星"+myStar.getName()+"見(jiàn)面了。"); } public void business() { System.out.println(myCompany.getName()+"與明星"+myStar.getName()+"洽淡業(yè)務(wù)。"); } } //明星 class Star { private String name; Star(String name) { this.name=name; } public String getName() { return name; } } //粉絲 class Fans { private String name; Fans(String name) { this.name=name; } public String getName() { return name; } } //媒體公司 class Company { private String name; Company(String name) { this.name=name; } public String getName() { return name; } }
登錄后復(fù)制
程序的運(yùn)行結(jié)果如下:
粉絲韓丞與明星林心如見(jiàn)面了。 中國(guó)傳媒有限公司與明星林心如洽淡業(yè)務(wù)。
登錄后復(fù)制
到此,設(shè)計(jì)模式的六大原則就講完了。