学习.NET架构设计系列:学用设计模式(4)
我们可以在设备中处理Signal子类的创建,这样也不是不可以。但是,如果我们采用一个工厂,由他来负责Signal对象的建立,这样就完全隔离了设备和信号的每个子类的关系。设备在调用信号对象的时候,完全不需要知道这个实例是属于哪个类型。工厂的代码如下:
class SignalFactory
{
public static Signal CreateSignal(string dev, string sig)
{
//获取信号的定义
string sql =
"SELECT * FROM SIG WHERE DEV='" + dev + "' AND SIG='" + sig + "'";
//

//判断需要创建的类型
Signal signal = null;
if (type == 'A')
{
signal = new AnalogSignal();
}
else if (type == 'S')
{
signal = new StateSignal();
}
else if (type == 'P')
{
signal = new PresumeSignal();
}
else
{
return null;
}
//设置Signal的配置参数
signal.SetUnit(unit);
signal.SetStateDescription(state_description);
signal.SetPresumeFormule(presume_formule)
return signal;
}
}
如果我们需要显示设备上的某个信号,这样就可以了:
Signal sig = SignalFactory.CreateSignal(dev, sig);
string s = sig.GetDisplayString();
其实,我们还可以采用一些小手段,比如利用反射的方式,彻底的把Signal的各个子类与其他的代码隔离开,甚至连SignalFactory都不需要和子类产生联系。我们可以把信号配置的数据修改一下:
TYPE字段原先设计的是一个标志(A、S和P),现在直接记录类型的命名空间和名称。SignalFactory在创建实例的时候,直接查出TYPE字段的内容,然后按照这个类的名称,就可以用反射的方式创建需要的实例。这样,无论是Signal的创建者,还是调用者,都不需要知道他们创建和调用的实际类型是哪一个,各种信号的数据和显示处理完全是由Signal的每个子类负责,程序就很好的符合了开放闭合原则。假如以后出现了一些很独特的信号采集和计算方式,甚至不得不采用硬编码的方式去实现,也不会对其他代码造成不良影响影响,维护起来非常的方便。
我们利用一个工厂解决了信号数据采集的问题,并且为下一步可能发生的变化留下了扩展的可能。下面看看告警应该怎样处理。我们先简单的考虑一下告警的形成:首先是在设备上采集到最新的实时数据,然后按照某个规则去判断这些数据是不是符合了告警的条件。在符合条件的情况下,在设备上面产生告警。在大部分情况下,一个告警只和一个设备有关,但是也有这样的情况:某个告警条件需要同时判断多个设备上的多个信号。于是我们设计出下面这样的结构:
告警的定义保存在告警定义数据表里面,如下:
表里面的CONDITIONA字段表示告警条件,这是一个公式,判断的时候把信号的数值代入,然后判断公式条件是否得到满足。如果满足条件,就产生一个告警。
程序的运行时序是这样:设备对象得到自己所包含的信号上的实时数据,然后找到与自己相关的每一个告警对象,依次调用他们的Judge方法。Judge方法根据告警条件公式判断是否有告警存在,如果存在的话,就把相关设备的HasAlarm属性设置为True。就这样,程序的主要功能实现了。
下面是这个程序的一个客户端界面:
界面的左侧是一个树,表示设备的分类关系,每个叶子表示一个设备;当鼠标在树上点击一个设备,右侧的列表视图上显示这个设备上的信号和采集数据;如果设备上有告警,对应的树节点要标示一个显著的颜色,并且状态栏上要显示最近的告警。
要实现界面的刷新,最简单的方式莫过于在窗体上设置一个定时器,每隔一段时间检查一下所有设备的HasAlarm属性,发现有告警的设备,就把这个设备在树上的图标换掉,然后再把告警的内容显示在状态栏上。但是这样做有一个缺点,定时器的时间间隔无论怎样设置都是不合适的,时间太长了,可能会有一些告警要很久才能显示出来;时间太短的话,可能很多次刷新都没有告警,白白的消耗资源。这种刷新的机制是不合理的。要解决这个问题,可以采用观察模式(Observer)。一个对象需要等待另一个对象发出一个消息,然后再采取响应措施,等待消息的对象不需要知道消息如何发生、何时发生,发出消息的对象也不需要知道谁会关注这个消息、如何响应。这种情况就可以采用观察模式。
使用C#实现观察模式有一种非常简单的方法,那就是事件。我们可以在设备上定义一个事件:告警。当设备的HasAlarm属性被设置的时候,他会检查参数,如果发现参数为True,就发出告警事件。设备的代码片段如下:
class Device
{
public event System.EventHandler Alarm;//定义告警事件
public Device()
{
this.Alarm += new System.EventHandler(this.Device_Alarm);
}
public void SetHasAlarm(bool has)
{
if (has == true)
{
Alarm(this, null);//发出告警事件
}
}
private void Device_Alarm(object sender, EventArgs e)
{
}
}
当设备上产生告警的时候,设备对象会发出Alarm事件。界面上的树视图可以捕获这个事件,将对应的树节点图标设为红色;状态栏也可以捕获这个事件,把设备上的告警显示出来。比起定时轮循,这是一种更加合理高效的方式。
对象设计是不是合理,所参照的标准是这个设计是不是反映了业务需求的实际概念。要做到真实的反映业务需求,最根本的方法是要深刻的去理解需求,深入的探索业务人员的工作和思想,甚至去留意他们自己都无法用语言表达的思维环节。在一个涉及者众多的企业支撑系统中,这种情况很常见的。有经验的业务人员肯定会积累很多这样的思想,体现了这些思想的对象模型才是最优秀的。要让软件系统来帮助业务人员进行工作,也就必然无法回避这样的问题。合理的使用设计模式可以最大限度的降低系统的复杂程度,但是归根到底,复杂程度是由业务需求所决定的。当对象设计基本清晰之后,设计模式可以帮助设计人员更好的处理对象之间的复杂关系,建立一个更加简单、稳定的对象模型。同时,设计人员也可以从设计模式中得到启发,去发现一些原本没有留意的细节。