介紹 開閉原則是編程設計中最基本、最重要的原則。 定義:一個軟體實體如類、方法和模塊等,應該對擴展(提供方)開放,對修改(使用方)關閉。用抽象構建框架,用實現擴展細節。 也就是說,在需求發生新的變化時,我們不應該修改原來的代碼,而應該通過擴展來滿足新的需求。 例子引入 我們要實現一個畫圖的功能,能夠 ...
介紹
開閉原則是編程設計中最基本、最重要的原則。
定義:一個軟體實體如類、方法和模塊等,應該對擴展(提供方)開放,對修改(使用方)關閉。用抽象構建框架,用實現擴展細節。
也就是說,在需求發生新的變化時,我們不應該修改原來的代碼,而應該通過擴展來滿足新的需求。
例子引入
我們要實現一個畫圖的功能,能夠畫出圓形、矩形、三角形等,最常見的思路就是利用面向對象的思想,抽象出一個所有圖形對象的基類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原則。