很多时候(似乎昨天还有人在QQ上问Delphi怎么定义全局变量…),很多人都喜欢用全局变量来保存一些数据, 这样可以随时随地的调用他们。这样看似方便(实际上也很方便),但这个时候你却为自己埋下了很深的隐患。
全局,顾名思义就是在任何地方都可以访问到的,也是任何机制都可以修改或显示的(当然你可以对它加一些限制,但这些限制终归也是全局的),那么这个时候,它就成了一个不可控量。所谓不可控量简单的说就是一个量的的状态未知。
OOP设计中是坚决杜绝不可控量的存在的,因为不可控量的存在会破坏数据的封装性以及可维护性。
我们都有过这种体验:一个全局字段被设计出来,任何时候它都会被更改,但随着时间的推移,我们不得不使用越来越多的记忆细胞来记忆到底那里用到了它,而在设计是还要考虑对它的修改会影响到哪里的调用。往往这个时候是让人最抓狂的时候。最常见的情况就是我们在ASP/ASP.NET中使用的Session变量,很多人都会直接操作Session变量,并通过特征字符串来访问它,这就给这个程序带来了很大的不可控性。
另一种情形:一个操作需要一个生命周期很长的变量来进行,于是你在上面提到的建议下把它设计成了类,嗯,这是很好的开始,至少对于这个变量来说可能是可控的了,但是考虑到变量的生命周期,你把它设计成了静态类,呜,似乎问题被圆满解决了,全局和生命周期都兼顾了。可是接下来问题又来了,随着设计的往下进行,你发现原先设计的静态类需要修改,还有若干类似功能的类也要写出来,那么这个时候,你想到了继承…于是…你得到了编译器的友情提示:静态类不能被继承…这个时候,键盘会变成什么形状我们不得而知
上述两种情况在很多初学者甚至有丰富经验的人中出现频率很高,这在OOP的思想里是无法被接受的。
针对上述两种情况,一个被暂时称为“悬挂式全局”的设计方式可以比较好的解决。
所谓的悬挂式,是说把一个全局的逻辑或者数据“委托”给一个可控的量来管理。比如对于这样一个设计:
用户登录验证是我们最常用的功能模块,这个模块的设计一般是结合Session变量来设计的。首先我们会在登录的时候将数据存入Session,在使用的时候又读取Session中的数据,但是考虑到上述的问题,我们并不直接使用它。
在这里,我们设计两个类:
TRoleData,TRoleSession
其中TRoleData封装了与用户有关的所有的数据及其操作,TRoleSession则只是实现了对TRoleData的封装以及对一个特有Session变量的维护,如下代码:
…{
const string ROLE_KEY="SESSION_ROLE_KEY";
public void abandon()
…{
Session[ROLE_KEY] = null;
}
public TUserInfo LoginUser
…{
get
…{
return ((TRoleData)(Session[ROLE_KEY]));
}
}
public void login( string userid, string password )
…{
//————-
//登录验证代码
//————-
Session[ROLE_KEY] = xxx;
}
public bool hasLogin
…{
get …{
return Session[ROLE_KEY] != null;
}
}
}
上述的ROLE_KEY可以使用MD5或者GUID来生成此对象的唯一标识符,以避免其他的地方可能会通过标识字段修改Session的值,TRoleSessionl将危险的Session变量隐藏起来,只向外界提供必需的数据接口,这样TRoleData就只能通过TRoleSession来访问和控制了,最大程度的避免了全局变量带来的程序不可控性,另外TRoleData可以自由扩展,这个数据对象就像悬挂在了Session上。
一般来说,在MVC式设计方式当中,Model和View的没有实体逻辑及业务操作,它们不可以被有效的控制,所以是无法作为全局变量出现的,只有Control才能够凭借自身的可控性来管理上述两种数据的状态。那么由OOP原则来看,将Control也就是控制逻辑元素来作为全局是最理想的,“悬挂式全局”设计方式正是利用了这一点。
这种悬挂式全局有一个原则:
<br> 常量必须仅仅跟该类紧密耦合,适用于表示该类的状态。
<br> 而很多时候,常量还有别的用途,比如表示流程状态,一个继承体系的共享状态,此时这些常量需要导出,也就是全局的常量。
<br> 那么建议导出到一个不会被实例化的类中。
<br> 另外一个建议就是如非必要不要使用字符串,使用2进制数字,这样我们的状态可以叠加。