定義:軟件實體(類、模塊、函數等)應該是可以擴展的,但是不可修改。對于擴展是開放的,對于更改是封閉的。關鍵是抽象,將一個功能的通用部分和實現細節部分清晰的分離開來。
這里要求我們寫代碼要有抽象的概念。什么是抽象?指由實體抽離出概念的思考過程。就是從眾多的對象中抽離出共同的本質的特征。在寫代碼的過程中,需要抽象類的地方,只需要抓住這個類的本質功能,不要老想著它在這個工程中的具體功能。
我們繼續看開放封閉原則,這個原則要求,將一個功能的共用部分和實現部分清晰地分離開來。因為在最初搭建架構的時候你不能預測到所有將要發生的變化,那么這個類也就不是一塵不變的,隨著在各個模塊中具體實現的時候,你發現抽象的類適合這個功能,但是對另一個功能又不適合。那么是否要返回去修改抽象類呢?這個代價是很大的,需要重新全盤思考,還要調整具體細節。如果程序還沒發布還好一些,一旦程序發布了,那么返回修改抽象類,影響將更大。所以在開始抽象的時候就要防止這樣的現象發生,要遵循開放封閉原則。抽象類、接口是一種標準,在程序中一旦定義好,就不能輕易去修改,需求有變化,怎么辦呢?可以擴展這個接口,重寫方法,或者繼承后增加新的方法,但是一定不要去修改。
下面用兩個例子來說明開放封閉原則。
1、連接數據庫為例。
比如在程序中用到的不同類型的數據庫連接,Access和Oracle。直接連接如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class ConnectAccess { public string ConnectString() { string dataPath = "數據庫路徑" ; return string.Format( "Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Persist Security Info=True;Jet OLEDB:Database Password={1}" , dataPath, "密碼" ); } } class ConnectOracle { public string ConnectString() { return @ "server=localhost;database=命名空間;uid=用戶名;pwd=密碼" ; } } |
調用
1
2
3
4
5
6
7
8
9
10
11
12
|
static void Main(string[] args) { //連接Access ConnectAccess connAccess = new ConnectAccess(); OleDbConnection accessConnection = new OleDbConnection(connAccessConnectString()); //連接Oracle ConnectOracle connOracle = new ConnectOracle(); OracleConnection oracleConnection = new OracleConnection(connOracleConnectString()); } |
這樣每次都要去考慮OleDbConnection的參數到底用哪個。下面修改一下。抽象一個接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
interface ConnectDataBase { string ConnectString(); } class ConnectAccess : ConnectDataBase { #region ConnectDataBase 成員 public string ConnectString() { string dataPath = "數據庫路徑" ; return stringFormat( "Provider=MicrosoftJetOLEDB0;Data Source={0};Persist Security Info=True;Jet OLEDB:Database Password={1}" , dataPath, "密碼" ); } #endregion } class ConnectOracle : ConnectDataBase { #region ConnectDataBase 成員 public string ConnectString() { return @ "server=localhost;database=命名空間;uid=用戶名;pwd=密碼" ; } #endregion } |
調用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
static void Main(string[] args) { ConnectDataBase conn = null ; //連接Access conn = new ConnectAccess(); OleDbConnection accessConnection = new OleDbConnection(connConnectString()); //連接Oracle conn = new ConnectOracle(); OracleConnection oracleConnection = new OracleConnection(connConnectString()); } |
改過后,只需關心conn用哪一個類實例化,就可以了。不過大家可能看到,由于Oracle的連接必須OracleConnection,可能優勢不容易看出來。
2、基本類型作為方法參數為例。
一般的設計原則之所以強調方法參數盡量避免基本類型,原因正在于此。比較如下兩個方法定義:
1
2
3
4
5
|
//定義1 bool Connect(string userName, string password, string wifiAddress, int port) { return false ; } |
1
2
3
4
5
|
//定義2 bool Connect(Account account) { return false ; } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class Account { public string UserName { get; set; } public string Password { get; set; } public string WifiAddress { get; set; } public int Port { get; set; } } |
相比較而言,定義2多了一個Account類的定義,Connect()方法明顯更加穩定。如果Connect()方法wifiAddress發生改變,定義1必須修改該方法的接口,對應的,所有調用Connect()方法的對象都會受到影響;而定義2只需要修改Account類,由于Connect()方法的接口保持不變,而對于Connect()方法的調用者并不需wifiAddress,這樣的修改就完全不會影響調用者,從而降低需求變化帶來的影響。
總之開放封閉原則最關鍵的是抽象,但是也并不是說一旦抽象接口、類確定就一定不能修改。只是,我們在抽象的時候就要全面思考,爭取做到不用修改,一旦需求有變化,只需在實現的時候有變動。當然,需求是千變萬化的,一旦需要改動抽象的部分,那么只要前面嚴格遵循了該原則,影響也會小得多。當然了如果修改了,一定要進行單元測試,凡是用到的地方都要測試正確。