變化是不可避免的。無論你現在多麼欣賞自己的軟體。可能明天就要改變,而且有可能很難改變,這將反應在客戶的需求變動中,在這一章中拜訪一個老朋友,改進一下現有的系統,並且看看如何能以較少的變化解決較大的問題。
還記得原來那個吉他店的老闆嗎?我們可他開發了一個吉他的查詢系統。隨著生意越做越好,他要開始做Mandolin的生意,但是現有的系統無法對這種樂器進行查詢。
我們談了很多關於“好地分析設計是軟體重用和擴充的關鍵”,現在來看看如何相對較簡單的更改程式的結構以至於程式可以支援對Mandolin的查詢。首先看看原來那個查詢工具的類圖:
那我們如何將新的樂器加入查詢工具呢?實際看看那個樂器,和吉他有很多相似,我們可以抽象出一個介面或者我們可以做一個抽象類別型。
這裡要注意:抽象類別型實際上是實作類別型或者說衍生類別型的預留位置(Abstract classes are placeholders for actual implementation classes),抽象類別型定義了行為,並且衍生類別型實現這些行為(The abstract class defines behavior, and the subclasses implement that behavior)。無論何時你看到公用行為在一個或多個地方,注意要抽象這個行為到一個類型中,並且在公用類型中重用這些行為(Whenever you find common behavior in two or more places, look to abstract that behavior in a class, and then reuse that behavior in the common classes)。
Mandolin和吉他很類似,但是還是有一些區別,Mandolin有一個Style屬性,而且大多數Mandolin是4弦,對於Mandolin,弦的數量是沒有必要的。當然和吉他一樣,Mandolin也需要一個MandolinSpec類型。
現在,我們把所有的類型放到一起,形成一個系統的類型圖:
現在我們來看看編碼:
public abstract class Instrument
{
private String _SerialNumber;
private Double _Price;
private InstrumentSpec _Spec;
public String SerialNumber
{
get { return _SerialNumber; }
set { _SerialNumber = value; }
}
public Double Price
{
get { return _Price; }
set { _Price = value; }
}
public InstrumentSpec Spec
{
get { return _Spec; }
set { _Spec = value; }
}
public Instrument(String SerialNumber, Double Price, InstrumentSpec Spec)
{
_SerialNumber = SerialNumber;
_Price = Price;
_Spec = Spec;
}
public InstrumentSpec GetSpec()
{
return _Spec;
}
}
public abstract class InstrumentSpec
{
private String _builder;
private String _model;
private String _type;
private String _backWood;
private String _topWood;
public InstrumentSpec(String builder, String model, String type, String backWood, String topWood)
{
Builder = builder;
Model = model;
Type = type;
BackWood = backWood;
TopWood = topWood;
}
public String Builder
{
get { return _builder; }
set { _builder = value; }
}
public String Model
{
get { return _model; }
set { _model = value; }
}
public String Type
{
get { return _type; }
set { _type = value; }
}
public String BackWood
{
get { return _backWood; }
set { _backWood = value; }
}
public String TopWood
{
get { return _topWood; }
set { _topWood = value; }
}
public Boolean matches(InstrumentSpec OtherSpec)
{
Boolean Flag = true;
if (Builder != OtherSpec.Builder)
Flag = false;
if (Model != OtherSpec.Model)
Flag = false;
if (Type != OtherSpec.Type)
Flag = false;
if (BackWood != OtherSpec.BackWood)
Flag = false;
if (TopWood != OtherSpec.TopWood)
Flag = false;
return Flag;
}
}
public class GuitarSpec : InstrumentSpec
{
private int _numString;
public int numString
{
get { return _numString; }
set { _numString = value; }
}
public GuitarSpec(String builder, String model, String type,
String backWood, String topWood, int numString)
: base(builder, model, type, backWood, topWood)
{
numString = numString;
}
public Boolean matches(InstrumentSpec Spec)
{
Boolean Flag = true;
if (!base.matches(Spec))
Flag = false;
if (!(Spec is GuitarSpec))
Flag = false;
else
{
if (((GuitarSpec)Spec).numString != numString)
Flag = false;
}
return Flag;
}
}
public class Guitar : Instrument
{
public Guitar(String SerialNumber, Double Price, GuitarSpec Spec)
: base(SerialNumber, Price, Spec)
{ }
}
public class MandolinSpec : InstrumentSpec
{
private String _style;
public String Style
{
get { return _style; }
set { _style = value; }
}
public MandolinSpec(String builder, String model, String type,
String backWood, String topWood, String style)
: base(builder, model, type, backWood, topWood)
{
Style = style;
}
public Boolean mathes(InstrumentSpec Spec)
{
Boolean Flag = true;
if (!base.matches(Spec))
Flag = false;
if (!(Spec is MandolinSpec))
Flag = false;
else
{
if (((MandolinSpec)Spec).Style != Style)
Flag = false;
}
return Flag;
}
}
public class Mandolin : Instrument
{
public Mandolin(String SerialNumber, Double Price, MandolinSpec Spec)
: base(SerialNumber, Price, Spec)
{ }
}
public class Inventory
{
private List<Instrument> _inventory;
public List<Instrument> inventory
{
get { return _inventory; }
set { _inventory = value; }
}
public void addInventory(String serialNumber, Double price, InstrumentSpec spec)
{
Instrument instrument = null;
if (spec is GuitarSpec)
instrument = new Guitar(serialNumber, price, (GuitarSpec)spec);
else if (spec is MandolinSpec)
instrument = new Mandolin(serialNumber, price, (MandolinSpec)spec);
if (instrument != null)
inventory.Add(instrument);
}
public Instrument Get(String serialNumber)
{
Instrument getInstrument = null;
foreach (Instrument instrument in inventory)
{
if (instrument.SerialNumber == serialNumber)
{
getInstrument = instrument;
break;
}
}
return getInstrument;
}
public List<Mandolin> Search(MandolinSpec Spec)
{
List<Mandolin> list = new List<Mandolin>();
foreach (Instrument instrument in inventory)
{
if (instrument is Mandolin)
{
if (instrument.GetSpec().matches(Spec))
list.Add(((Mandolin)instrument));
}
}
return list;
}
public List<Guitar> Search(GuitarSpec Spec)
{
List<Guitar> list = new List<Guitar>();
foreach (Instrument instrument in inventory)
{
if (instrument is Guitar)
{
if (instrument.GetSpec().matches(Spec))
list.Add(((Guitar)instrument));
}
}
return list;
}
}
現在來測試一下:
class Program
{
static Inventory inventory;
static void Main(string[] args)
{
InventoryInit();
List<Guitar> guitarlist = inventory.Search(new GuitarSpec("builder1", "model1", "type1", "backwood1", "topwood1",6));
if (guitarlist != null)
{
foreach (Guitar guitar in guitarlist)
{
Console.WriteLine("Guitar's SerialNumber is " + guitar.SerialNumber);
}
}
List<Mandolin> mandolinlist = inventory.Search(new MandolinSpec("builder3", "model3", "type3", "backwood3", "topwood3", "style1"));
if (mandolinlist != null)
{
foreach (Mandolin mandolin in mandolinlist)
{
Console.WriteLine("Mandolin's SerialNumber is " + mandolin.SerialNumber);
}
}
Console.Read();
}
public static void InventoryInit()
{
inventory = new Inventory();
inventory.inventory = new List<Instrument>();
inventory.inventory.Add(new Guitar("001", 100, new GuitarSpec("builder1", "model1", "type1", "backwood1", "topwood1",6)));
inventory.inventory.Add(new Guitar("002", 123, new GuitarSpec("builder2", "model2", "type2", "backwood2", "topwood2",6)));
inventory.inventory.Add(new Mandolin("003", 124, new MandolinSpec("builder3", "model3", "type3", "backwood3", "topwood3","style1")));
}
}
輸出結果是:
看看現在的程式應該比原來的那個要好一些,使用抽象類別型避免了代碼的重複拷貝,Instrument類型中封裝的屬性從Spec類型中分離,還記得編寫好軟體的三個步驟嗎?現在這個查詢工具是否能夠算得上是一個好的軟體?來看看下面幾個問題:
1、 新的查詢工具是否能夠滿足使用者的需求?可以,它可以找到guitar和mandolin兩種樂器,雖然不能同時查詢兩種樂器
2、 是否使用了物件導向原則,如:封裝,避免代碼拷貝,軟體容易擴充?在InstrumentSpec類型中使用封裝,在開發超類型時繼承Instrument和InstrumentSpec
3、 是否能簡單的重用這個程式?改變程式中的一部分會不會是很多地方都需要改變?模組中是否解耦合?對於這個程式有一些難度。所有的模組都緊密地連接
最好的檢驗是否有很好的軟體設計是試著改變
如果你的軟體很難去改變,那麼問題可能出現在你要改進設計,現在看看如果在加上幾種樂器是否能很容易的改變。在現有的程式中如果加入一個新的樂器,我們就要加入一個新的樂器規格,並且在Inventory中加入一個新的查詢方法,看起來改變並不容易!
看起來要把這個查詢工具寫好我們還有很多工作要做。當然並不是說現在已經完成的工作不重要。很多時候我們必須改進我們的設計,才能發現新的問題。我們現在已經在這個查詢工具上實現了物件導向的原則。在我們繼續改進設計之前有一個新的話題:物件導向的災難。