面向?qū)ο蟮牧笤瓌t

面向?qū)ο罅笤瓌t
  1. 單一職責(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)力。

  2. 開閉原則--OCP(Open Close Principles)

    • 對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉

      即設(shè)計(jì)的類在滿足其職責(zé)的基礎(chǔ)上,在不修改類中代碼的情況下,能夠良好的擴(kuò)展.

      單一職責(zé)原則和開閉原則一起,構(gòu)成了設(shè)計(jì)一個(gè)類的指導(dǎo)思想

  3. 里式替換原則--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的子類,做出西紅柿炒蛋,辣椒炒肉等等。

  1. 依賴倒置原則--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作為低層模塊使用了

  1. 接口隔離原則--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ì)造成困擾。

  1. 迪米特原則(最少知識(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é):

      1. 每個(gè)單元對(duì)于其他的單元只能擁有有限的知識(shí):只是與當(dāng)前單元緊密聯(lián)系的單元;

      2. 每個(gè)單元只能和它的朋友交談:不能和陌生單元交談;

      3. 只和自己直接的朋友交談。

      這個(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)限。

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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