【設計模式Android】設計模式六大原則

有幾天沒發文章了,一直有人在公眾號問我關于觀察者模式的問題,所以我決定抽時間寫一寫關于設計模式的內容。今天先介紹一些基礎的東西。


六大原則

我以前在面試別的人的時候,總是喜歡聊聊設計模式,因為總感覺功能部分都能寫出來,但是代碼質量和代碼設計的東西熟練,才能更好地跟團隊配合,方便產品的迭代。
六大原則是:

  • 單一職責原則
  • 里氏替換原則
  • 依賴倒置原則
  • 接口隔離原則
  • 迪米特原則
  • 開閉原則

也有人說是五大原則的,少了迪米特原則。乍一看,其中開發者們最熟悉的或者說聽得最多的也就是開閉原則了,其它聽起來都會有一些陌生。下面就一個個介紹一下。

單一職責原則

這是一個最簡單,卻最難做到的原則。為什么這么說呢?
它的定義只有一句話:不要存在多于一個導致類變更的原因。通俗的說,即一個類只負責一項職責。但為什么說很難做到呢,我剛才想去我的代碼中找到一個比較合適的例子來說明這個問題,卻沒有一個具有代表性的,因為職責這個概念有些主觀,接口根據職責劃分。下面想個簡單的例子吧。
例如一個用戶系統,有姓名和年齡兩個屬性:

public class Person {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
    public void changeAge(String age) {

    }
    public void changeName(String age) {

    }
}

這個類建立有什么問題呢?name和age的set,get方法是數據類型,也就是業務對象,但是changeAge和changeName需要跟服務器進行交互,屬于業務邏輯,甚至于在這兩個方法中還需要調用set,get方法。
根據單一職責原則,我們需要將業務和數據分開:

public class PersonObj {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}
public class PersonLogic {
    public void changeAge(String age) {

    }
    public void changeName(String age) {

    }
}

單一原則的好處:

  • 可以降低類的復雜度,一個類只負責一項職責,其邏輯肯定要比負責多項職責簡單的多;
  • 提高類的可讀性,提高系統的可維護性;
  • 變更引起的風險降低,變更是必然的,如果單一職責原則遵守的好,當修改一個功能時,可以顯著降低對其他功能的影響。

里氏替換原則

定義1:如果對每一個類型為 T1的對象 o1,都有類型為 T2 的對象o2,使得以 T1定義的所有程序 P 在所有的對象 o1 都代換成 o2 時,程序 P 的行為沒有發生變化,那么類型 T2 是類型 T1 的子類型。
定義2:所有引用基類的地方必須能透明地使用其子類的對象。

根據定義我們可以這樣理解:

  • 子類必須完全實現父類的方法
  • 子類可以有自己的個性
  • 覆寫或實現父類的方法時輸入參數可以寬于或等于父類參數
  • 覆寫或實現父類的方法時輸出結果可以窄于或等于父類參數

第一點好理解,后面是什么意思呢?看個例子:

public class Father {
    public void printf(HashMap map){
        System.out.printf("父類方法");
    }
  
}
public class Son {
    public void printf(Map map){
        System.out.printf("父類方法");
    }
}

這樣只要傳入的參數是HashMap都是執行父類的方法,子類由于比父類參數要寬,相當于重載了printf方法。這樣做的目的不容易引起邏輯的混亂

依賴導致原則

定義:高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象。

依賴倒置原則的核心思想是面向接口編程,我們依舊用一個例子來說明面向接口編程比相對于面向實現編程好在什么地方。場景是這樣的,父親給孩子講故事,需要給他一本書,或一份報紙:

class Book{  
    public String getContent(){  
        return "讀書";  
    }  
}  
 class NewsPaper{  
    public String getContent(){  
        return "報紙";  
    }  
}  
class Father{  
    public void read(Book book){  
        System.out.println("爸爸"+book.getContent());  
    }  
public void read(NewsPaper news){  
        System.out.println("爸爸"+news.getContent());  
    }  
}  
  
public class Client{  
    public static void main(String[] args){  
        Father f = new Father();  
        f.read(new Book()); 
         f.read(new NewsPaper());   
    }  
}  

上述代碼沒有什么問題,但是有一天我們再新增一個可讀的東西,如pad,那我們要重新寫一個pad類,同時Father類還要新增一個read方法。如果哪天再增加一個類,還有做如上處理,太麻煩了。
我們可以這樣優化一下:

interface IReader{  
    public String getContent();  
}  

然后讓Book Newspaper Pad類都實現這個接口,這樣父類只需要寫一個方法即可:

class Father{  
    public void read(IReader reader){  
        System.out.println("爸爸"+reader.getContent());  
    }  

}  

接口隔離原則

