DesignPattern系列__05開閉原則
介紹
開閉原則是程式設計設計中最基本、最重要的原則。
定義:一個軟體實體如類、方法和模組等,應該對擴充套件(提供方)開放,對修改(使用方)關閉。用抽象構建框架,用實現擴充套件細節。
也就是說,在需求發生新的變化時,我們不應該修改原來的程式碼,而應該通過擴充套件來滿足新的需求。
例子引入
我們要實現一個畫圖的功能,能夠畫出圓形、矩形、三角形等,最常見的思路就是利用面向物件的思想,抽象出一個所有圖形物件的基類Shape,具體的圖形如矩形、圓形燈繼承自該類。在Shape中定義一個變數shapeType來儲存具體的圖形的型別。 定義一個繪圖類GraphicEditor,在執行具體的繪圖方法(如畫一個矩形)時,根據傳入的shapeType來執行對應圖形的繪製方法。 類圖設計如下:
功能初步實現了,但是有什麼缺陷嗎?讓我們來給專案適當的“鬆鬆土”:現在我們想要畫一個三角形,如何實現呢? 也很簡單:再定義一個類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原則。