一个糟糕的设计

      3月份开始,就开始做一个WinForm的项目,现在快要收工了。这是偶参与的第一个Windows应用。这也是一个典型的三层应用,现在也到了开始总结经验教训的时候了,温故而知新嘛。

今天要总结的是其中一个糟糕的设计,这个设计存在于一个数据访问类中,至今没有被解决掉因为修改设计需要修改大量的代码,而且不修改设计的话,我们的软件确实可以工作至少目前是这样的。

 

这个类的类图大概是这样的

 

为了简单,我们省略了其中存在的大量的public方法(包括执行存储过程、获取DataSetDataReader的大量方法和重载方法)和一些异常处理代码。图中的Instance属性实际上实现了Singleton模式。

其中,我们已ExecuteNoneQuery的执行过程为例

 
 

从图中我们可以看出,方法首先调用this.Open()来打开数据库连接,然后执行任务,最后调用this.Close()来关闭连接。

这看起来没有什么问题,调用起来也很方便,直接CommonDataAccess.Instance.ExecuteNoneQuery就可以了,而且,由于采用了单体模式,也带来了一些其他一些好处,比如内存使用上的。事实证明,很久以来,我们都对这种模式相当满意,但是随着项目的推进,问题慢慢浮现了,由于我们的系统经常要同远端的WebServive服务连接,为了解决用户响应上的问题,我们开始使用多线程我们把长时间之行的任务放到新的线程里面执行,这些任务里面当然也包括数据访问。那么问题就是,我们的数据访问类是为单线程应用设计的,在引入其他线程以后,它开始出现不同线程访问同一数据连接在多数情况下这将导致并发错误!也就是说,当我的连接在执行一项查询的时候,如果另外一个线程使用它执行另外一个查询或者是更新,将出现错误。

我们试着修改这个类,让它可以工作在多线程环境中。但是我们发现要修改的代码非常惊人,几乎每一个使用this.Open()和使用数据连接的地方都需要更改。我们比较懒,所以我们决定只修改了一处那就是Instance属性的get访问器。

 

原先的代码是这样的

       

        CommonDataAccess instance ; 

        
public CommonDataAccess Instance
        

              
get 
              

                   
if( instance == null ) 
                       instance 
= new CommonDataAccess();   

                   
return instance ; 
              }
 
     }
 

 

修改以后成了这样的

      

         CommonDataAccess instance ; 

         
public CommonDataAccess Instance
         

              
get 
              

                   
/* 

                       if( instance == null ) 

                       instance = new CommonDataAccess(); 

                   
*/
 
  

                   
return new CommonDataAccess(); 
              }
 

     }
 

不许笑,这确实解决了多线程的问题,但再也不是Singleton了,倒是成了不断生产新产品的Factory了,而且即使在一个线程内部,我们使用Instance属性的时候,都创建了新的CommonDataAccess实例,而在我们写那些代码的时候,完全没有预料到。

这真是一个糟糕的设计,那么应该有改良的办法吧。

类图:




ExecuteNoneQuery的序列图

 

 

如果你还没有明白,那还是看代码吧:

 

 public void ExecuteNoneQuery(string cmdText)
         

              
using ( IDbConnection conn = this.Open() ) 
              

                   IDbCommand cmd 
= new SqlCommand(cmdText); 
                   cmd.Connection  
= conn;  

                   cmd.ExecuteNonQuery(); 
              }
 
     }
   

         IDbConnection Open() 
         

              IDbConnection conn 
= new SqlConnection(connStr); 
              conn.Open(); 
              
return conn ; 
         }
 

  

        也就是说,每次Open的时候,产生新的连接,我们不必担心new SqlConnection的效率,事实上,使用Ado.net,连接池是DotNetFrameWork自动维护的,从连接池中获取连接的速度也非常快。如果你愿意的话,你甚至可以在Open()方法中使用自己的连接池逻辑。

posted @   quitgame  阅读(808)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述
点击右上角即可分享
微信分享提示