将代码重构到底
——记录一次简单的代码重构
当前开发的系统中非模态窗体要求进行主窗口管理,目前大部分代码是在每次打开窗体时进行设置,如:
FrmParaeter frm=new FrmParameter();
frm.MdiParent = Application.OpenForms[0];//设置mdi窗口
frm.show();
这种做法缺点是:
每次打开窗体的时候都需要设置窗体的MdiParent ,进行设置的方式也是一样的,较为繁琐,而且设置主窗体时未判断异常情况。
一种改进做法是提供:在FrmParaeter 窗体中中重写Show()函数,如下
public new void Show()
{
try
{
this.MdiParent = Application.OpenForms[0];//设置MdiParent
}
finally
{
base.Show();//确保出现异常之后窗体依旧能够打开
}
}
这样窗体FrmParaeter 就具有show的时候在主窗口管理的功能,而不是需要每次进行设置,同时也能够保证存在异常的情况下窗体也能打开。
上述做法从单个窗体类Show函数调用的角度进行了公共代码的集中,但是对于系统中其他窗体类同样需要这种功能,于是可以考虑增加窗体的基类FormMDIChild,将公用代码提取到基类中,需要主窗口管理的窗体从该窗体基类派生。
重构进行到现在,似乎还比较完美o(∩_∩)o…,直到有一天,我使用反射创建窗口并且显示时,代码如下:
Type tp = Type.GetType("xxx.FrmParmeters,xxx");
if (tp != null)
{
object obj = Activator.CreateInstance(tp, ids, count);
Form frm = obj as Form;
frm.Show();
}
虽然FrmParmeters继承的基类具备主窗口管理功能,但是新增的Show函数隐藏了Form基类的Show函数,所以使用Form调用Show,还是调用Form类的Show方法而不是FormMDIChild的。在这里,使用函数重载隐藏基类同名函数是不恰当的。改进的方向是重载虚函数,通常大多数API的公共方法设计为非虚,某些可能存在变化的方法设置为Protected的虚方法(参考模板方法模式),事件的设计也是如此。派生类需要扩展基类时,优选选择的是重载虚方法。所以,进一步改进如下:在FormMDIChild重载OnShown方法。
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
try
{
if (Application.OpenForms.Count>0) this.MdiParent = Application.OpenForms[0];
}
Catch{}//不处理异常
finally
{
base.OnShown(e);//确保能够打开
}
}
上述做法在考虑在设置MdiParent即使出现异常也能够将窗体正常显示,但是Catch所有异常而不处理不是一种好的做法,如果我们知道如何避免设置MdiParent而不出现异常那么就不需要这种恶心的以异常处理代替参数判断的方式。
在查询msdn,同时也使用Reflector查看MdiParent属性源码设置MdiParent时可能触发的异常包括:
分配给此属性的 Form 没有被标记为 MDI 容器。
- 或 -
分配给此属性的 Form 同时作为子 MDI 窗体和 MDI 容器窗体。
- 或 -
分配给此属性的 Form 位于其他线程上。
其实主要就是当前窗体必须是非MDI 窗体而上级窗体必须是MDI 窗体,所以,进一步修改的代码如下:
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
if (Application.OpenForms.Count > 0)
{
//顶层窗口存在
Form parent = Application.OpenForms[0];
//如果顶级窗口是MdiContainer,并且当前窗口不是MdiContainer
if (parent.IsMdiContainer && !this.IsMdiContainer)
{
this.MdiParent = parent;
}
}
}
虽然OnShown函数是在窗体显示之后触发,但是不影响效果。另一种做法是在将上述代码放置基类FormMDIChild的构造函数中,效果一样,而且更加能够保证该段代码得到调用。
这样,是不是更加完美o(∩_∩)o…欢迎高人指点进一步的改进。
总结:
IsMdiContainer属性,设置该属性为true(默认为false)表示该窗体为mdi窗体,mdi窗体特点就是该窗体关闭,其子窗体也都关闭。使用MdiParent属性设置窗体的mdi窗体,必须父窗体为mdi窗体但是子窗体是非mdi窗体。
避免使用异常处理作为程序逻辑处理,在调用api之前应该参考其有可能产生的异常说明,进行预先判断。
三、除非必须,否则不要使用new关键字重载基类同名方法,使用基类调用Show和子类调用Show方法的效果不一致,违反了里氏替换原则LSP:任何基类可以出现的地方,子类一定可以出现。
四、重构的目的之一是将公共的代码(方法块)集中,本次重构先将设置主窗体mdiparent属性的代码集中到窗体类,当各个窗体都需要该功能时又构造一个基类,将该方法提取到基类中。