DesignPattern系列__05開閉原則
介紹
開閉原則是編程設(shè)計中最基本、最重要的原則。
定義:一個軟件實體如類、方法和模塊等,應(yīng)該對擴展(提供方)開放,對修改(使用方)關(guān)閉。用抽象構(gòu)建框架,用實現(xiàn)擴展細節(jié)。
也就是說,在需求發(fā)生新的變化時,我們不應(yīng)該修改原來的代碼,而應(yīng)該通過擴展來滿足新的需求。
例子引入
我們要實現(xiàn)一個畫圖的功能,能夠畫出圓形、矩形、三角形等,最常見的思路就是利用面向?qū)ο蟮乃枷耄橄蟪鲆粋€所有圖形對象的基類Shape,具體的圖形如矩形、圓形燈繼承自該類。在Shape中定義一個變量shapeType來保存具體的圖形的類型。
定義一個繪圖類GraphicEditor,在執(zhí)行具體的繪圖方法(如畫一個矩形)時,根據(jù)傳入的shapeType來執(zhí)行對應(yīng)圖形的繪制方法。
類圖設(shè)計如下:

功能初步實現(xiàn)了,但是有什么缺陷嗎?讓我們來給項目適當?shù)摹八伤赏痢保含F(xiàn)在我們想要畫一個三角形,如何實現(xiàn)呢?
也很簡單:再定義一個類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對象,然后根據(jù)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,新的需求也實現(xiàn)了,現(xiàn)在,發(fā)現(xiàn)問題了嗎?
我們每次遇見新需求之外,除了定義新的圖形類,還要對類GraphicEditor進行修改。
根據(jù)前面提到的“開閉原則”中提到的,應(yīng)該對修改關(guān)閉,對擴展開放,我們不應(yīng)該修改類GraphicEditor,這樣會嚴重影響代碼的穩(wěn)定性和可維護性。
現(xiàn)在,我們嘗試按照“開閉原則”來實現(xiàn)這個功能。
根據(jù)“開閉原則”,我們應(yīng)該封裝變化,在這里,我們在Shape中定義一個抽象的繪圖方法,并在各自實現(xiàn)類內(nèi)進行具體實現(xiàn)。在類GraphicEditor中,只定義一個接受參數(shù)為抽象(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對象,然后根據(jù)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中,并通過子類各自實現(xiàn)對應(yīng)的畫圖方法。并且,對于類GraphicEditor而言,只需定義一個接受基類作為參數(shù)的方法即可,代碼變得整潔、易于維護。
使用注意事項
在實際使用中,需要注意以下幾個方面:
1.抽象約束
這點的含義包含三個意思:
1.通過接口或者抽象類約束擴展,對擴展進行邊界限定,不允許出現(xiàn)在接口或者抽象類中沒有定義的public方法;
2.參數(shù)類型,要盡量使用接口或者抽象類,不應(yīng)該使用實現(xiàn)類。
3.抽象層作為約束,應(yīng)該盡量保持穩(wěn)定,一旦確定不容修改。
2.元數(shù)據(jù)控制模塊行為
在實際開發(fā)中,要盡量使用注解或者配置文件來控制程序的行為,減少重復(fù)開發(fā)。比如搭建ssm框架中,使用注解或者配置文件來注入bean。
3.約定優(yōu)于配置
對于大家普遍遵循的章程或者約定,我們要嚴格遵守,這樣能減少配置文件的編寫。比如MyBatis框架對xml文件的掃描,默認會去和接口同名的包下去查找,只要我們遵循這一約定, 就無需格外配置。
4.封裝變化
對變化的封裝包括兩點:
1.相同的變化,應(yīng)該封裝到一個接口或者抽象類中;
2.不同的變化,應(yīng)該封裝到不同的接口或者抽象類中,不應(yīng)該有兩個不同的變化封裝在一個接口或者抽象類中。
一句話總結(jié)
開閉原則,是一切設(shè)計模式的基礎(chǔ),可以說其他原則和設(shè)計模式都是為了實現(xiàn)開閉i原則。

浙公網(wǎng)安備 33010602011771號