需求變化往往是一個項目中不可避免的,當然也是常常是使開發人員頭痛的。使用者在使用軟體後常常會出現這樣或那樣的想法。目前的軟體雖然已經滿足了他們先前提出的要求。但是要知道,結束意味著新的開始。還記得上一篇文章中的那個客戶嗎?他們又回來了!
上一篇中我們為他們開發了一個Dog Door。在使用中都很好,但現在他們有了一些新的建議。他們常常亂放遙控器,以至於當小狗要出去或者近來時,不得不去找一下遙控器放在那裡了。(有點頭大,但是使用者往往是對的,需求經常變化即使你有一個好的用例,你常常要快速改變你的軟體以適應那些新的需求。)
根據他們的需求,我們需要相應的修改我們的用例:看到需求後,感覺我們只需要在小狗barking的時候加一個聲音識別裝置來接收聲音訊號,然後再開門。
看了這個用例的確讓人疑惑,因為新加入的步驟讓人覺得比較混亂。當用例讓我們疑惑時,我們需要重寫用例。上面用例的問題在於新加入的步驟並不是一個子步驟或者是一個先後的順序,而是並列的關係。而且根據上面的需求,客戶希望Dog Door主動識別小狗的叫聲並開門,而不再過多地依靠他們。那好,用例應該是這樣的:
注意:現在加了兩個標籤,Main Path & Alternate Paths
Main Path:我理解為在一般的情況下都會發生的途徑,或者說主要發生的途徑
Alternate Paths:我理解為有可能發生的途徑,替代途徑
對於一個用例來說,從開始到結束可能有很多的途徑,這時有一個scenario概念出現,可以理解為一個情境或者一種可能發生的途徑。書上是這樣寫的:A complete path through a use case, from the first step to the last, is called scenario. 大多數用例有一些不同的scenario,但是這些scenario一般共有一個目標(說到這裡,我想到上一篇文章中提到的:一個用例要定義一個開始點和一個結束點)。
Alternate Path也是用例中重要的部分,不經常發生的並不是不發生,比如在上面的例子中,如果聲音辨識器壞掉的情況下…而且Alternate Path使得用例更完整。
現在用例已經修改完畢了,但是要注意,在編寫代碼之前,查看一下用例和原來的需求是否可以對應。任何時候改變用例都要回來查看需求的變化。下面來修改我們的需求列表。
下面我們要開始完成代碼的編寫,首先,需要一個聲音辨識器類型:
public class BarkRecognizer
{
private DogDoor _door;
public BarkRecognizer(DogDoor door)
{
this._door = door;
}
public void recoginze(String StrBark)
{
Console.WriteLine(" BarkRecognizer heard a '" + StrBark + "'");
_door.Open();
}
}
然後我們在對測試代碼進行修改:
static void Main(string[] args)
{
DogDoor door = new DogDoor();
BarkRecognizer recognizer = new BarkRecognizer(door);
Remote remote = new Remote(door);
Console.WriteLine("The dog is barking, he wanna ");
//remote.pressButton();
recognizer.recoginze("Woof");
Console.WriteLine("The dog is outside and ");
Thread.Sleep(10000);
//remote.pressButton();
Console.WriteLine("The dog is barking, he wanna inside");
//remote.pressButton();
recognizer.recoginze("Woof");
Console.WriteLine("The dog is inside");
//remote.pressButton();
Console.Read();
}
測試結果如下:
這次修改中發生了一個問題:Dog door 無法自動關閉了!在實際開發中,需求變化導致的修改往往會讓我們的程式千瘡百孔,對於這樣一個小的程式都會出現這種問題,何況我們的項目呢?但是,這種情況不是不能避免,問題也許是由於原先的設計,也許在於後來的開發上,及時調整,避免問題到了後面,不可收拾。
我原來把自動關閉的代碼寫在了Remote中,現在可以同樣在BarkRecognizer中貼入同樣的代碼,但是這並不是一個好的主意。想想如果說今後對於自動關閉的方法有修改時,那麼需要修改所有貼有這個代碼的地方,如果客戶又要加入一種開門的方式,就又要粘貼一下這段代碼,顯然這與物件導向原則是背道而馳的。相對較好的解決方式應該是修改DogDoor類型,因為自動關閉是DogDoor本身的行為。修改代碼如下:
public class Remote
{
private DogDoor _door;
public Remote(DogDoor door)
{
this._door = door;
}
public void pressButton()
{
if (_door.IsOpen)
{
_door.Close();
}
else
{
_door.Open();
}
}
}
public class DogDoor
{
private Boolean _isOpen;
Timer time;
TimerCallback callback;
public Boolean IsOpen
{
get { return _isOpen; }
set { _isOpen = value; }
}
public DogDoor()
{
_isOpen = false;
}
public void Open()
{
_isOpen = true;
Console.WriteLine("The dog door is opened.");
callback = new TimerCallback(this.Close);
time = new Timer(callback, this, 1000, 0);
}
public void Close()
{
_isOpen = false;
Console.WriteLine("The dog door is closed");
}
public void Close(object source)
{
this.Close();
}
}
現在正常了:
通過修改,我們可以看出:有時候需求中的一個變化能反映出系統的一些問題,你甚至不知道有這些問題。(Sometimes a change in requirements reveals problems with your system that you didn’t even know were there.)
變化是經常的,系統也應該在你修改或者編寫他的時候經常有所改善。(Change is constant, and your system should always improve every time you work on it.)
故事講還在繼續,但現在我們可以總結一下這兩章中學習到的東西,一些OOA&D的工具或者說思想:
需求:
1、 好的需求保證了你的系統工作如同你的客戶所期望的
2、 確定你的需求覆蓋了系統用例中的所有步驟
3、 使用你的用例並找出客戶忘記告訴你的事情
4、 你的用例往往會展現一些需求中不完善或者是丟失的東西,而這些東西你應該加入你的系統中
5、 隨著時間流逝,需求會變化或者膨脹
物件導向原則:
1、 封裝變化