DesignPattern系列__05開閉原則

本墨發表於2019-08-05

介紹

開閉原則是程式設計設計中最基本、最重要的原則。

定義:一個軟體實體如類、方法和模組等,應該對擴充套件(提供方)開放,對修改(使用方)關閉。用抽象構建框架,用實現擴充套件細節。

也就是說,在需求發生新的變化時,我們不應該修改原來的程式碼,而應該通過擴充套件來滿足新的需求。

例子引入

我們要實現一個畫圖的功能,能夠畫出圓形、矩形、三角形等,最常見的思路就是利用物件導向的思想,抽象出一個所有圖形物件的基類Shape,具體的圖形如矩形、圓形燈繼承自該類。在Shape中定義一個變數shapeType來儲存具體的圖形的型別。
定義一個繪圖類GraphicEditor,在執行具體的繪圖方法(如畫一個矩形)時,根據傳入的shapeType來執行對應圖形的繪製方法。
類圖設計如下:
DesignPattern系列__05開閉原則

功能初步實現了,但是有什麼缺陷嗎?讓我們來給專案適當的“鬆鬆土”:現在我們想要畫一個三角形,如何實現呢?
也很簡單:再定義一個類Triangle繼承自Shape,並且在GraphicEditor修改方法,加入對三角形的型別判斷,具體的程式碼如下:

public class Ocp {
    public static void main(String[] args) {
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());
    }
}

//這是一個用於繪圖的類 [使用方]
class GraphicEditor {
    //接收Shape物件,然後根據type,來繪製不同的圖形
    public void drawShape(Shape s) {
        if (s.shapeType == 1)
            drawRectangle(s);
        else if (s.shapeType == 2)
            drawCircle(s);
        else if (s.shapeType == 3)
            drawTriangle(s);
    }

    //繪製矩形
    public void drawRectangle(Shape r) {
        System.out.println(" 繪製矩形 ");
    }

    //繪製圓形
    public void drawCircle(Shape r) {
        System.out.println(" 繪製圓形 ");
    }

    //繪製三角形
    public void drawTriangle(Shape r) {
        System.out.println(" 繪製三角形 ");
    }
}

class Shape {
    int shapeType;
}

class Rectangle extends Shape {
    public Rectangle() {
        super.shapeType = 1;
    }
}

class Circle extends Shape {
    public Circle() {
        super.shapeType = 2;
    }
}

//新增畫三角形
class Triangle extends Shape {
    Triangle() {
        super.shapeType = 3;
    }
}

OK,新的需求也實現了,現在,發現問題了嗎?
我們每次遇見新需求之外,除了定義新的圖形類,還要對類GraphicEditor進行修改。
根據前面提到的“開閉原則”中提到的,應該對修改關閉,對擴充套件開放,我們不應該修改類GraphicEditor,這樣會嚴重影響程式碼的穩定性和可維護性。
現在,我們嘗試按照“開閉原則”來實現這個功能。
根據“開閉原則”,我們應該封裝變化,在這裡,我們在Shape中定義一個抽象的繪圖方法,並在各自實現類內進行具體實現。在類GraphicEditor中,只定義一個接受引數為抽象(Shape)的方法,使得類不再去受到型別影響,滿足了“開閉原則”。
具體的程式碼如下:

public class Ocp {
    public static void main(String[] args) {
        //使用看看存在的問題
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());
    }
}

//這是一個用於繪圖的類 [使用方]
class GraphicEditor {
    //接收Shape物件,然後根據type,來繪製不同的圖形
    public void drawShape(Shape s) {
        s.draw();
    }


}

abstract class Shape {
    int shapeType;
    //定義一個抽象的畫圖方法
    public abstract void draw();
}

class Rectangle extends Shape {
    public Rectangle() {
        super.shapeType = 1;
    }

    @Override
    public void draw() {
        System.out.println("繪製矩形");
    }
}

class Circle extends Shape {
    public Circle() {
        super.shapeType = 2;
    }

    @Override
    public void draw() {
        System.out.println("繪製圓形");
    }
}

//新增畫三角形
class Triangle extends Shape {
    Triangle() {
        super.shapeType = 3;
    }

    @Override
    public void draw() {
        System.out.println("繪製三角形");
    }
}

在改進的程式碼中,我們將畫圖方法進行抽象,定義在基類Shape中,並通過子類各自實現對應的畫圖方法。並且,對於類GraphicEditor而言,只需定義一個接受基類作為引數的方法即可,程式碼變得整潔、易於維護。

使用注意事項

在實際使用中,需要注意以下幾個方面:

1.抽象約束

這點的含義包含三個意思:
1.通過介面或者抽象類約束擴充套件,對擴充套件進行邊界限定,不允許出現在介面或者抽象類中沒有定義的public方法;
2.引數型別,要儘量使用介面或者抽象類,不應該使用實現類。
3.抽象層作為約束,應該儘量保持穩定,一旦確定不容修改。

2.後設資料控制模組行為

在實際開發中,要儘量使用註解或者配置檔案來控制程式的行為,減少重複開發。比如搭建ssm框架中,使用註解或者配置檔案來注入bean。

3.約定優於配置

對於大家普遍遵循的章程或者約定,我們要嚴格遵守,這樣能減少配置檔案的編寫。比如MyBatis框架對xml檔案的掃描,預設會去和介面同名的包下去查詢,只要我們遵循這一約定, 就無需格外配置。

4.封裝變化

對變化的封裝包括兩點:
1.相同的變化,應該封裝到一個介面或者抽象類中;
2.不同的變化,應該封裝到不同的介面或者抽象類中,不應該有兩個不同的變化封裝在一個介面或者抽象類中。

一句話總結

開閉原則,是一切設計模式的基礎,可以說其他原則和設計模式都是為了實現開閉i原則。

相關文章