一个糟糕的设计
今天要总结的是其中一个糟糕的设计,这个设计存在于一个数据访问类中,至今没有被解决掉—因为修改设计需要修改大量的代码,而且不修改设计的话,我们的软件确实可以工作—至少目前是这样的。
这个类的类图大概是这样的
为了简单,我们省略了其中存在的大量的public方法(包括执行存储过程、获取DataSet、DataReader的大量方法和重载方法)和一些异常处理代码。图中的Instance属性实际上实现了Singleton模式。
其中,我们已ExecuteNoneQuery的执行过程为例
从图中我们可以看出,方法首先调用this.Open()来打开数据库连接,然后执行任务,最后调用this.Close()来关闭连接。
这看起来没有什么问题,调用起来也很方便,直接CommonDataAccess.Instance.ExecuteNoneQuery就可以了,而且,由于采用了单体模式,也带来了一些其他一些好处,比如内存使用上的。事实证明,很久以来,我们都对这种模式相当满意,但是随着项目的推进,问题慢慢浮现了,由于我们的系统经常要同远端的WebServive服务连接,为了解决用户响应上的问题,我们开始使用多线程—我们把长时间之行的任务放到新的线程里面执行,这些任务里面当然也包括数据访问。那么问题就是,我们的数据访问类是为单线程应用设计的,在引入其他线程以后,它开始出现不同线程访问同一数据连接—在多数情况下—这将导致并发错误!也就是说,当我的连接在执行一项查询的时候,如果另外一个线程使用它执行另外一个查询或者是更新,将出现错误。
我们试着修改这个类,让它可以工作在多线程环境中。但是我们发现要修改的代码非常惊人,几乎每一个使用this.Open()和使用数据连接的地方都需要更改。我们比较懒,所以我们决定只修改了一处—那就是Instance属性的get访问器。
原先的代码是这样的














修改以后成了这样的





















不许笑,这确实解决了多线程的问题,但再也不是Singleton了,倒是成了不断生产新产品的Factory了,而且即使在一个线程内部,我们使用Instance属性的时候,都创建了新的CommonDataAccess实例,而在我们写那些代码的时候,完全没有预料到。
这真是一个糟糕的设计,那么应该有改良的办法吧。
类图:
ExecuteNoneQuery的序列图
如果你还没有明白,那还是看代码吧:





















也就是说,每次Open的时候,产生新的连接,我们不必担心new SqlConnection的效率,事实上,使用Ado.net,连接池是DotNetFrameWork自动维护的,从连接池中获取连接的速度也非常快。如果你愿意的话,你甚至可以在Open()方法中使用自己的连接池逻辑。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述