作者:移動的紅燒肉
原文鏈接:http://www.reibang.com/p/d7ded306787c
來源:簡書
簡書著作權(quán)歸作者所有沦泌,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。
說明:我是怕原作者刪了,自己這里記下昆雀,方便后續(xù)查看
1.開閉原則 - Open Close Principle(OCP)
1)定義
一個軟件實(shí)體如類绿满、模塊和函數(shù)應(yīng)該對擴(kuò)展開放亿胸,對修改關(guān)閉
Software entities like classes,modules and functions should be open for extension but closed for modifications.
2)基本概念
開:對擴(kuò)展開放汹想,支持方便的擴(kuò)展
閉:對修修改關(guān)閉,嚴(yán)格限制對已有的內(nèi)容修改
說明:一個軟件產(chǎn)品只要在生命周期內(nèi)腺律,都會發(fā)生變化奕短,即然變化是一個事實(shí),我們就應(yīng)該在設(shè)計時盡量適應(yīng)這些變化匀钧,以提高項目的穩(wěn)定性和靈活性翎碑,真正實(shí)現(xiàn)“擁抱變化”。開閉原則告訴我們應(yīng)盡量通過擴(kuò)展軟件實(shí)體的行為來實(shí)現(xiàn)變化榴捡,而不是通過修改現(xiàn)有代碼來完成變化杈女,它是為軟件實(shí)體的未來事件而制定的對現(xiàn)行開發(fā)設(shè)計進(jìn)行約束的一個原則
3)優(yōu)點(diǎn)
提高系統(tǒng)的靈活性、可復(fù)用性和可維護(hù)性
4)示例:現(xiàn)在吊圾,以課程為例說明什么是開閉原則
/**
* 定義課程接口
*/
public interface ICourse {
String getName(); // 獲取課程名稱
Double getPrice(); // 獲取課程價格
Integer getType(); // 獲取課程類型
}
/**
* 英語課程接口實(shí)現(xiàn)
*/
public class EnglishCourse implements ICourse {
private String name;
private Double price;
private Integer type;
public EnglishCourse(String name, Double price, Integer type) {
this.name = name;
this.price = price;
this.type = type;
}
@Override
public String getName() {
return null;
}
@Override
public Double getPrice() {
return null;
}
@Override
public Integer getType() {
return null;
}
}
// 測試
public class Main {
public static void main(String[] args) {
ICourse course = new EnglishCourse("小學(xué)英語", 199D, "Mr.Zhang");
System.out.println(
"課程名字:"+course.getName() + " " +
"課程價格:"+course.getPrice() + " " +
"課程作者:"+course.getAuthor()
);
}
}
項目上線,課程正常銷售翰蠢,但是我們產(chǎn)品需要做些活動來促進(jìn)銷售项乒,比如:打折。那么問題來了:打折這一動作就是一個變化梁沧,而我們要做的就是擁抱變化檀何,現(xiàn)在開始考慮如何解決這個問題,可以考慮下面三種方案:
1)修改接口
在之前的課程接口中添加一個方法 getSalePrice() 專門用來獲取打折后的價格廷支;
如果這樣修改就會產(chǎn)生兩個問題频鉴,所以此方案否定
(1) ICourse 接口不應(yīng)該被經(jīng)常修改,否則接口作為契約的作用就失去了
(2) 并不是所有的課程都需要打折恋拍,加入還有語文課垛孔,數(shù)學(xué)課等都實(shí)現(xiàn)了這一接口,但是只有英語課打折施敢,與實(shí)際業(yè)務(wù)不符
public interface ICourse {
// 獲取課程名稱
String getName();
// 獲取課程價格
Double getPrice();
// 獲取課程類型
String getAuthor();
// 新增:打折接口
Double getSalePrice();
}
2)修改實(shí)現(xiàn)類
在接口實(shí)現(xiàn)里直接修改 getPrice()方法周荐,此方法會導(dǎo)致獲取原價出問題狭莱;或添加獲取打折的接口 getSalePrice(),這樣就會導(dǎo)致獲取價格的方法存在兩個概作,所以這個方案也否定腋妙,此方案不貼代碼了。
3)通過擴(kuò)展實(shí)現(xiàn)變化
直接添加一個子類 SaleEnglishCourse ,重寫 getPrice()方法讯榕,這個方案對源代碼沒有影響骤素,符合開閉原則,所以是可執(zhí)行的方案,代碼如下愚屁,代碼如下:
public class SaleEnglishCourse extends EnglishCourse {
public SaleEnglishCourse(String name, Double price, String author) {
super(name, price, author);
}
@Override
public Double getPrice() {
return super.getPrice() * 0.85;
}
}
綜上所述济竹,如果采用第三種,即開閉原則集绰,以后再來個語文課程规辱,數(shù)學(xué)課程等等的價格變動都可以采用此方案,維護(hù)性極高而且也很靈活
2.單一職責(zé)原則 - Single Responsibility Principle(SRP)
1)定義
不要存在多于一個導(dǎo)致類變更的原因
There should never be more than one reason for a class to change.
2)基本概念
單一職責(zé)是高內(nèi)聚低耦合的一個體現(xiàn)
說明:通俗的講就是一個類只能負(fù)責(zé)一個職責(zé),修改一個類不能影響到別的功能,也就是說只有一個導(dǎo)致該類被修改的原因
3)優(yōu)點(diǎn)
低耦合性栽燕,影響范圍小
降低類的復(fù)雜度罕袋,職責(zé)分明,提高了可讀性
變更引起的風(fēng)險低碍岔,利于維護(hù)
4)示例:現(xiàn)在浴讯,以動物為例說明什么是單一原則
假如說,類 A 負(fù)責(zé)兩個不同的職責(zé)蔼啦,T1 和 T2,當(dāng)由于職責(zé) T1 需求發(fā)生改變而需要修改類 A 時榆纽,有可能會導(dǎo)致原本運(yùn)行正常的職責(zé) T2 功能發(fā)生改變或出現(xiàn)異常。為什么會出現(xiàn)這種問題呢捏肢?代碼耦合度太高奈籽,實(shí)現(xiàn)復(fù)雜,簡單一句話就是:不夠單一鸵赫。那么現(xiàn)在提出解決方案:分別建立兩個類 A 和 B 衣屏,使 A 完成職責(zé) T1 功能,B 完成職責(zé) T2 功能辩棒,這樣在修改 T1 時就不會影響 T2 了狼忱,反之亦然。
說到單一職責(zé)原則一睁,很多人都會不屑一顧钻弄。因為它太簡單了。稍有經(jīng)驗的程序員即使從來沒有讀過設(shè)計模式者吁、從來沒有聽說過單一職責(zé)原則窘俺,在設(shè)計軟件時也會自覺的遵守這一重要原則,因為這是常識砚偶。在軟件編程中批销,誰也不希望因為修改了一個功能導(dǎo)致其他的功能發(fā)生故障洒闸。而避免出現(xiàn)這一問題的方法便是遵循單一職責(zé)原則。雖然單一職責(zé)原則如此簡單均芽,并且被認(rèn)為是常識丘逸,但是即便是經(jīng)驗豐富的程序員寫出的程序,也會有違背這一原則的代碼存在掀宋。為什么會出現(xiàn)這種現(xiàn)象呢深纲?因為有職責(zé)擴(kuò)散。所謂職責(zé)擴(kuò)散劲妙,就是因為某種原因湃鹊,職責(zé) T 被分化為粒度更細(xì)的職責(zé) T1 和 T2
/**
* 定義動物類
*/
public class Animal {
public void move(String animal){
System.out.println(animal + "用翅膀飛");
}
}
/**
* 第一次測試
*/
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.move("麻雀");
animal.move("老鷹");
animal.move("鯨魚");
}
}
經(jīng)過上面代碼示例發(fā)現(xiàn):麻雀和老鷹會飛是可以理解的,但是鯨魚就有點(diǎn)不合常理了镣奋,那么我們遵循單一對代碼進(jìn)行第(一)次修改,代碼如下:
/**
* 會飛的動物
*/
public class FlyAnimal {
public void move(String animal){
System.out.println(animal + "用翅膀飛");
}
}
/**
* 在水里的動物
*/
public class WaterAnimal {
public void move(String animal){
System.out.println(animal + "在水里游泳");
}
}
/**
* 測試
*/
public class Main {
public static void main(String[] args) {
FlyAnimal flyAnimal = new FlyAnimal();
flyAnimal.move("麻雀");
flyAnimal.move("老鷹");
WaterAnimal waterAnimal = new WaterAnimal();
waterAnimal.move("鯨魚");
}
}
遵循單一原則發(fā)現(xiàn)確實(shí)是職責(zé)單一了币呵,但是我們會發(fā)現(xiàn)如果這樣修改花銷是很大的,除了將原來的類分解之外還需要修改客戶端代碼侨颈。如果我們不去遵循單一原則余赢,而是直接在原有代碼進(jìn)行第(二)次修改,代碼如下:
/**
* 直接修改源代碼
*/
public class Animal {
public void move(String animal){
if ("鯨魚".equals(animal)) {
System.out.println(animal + "在水里游泳");
} else {
System.out.println(animal + "用翅膀飛");
}
}
}
/**
* 第三次測試
*/
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.move("麻雀");
animal.move("老鷹");
animal.move("鯨魚");
}
}
直接修改源碼確實(shí)簡單很多哈垢,但是卻存在巨大的隱患妻柒,加入需求變了:將在水里的動物分為淡水的和海水的,那么又要繼續(xù)修改 Animal 類的 move() 方法耘分,這也就對會飛的動物造成了一定的風(fēng)險举塔,所以我們繼續(xù)摒棄單一原則對代碼進(jìn)行第(三)次修改,代碼如下:
public class Animal {
public void move(String animal){
System.out.println(animal + "在水里游泳");
}
public void moveA(String animal){
System.out.println(animal + "用翅膀飛");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.move("麻雀");
animal.move("老鷹");
animal.moveA("鯨魚");
}
}
在最后一種修改中可以看到求泰,這種修改方式?jīng)]有改動原來的方法央渣,而是在類中新加了一個方法,這樣雖然也違背了單一職責(zé)原則渴频,但在方法級別上卻是符合單一職責(zé)原則的痹屹,因為它并沒有動原來方法的代碼。綜上所述枉氮,這三種方式各有優(yōu)缺點(diǎn),那么在實(shí)際編程中暖庄,采用哪一中呢聊替?結(jié)論:只有邏輯足夠簡單,才可以在代碼級別上違反單一職責(zé)原則培廓;只有類中方法數(shù)量足夠少惹悄,才可以在方法級別上違反單一職責(zé)原則;
3.里士替換原則 - Liskov Substitution Principle(LSP)
1)定義
定義一:所有引用基類的地方必須能透明地使用其子類的對象。
定義二:如果對每一個類型為 T1的對象 o1肩钠,都有類型為 T2 的對象o2泣港,使得以 T1定義的所有程序 P 在所有的對象 o1 都代換成 o2 時暂殖,程序 P 的行為沒有發(fā)生變化,那么類型 T2 是類型 T1 的子類型当纱。
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
2)基本概念
強(qiáng)調(diào)的是設(shè)計和實(shí)現(xiàn)要依賴于抽象而非具體呛每;子類只能去擴(kuò)展基類,而不是隱藏或者覆蓋基類它包含以下4層含義
1)子類可以實(shí)現(xiàn)父類的抽象方法坡氯,但不能覆蓋父類的非抽象方法晨横。
2)子類中可以增加自己特有的方法。
3)當(dāng)子類的方法重載父類的方法時箫柳,方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松手形。
4)當(dāng)子類的方法實(shí)現(xiàn)父類的抽象方法時,方法的后置條件(即方法的返回值)要比父類更嚴(yán)格悯恍。
3)優(yōu)點(diǎn)
開閉原則的體現(xiàn)库糠,約束繼承泛濫
提高系統(tǒng)的健壯性、擴(kuò)展性和兼容性
4)示例:
代碼講解第三個概念 :
當(dāng)子類的方法重載父類的方法時涮毫,方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松
public class ParentClazz {
public void say(CharSequence str) {
System.out.println("parent execute say " + str);
}
}
public class ChildClazz extends ParentClazz {
public void say(String str) {
System.out.println("child execute say " + str);
}
}
/**
* 測試
*/
public class Main {
public static void main(String[] args) {
ArrayList list = new ArrayList();
ParentClazz parent = new ParentClazz();
parent.say("hello");
ChildClazz child = new ChildClazz();
child.say("hello");
}
}
執(zhí)行結(jié)果:
parent execute say hello
child execute say hello
以上代碼中我們并沒有重寫父類的方法瞬欧,只是重載了同名方法,具體的區(qū)別是:子類的參數(shù) String 實(shí)現(xiàn)了父類的參數(shù) CharSequence窒百。此時執(zhí)行了子類方法黍判,在實(shí)際開發(fā)中,通常這不是我們希望的篙梢,父類一般是抽象類顷帖,子類才是具體的實(shí)現(xiàn)類,如果在方法調(diào)用時傳遞一個實(shí)現(xiàn)的子類可能就會產(chǎn)生非預(yù)期的結(jié)果渤滞,引起邏輯錯誤贬墩,根據(jù)里士替換原則的子類的輸入?yún)?shù)要寬于或者等于父類的輸入?yún)?shù),我們可以修改父類參數(shù)為String妄呕,子類采用更寬松的 CharSequence陶舞,如果你想讓子類的方法運(yùn)行,就必須覆寫父類的方法绪励。代碼如下:
public class ParentClazz {
public void say(String str) {
System.out.println("parent execute say " + str);
}
}
public class ChildClazz extends ParentClazz {
public void say(CharSequence str) {
System.out.println("child execute say " + str);
}
}
public class Main {
public static void main(String[] args) {
ParentClazz parent = new ParentClazz();
parent.say("hello");
ChildClazz child = new ChildClazz();
child.say("hello");
}
}
執(zhí)行結(jié)果:
parent execute say hello
parent execute say hello
代碼講解第四個概念 :
public abstract class Father {
public abstract Map hello();
}
public class Son extends Father {
@Override
public Map hello() {
HashMap map = new HashMap();
System.out.println("son execute");
return map;
}
}
public class Main {
public static void main(String[] args) {
Father father = new Son();
father.hello();
}
}
執(zhí)行結(jié)果:
son execute
4.依賴倒置原則 - Dependence Inversion Principle(DIP)
1)定義
高層模塊不應(yīng)該依賴低層模塊肿孵,二者都應(yīng)該依賴其抽象;抽象不應(yīng)該依賴細(xì)節(jié)疏魏,細(xì)節(jié)應(yīng)該依賴抽象停做。
High level modules should not depend upon low level modules,Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstracts.
2)基本概念
依賴倒置原則的核心就是要我們面向接口編程,理解了面向接口編程大莫,也就理解了依賴倒置
低層模塊盡量都要有抽象類或接口蛉腌,或者兩者都有
變量的聲明類型盡量是抽象類或接口
使用繼承時遵循里氏替換原則
設(shè)計和實(shí)現(xiàn)要依賴于抽象而非具體。一方面抽象化更符合人的思維習(xí)慣;另一方面烙丛,根據(jù)里氏替換原則舅巷,可以很容易將原來的抽象替換為擴(kuò)展后的具體,這樣可以很好的支持開-閉原則
3)優(yōu)點(diǎn)
減少類間的耦合性河咽,提高系統(tǒng)的穩(wěn)定性
降低并行開發(fā)引起的風(fēng)險
提高代碼的可讀性和可維護(hù)性
4)示例:
public class MrZhang {
public void study(ChineseCourse course) {
course.content();
}
}
public class ChineseCourse {
public void content() {
System.out.println("開始學(xué)習(xí)語文課程");
}
}
public class Main {
public static void main(String[] args) {
MrZhang zhang = new MrZhang();
zhang.study(new ChineseCourse());
}
}
執(zhí)行結(jié)果:
開始學(xué)習(xí)語文課程
執(zhí)行之后钠右,結(jié)果正常。那么库北,考慮一個問題假如此時要學(xué)習(xí)數(shù)學(xué)課程呢爬舰? 數(shù)學(xué)課程代碼如下:
public class MathCourse {
public void content() {
System.out.println("開始學(xué)習(xí)數(shù)學(xué)課程");
}
}
很顯然,MrZhang 無法學(xué)習(xí)寒瓦,因為他只能接受 ChineseCourse 情屹,學(xué)習(xí)語文課程。當(dāng)然如果我們修改接受參數(shù)為 MathCourse 的話就可以學(xué)習(xí)了杂腰,但是不能學(xué)習(xí)語文垃你,英語,化學(xué)等等喂很。造成此現(xiàn)象的具體原因是:MrZhang 和 ChineseCourse 耦合度太高了惜颇,必須降低耦合度才可以。代碼如下:
public interface ICourse {
void content();
}
public class MrZhang {
public void study(ICourse course) {
course.content();
}
}
public class ChineseCourse implements ICourse {
@Override
public void content() {
System.out.println("開始學(xué)習(xí)語文課程");
}
}
public class MathCourse implements ICourse {
@Override
public void content() {
System.out.println("開始學(xué)習(xí)數(shù)學(xué)課程");
}
}
public class Main {
public static void main(String[] args) {
MrZhang zhang = new MrZhang();
zhang.study(new ChineseCourse());
zhang.study(new MathCourse());
}
}
MrZhang 與 ICourse 具有依賴關(guān)系少辣,ChineseCourse 和 MathCourse 屬于課程范疇凌摄,并且各自實(shí)現(xiàn)了 ICourse 接口,這樣就符合了依賴倒置原則漓帅。這樣修改后無論再怎么擴(kuò)展 Main 類锨亏,都不用繼續(xù)修改 MrZhang 了,MrZhang.java 作為高層模塊就不會依賴低層模塊的修改而引起變化忙干,減少了修改程序造成的風(fēng)險器予。
5.接口隔離原則 - Interface Segration Principle(ISP)
1)定義
定義一:客戶端不應(yīng)該依賴它不需要的接口
Clients should not be forced to depend upon interfaces that they don’t use.
定義二:類間的依賴關(guān)系應(yīng)該建立在最小的接口上
The dependency of one class to another one should depend on the
smallest possible interface.
2)基本概念
一個類對另一個類的依賴應(yīng)該建立在最小的接口上,通俗的講就是需要什么就提供什么,不需要的就不要提供
接口中的方法應(yīng)該盡量少捐迫,不要使接口過于臃腫乾翔,不要有很多不相關(guān)的邏輯方法
3)優(yōu)點(diǎn)
高內(nèi)聚,低耦合
可讀性高施戴,易于維護(hù)
4)代碼示例:
public interface IAnimal {
void eat();
void talk();
void fly();
}
public class BirdAnimal implements IAnimal {
@Override
public void eat() {
System.out.println("鳥吃蟲子");
}
@Override
public void talk() {
//并不是所有的鳥都會說話
}
@Override
public void fly() {
//并不是所有的鳥都會飛
}
}
public class DogAnimal implements IAnimal {
@Override
public void eat() {
System.out.println("狗狗吃飯");
}
@Override
public void talk() {
//狗不會說話
}
@Override
public void fly() {
//狗不會飛
}
}
通過上面的代碼發(fā)現(xiàn):狗實(shí)現(xiàn)動物接口反浓,必須實(shí)現(xiàn)三個接口,根據(jù)常識我們得知赞哗,第二個和第三個接口不一定會有實(shí)際意義勾习,換句話說也就是這個方法有可能一直不會被調(diào)用。但是就是這樣我們還必須實(shí)現(xiàn)這個方法懈玻,盡管方法體可以為空,但是這就違反了接口隔離的定義。我們知道 由于Java類支持實(shí)現(xiàn)多個接口涂乌,可以很容易的讓類具有多種接口的特征艺栈,同時每個類可以選擇性地只實(shí)現(xiàn)目標(biāo)接口,基于此特點(diǎn)我們可以對功能進(jìn)一步的細(xì)化湾盒,編寫一個或多個接口湿右,代碼如下:
public interface IEat {
void eat();
}
public interface IFly {
void fly();
}
public interface ITalk {
void talk();
}
public class DogAnimal implements IEat {
@Override
public void eat() {
System.out.println("狗狗吃飯");
}
}
public class ParrotAnimal implements IEat, IFly, ITalk {
@Override
public void eat() {
System.out.println("鸚鵡吃東西");
}
@Override
public void fly() {
System.out.println("鸚鵡吃飛翔");
}
@Override
public void talk() {
System.out.println("鸚鵡說話");
}
}
說到這里,很多人會覺的接口隔離原則跟之前的單一職責(zé)原則很相似罚勾,其實(shí)不然毅人。
其一,單一職責(zé)原則原注重的是職責(zé)尖殃;而接口隔離原則注重對接口依賴的隔離丈莺。
其二,單一職責(zé)原則主要是約束類送丰,其次才是接口和方法缔俄,它針對的是程序中的實(shí)現(xiàn)和細(xì)節(jié);而接口隔離原則主要約束接口接口器躏,主要針對抽象俐载,針對程序整體框架的構(gòu)建
接口隔離原則一定要適度使用,接口設(shè)計的過大或過小都不好登失,過分的細(xì)粒度可能造成接口數(shù)量龐大不易于管理
6.迪米特法則/最少知道原則 - Law of Demeter or Least Knowledge Principle(LoD or LKP)
1)定義
一個對象應(yīng)該對其他對象保持最少的了解
這個原理的名稱來源于希臘神話中的農(nóng)業(yè)女神遏佣,孤獨(dú)的得墨忒耳。
2)基本概念
每個單元對于其他的單元只能擁有有限的知識:只是與當(dāng)前單元緊密聯(lián)系的單元揽浙;
每個單元只能和它的朋友交談:不能和陌生單元交談状婶;
只和自己直接的朋友交談。
3)優(yōu)點(diǎn)
使得軟件更好的可維護(hù)性與適應(yīng)性
對象較少依賴其它對象的內(nèi)部結(jié)構(gòu)捏萍,可以改變對象容器(container)而不用改變它的調(diào)用者(caller)
4)詳細(xì)講解:
迪米特法則通俗的來講太抓,就是一個類對自己依賴的類知道的越少越好。也就是說令杈,對于被依賴的類來說走敌,無論邏輯多么復(fù)雜,都盡量地的將邏輯封裝在類的內(nèi)部逗噩,對外除了提供的public方法掉丽,不對外泄漏任何信息。迪米特法則還有一個更簡單的定義:只與直接的朋友通信异雁。首先來解釋一下什么是直接的朋友:每個對象都會與其他對象有耦合關(guān)系捶障,只要兩個對象之間有耦合關(guān)系,我們就說這兩個對象之間是朋友關(guān)系纲刀。耦合的方式很多项炼,依賴、關(guān)聯(lián)、組合锭部、聚合等暂论。其中,我們稱出現(xiàn)成員變量拌禾、方法參數(shù)取胎、方法返回值中的類為直接的朋友,而出現(xiàn)在局部變量中的類則不是直接的朋友湃窍。也就是說闻蛀,陌生的類最好不要作為局部變量的形式出現(xiàn)在類的內(nèi)部。
代碼舉例:通過老師要求班長告知班級人數(shù)為例您市,講解迪米特法則觉痛。先來看一下違反迪米特法則的設(shè)計,代碼如下
public class Student {
private Integer id;
private String name;
public Student(Integer id, String name) {
this.id = id;
this.name = name;
}
}
public class Teacher {
public void call(Monitor monitor) {
List<Student> sts = new ArrayList<>();
for (int i = 0; i < 10; i++) {
sts.add(new Student(i + 1, "name" + i));
}
monitor.getSize(sts);
}
}
public class Monitor {
public void getSize(List list) {
System.out.println("班級人數(shù):" + list.size());
}
}
現(xiàn)在這個設(shè)計的主要問題出在 Teacher 中墨坚,根據(jù)迪米特法則秧饮,只與直接的朋友發(fā)生通信,而 Student 類并不是 Teacher 類的直接朋友(以局部變量出現(xiàn)的耦合不屬于直接朋友)泽篮,從邏輯上講 Teacher 只與 Monitor 耦合就行了盗尸,與 Student 并沒有任何聯(lián)系,這樣設(shè)計顯然是增加了不必要的耦合帽撑。按照迪米特法則泼各,應(yīng)該避免類中出現(xiàn)這樣非直接朋友關(guān)系的耦合。修改后的代碼如下:
public class Student {
private Integer id;
private String name;
public Student(Integer id, String name) {
this.id = id;
this.name = name;
}
}
public class Teacher {
public void call(Monitor monitor) {
monitor.getSize();
}
}
public class Monitor {
public void getSize() {
List<Student> sts = new ArrayList<>();
for (int i = 0; i < 10; i++) {
sts.add(new Student(i + 1, "name" + i));
}
System.out.println("班級人數(shù)" + sts.size());
}
}
將Student 從 Teacher 抽掉亏拉,也就達(dá)到了 Student 和 Teacher 的解耦扣蜻,從而符合了迪米特原則。
迪米特法則的初衷是降低類之間的耦合及塘,由于每個類都減少了不必要的依賴莽使,因此的確可以降低耦合關(guān)系。但是凡事都有度笙僚,雖然可以避免與非直接的類通信芳肌,但是要通信,必然會通過一個“中介”來發(fā)生聯(lián)系肋层,例如本例中亿笤,老師(Teacher)就是通過班長(Monitor)這個“中介”來與 學(xué)生(Student)發(fā)生聯(lián)系的。過分的使用迪米特原則栋猖,會產(chǎn)生大量這樣的中介和傳遞類净薛,導(dǎo)致系統(tǒng)復(fù)雜度變大。所以在采用迪米特法則時要反復(fù)權(quán)衡蒲拉,既做到結(jié)構(gòu)清晰肃拜,又要高內(nèi)聚低耦合痴腌。
7.合成/聚合復(fù)用原則 - Composite/Aggregate Reuse Principle(CARP / CRP)
1) 定義
盡量采用組合(contains-a)、聚合(has-a)的方式而不是繼承(is-a)的關(guān)系來達(dá)到軟件的復(fù)用目的
2)基本概念
如果新對象的某些功能在別的已經(jīng)創(chuàng)建好的對象里面已經(jīng)實(shí)現(xiàn)爆班,那么應(yīng)當(dāng)盡量使用別的對象提供的功能衷掷,使之成為新對象的一部分,而不要再重新創(chuàng)建
組合/聚合的優(yōu)缺點(diǎn):類之間的耦合比較低柿菩,一個類的變化對其他類造成的影響比較少,缺點(diǎn):類的數(shù)量增多實(shí)現(xiàn)起來比較麻煩
繼承的優(yōu)點(diǎn):由于很多方法父類已經(jīng)實(shí)現(xiàn)雨涛,子類的實(shí)現(xiàn)會相對比較簡單枢舶,缺點(diǎn):將父類暴露給了子類,一定程度上破壞了封裝性替久,父類的改變對子類影響比較大
3)優(yōu)點(diǎn)
可以降低類與類之間的耦合程度
提高了系統(tǒng)的靈活性
4)講解
public class Person {
public void talk(String name) {
System.out.println(name + " say hello");
}
public void walk(String name) {
System.out.println(name + " move");
}
}
public class Manager extends Person {
}
public class Employee extends Person {
}
按照組合復(fù)用原則我們應(yīng)該首選組合凉泄,然后才是繼承,使用繼承時應(yīng)該嚴(yán)格的遵守里氏替換原則蚯根,必須滿足“Is-A”的關(guān)系是才能使用繼承后众,而組合卻是一種“Has-A”的關(guān)系。導(dǎo)致錯誤的使用繼承而不是使用組合的一個重要原因可能就是錯誤的把“Has-A”當(dāng)成了“Is-A”颅拦。
由上沒看的代碼可以看出蒂誉,經(jīng)理和員工繼承了人,但實(shí)際中每個不同的職位擁有不同的角色距帅,如果我們添加了角色這個類右锨,那么繼續(xù)使用繼承的話只能使每個人只能具有一種角色,這顯然是不合理的碌秸。
備注:Java設(shè)計模式的橋接模式很好的體現(xiàn)了這一原則