定義:客戶端不應該依賴它不需要的接口;一個類對另一個類的依賴應該建立在最小的接口上。
簡單翻譯一下這句話,就是一個類去實現接口的時候,不應該去實現他不需要的方法。

interface I {  
    public void method1();  
    public void method2();  
    public void method3();  
    public void method4();  
    public void method5();  
}  
  
class A{  
    public void depend1(I i){  
        i.method1();  
    }  
    public void depend2(I i){  
        i.method2();  
    }  
    public void depend3(I i){  
        i.method3();  
    }  
}  
  
class B implements I{  
    public void method1() {  
        System.out.println("類B實現接口I的方法1");  
    }  
    public void method2() {  
        System.out.println("類B實現接口I的方法2");  
    }  
    public void method3() {  
        System.out.println("類B實現接口I的方法3");  
    }  
    public void method4() {}  
    public void method5() {}  
}  
  
class C{  
    public void depend1(I i){  
        i.method1();  
    }  
    public void depend2(I i){  
        i.method4();  
    }  
    public void depend3(I i){  
        i.method5();  
    }  
}  
  
class D implements I{  
    public void method1() {  
        System.out.println("類D實現接口I的方法1");  
    }  
    //對于類D來說,method2和method3不是必需的,但是由于接口A中有這兩個方法,  
    //所以在實現過程中即使這兩個方法的方法體為空,也要將這兩個沒有作用的方法進行實現。  
    public void method2() {}  
    public void method3() {}  
  
    public void method4() {  
        System.out.println("類D實現接口I的方法4");  
    }  
    public void method5() {  
        System.out.println("類D實現接口I的方法5");  
    }  
}  
  
public class Client{  
    public static void main(String[] args){  
        A a = new A();  
        a.depend1(new B());  
        a.depend2(new B());  
        a.depend3(new B());  
          
        C c = new C();  
        c.depend1(new D());  
        c.depend2(new D());  
        c.depend3(new D());  
    }  
}  

可以看到,如果接口過于臃腫,只要接口中出現的方法,不管對依賴于它的類有沒有用處,實現類中都必須去實現這些方法,這顯然不是好的設計。如果將這個設計修改為符合接口隔離原則,就必須對接口I進行拆分。在這里我們將原有的接口I拆分為三個接口。

nterface I1 {  
    public void method1();  
}  
  
interface I2 {  
    public void method2();  
    public void method3();  
}  
  
interface I3 {  
    public void method4();  
    public void method5();  
}  
  
class A{  
    public void depend1(I1 i){  
        i.method1();  
    }  
    public void depend2(I2 i){  
        i.method2();  
    }  
    public void depend3(I2 i){  
        i.method3();  
    }  
}  
  
class B implements I1, I2{  
    public void method1() {  
        System.out.println("類B實現接口I1的方法1");  
    }  
    public void method2() {  
        System.out.println("類B實現接口I2的方法2");  
    }  
    public void method3() {  
        System.out.println("類B實現接口I2的方法3");  
    }  
}  
  
class C{  
    public void depend1(I1 i){  
        i.method1();  
    }  
    public void depend2(I3 i){  
        i.method4();  
    }  
    public void depend3(I3 i){  
        i.method5();  
    }  
}  
  
class D implements I1, I3{  
    public void method1() {  
        System.out.println("類D實現接口I1的方法1");  
    }  
    public void method4() {  
        System.out.println("類D實現接口I3的方法4");  
    }  
    public void method5() {  
        System.out.println("類D實現接口I3的方法5");  
    }  
} 

接口隔離原則的含義是:建立單一接口,不要建立龐大臃腫的接口,盡量細化接口,接口中的方法盡量少。也就是說,我們要為各個類建立專用的接口,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調用。本

迪米特法則

定義:一個對象應該對其他對象保持最少的了解。

說白點就是盡量降低耦合。
我在網上找到了這樣一個簡單例子,可以看一下,做個對比:
有一個集團公司,下屬單位有分公司和直屬部門,現在要求打印出所有下屬單位的員工ID。先來看一下違反迪米特法則的設計。

//總公司員工  
class Employee{  
    private String id;  
    public void setId(String id){  
        this.id = id;  
    }  
    public String getId(){  
        return id;  
    }  
}  
  
//分公司員工  
class SubEmployee{  
    private String id;  
    public void setId(String id){  
        this.id = id;  
    }  
    public String getId(){  
        return id;  
    }  
}  
  
class SubCompanyManager{  
    public List<SubEmployee> getAllEmployee(){  
        List<SubEmployee> list = new ArrayList<SubEmployee>();  
        for(int i=0; i<100; i++){  
            SubEmployee emp = new SubEmployee();  
            //為分公司人員按順序分配一個ID  
            emp.setId("分公司"+i);  
            list.add(emp);  
        }  
        return list;  
    }  
}  
  
class CompanyManager{  
  
