变化是不可避免的。无论你现在多么欣赏自己的软件。可能明天就要改变,而且有可能很难改变,这将反应在客户的需求变动中,在这一章中拜访一个老朋友,改进一下现有的系统,并且看看如何能以较少的变化解决较大的问题。
还记得原来那个吉他店的老板吗?我们可他开发了一个吉他的查询系统。随着生意越做越好,他要开始做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中加入一个新的查询方法,看起来改变并不容易!
看起来要把这个查询工具写好我们还有很多工作要做。当然并不是说现在已经完成的工作不重要。很多时候我们必须改进我们的设计,才能发现新的问题。我们现在已经在这个查询工具上实现了面向对象的原则。在我们继续改进设计之前有一个新的话题:面向对象的灾难。