開閉原則的定義
開閉原則定義如下:
軟件中的對象(類,模塊,函數(shù)等等)應(yīng)該對于擴展是開放的,但是對于修改是封閉的
如何理解開閉原則
這個定義不能分開來理解,“對擴展開放,對修改封閉”,表達的是同一個含義,即“可以方便的通過添加類/模塊/函數(shù)就能夠擴展現(xiàn)有類/模塊/函數(shù)的功能,而不需要修改現(xiàn)有的代碼”
這個的含義十分抽象,它并沒有直接給開閉原則下定義,而是表述了一個符合開閉原則的設(shè)計應(yīng)該具備的特性。首先讓我們通過一個例子來看看怎樣的實現(xiàn)才是遵循開閉原則的。
考慮我們需要實現(xiàn)一個負責(zé)打印log的類,它支持多種不同風(fēng)格的log樣式,第一種實現(xiàn)如下:
public class Logger {
private int mFormat;
public void setFormat(int format) {
this.mFormat = format;
}
public void print(String log) {
System.out.println(format(log));
}
private String format(String log) {
switch (mFormat) {
case SHORT:
return formatShort(log);
case NORMAL:
return formatNormal(log);
case BEAUTY:
default:
return formatBeauty(log);
}
}
...
}
顯然,這個類是不符合開閉原則的,假設(shè)現(xiàn)在我們需要給Logger添加一種可以打印帶日期的log的能力,那么除了修改Loger.format函數(shù)之外,就沒有其他更好的方法了。
下面是第二種實現(xiàn)
public class Logger {
public interface Formatter {
String format(String log);
}
private Formatter mFormatter;
public void setFormat(Formatter formatter) {
this.mFormatter = formatter;
}
public void print(String log) {
System.out.println(mFormatter.format(log));
}
...
}
我們將Logger類中的format功能從直接實現(xiàn)改為委托給Formatter接口來實現(xiàn),這樣如果我們需要添加一種新的格式只需要創(chuàng)建一個實現(xiàn)了Formatter接口的類來提供這個功能,然后將其傳給Logger類即可,在這個過程中,完全不需要對類進行修改,這正與開閉原則的要求相吻合。
下面我們來比較一下兩種實現(xiàn),顯然,第二種實現(xiàn)是一種更優(yōu)的實現(xiàn)方式。原因如下:
- Logger類更加符合單一職責(zé)原則。format功能被從其中分離出去了,Logger類只負責(zé)打印。
- Logger類與外界耦合更少。在第一種實現(xiàn)中,調(diào)用者需要關(guān)心Logger是如何對代碼進行格式化的,如果要添加一種格式化策略,對調(diào)用者而言非常麻煩,而第二種實現(xiàn)中,調(diào)用者完全不用考慮這個問題,因為格式化的策略就是調(diào)用者提供的。
第二種實現(xiàn)具有這些優(yōu)點并不偶然,一個類/模塊想要達到開閉原則的要求,必須具備下面兩個特點
- 功能清晰、明確。這樣才能避免在擴展的時候?qū)ζ溥M行修改
- 更外界的接口簡單,清楚。這樣在才能夠方便的添加類來對其進行擴展。
這兩點實際上就是面向?qū)ο蟮脑O(shè)計最核心的要求 —— 高內(nèi)聚,低耦合,因此開閉原則是面向?qū)ο笤O(shè)計五大原則中的核心原則,其它原則實際上都是圍繞它展開的。
遵循開閉原則的優(yōu)點
遵循開閉原則的設(shè)計一般都具有以下的優(yōu)點
- 易擴展。開閉原則的定義就要求對擴展開放。
- 易維護。軟件開發(fā)中,對現(xiàn)有代碼的修改是一件很有風(fēng)險的事情,符合開閉原則的設(shè)計在擴展時無需修改現(xiàn)有代碼,規(guī)避了這個風(fēng)險,大大提交了可維護性。
如何遵循開閉原則
目前并沒有一個通用的方法來設(shè)計出一個遵循開閉原則的軟件,下面是我自己對這個問題的思考。
開閉原則重點強調(diào)的是類/模塊的擴展能力,所謂擴展,一定是由變化引起的,因此,在軟件設(shè)計的時候,我們要不斷的識別那些可能存在的易變的點,思索它們未來可能的變化,并針對這些變化做出有彈性的包裝,這樣一旦變化來臨時,才能達到開閉原則所要求的效果。
同時也要小心,一個軟件一般只有少數(shù)一部分是易變的,大部分都是穩(wěn)定不變的,我們在識別易變點時一定要仔細甄別,如果將很多不變的地方也進行包裝就會陷入過度設(shè)計的誤區(qū)。一個軟件不可能處處都符合開閉原則,我們只需要做到在易變的地方符合開閉原則即可。
PS: 面向?qū)ο笤O(shè)計五大原則的其它文章
面向?qū)ο笤O(shè)計五大原則(1)—— 單一職責(zé)原則