1.開閉原則 - Open Close Principle(OCP)
1)定義
- 一個(gè)軟件實(shí)體如類奸鬓、模塊和函數(shù)應(yīng)該對(duì)擴(kuò)展開放充坑,對(duì)修改關(guān)閉
- Software entities like classes,modules and functions should be open for extension but closed for modifications.
2)基本概念
- 開:對(duì)擴(kuò)展開放肩刃,支持方便的擴(kuò)展
- 閉:對(duì)修修改關(guān)閉,嚴(yán)格限制對(duì)已有的內(nèi)容修改
- 說(shuō)明:一個(gè)軟件產(chǎn)品只要在生命周期內(nèi),都會(huì)發(fā)生變化,即然變化是一個(gè)事實(shí)情连,我們就應(yīng)該在設(shè)計(jì)時(shí)盡量適應(yīng)這些變化,以提高項(xiàng)目的穩(wěn)定性和靈活性览效,真正實(shí)現(xiàn)“擁抱變化”却舀。開閉原則告訴我們應(yīng)盡量通過(guò)擴(kuò)展軟件實(shí)體的行為來(lái)實(shí)現(xiàn)變化虫几,而不是通過(guò)修改現(xiàn)有代碼來(lái)完成變化,它是為軟件實(shí)體的未來(lái)事件而制定的對(duì)現(xiàn)行開發(fā)設(shè)計(jì)進(jìn)行約束的一個(gè)原則
3)優(yōu)點(diǎn)
- 提高系統(tǒng)的靈活性挽拔、可復(fù)用性和可維護(hù)性
4)示例:現(xiàn)在辆脸,以課程為例說(shuō)明什么是開閉原則
/**
* 定義課程接口
*/
public interface ICourse {
String getName(); // 獲取課程名稱
Double getPrice(); // 獲取課程價(jià)格
Integer getType(); // 獲取課程類型
}
/**
* 英語(yǔ)課程接口實(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;
}
}
// 測(cè)試
public class Main {
public static void main(String[] args) {
ICourse course = new EnglishCourse("小學(xué)英語(yǔ)", 199D, "Mr.Zhang");
System.out.println(
"課程名字:"+course.getName() + " " +
"課程價(jià)格:"+course.getPrice() + " " +
"課程作者:"+course.getAuthor()
);
}
}
項(xiàng)目上線,課程正常銷售篱昔,但是我們產(chǎn)品需要做些活動(dòng)來(lái)促進(jìn)銷售每强,比如:打折。那么問(wèn)題來(lái)了:打折這一動(dòng)作就是一個(gè)變化州刽,而我們要做的就是擁抱變化,現(xiàn)在開始考慮如何解決這個(gè)問(wèn)題浪箭,可以考慮下面三種方案:
1)修改接口
- 在之前的課程接口中添加一個(gè)方法 getSalePrice() 專門用來(lái)獲取打折后的價(jià)格穗椅;
- 如果這樣修改就會(huì)產(chǎn)生兩個(gè)問(wèn)題,所以此方案否定
? (1) ICourse 接口不應(yīng)該被經(jīng)常修改奶栖,否則接口作為契約的作用就失去了
? (2) 并不是所有的課程都需要打折匹表,加入還有語(yǔ)文課,數(shù)學(xué)課等都實(shí)現(xiàn)了這一接口宣鄙,但是只有英語(yǔ)課打折袍镀,與實(shí)際業(yè)務(wù)不符
public interface ICourse {
// 獲取課程名稱
String getName();
// 獲取課程價(jià)格
Double getPrice();
// 獲取課程類型
String getAuthor();
// 新增:打折接口
Double getSalePrice();
}
2)修改實(shí)現(xiàn)類
- 在接口實(shí)現(xiàn)里直接修改 getPrice()方法,此方法會(huì)導(dǎo)致獲取原價(jià)出問(wèn)題冻晤;或添加獲取打折的接口 getSalePrice()苇羡,這樣就會(huì)導(dǎo)致獲取價(jià)格的方法存在兩個(gè),所以這個(gè)方案也否定鼻弧,此方案不貼代碼了设江。
3)通過(guò)擴(kuò)展實(shí)現(xiàn)變化
- 直接添加一個(gè)子類 SaleEnglishCourse ,重寫 getPrice()方法,這個(gè)方案對(duì)源代碼沒(méi)有影響攘轩,符合開閉原則叉存,所以是可執(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;
}
}
綜上所述度帮,如果采用第三種歼捏,即開閉原則,以后再來(lái)個(gè)語(yǔ)文課程笨篷,數(shù)學(xué)課程等等的價(jià)格變動(dòng)都可以采用此方案瞳秽,維護(hù)性極高而且也很靈活
2.單一職責(zé)原則 - Single Responsibility Principle(SRP)
1)定義
- 不要存在多于一個(gè)導(dǎo)致類變更的原因
- There should never be more than one reason for a class to change.
2)基本概念
- 單一職責(zé)是高內(nèi)聚低耦合的一個(gè)體現(xiàn)
- 說(shuō)明:通俗的講就是一個(gè)類只能負(fù)責(zé)一個(gè)職責(zé),修改一個(gè)類不能影響到別的功能,也就是說(shuō)只有一個(gè)導(dǎo)致該類被修改的原因
3)優(yōu)點(diǎn)
- 低耦合性,影響范圍小
- 降低類的復(fù)雜度冕屯,職責(zé)分明寂诱,提高了可讀性
- 變更引起的風(fēng)險(xiǎn)低,利于維護(hù)
4)示例:現(xiàn)在安聘,以動(dòng)物為例說(shuō)明什么是單一原則
假如說(shuō)痰洒,類 A 負(fù)責(zé)兩個(gè)不同的職責(zé)瓢棒,T1 和 T2,當(dāng)由于職責(zé) T1 需求發(fā)生改變而需要修改類 A 時(shí),有可能會(huì)導(dǎo)致原本運(yùn)行正常的職責(zé) T2 功能發(fā)生改變或出現(xiàn)異常丘喻。為什么會(huì)出現(xiàn)這種問(wèn)題呢脯宿?代碼耦合度太高,實(shí)現(xiàn)復(fù)雜泉粉,簡(jiǎn)單一句話就是:不夠單一连霉。那么現(xiàn)在提出解決方案:分別建立兩個(gè)類 A 和 B ,使 A 完成職責(zé) T1 功能嗡靡,B 完成職責(zé) T2 功能跺撼,這樣在修改 T1 時(shí)就不會(huì)影響 T2 了,反之亦然讨彼。
說(shuō)到單一職責(zé)原則歉井,很多人都會(huì)不屑一顧。因?yàn)樗?jiǎn)單了哈误。稍有經(jīng)驗(yàn)的程序員即使從來(lái)沒(méi)有讀過(guò)設(shè)計(jì)模式哩至、從來(lái)沒(méi)有聽(tīng)說(shuō)過(guò)單一職責(zé)原則,在設(shè)計(jì)軟件時(shí)也會(huì)自覺(jué)的遵守這一重要原則蜜自,因?yàn)檫@是常識(shí)菩貌。在軟件編程中,誰(shuí)也不希望因?yàn)樾薷牧艘粋€(gè)功能導(dǎo)致其他的功能發(fā)生故障重荠。而避免出現(xiàn)這一問(wèn)題的方法便是遵循單一職責(zé)原則箭阶。雖然單一職責(zé)原則如此簡(jiǎn)單,并且被認(rèn)為是常識(shí)晚缩,但是即便是經(jīng)驗(yàn)豐富的程序員寫出的程序尾膊,也會(huì)有違背這一原則的代碼存在。為什么會(huì)出現(xiàn)這種現(xiàn)象呢荞彼?因?yàn)橛新氊?zé)擴(kuò)散冈敛。所謂職責(zé)擴(kuò)散,就是因?yàn)槟撤N原因鸣皂,職責(zé) T 被分化為粒度更細(xì)的職責(zé) T1 和 T2
/**
* 定義動(dòng)物類
*/
public class Animal {
public void move(String animal){
System.out.println(animal + "用翅膀飛");
}
}
/**
* 第一次測(cè)試
*/
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.move("麻雀");
animal.move("老鷹");
animal.move("鯨魚");
}
}
經(jīng)過(guò)上面代碼示例發(fā)現(xiàn):麻雀和老鷹會(huì)飛是可以理解的抓谴,但是鯨魚就有點(diǎn)不合常理了,那么我們遵循單一對(duì)代碼進(jìn)行第(一)次修改,代碼如下:
/**
* 會(huì)飛的動(dòng)物
*/
public class FlyAnimal {
public void move(String animal){
System.out.println(animal + "用翅膀飛");
}
}
/**
* 在水里的動(dòng)物
*/
public class WaterAnimal {
public void move(String animal){
System.out.println(animal + "在水里游泳");
}
}
/**
* 測(cè)試
*/
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é)單一了寞缝,但是我們會(huì)發(fā)現(xiàn)如果這樣修改花銷是很大的癌压,除了將原來(lái)的類分解之外還需要修改客戶端代碼。如果我們不去遵循單一原則荆陆,而是直接在原有代碼進(jìn)行第(二)次修改滩届,代碼如下:
/**
* 直接修改源代碼
*/
public class Animal {
public void move(String animal){
if ("鯨魚".equals(animal)) {
System.out.println(animal + "在水里游泳");
} else {
System.out.println(animal + "用翅膀飛");
}
}
}
/**
* 第三次測(cè)試
*/
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.move("麻雀");
animal.move("老鷹");
animal.move("鯨魚");
}
}
直接修改源碼確實(shí)簡(jiǎn)單很多,但是卻存在巨大的隱患被啼,加入需求變了:將在水里的動(dòng)物分為淡水的和海水的帜消,那么又要繼續(xù)修改 Animal 類的 move() 方法棠枉,這也就對(duì)會(huì)飛的動(dòng)物造成了一定的風(fēng)險(xiǎn)买优,所以我們繼續(xù)摒棄單一原則對(duì)代碼進(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)]有改動(dòng)原來(lái)的方法犀被,而是在類中新加了一個(gè)方法娄猫,這樣雖然也違背了單一職責(zé)原則贱除,但在方法級(jí)別上卻是符合單一職責(zé)原則的,因?yàn)樗](méi)有動(dòng)原來(lái)方法的代碼媳溺。綜上所述月幌,這三種方式各有優(yōu)缺點(diǎn),那么在實(shí)際編程中悬蔽,采用哪一中呢飞醉?結(jié)論:只有邏輯足夠簡(jiǎn)單,才可以在代碼級(jí)別上違反單一職責(zé)原則屯阀;只有類中方法數(shù)量足夠少,才可以在方法級(jí)別上違反單一職責(zé)原則;
3.里士替換原則 - Liskov Substitution Principle(LSP)
1)定義
- 定義一:所有引用基類的地方必須能透明地使用其子類的對(duì)象轴术。
- 定義二:如果對(duì)每一個(gè)類型為 T1的對(duì)象 o1难衰,都有類型為 T2 的對(duì)象o2,使得以 T1定義的所有程序 P 在所有的對(duì)象 o1 都代換成 o2 時(shí)逗栽,程序 P 的行為沒(méi)有發(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è)計(jì)和實(shí)現(xiàn)要依賴于抽象而非具體彼宠;子類只能去擴(kuò)展基類鳄虱,而不是隱藏或者覆蓋基類它包含以下4層含義
1)子類可以實(shí)現(xiàn)父類的抽象方法,但不能覆蓋父類的非抽象方法凭峡。
2)子類中可以增加自己特有的方法拙已。
3)當(dāng)子類的方法重載父類的方法時(shí),方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松摧冀。
4)當(dāng)子類的方法實(shí)現(xiàn)父類的抽象方法時(shí)倍踪,方法的后置條件(即方法的返回值)要比父類更嚴(yán)格。
3)優(yōu)點(diǎn)
- 開閉原則的體現(xiàn)索昂,約束繼承泛濫
- 提高系統(tǒng)的健壯性建车、擴(kuò)展性和兼容性
4)示例:
代碼講解第三個(gè)概念 :
當(dāng)子類的方法重載父類的方法時(shí),方法的前置條件(即方法的形參)要比父類方法的輸入?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);
}
}
/**
* 測(cè)試
*/
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
以上代碼中我們并沒(méi)有重寫父類的方法椒惨,只是重載了同名方法缤至,具體的區(qū)別是:子類的參數(shù) String 實(shí)現(xiàn)了父類的參數(shù) CharSequence。此時(shí)執(zhí)行了子類方法康谆,在實(shí)際開發(fā)中领斥,通常這不是我們希望的嫉到,父類一般是抽象類,子類才是具體的實(shí)現(xiàn)類戒突,如果在方法調(diào)用時(shí)傳遞一個(gè)實(shí)現(xiàn)的子類可能就會(huì)產(chǎn)生非預(yù)期的結(jié)果屯碴,引起邏輯錯(cuò)誤,根據(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
代碼講解第四個(gè)概念 :
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è)計(jì)和實(shí)現(xiàn)要依賴于抽象而非具體轴合。一方面抽象化更符合人的思維習(xí)慣创坞;另一方面,根據(jù)里氏替換原則受葛,可以很容易將原來(lái)的抽象替換為擴(kuò)展后的具體题涨,這樣可以很好的支持開-閉原則
3)優(yōu)點(diǎn)
- 減少類間的耦合性,提高系統(tǒng)的穩(wěn)定性
- 降低并行開發(fā)引起的風(fēng)險(xiǎn)
- 提高代碼的可讀性和可維護(hù)性
4)示例:
public class MrZhang {
public void study(ChineseCourse course) {
course.content();
}
}
public class ChineseCourse {
public void content() {
System.out.println("開始學(xué)習(xí)語(yǔ)文課程");
}
}
public class Main {
public static void main(String[] args) {
MrZhang zhang = new MrZhang();
zhang.study(new ChineseCourse());
}
}
執(zhí)行結(jié)果:
開始學(xué)習(xí)語(yǔ)文課程
執(zhí)行之后总滩,結(jié)果正常纲堵。那么,考慮一個(gè)問(wèn)題假如此時(shí)要學(xué)習(xí)數(shù)學(xué)課程呢闰渔? 數(shù)學(xué)課程代碼如下:
public class MathCourse {
public void content() {
System.out.println("開始學(xué)習(xí)數(shù)學(xué)課程");
}
}
很顯然席函,MrZhang 無(wú)法學(xué)習(xí),因?yàn)樗荒芙邮? ChineseCourse 澜建,學(xué)習(xí)語(yǔ)文課程向挖。當(dāng)然如果我們修改接受參數(shù)為 MathCourse 的話就可以學(xué)習(xí)了,但是不能學(xué)習(xí)語(yǔ)文炕舵,英語(yǔ)何之,化學(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í)語(yǔ)文課程");
}
}
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 接口蒜危,這樣就符合了依賴倒置原則虱痕。這樣修改后無(wú)論再怎么擴(kuò)展 Main 類,都不用繼續(xù)修改 MrZhang 了辐赞,MrZhang.java 作為高層模塊就不會(huì)依賴低層模塊的修改而引起變化部翘,減少了修改程序造成的風(fēng)險(xiǎn)。
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)基本概念
- 一個(gè)類對(duì)另一個(gè)類的依賴應(yīng)該建立在最小的接口上,通俗的講就是需要什么就提供什么响委,不需要的就不要提供
- 接口中的方法應(yīng)該盡量少新思,不要使接口過(guò)于臃腫,不要有很多不相關(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() {
//并不是所有的鳥都會(huì)說(shuō)話
}
@Override
public void fly() {
//并不是所有的鳥都會(huì)飛
}
}
public class DogAnimal implements IAnimal {
@Override
public void eat() {
System.out.println("狗狗吃飯");
}
@Override
public void talk() {
//狗不會(huì)說(shuō)話
}
@Override
public void fly() {
//狗不會(huì)飛
}
}
通過(guò)上面的代碼發(fā)現(xiàn):狗實(shí)現(xiàn)動(dòng)物接口,必須實(shí)現(xiàn)三個(gè)接口邀窃,根據(jù)常識(shí)我們得知荸哟,第二個(gè)和第三個(gè)接口不一定會(huì)有實(shí)際意義,換句話說(shuō)也就是這個(gè)方法有可能一直不會(huì)被調(diào)用瞬捕。但是就是這樣我們還必須實(shí)現(xiàn)這個(gè)方法鞍历,盡管方法體可以為空,但是這就違反了接口隔離的定義肪虎。我們知道 由于Java類支持實(shí)現(xiàn)多個(gè)接口堰燎,可以很容易的讓類具有多種接口的特征,同時(shí)每個(gè)類可以選擇性地只實(shí)現(xiàn)目標(biāo)接口笋轨,基于此特點(diǎn)我們可以對(duì)功能進(jìn)一步的細(xì)化,編寫一個(gè)或多個(gè)接口赊淑,代碼如下:
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("鸚鵡說(shuō)話");
}
}
說(shuō)到這里爵政,很多人會(huì)覺(jué)的接口隔離原則跟之前的單一職責(zé)原則很相似,其實(shí)不然陶缺。
其一钾挟,單一職責(zé)原則原注重的是職責(zé);而接口隔離原則注重對(duì)接口依賴的隔離饱岸。
其二掺出,單一職責(zé)原則主要是約束類,其次才是接口和方法苫费,它針對(duì)的是程序中的實(shí)現(xiàn)和細(xì)節(jié)汤锨;而接口隔離原則主要約束接口接口,主要針對(duì)抽象百框,針對(duì)程序整體框架的構(gòu)建
接口隔離原則一定要適度使用闲礼,接口設(shè)計(jì)的過(guò)大或過(guò)小都不好,過(guò)分的細(xì)粒度可能造成接口數(shù)量龐大不易于管理
6.迪米特法則/最少知道原則 - Law of Demeter or Least Knowledge Principle(LoD or LKP)
1)定義
- 一個(gè)對(duì)象應(yīng)該對(duì)其他對(duì)象保持最少的了解
- 這個(gè)原理的名稱來(lái)源于希臘神話中的農(nóng)業(yè)女神,孤獨(dú)的得墨忒耳柬泽。
2)基本概念
- 每個(gè)單元對(duì)于其他的單元只能擁有有限的知識(shí):只是與當(dāng)前單元緊密聯(lián)系的單元慎菲;
- 每個(gè)單元只能和它的朋友交談:不能和陌生單元交談;
- 只和自己直接的朋友交談锨并。
3)優(yōu)點(diǎn)
- 使得軟件更好的可維護(hù)性與適應(yīng)性
- 對(duì)象較少依賴其它對(duì)象的內(nèi)部結(jié)構(gòu)露该,可以改變對(duì)象容器(container)而不用改變它的調(diào)用者(caller)
4)詳細(xì)講解:
迪米特法則通俗的來(lái)講,就是一個(gè)類對(duì)自己依賴的類知道的越少越好第煮。也就是說(shuō)解幼,對(duì)于被依賴的類來(lái)說(shuō),無(wú)論邏輯多么復(fù)雜空盼,都盡量地的將邏輯封裝在類的內(nèi)部书幕,對(duì)外除了提供的public方法,不對(duì)外泄漏任何信息揽趾。迪米特法則還有一個(gè)更簡(jiǎn)單的定義:只與直接的朋友通信台汇。首先來(lái)解釋一下什么是直接的朋友:每個(gè)對(duì)象都會(huì)與其他對(duì)象有耦合關(guān)系,只要兩個(gè)對(duì)象之間有耦合關(guān)系篱瞎,我們就說(shuō)這兩個(gè)對(duì)象之間是朋友關(guān)系苟呐。耦合的方式很多,依賴俐筋、關(guān)聯(lián)牵素、組合、聚合等澄者。其中笆呆,我們稱出現(xiàn)成員變量、方法參數(shù)粱挡、方法返回值中的類為直接的朋友赠幕,而出現(xiàn)在局部變量中的類則不是直接的朋友。也就是說(shuō)询筏,陌生的類最好不要作為局部變量的形式出現(xiàn)在類的內(nèi)部。
代碼舉例:通過(guò)老師要求班長(zhǎng)告知班級(jí)人數(shù)為例逆屡,講解迪米特法則踱讨。先來(lái)看一下違反迪米特法則的設(shè)計(jì)魏蔗,代碼如下
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("班級(jí)人數(shù):" + list.size());
}
}
現(xiàn)在這個(gè)設(shè)計(jì)的主要問(wèn)題出在 Teacher 中,根據(jù)迪米特法則沫勿,只與直接的朋友發(fā)生通信,而 Student 類并不是 Teacher 類的直接朋友(以局部變量出現(xiàn)的耦合不屬于直接朋友)产雹,從邏輯上講 Teacher 只與 Monitor 耦合就行了诫惭,與 Student 并沒(méi)有任何聯(lián)系,這樣設(shè)計(jì)顯然是增加了不必要的耦合蔓挖。按照迪米特法則,應(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("班級(jí)人數(shù)" + sts.size());
}
}
將Student 從 Teacher 抽掉篮撑,也就達(dá)到了 Student 和 Teacher 的解耦匆瓜,從而符合了迪米特原則。
迪米特法則的初衷是降低類之間的耦合茧妒,由于每個(gè)類都減少了不必要的依賴左冬,因此的確可以降低耦合關(guān)系拇砰。但是凡事都有度,雖然可以避免與非直接的類通信除破,但是要通信皂岔,必然會(huì)通過(guò)一個(gè)“中介”來(lái)發(fā)生聯(lián)系展姐,例如本例中,老師(Teacher)就是通過(guò)班長(zhǎng)(Monitor)這個(gè)“中介”來(lái)與 學(xué)生(Student)發(fā)生聯(lián)系的教馆。過(guò)分的使用迪米特原則土铺,會(huì)產(chǎn)生大量這樣的中介和傳遞類,導(dǎo)致系統(tǒng)復(fù)雜度變大究恤。所以在采用迪米特法則時(shí)要反復(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)系來(lái)達(dá)到軟件的復(fù)用目的
2)基本概念
- 如果新對(duì)象的某些功能在別的已經(jīng)創(chuàng)建好的對(duì)象里面已經(jīng)實(shí)現(xiàn)落蝙,那么應(yīng)當(dāng)盡量使用別的對(duì)象提供的功能,使之成為新對(duì)象的一部分赚瘦,而不要再重新創(chuàng)建
組合/聚合的優(yōu)缺點(diǎn):類之間的耦合比較低奏寨,一個(gè)類的變化對(duì)其他類造成的影響比較少病瞳,缺點(diǎn):類的數(shù)量增多實(shí)現(xiàn)起來(lái)比較麻煩
繼承的優(yōu)點(diǎn):由于很多方法父類已經(jīng)實(shí)現(xiàn),子類的實(shí)現(xiàn)會(huì)相對(duì)比較簡(jiǎn)單亲善,缺點(diǎn):將父類暴露給了子類逗柴,一定程度上破壞了封裝性戏溺,父類的改變對(duì)子類影響比較大
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)該首選組合旷祸,然后才是繼承,使用繼承時(shí)應(yīng)該嚴(yán)格的遵守里氏替換原則骚烧,必須滿足“Is-A”的關(guān)系是才能使用繼承,而組合卻是一種“Has-A”的關(guān)系既峡。導(dǎo)致錯(cuò)誤的使用繼承而不是使用組合的一個(gè)重要原因可能就是錯(cuò)誤的把“Has-A”當(dāng)成了“Is-A”凭戴。
由上沒(méi)看的代碼可以看出,經(jīng)理和員工繼承了人者冤,但實(shí)際中每個(gè)不同的職位擁有不同的角色涉枫,如果我們添加了角色這個(gè)類腐螟,那么繼續(xù)使用繼承的話只能使每個(gè)人只能具有一種角色,這顯然是不合理的衬廷。
備注:Java設(shè)計(jì)模式的橋接模式很好的體現(xiàn)了這一原則