    public List<Employee> getAllEmployee(){  
        List<Employee> list = new ArrayList<Employee>();  
        for(int i=0; i<30; i++){  
            Employee emp = new Employee();  
            //為總公司人員按順序分配一個ID  
            emp.setId("總公司"+i);  
            list.add(emp);  
        }  
        return list;  
    }  
      
    public void printAllEmployee(SubCompanyManager sub){  
        List<SubEmployee> list1 = sub.getAllEmployee();  
        for(SubEmployee e:list1){  
            System.out.println(e.getId());  
        }  
  
        List<Employee> list2 = this.getAllEmployee();  
        for(Employee e:list2){  
            System.out.println(e.getId());  
        }  
    }  
}  
  
public class Client{  
    public static void main(String[] args){  
        CompanyManager e = new CompanyManager();  
        e.printAllEmployee(new SubCompanyManager());  
    }  
}  
    現在這個設計的主要問題出在CompanyManager中,根據迪米特法則,只與直接的朋友發生通信,而SubEmployee類并不是CompanyManager類的直接朋友(以局部變量出現的耦合不屬于直接朋友),從邏輯上講總公司只與他的分公司耦合就行了,與分公司的員工并沒有任何聯系,這樣設計顯然是增加了不必要的耦合。按照迪米特法則,應該避免類中出現這樣非直接朋友關系的耦合。修改后的代碼如下:
class SubCompanyManager{  
    public List<SubEmployee> getAllEmployee(){  
        List<SubEmployee> list = new ArrayList<SubEmployee>();  
        for(int i=0; i<100; i++){  
            SubEmployee emp = new SubEmployee();  
            //為分公司人員按順序分配一個ID  
            emp.setId("分公司"+i);  
            list.add(emp);  
        }  
        return list;  
    }  
    public void printEmployee(){  
        List<SubEmployee> list = this.getAllEmployee();  
        for(SubEmployee e:list){  
            System.out.println(e.getId());  
        }  
    }  
}  
  
class CompanyManager{  
    public List<Employee> getAllEmployee(){  
        List<Employee> list = new ArrayList<Employee>();  
        for(int i=0; i<30; i++){  
            Employee emp = new Employee();  
            //為總公司人員按順序分配一個ID  
            emp.setId("總公司"+i);  
            list.add(emp);  
        }  
        return list;  
    }  
      
    public void printAllEmployee(SubCompanyManager sub){  
        sub.printEmployee();  
        List<Employee> list2 = this.getAllEmployee();  
        for(Employee e:list2){  
            System.out.println(e.getId());  
        }  
    }  
}  

修改后,為分公司增加了打印人員ID的方法,總公司直接調用來打印,從而避免了與分公司的員工發生耦合。

開閉原則

這應該是我們聽得最多的一個原則了,在平時的項目開發中,用的也最多,因為誰也不想,一次產品的迭代,還需要修改核心代碼,然后全部重新測試一次。

定義:一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。

這個定義沒有那么復雜難理解,我不再做過多的解釋,我這里還是通過一個小例子來說明:

public interface ICar {
    public String getName();
    public float getPrice();

}
public class Car implements ICar{

    private String name;
    private float price;
    public Car(String name,float price){
        this.name = name;
        this.price = price;
    }
    @Override
    public String getName() {
        return name;
    }

    @Override
    public float getPrice() {
        return price;
    }
}

當有一天我們獲取車的價格需要打折時,可以重新寫一個類SaleCar:

public class SaleCar extends Car{
    public SaleCar(String name, float price) {
        super(name, price);
    }

    @Override
    public float getPrice() {
        return super.getPrice()*8/10;
    }
}


我們這樣做的目的是當有新功能出現的時候,盡量不要去修改原有的邏輯,可以實現一個新的類,然后覆寫父類的方法,這樣,原有的邏輯沒有變,新的需求也實現了。當有一天出現bug了,可以直接修改這一個類就可以。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容

  • 設計模式六大原則 設計模式六大原則(1):單一職責原則 定義:不要存在多于一個導致類變更的原因。通俗的說,即一個類...
    viva158閱讀 780評論 0 1
  • 轉載標注聲明:http://www.uml.org.cn/sjms/201211023.asp 目錄:[設計模式六...
    Bloo_m閱讀 733評論 0 7
  • 設計模式六大原則(1):單一職責原則 定義:不要存在多于一個導致類變更的原因。通俗的說,即一個類只負責一項職責。 ...
    Jabir_Zhang閱讀 650評論 0 3
  • 單一職責原則 (SRP) 全稱 SRP , Single Responsibility Principle 單一職...
    米莉_L閱讀 1,784評論 2 5
  • 設計模式之六大原則(轉載) 關于設計模式的六大設計原則的資料網上很多...
    霄霄霄霄閱讀 910評論 0 1