面向對象的7大設計原則 之舉例

一、面向對象的7大設計原則

  • 開閉原則
    • 對擴展開放,對更改關閉
    • 類模塊應該是可擴展的,但是不可修改。
  • 里氏代換原則
    • 子類必須能夠替換它們的基類(IS-A)
    • 繼承表達類型抽象
  • 迪米特原則
    • 要求一個對象應該對其他對象有最少的瞭解,所以迪米特法則又叫做最少知識原則(Least Knowledge Principle, LKP)
  • 單一職責原則
    • 一個類應該僅有一個引起它變化的原因
    • 變化的方向隱含着類的責任
  • 接口分離原則
    • 不應該強迫客戶程序依賴它們不用的方法
    • 接口應該小而完備
  • 依賴倒置原則
    • 高層模塊(穩定)不應該依賴於低層模塊(變化),二者都應該依賴於抽象(穩定)
    • 抽象(穩定)不應該依賴於實現細節(變化),實現細節應該依賴於抽象(穩定)
  • 組合/聚合複用原則
    • 儘量使用組合/聚合,不要使用類繼承
    • 在一個新的對象裏面使用一些已有的對象,使之成爲新對象的一部分,新對象通過向這些對象的委派達到複用已有功能的目的。就是說要儘量的使用合成和聚合,而不是繼承關係達到複用的目的

二、一句話理解七大原則

  • 開閉原則
/**
* 定義課程接口
*/
public interface ICourse {
    String getName();  // 獲取課程名稱
    Double getPrice(); // 獲取課程價格
    Integer getType(); // 獲取課程類型
}

/**
 * 英語課程接口實現
 */
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("小學英語", 199D, "Mr.Zhang");
        System.out.println(
                "課程名字:"+course.getName() + " " +
                "課程價格:"+course.getPrice() + " " +
                "課程作者:"+course.getAuthor()
        );
    }
}

如果課程要打折,有一種方法是往接口中添加方法;

public interface ICourse {

    // 獲取課程名稱
    String getName();

    // 獲取課程價格
    Double getPrice();

    // 獲取課程類型
    String getAuthor();

    // 新增:打折接口
    Double getSalePrice();
}

這就違反了開閉原則,會導致所有的其他課程都需要實現打折接口,更好的方法是用擴展代替修改;

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;
    }
}
  • 里氏代換原則
    里氏替換原則強調的是設計和實現要依賴於抽象而非具體;子類只能去擴展基類,而不是隱藏或者覆蓋基類,它包含以下4層含義
    • 子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法
    • 子類可以有自己的個性
    • 覆蓋或實現父類的方法時輸入參數可以被放大
    • 覆寫或實現父類的方法時輸出結果可以被縮小
  • 迪米特原則
    通過老師要求班長告知班級人數爲例,講解迪米特原則。先來看一下違反迪米特法則的設計,代碼如下
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("班級人數:" + list.size());
    }
}

從邏輯上講 Teacher 只與 Monitor 耦合就行了,與 Student 並沒有任何聯繫,這樣設計顯然是增加了不必要的耦合。按照迪米特原則,應該避免類中出現這樣非直接朋友關係的耦合。

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("班級人數" + sts.size());
    }
}
  • 單一職責原則
    用一個類描述動物呼吸這個場景:
class Animal {
    func breathe(animal: String) {
        print("\(animal)呼吸空氣")
    }
}

let animal = Animal()
animal.breathe(animal: "牛")
animal.breathe(animal: "羊")
animal.breathe(animal: "豬")

程序上線後,發現問題了,並不是所有的動物都呼吸空氣的,比如魚就是呼吸水的。修改時如果遵循單一職責原則,需要將 Animal 類細分爲陸生動物類 Terrestrial,水生動物 Aquatic,代碼如下:

class Terrestrial {
    func breathe(animal: String) {
        print("\(animal)呼吸空氣")
    }
}

class Aquatic {
    func breathe(animal: String) {
        print("\(animal)呼吸水")
    }
}

let terrestrial = Terrestrial()
terrestrial.breathe(animal: "牛")
terrestrial.breathe(animal: "羊")
terrestrial.breathe(animal: "豬")

let aquatic = Aquatic()
aquatic.breathe(animal: "魚")

違反單一職責的修改:

class Animal {
    func breathe(animal: String) {
        if (animal == "魚") {
            print("\(animal)呼吸水")
        } else {
            print("\(animal)呼吸空氣")
        }
    }
}

let animal = Animal()
animal.breathe(animal: "牛")
animal.breathe(animal: "羊")
animal.breathe(animal: "豬")
animal.breathe(animal: "魚")

// 這種修改方式要簡單的多。但是卻存在着隱患:有一天需要將魚分爲呼吸淡水的魚和呼吸海水的魚,則又需要修改Animal類的breathe方法,而對原有代碼的修改會對調用 “豬” “牛” “羊” 等相關功能帶來風險,也許某一天你會發現程序運行的結果變爲“牛呼吸水”了。
  • 接口分離原則
    • 接口中的方法應該儘量少,不要使接口過於臃腫,不要有很多不相關的邏輯方法。
  • 依賴倒置原則
    場景是這樣的,母親給孩子講故事,只要給她一本書,她就可以照着書給孩子講故事了。代碼如下:
class Book {
    func getContent() -> String{
        return "很久很久以前有一個阿拉伯的故事……"
    }
}

class Mother {
    func narrate(book: Book) {
        print("媽媽開始講故事");
        print("\(book.getContent())")
    }
}

let mother = Mother()
mother.narrate(book: Book())

運行良好,假如有一天,需求變成這樣:不是給書而是給一份報紙,讓這位母親講一下報紙上的故事,報紙的代碼如下:

class NewsPaper {
    func getContent() -> String {
        return "70 週年慶..."
    }
}

只是將書換成報紙,居然必須要修改Mother才能讀。假如以後需求換成雜誌呢?換成網頁呢?還要不斷地修改Mother,這顯然不是好的設計。原因就是Mother與Book之間的耦合性太高了,必須降低他們之間的耦合度纔行。我們引入一個抽象的接口IReader。讀物,只要是帶字的都屬於讀物:

protocol IReader {
    func getContent() -> String
}

class Book: IReader {

    func getContent() -> String{
        return "很久很久以前有一個阿拉伯的故事……"
    }
}

class NewsPaper: IReader{
    func getContent() -> String {
        return "70 週年慶..."
    }
}

class Mother{
    func narrate(reader: IReader) {
        print("媽媽開始講故事");
        print("\(reader.getContent())")
    }
}

let mother = Mother()
mother.narrate(reader: Book())
mother.narrate(reader: NewsPaper())

這樣修改後,無論以後怎樣擴展,都不需要再修改 Mother 類了。

  • 組合/聚合複用原則
    • 類之間有三種基本關係,分別是:關聯(聚合和組合)、泛化(與繼承同一概念)、依賴。
      • 聚合,用來表示整體與部分的關係或者 “擁有” 關係。其中,代表部分的對象可能會被代表多個整體的對象所擁有,但是並不一定會隨着整體對象的銷燬而銷燬,部分的生命週期可能會超越整體。好比班級和學生,班級銷燬或解散後學生還是存在的,學生可以繼續存在某個培訓機構或步入社會,生命週期不同於班級甚至大於班級。
      • 合成,用來表示一種強得多的 “擁有” 關係。其中,部分和整體的生命週期是一致的,一個合成的新的對象完全擁有對其組成部分的支配權,包括創建和泯滅。好比人的各個器官組成人一樣,一旦某個器官衰竭,人也不復存在,這是一種 “強” 關聯。

三、溫故而知新

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章