面向?qū)ο罅笤瓌t
-
單一職責(zé)原則--SRP(Single Responsibility Principles)
-
每個(gè)類都有唯一的職責(zé)
設(shè)計(jì)一個(gè)類,首先要明白這個(gè)類要去干什么,即為這個(gè)類的職責(zé)。
發(fā)動(dòng)機(jī)是一個(gè)復(fù)雜的系統(tǒng),但是無論它多復(fù)雜,它也只有一個(gè)職責(zé):輸出動(dòng)力。
-
-
開閉原則--OCP(Open Close Principles)
-
對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉
即設(shè)計(jì)的類在滿足其職責(zé)的基礎(chǔ)上,在不修改類中代碼的情況下,能夠良好的擴(kuò)展.
單一職責(zé)原則和開閉原則一起,構(gòu)成了設(shè)計(jì)一個(gè)類的指導(dǎo)思想
-
-
里式替換原則--LSP(Liskov Substitution Principles)
-
所有的基類出現(xiàn)的地方,都能透明的被其子類替換
里式替換原則是對(duì)擴(kuò)展開放的基礎(chǔ),A依賴于B,那么A就是可以使用B的子類,來實(shí)現(xiàn)功能,從而讓A具有了擴(kuò)展性。
看下面的例子:
-
/**
* 廚師
*/
public class Chef {
private Cai cai;
?
public void cook() {
if (cai == null) {
System.out.print("我是廚師,我沒菜做,我在和Lance吹牛逼...\n");
} else {
System.out.print("我是廚師,我在做" + cai.getNameToCook()+"\n");
}
}
?
public void setCai(Cai cai){
this.cai = cai;
}
}
?
?
/**
* 菜的父類
*/
public class Cai {
public String getNameToCook() {
return "菜";
}
}
?
/**
* 辣椒炒肉
*/
public class LaJiaoChaoRou extends Cai {
public String getNameToCook() {
return "辣椒炒肉";
}
}
?
?
/**
* 西紅柿炒蛋
*/
public class XiHongShiChaoDan extends Cai {
public String getNameToCook() {
return "西紅柿炒蛋";
}
}
?
?
@Test
public void testChef(){
Chef chef = new Chef();
chef.cook();
?
Cai cai = new Cai();
chef.setCai(cai);
chef.cook();
?
XiHongShiChaoDan xiHongShiChaoDan = new XiHongShiChaoDan();
chef.setCai(xiHongShiChaoDan);
chef.cook();
?
LaJiaoChaoRou laJiaoChaoRou = new LaJiaoChaoRou();
chef.setCai(laJiaoChaoRou);
chef.cook();
}
?
//打印結(jié)果如下
我是廚師,我沒菜做,我在和Lance吹牛逼...
我是廚師,我在做菜
我是廚師,我在做西紅柿炒蛋
我是廚師,我在做辣椒炒肉
可以看出,由于里式替換原則的存在,廚師類Chef雖然依賴于Cai這個(gè)類,但是它一樣可以使用Cai的子類,做出西紅柿炒蛋,辣椒炒肉等等。
-
依賴倒置原則--DIP(Dependence Inversion Principles)
高層模塊不應(yīng)該依賴低層模塊,他們都應(yīng)該依賴其抽象
-
抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象
這是一個(gè)解耦原則,依據(jù)該原則對(duì)類進(jìn)行設(shè)計(jì),能夠使類與被其使用的其他類之間最大限度的解耦。
什么是抽象? ? 接口或者抽象類是抽象 什么是細(xì)節(jié)? ? 實(shí)現(xiàn)類是細(xì)節(jié)
在里式替換的列子中,類Chef依賴于類Cai,Chef是高層模塊,Cai是低層模塊,高層模塊直接依賴了低層模塊。這樣做有什么壞處呢?即Chef的cook()方法必須使用Cai或者Cai的子類,其他類即使有g(shù)etNameToCook()這個(gè)方法,但是也一無法提供給A使用,例如下面的類
public class CaiBaoZi{
public String getNameToCook(){
return "菜包子";
}
}
CaiBaoZi這個(gè)類雖然有g(shù)etNameOfFood()方法,但是它不是Cai的子類,無法被Chef使用 也就是說,低層模塊Cai限制了高層模塊Chef的能力,這就是耦合對(duì)一個(gè)類帶來的傷害。 下面我們對(duì)上面的例子做一下修改:
/**
* 提供菜名
*/
public interface IName{
String getNameToCook();
}
?
/**
* 廚師
*/
public class Chef {
private IName cookedObject;
?
public void cook() {
if (cookedObject == null) {
System.out.print("我是廚師,我沒菜做,我在和Lance吹牛逼...\n");
} else {
System.out.print("我是廚師,我在做" + cookedObject.getNameToCook()+"\n");
}
}
?
public void setCai(IName cookedObjec){
this.cookedObject = cookedObjec;
}
}
?
?
/**
* 菜的父類
*/
public class Cai implements IName {
public String getNameToCook() {
return "菜";
}
}
?
/**
* 辣椒炒肉
*/
public class LaJiaoChaoRou extends Cai {
public String getNameToCook() {
return "辣椒炒肉";
}
}
?
?
/**
* 西紅柿炒蛋
*/
public class XiHongShiChaoDan extends Cai {
public String getNameToCook() {
return "西紅柿炒蛋";
}
}
?
/**
* 菜包子
*/
public class CaiBaoZi implements IName{
@Override
public String getNameToCook() {
return "菜包子";
}
}
?
@Test
public void testChef(){
Chef chef = new Chef();
chef.cook();
?
Cai cai = new Cai();
chef.setCai(cai);
chef.cook();
?
XiHongShiChaoDan xiHongShiChaoDan = new XiHongShiChaoDan();
chef.setCai(xiHongShiChaoDan);
chef.cook();
?
LaJiaoChaoRou laJiaoChaoRou = new LaJiaoChaoRou();
chef.setCai(laJiaoChaoRou);
chef.cook();
?
CaiBaoZi caiBaoZi = new CaiBaoZi();
chef.setCai(caiBaoZi);
chef.cook();
}
?
//打印結(jié)果如下:
我是廚師,我沒菜做,我在和Lance吹牛逼...
我是廚師,我在做菜
我是廚師,我在做西紅柿炒蛋
我是廚師,我在做辣椒炒肉
我是廚師,我在做菜包子</pre>
通過上面的更改,Chef可以使用CaiBaoZi了,顯然CaiBaoZi不是Cai的子類,Chef的擴(kuò)展性得到更大的發(fā)展,對(duì)應(yīng)到依賴倒置原則的定義: Chef是高層模塊,是細(xì)節(jié) Cai是低層模塊,是細(xì)節(jié) CaiBaoZi也是低層模塊,是細(xì)節(jié) IName就是抽象 Chef、Cai、CaiBaoZi都依賴于IName 有了IName這個(gè)抽象,A就再也不會(huì)受到具體的對(duì)象的限制了,只要是實(shí)現(xiàn)了IName接口的類,都能夠被Chef使用,Chef具有了無限可能,可以說是可以使用任意的類來完成其cook()功能。
那有同學(xué)就說了,我有一個(gè)第三方SDK提供的類HongJiu:
public class HongJiu{
public String getNameOfDrink(){
return "紅酒";
}
}
這個(gè)類封裝在SDK中,我沒辦法去更改,怎么讓被Chef使用呢?我們這么干:
public class HongJiuForChef implements IName{
HongJiu hongJiu = new HongJiu();
?
@Override
public String getNameToCook() {
return hongJiu.getNameOfDrink();
}
}
?
@Test
public void testChef() {
Chef chef = new Chef();
chef.cook();
?
Cai cai = new Cai();
chef.setCai(cai);
chef.cook();
?
XiHongShiChaoDan xiHongShiChaoDan = new XiHongShiChaoDan();
chef.setCai(xiHongShiChaoDan);
chef.cook();
?
LaJiaoChaoRou laJiaoChaoRou = new LaJiaoChaoRou();
chef.setCai(laJiaoChaoRou);
chef.cook();
?
CaiBaoZi caiBaoZi = new CaiBaoZi();
chef.setCai(caiBaoZi);
chef.cook();
?
HongJiuForChef hongJiuForChef = new HongJiuForChef();
chef.setCai(hongJiuForChef);
chef.cook();
}
?
//打印結(jié)果
我是廚師,我沒菜做,我在和Lance吹牛逼...
我是廚師,我在做菜
我是廚師,我在做西紅柿炒蛋
我是廚師,我在做辣椒炒肉
我是廚師,我在做菜包子
我是廚師,我在做紅酒</pre>
牛逼了,我們的廚師能夠做紅酒了,這就是高層模塊和低層模塊的解耦。
那么問題來了,我怎么知道我要使用一個(gè)什么樣的抽象呢? 答案是:根據(jù)高層模塊完成功能H,需要低層模塊提供什么樣的功能L,要使用的抽象,就是這個(gè)功能L的抽象。 拿上面的例子來說,Chef要順利的完成cook()方法需要什么?需要外界提供一個(gè)名字,那么我們對(duì)這個(gè)需求進(jìn)行抽象,就抽象出來了一個(gè)接口IName,只要實(shí)現(xiàn)了這個(gè)接口的模塊,就都可以被Chef作為低層模塊使用了
-
接口隔離原則--ISP(Interface Segregation Principles)
類不應(yīng)該依賴它不需要的接口。
-
類間的依賴關(guān)系,應(yīng)該建立在最小接口上。
了解了以上四個(gè)原則之后,我們已經(jīng)能夠確定,接口即抽象對(duì)一個(gè)良好的設(shè)計(jì)的重要性。而接口隔離原則就是設(shè)計(jì)一個(gè)良好的抽象的指導(dǎo)原則。
看下面的例子:
public interface B {
void hear();
void talk();
void read();
void write();
}
?
public class A implements B{
?
@Override
public void hear() {
System.out.print("A hear");
}
?
@Override
public void talk() {
System.out.print("A talk");
}
?
@Override
public void read() {
//A 不需要這個(gè)方法
}
?
@Override
public void write() {
//A 不需要這個(gè)方法
}
}
?
public class C implements B{
?
@Override
public void hear() {
System.out.print("C hear");
}
?
@Override
public void talk() {
//C 不需要這個(gè)方法
}
?
@Override
public void read() {
System.out.print("C read");
}
?
@Override
public void write() {
System.out.print("C write");
}
}
可以看出,A被迫實(shí)現(xiàn)了它不需要的方法read、write,而C被迫實(shí)現(xiàn)它不需要的方法talk。方法就是實(shí)現(xiàn)的功能,本來A是沒有read和write的功能的,但是由于實(shí)現(xiàn)了接口B,它就多出了這兩個(gè)功能,這是和設(shè)計(jì)的初衷所不符的,也容易造成使用者的誤解,對(duì)于后期的必要的修改也會(huì)造成干擾,例如我要對(duì)A增加一項(xiàng)功能run,如果將該功能抽象到B接口中,那么C也會(huì)被迫跟著修改,這顯然不是一個(gè)良好的設(shè)計(jì)。
下面是修改后的設(shè)計(jì):
public interface B {
void hear();
}
?
public interface B2A extends B{
void talk();
void run();
}
public interface B2C extends B{
void read();
void write();
}
public class A implements B2A{
?
@Override
public void hear() {
System.out.print("A hear");
}
?
@Override
public void talk() {
System.out.print("A talk");
}
?
@Override
public void run() {
System.out.print("A run");
}
}
?
public class C implements B2C{
?
@Override
public void hear() {
System.out.print("C hear");
}
?
@Override
public void read() {
System.out.print("C read");
}
?
@Override
public void write() {
System.out.print("C write");
}
}
修改之后,將接口進(jìn)行了拆分隔離,再需要修改的時(shí)候,公共方法就在接口B中修改,其他的根據(jù)需要分別在接口B2A和接口B2C中修改,再也不會(huì)對(duì)其他的類造成不必要的影響;雖然接口的數(shù)量增加了,但是無論是對(duì)于以后代碼的維護(hù)拓展,還是對(duì)于使用者的理解,都是非常友好的。
當(dāng)然,對(duì)于最小接口的設(shè)計(jì)要注意把控好度,拆分的粒度不夠細(xì)膩,就會(huì)維護(hù)困難,拆分的太細(xì)又會(huì)大量的增加接口數(shù)量,也會(huì)造成困擾。
-
迪米特原則(最少知識(shí)原則)--LOD(Law of Demeter)
一個(gè)對(duì)象,應(yīng)該對(duì)其他的對(duì)象有最少的了解。
-
維基百科上定義如下: 得墨忒耳定律(Law of Demeter,縮寫LoD)亦稱為“最少知識(shí)原則(Principle of Least Knowledge)”,是一種軟件開發(fā)的設(shè)計(jì)指導(dǎo)原則,特別是面向?qū)ο蟮某绦蛟O(shè)計(jì)。得墨忒耳定律是松耦合的一種具體案例。該原則是美國東北大學(xué)在1987年末在發(fā)明的,可以簡單地以下面任一種方式總結(jié):
每個(gè)單元對(duì)于其他的單元只能擁有有限的知識(shí):只是與當(dāng)前單元緊密聯(lián)系的單元;
每個(gè)單元只能和它的朋友交談:不能和陌生單元交談;
只和自己直接的朋友交談。
這個(gè)原理的名稱來源于希臘神話中的農(nóng)業(yè)女神,孤獨(dú)的得墨忒耳。
很多面向?qū)ο蟪绦蛟O(shè)計(jì)語言用"."表示對(duì)象的域的解析算符,因此得墨忒耳定律可以簡單地陳述為“只使用一個(gè).算符”。因此,a.b.Method()違反了此定律,而a.Method()不違反此定律。一個(gè)簡單例子是,人可以命令一條狗行走(walk),但是不應(yīng)該直接指揮狗的腿行走,應(yīng)該由狗去指揮控制它的腿如何行走。
-
百度百科上有如下描述:
狹義的迪米特法則
如果兩個(gè)類不必彼此直接通信,那么這兩個(gè)類就不應(yīng)當(dāng)發(fā)生直接的相互作用。如果其中的一個(gè)類需要調(diào)用另一個(gè)類的某一個(gè)方法的話,可以通過第三者轉(zhuǎn)發(fā)這個(gè)調(diào)用。
朋友圈的確定
“朋友”條件:
1)當(dāng)前對(duì)象本身(this)
2)以參量形式傳入到當(dāng)前對(duì)象方法中的對(duì)象
3)當(dāng)前對(duì)象的實(shí)例變量直接引用的對(duì)象
4)當(dāng)前對(duì)象的實(shí)例變量如果是一個(gè)聚集,那么聚集中的元素也都是朋友
5)當(dāng)前對(duì)象所創(chuàng)建的對(duì)象
任何一個(gè)對(duì)象,如果滿足上面的條件之一,就是當(dāng)前對(duì)象的“朋友”;否則就是“陌生人”。
狹義的迪米特法則的缺點(diǎn):
在系統(tǒng)里造出大量的小方法,這些方法僅僅是傳遞間接的調(diào)用,與系統(tǒng)的商務(wù)邏輯無關(guān)。
遵循類之間的迪米特法則會(huì)是一個(gè)系統(tǒng)的局部設(shè)計(jì)簡化,因?yàn)槊恳粋€(gè)局部都不會(huì)和遠(yuǎn)距離的對(duì)象有直接的關(guān)聯(lián)。但是,這也會(huì)造成系統(tǒng)的不同模塊之間的通信效率降低,也會(huì)使系統(tǒng)的不同模塊之間不容易協(xié)調(diào)。
門面模式和調(diào)停者模式實(shí)際上就是迪米特法則的應(yīng)用。
廣義的迪米特法則在類的設(shè)計(jì)上的體現(xiàn):
優(yōu)先考慮將一個(gè)類設(shè)置成不變類。
盡量降低一個(gè)類的訪問權(quán)限。
謹(jǐn)慎使用Serializable。
盡量降低成員的訪問權(quán)限。