又一个“众所周知”的DAL层设计BUG
DAL层使用抽象工厂是大家再熟悉不过的设计方法了。最近在回顾项目的时候,发现网上流传的方法大多都存在一个不大不小BUG。对于整个系统而言,轻则需要重新更新配置,重则需要重启。
好了废话不多说,先看代码
private static void GetProvider() { try { _instance = (IDataProvider)Activator.CreateInstance(Type.GetType(string.Format("xxxx", ConnTypeString), false, true)); } catch { _instance = null; throw new Exception(); } } public static IDataProvider GetInstance() { if (_instance == null) { lock (_lockHelper) { if (_instance == null) { GetProvider(); } } } return _instance; }
我相信绝大多数的抽象工厂都是这样写的。因为设计模式的书上也是这样写的,网上能够找到的例子也是这样写的,许多大型项目(譬如DZNT)也是这样写的。
我为什么说这样的设计存在BUG呢?
这还要回到我们使用反射+配置文件+抽象工厂的DAL层的设计初衷。我们不希望把DAL使用何种数据库写死在代码中,而是希望灵活的切换数据库。回头看看代码,似乎没有发现什么问题。再细想“灵活切换数据”,在系统正在运行的时候切换数据库可以吗?答案是不可以。因为系统启动之后instance已经被初始化了,当你再次修改配置信息的时候就不会再次反射出新的instance。所以这里存在这样一个BUG。
修改之后的代码:
public static IDataProvider GetInstance() { string connType = Configs.GetConfigs().DbConfigInfo.ConnType; if (_currentConnType != connType || _instance == null) { lock (_lockHelper) { if (_currentConnType != connType || _instance == null) { _currentConnType = connType; GetProvider(); } } } return _instance; }
你在看完以后可以有种种理由反驳我
- “我在修改配置文件的时候直接重置instance就可以了”
- “我的程序只需要在启动的时候判断使用什么数据库就足够了,在运行时不会切换数据库”
- “切换数据库必须保证数据安全,需要关闭系统,重新备份数据库再启动系统”
我并没有充足的理由回应你的驳斥。后两个观点更有理由让这个BUG存在。的确,在系统中我们没有必要考虑到每一个细节,只要能够让系统按照需求运行正常即可。所以我只想针对第一点说说我的看法。首先DZNT(老版本,新版代码未阅读)就是这样做的,提供了一个Reset重置instance。但这样设计似乎违背了“单一职责原则”,“一个类,最好只做一件事,只有一个引起它变化的原因”。所以在修改配置的时候就不应该去管DAL的事情,DAL的事情应该由DAL自己解决。同时也避免了在配置完数据库类型之后忘记Reset引起的BUG。