有人提出要用OO的思路來解決算法問題,OK,沒問題,eaglet 今天就嘗試用OO來實現算法。既然談OO,我們就重點討論設計,不重點討論算法的效率了。
在開始OO之前,我想先說說什麼是OO設計。OO設計就是面向對象設計,有人說不要認爲你用了C#就OO了,不錯用C#照樣可以寫出面向過程的代碼。不過 eaglet 還有補充一下,不要認爲你用了class 就OO了。程序是否OO,要看程序是否很好的體現了面向對象的思想。面向對象程序設計可以被視作一種在程序中包含各種獨立而又互相調用的單位和對象的思想,這與傳統的思想剛好相反:傳統的程序設計主張將程序看作一系列函數的集合,或者直接就是一系列對電腦下達的指令。面向對象程序設計中的每一個對象都應該能夠接受數據、處理數據並將數據傳達給其它對象,因此它們都可以被看作一個小型的“機器”,或者說是負有責任的角色。
下面再簡單列舉一下面向對象的基本設計原則:
1) 單一職責原則 (The Single Responsiblity Principle,簡稱SRP)
2) 開放-封閉原則 (The Open-Close Principle,簡稱OCP)
3) Liskov 替換原則(The Liskov Substitution Principle,簡稱LSP)
4) 依賴倒置原則(The Dependency Inversion Pricinple,簡稱DIP)
5) 接口隔離原則 (The Interface Segregation Principle,簡稱ISP)
一、單一職責原則(SRP)
就一個類而言,應該僅有一個引起它變化的原因。軟件設計真正要做的許多內容,就是發現職責並把那些職責相互分離。測試驅動的開發實踐常常會在設計出現臭味之前就迫使我們分離職責。
二、開閉原則(OCP)
軟件實體(類、模塊、函數)應該是可擴展的,但是不可修改的。也就是說:對於擴展是開放的,對於更改是封閉的。怎樣可能在不改動模塊源代碼的情況下去更改它的行爲呢?怎樣才能在無需對模塊進行改動的情況下就改變它的功能呢?關鍵是抽象!因此在進行面向對象設計時要儘量考慮接口封裝機制、抽象機制和多態技術。該原則同樣適合於非面向對象設計的方法,是軟件工程設計方法的重要原則之一。
三、替換原則(LSP)
子類應當可以替換父類並出現在父類能夠出現的任何地方。這個原則是Liskov於1987年提出的設計原則。它同樣可以從Bertrand Meyer 的DBC (Design by Contract〔基於契約設計〕) 的概念推出。
四、依賴倒置原則(DIP)
1、高層模塊不應該依賴於低層模塊。二者都應該依賴於抽象。2、抽象不應該依賴於細節。細節應該依賴於抽象。在進行業務設計時,與特定業務有關的依賴關係應該儘量依賴接口和抽象類,而不是依賴於具體類。具體類只負責相關業務的實現,修改具體類不影響與特定業務有關的依賴關係。在結構化設計中,我們可以看到底層的模塊是對高層抽象模塊的實現(高層抽象模塊通過調用底層模塊),這說明,抽象的模塊要依賴具體實現相關的模塊,底層模塊的具體實現發生變動時將會嚴重影響高層抽象的模塊,顯然這是結構化方法的一個"硬傷"。面向對象方法的依賴關係剛好相反,具體實現類依賴於抽象類和接口。
五、接口分離原則(ISP)
採用多個與特定客戶類有關的接口比採用一個通用的涵蓋多個業務方法的接口要好。 ISP原則是另外一個支持諸如COM等組件化的使能技術。缺少 ISP,組件、類的可用性和移植性將大打折扣。這個原則的本質相當簡單。如果你擁有一個針對多個客戶的類,爲每一個客戶創建特定業務接口,然後使該客戶類繼承多個特定業務接口將比直接加載客戶所需所有方法有效。
當然並不是說面向對象設計必須要嚴格遵守這些原則,但我們在設計時至少需要參照這些原則,就如數據庫設計需要參照範式原則一樣。
下面我們開始正題:
我們就拿那個第一題來舉例子,題目見 有道難題之eaglet的算法
這個題目本身很簡單,既然要面向對象,我們需要考慮我們對這種問題做面向對象設計的目的是什麼?我們假設我們有一個系統,這個系統專門用來計算農田中的胡蘿蔔,我們除了要計算特殊值在某個範圍胡蘿蔔總數,可能還要計算這些符合條件的特殊值總和,可能還有很多很多業務需求。另外我們可能還需要能夠方便的定義特殊值,有時候特殊值定義爲某個網格中周圍的胡蘿蔔數,有時候則爲周圍胡蘿蔔數加上自身的胡蘿蔔數。用戶界面可以指定採用哪種特殊值定義方法來計算。有了這些需求,我們可以開始設計了。
首先我們需要把業務邏輯和數據分開,業務邏輯層只知道從給定的農田的各個網格中取出數據,並不關心這些數據是通過如何計算得來的。數據訪問層則需要能夠讓業務層不進行代碼修改的情況下可以得到不同定義的特殊值數據。
首先我們來設計數據訪問層
第一步我們定義一個實體類
這個實體類提供某個網格的特殊值數據
{
private int _SpecialNumber;
public int SpecialNumber
{
get { return _SpecialNumber; }
}
public Cell( int specialNumber)
{
_SpecialNumber = specialNumber;
}
}
接下來考慮到業務層需要不進行修改就能夠更改網格的特殊值定義,我們設計一個抽象工廠,通過這個抽象工廠抽象出不同類型
的工廠以生產不同特殊值定義的網格對象。
/// 生產Cell的工廠抽象接口
/// </summary>
public interface ICellFactory
{
void Init( string [] field);
/// <summary>
/// 根據指定的 i, j 生產出一個Cell
/// </summary>
/// <param name="i"></param>
/// <param name="j"></param>
/// <returns></returns>
Cell Output( int i, int j);
}
完成了這兩個,數據訪問層的設計就結束了,下面就是業務邏輯層了。
業務邏輯層比較簡單,就是根據需求從數據層獲取數據並進行一定的邏輯計算得到結果提供給調用者。
簡單起見,我只實現了題目要求的業務邏輯
{
public static int countSpecialNumbers( string [] field, int A, int B, ICellFactory cellFactory)
{
// Check paramaters
if (field == null )
{
return 0 ;
}
if (field.Length == 0 )
{
return 0 ;
}
int width = field[ 0 ].Length;
if (width == 0 )
{
return 0 ;
}
if (A > B)
{
throw new ArgumentException( " A > B " );
}
cellFactory.Init(field);
// Begin calculate
int count = 0 ;
for ( int i = 0 ; i < field.Length; i ++ )
{
for ( int j = 0 ; j < field[i].Length; j ++ )
{
Cell cell = cellFactory.Output(i, j);
if (cell.SpecialNumber >= A && cell.SpecialNumber <= B)
{
count ++ ;
}
}
}
return count;
}
}
和原題的區別是這裏多了一個工廠接口的輸入參數。
設計到這裏實際上已經完成,下面要做的是編寫兩個工廠類,一個用於生產第一種特殊值定義的網格對象(特殊值定義爲某個網格中周圍的胡蘿蔔數),一個用於
生產第二種特殊值定義的網格對象(特殊值定義爲周圍胡蘿蔔數加上自身的胡蘿蔔數)。
/// 特殊值定義爲某個網格中周圍的胡蘿蔔數
/// </summary>
public class Factory1 : ICellFactory
{
string [] _Matrix;
int _Width;
private int GetSpecialNumbers( int i, int j)
{
int y = i - 1 >= 0 ? i - 1 : 0 ;
int sum = 0 ;
while (y <= i + 1 && y < _Matrix.Length)
{
int x = j - 1 >= 0 ? j - 1 : 0 ;
while (x <= j + 1 && x < _Width)
{
if (x != j || y != i)
{
sum += _Matrix[y][x] - ' 0 ' ;
}
x ++ ;
}
y ++ ;
}
return sum;
}
ICellFactory Members
}
/// 特殊值定義爲周圍胡蘿蔔數加上自身的胡蘿蔔數
/// </summary>
public class Factory2 : ICellFactory
{
string [] _Matrix;
int _Width;
private int GetSpecialNumbers( int i, int j)
{
int y = i - 1 >= 0 ? i - 1 : 0 ;
int sum = 0 ;
while (y <= i + 1 && y < _Matrix.Length)
{
int x = j - 1 >= 0 ? j - 1 : 0 ;
while (x <= j + 1 && x < _Width)
{
sum += _Matrix[y][x] - ' 0 ' ;
x ++ ;
}
y ++ ;
}
return sum;
}
ICellFactory Members
}
測試代碼
結果
面向對象 工廠1
5
9
3
0
0
26
面向對象 工廠2
8
4
2
1
1
26
下面我們再對照面向對象設計要求來檢查這個設計
一、單一職責原則(SRP)
就一個類而言,應該僅有一個引起它變化的原因。軟件設計真正要做的許多內容,就是發現職責並把那些職責相互分離。測試驅動的開發實踐常常會在設計出現臭味之前就迫使我們分離 職責
數據實體類 Cell 只在構造的時候引起變化,其職責和其他類完全分離。
二、開閉原則(OCP)
軟 件實體(類、模塊、函數)應該是可擴展的,但是不可修改的。也就是說:對於擴展是開放的,對於更改是封閉的。怎樣可能在不改動模塊源代碼的情況下去更改它的行爲呢?怎樣才能在無需對模塊進行改動的情況下就改變它的功能呢?關鍵是抽象!因此在進行面向對象設計時要儘量考慮接口封裝機制、抽象機制和多態技術。該原則同樣適合於非面向對象設計的方法,是軟件工程設計方法的重要原則之一 。
更改特殊值定義的行爲不需要改動業務邏輯層和數據訪問層代碼,只需要實現對應的接口就可以完成。
三、替換原則(LSP)
子類應當可以替換父類並出現在父類能夠出現的任何地方。這個原則是Liskov於1987年提出的設計原則。它同樣可以從Bertrand Meyer 的DBC (Design by Contract〔基於契約設計〕) 的概念推出。
這裏沒有設計子類和父類,忽略。
四、依賴倒置原則(DIP)
1、高層模塊不應該依賴於低層模塊。二者都應該依賴於抽象。2、抽象不應該依賴於細節。細節應該依賴於抽象。在進行業務設計時,與特定業務有關的依賴關係應該儘量依賴接口和抽象類,而不是依賴於具體類。具體類只負責相關業務的實現,修改具體類不影響與特定業務有關的依賴關係。在結構化設計中,我們可以看到底層的模塊是對高層抽象模塊的實現(高層抽象模塊通過調用底層模塊),這說明,抽象的模塊要依賴具體實現相關的模塊,底層模塊的具體實現發生變動時將會嚴重影響高層抽象的模塊,顯然這是結構化方法的一個"硬傷"。面向對象方法的依賴關係剛好相反,具體實現類依賴於抽象類和接口。
業務邏輯層不依賴於底層的數據訪問層,其依賴於工廠抽象。工廠抽象只抽象爲初始化和獲取網格對象,不依賴於任何細節,比如計算特殊值的方法啦什麼的。
五、接口分離原則(ISP)
採用多個與特定客戶類有關的接口比採用一個通用的涵蓋多個業務方法的接口要好。 ISP原則是另外一個支持諸如COM等組件化的使能技術。缺少 ISP,組件、類的可用性和移植性將大打折扣。這個原則的本質相當簡單。如果你擁有一個針對多個客戶的類,爲每一個客戶創建特定業務接口,然後使該客戶類繼承多個特定業務接口將比直接加載客戶所需所有方法有效。
這裏只有一個接口,忽略。