学习.NET架构设计系列:OO设计初次见面(6)
面向对象的设计就是要这样,不要急于确定系统需要做哪些功能点和哪些界面,而是首先要深入的探索需求中出现的概念。在具体的流程不甚清楚的情况下,先把这些概念搞清楚,一个一个的开发出来。然后只要把这些做好的零件拿过来,千变万化的流程其实就变得很简单了,一番搭积木式的装配就可以比较轻松的实现。
另一个重要的类型也渐渐清晰的浮现在我们的眼前:账单(Bill)。他的代码片段如下:
{
public DateTime GetBeginTime()
{
//返回账单周期的起始时间
}
public DateTime GetEndTime()
{
//返回账单周期的终止时间
}
public Fee GetFee()
{
//返回账单的费用
}
public float GetPenalty()
{
//返回账单的滞纳金
}
public void CaculatePenalty()
{
//计算账单的滞纳金
}
public float GetPaid()
{
//返回已支付金额
}
public float GetDebt()
{
//返回欠费
//账单费用加上滞纳金,再减去支付金额,就是欠费
return GetFee().GetValue() + GetPanalty() - GetPaid();
}
public Entry GetEntrees()
{
//返回相关的存入和支取的账目
}
public Bill Merge(Bill bill)
{
//合并两个账单,返回合并后的账单
//合并后的账单可以打印在一张发票上
}
}
Bill类有两个与滞纳金有关的方法,这使开发者想到了原先忽略的一个流程:计算滞纳金。经过与电信公司的确认,决定每个月进行一次计算滞纳金的工作。开发人员写了一个脚本,先得到系统中所有的欠费账单,然后一一调用他们的CaculatePenalty方法。每个月将这个脚本执行一次,就可以完成滞纳金的计算工作。
Bill对象中有账户的基本属性和各级账目的金额和销账的情况,要打印发票,只有这些数值是不够的。还要涉及到上次结余、本次结余和本次实缴,这三个数值是需要从账目中查到的。并且发票有严格的格式要求,也不需要显示费用的细节,只要显示一级和二级的费用类就可以了。应该把这些东西另外封装成一个类:发票(Invoice):
通信公司后来又提出了新的需求:有些账号和银行签订了托收协议,每个月通信公司打印出这些账户的托收单交给银行,银行从个人结算账户上扣除这笔钱,再把一个扣费单交给通信公司。通信公司根据这个扣费单冲销用户的欠费。于是开发人员可以再做一个托收单(DeputyBill):
账单中的GetFee方法的返回值类型是Fee,Fee类型包含了费用的名称、金额和他包含的其他费用。例如下面的情况:
我们可以用这样的一个类来表示费用(Fee),一个费用可以包含其他的费用,他的金额是子费用的金额和。代码片段如下:
{
private float valuee = 0;
public string GetName()
{
//返回费用的名称
}
public bool HasChildren()
{
//该费用类型是否有子类型
}
public Fee[] GetChildren()
{
//返回该费用类型的子类型
}
public float GetValue()
{
//返回费用的金额
if (HasChildren())
{
float f = 0;
Fee[] children = GetChildren();
for (int i = 0; i < children.Length; i ++)
{
f += children[i].GetValue();
}
return f;
}
else
{
return valuee;
}
}
}
现在开发者设计出了这么一堆类,构成软件系统的主要零件就这么制造出来了。下面要做的就是把这些零件串在一起,去实现需要的功能。OO设计的重点就是要找到这些零件。就像是设计一辆汽车,仅仅知道油路、电路、传动的各项流程是不够的,重要的是知道造一辆汽车需要先制造哪些零件。要想正确的把这些零件设计出来不是一件容易的事情,很少有开发者一开始就了解系统的需求,设计出合理的对象关系。根本的原因在于领域知识的贫乏,开发者和用户之间也缺乏必要的交流。很多人在软件开发的过程中才渐渐意识到原来的设计中存在一些难受的地方,然后探索下去,才知道了正确的方式,这就是业务知识的一个突破。不幸的是,当这个突破到来的时候,程序员经常是已经忙得热火朝天,快把代码写完了。要把一切恢复到正常的轨道上,需要勇气,时间,有远见的领导者,也需要有运气。