Repository: Singletone, Static or none?

  这两天都在研究组里产品的架构。TMS的数据访问层采用的是Repository设计模式,现在发现一个问题。

  上图是某个业务对象对应的Repository类。可以发现在每执行一次查询操作时,都会生成SelectionFactory和GetFactory的实例。而SelectionFactory的逻辑很简单,只是按照参数生成相应的DbCommand,如下图:

  假如我们把所有的SelectionFactory和GetFactory都单例化,或者干脆直接使用静态方法,那么无论有多少请求都只会创建一次类的实例,比如像下面这样:

  这样做会有什么问题呢?通过Google,得到如下结论:

  首先有人提到,生成一大堆类的开销并不大:Instantiating a Repository isn’t an expensive operation especially when measured against the normal round trip time of a web request.

  其次,不使用静态类和单例类主要是基于以下几点考虑:

  • Consumers of repository will be coupled to repository implementation. This will negatively affect extensibility and testabiliy. This is a DIP violation. Depend on abstractions, not on concretions.
  • Repository implementation will have to violate SRP because it will most likely end up managing ORM session, database connection and potentially transactions. It would also have to make thread safe guarantees because it potentially can be used from multiple threads. Instead, database connection (ORM Session) should be injected into repository implementation so that consuming code can arrange multiple repository calls into transaction.

  还有人说:

  Pros:

  • Repository is accessible everywhere

  Cons:

  • Repositories don’t implement a contract which leads to a strong coupling between consumers of the repository and the implementation
  • Impossible to unit test
  • Might run into threading issues

  Remark: Instantiating the repository on every request shouldn’t be regarded as a performance issue.

  同时不scalable:

  但也有人说:

  I personally respectfully disagree with previous answers.

  I have developed multiple websites (one with 7 millions page views per month) and never had a single problem with my static repositories.

  My static repository implementation is pretty simple and only contains objects providers as properties. A single repository can contain as many providers as you need.

  Then, the providers are responsible to manage database connection and transactions. Using TransactionScope, the consumer could manage the transactions or let it to the providers.

  Every providers are developed using the singleton pattern.

  This way, I can get my objects by simply calling this :

  varmyObj =MyRepository.MyProvider.GetMyObject(id);

  At any time, there is only one repository and one provider of each type in every web pool of my application. Depending on how many users you have at the same time on your site, you could set more than one web pool (but most of the time one is just enough).

  I don’t see where my repository/providers consumers are coupled with my repository. In fact, the implementations of my providers are totally abstracted from them. Of course, all providers returned by my repository are interfaces and I could easily change the implementation of them at any time and push my new dll on the web server. If I want to create a completely new provider with the same interface, I only have to change it in ONE place: my repository.

  This way, no need to add dependency injection or having to create your own ControllerFactory (for MVC procjects).

  And you still have the advantage of clean code in your controllers. You will also save a lot of repository creation and destruction every time a page is requested (which normally use reflection in your ControllerFactory).

  If you are looking for a scalable solution (if you really need it which most of the time is not really a problem), my way of developing repositories should never be a problem compared to dependency injection.

对于不推荐Singleton和static的人,大多主要还是关心Dependency Injection:Dependency injection is a software design pattern that allows a choice of component to be made at run-time rather than compile time. This can be used, for example, as a simple way to load plugins dynamically or to choose mock objects in test environments vs. real objects in production environments. This software design pattern injects the depended-on element (object or value etc) to the destination automatically by knowing the requirement of the destination. Another pattern, called dependency lookup, is a regular process and reverse process to dependency injection.(摘自维基百科)。

Sometimes a static methods is a factory for other objects. This further exuberates the testing problem. In tests we rely on the fact that we can wire objects differently replacing important dependencies with mocks. Once a new operator is called we can not override the method with a sub-class. A caller of such a static factory is permanently bound to the concrete classes which the static factory method produced. In other words the damage of the static method is far beyond the static method itself. Butting object graph wiring and construction code into static method is extra bad, since object graph wiring is how we isolate things for testing.

  所以既然生成很多的实例对象不会有性能上的问题,那么还是最好不要使用单例或者静态类?

Like we all say, I’d say “it depends.”. Like Paul mentions in his answer, it’s good to use Dependency Injection–but whether you do or not doesn’t really have bearing on the core of your question.

  PS:对象数量确实不是一个大问题,我们的程序总是产生一大堆的对象。

✏️ 有任何想法?欢迎发邮件告诉老夫:daozhihun@outlook.com