【发布时间】:2011-11-01 00:50:31
【问题描述】:
上下文:
(注意:在下文中,我使用“项目”来指代面向单个客户或特定市场的软件可交付成果的集合。我不是指“项目”,因为它在 Visual Studio 中用于指在解决方案中构建单个 EXE 或 DLL 的配置。)
我们有一个相当大的系统,由三层组成:
- 包含跨项目共享的代码的层
- 包含在项目中的不同应用程序之间共享的代码的层
- 包含特定于项目中特定应用程序或网站的代码的层。
前两层内置在 DLL 程序集中。顶层是各种 EXE 和/或 .aspx Web 应用程序。
IIRC,我们有许多不同的项目使用这种模式。所有四个共享第 1 层(尽管版本通常略有不同,由 VCS 管理)。他们每个人都有自己的第 2 层。他们每个人都有自己的一组可交付成果,范围可以从一个网站、一个网站和一个后台服务,到我们最大和最复杂的(以及我们的面包和黄油)。业务),其中包括五个独立的 Web 应用程序、20 多个控制台应用程序/后台服务、三个或四个独立的 Web 服务、六个桌面 GUI 应用程序等。
我们打算将尽可能多的代码推送到第 1 层和第 2 层,以避免在顶层重复逻辑。我们已经做到了这一点。
第 1 层和第 2 层中的每一层都生成三个可交付成果,一个包含与 Web 无关的代码的 DLL,一个包含与 Web 相关的代码的 DLL,以及一个包含单元测试的 DLL。
问题:
编写较低级别是为了广泛使用单例。
第 1 层中的非 Web DLL 包含用于处理 INI 文件、日志记录、自定义构建的对象关系映射器(用于处理数据库连接等)的类。所有这些都使用单例。
当我们开始在网络上构建东西时,所有这些单例都成了问题。不同的用户会访问网站、登录并开始做不同的事情。他们会做一些生成查询的事情,这将导致调用单例 ORM 以获取新的数据库连接,该连接将访问单例配置对象以获取连接字符串,然后将要求连接执行询问。并且在查询中连接将访问单例记录器以记录生成的 SQL 语句,记录器将访问单例配置对象以获取当前用户名,以便将其包含在日志中,如果其他人已登录与此同时,单例配置对象将拥有不同的当前用户。真是一团糟。
所以当我们开始使用此代码库编写 Web 应用程序时,我们所做的是创建一个单例工厂类,它本身就是一个单例。其他每一个单例都有一个公共静态 instance() 方法,该方法一直在调用私有构造函数。相反,公共静态 instance() 方法获得了对单例工厂对象的引用,然后在其上调用一个方法来获得对相关类的单个实例的引用。
换句话说,我们现在有一个单独的类维护一个静态引用,而不是有十几个类,每个类维护一个单独的静态引用,并且它维护一个引用的对象包含十几个对其他以前的单例类。
现在我们只需要处理一个单例。在它的公共静态 instance() 方法中,我们添加了一些特定于 Web 的逻辑。如果我们有一个 HTTPContext 并且该上下文在其会话中具有工厂的实例,我们将从会话中返回该实例。如果我们有一个 HTTPContext,并且它的会话中没有工厂,我们将构建一个新工厂并将其存储在会话中,然后返回它。如果我们没有 HTTPContext,我们只需构建一个新工厂并返回它。
用于此的代码放置在我们从 Page、WebControl 和 MasterPage 派生的类中,然后我们在更高级别的代码中使用我们的类。
对于 .aspx Web 应用程序来说,这很好用,用户登录并维护会话。它适用于在这些 Web 应用程序中运行的 .asmx Web 服务。但它有真正的局限性。
特别是,它在没有会话的情况下不起作用。我们感到压力要提供服务于更大用户群的网站——可能有成千上万的用户动态地访问它们。到目前为止,我们的用户一直是非常典型的桌面业务用户。他们登录我们的网站,并在一天的大部分时间里呆在其中,使用我们的网络应用程序作为桌面应用程序的替代品。一个给定的客户可能有多达 6 个可能使用我们网站的用户,而我们有 1000 或更多的客户,它们加起来并不会造成那么大的负担。但我们目前的架构不会扩展到那个。
我们还遇到了 ASP.NET MVC 比 .aspx Web 表单更适合构建 Web UI 的情况。我们正在探索构建与独立 WFC Web 服务通信的移动应用程序。虽然在这两种情况下,看起来都可以在有会话的环境中运行它们,但看起来相当严重地限制了它们的灵活性和性能。
所以,我们确实在寻找消除这些单例的方法。
我真正想要的:
我正在尝试设想一系列重构,最终将导致结构更好、更灵活的架构。在我们的情况下,我可以很容易地看到 IoC 框架的优势。
但事情是这样的——从我所看到的 IoC 框架来看,它们需要通过构造函数参数从外部提供它们的依赖项。例如,我的记录器类需要我的配置类的一个实例,从中获取当前用户。目前,它是使用 config 类上的 public static instance() 方法来获取它。要使用 IoC 框架,我需要将其作为构造函数传递。
换句话说,从我的角度来看,第一个也是不可避免的任务是更改每个使用这些单例的类,以便将单例工厂作为构造函数参数。这是大量的工作。
举个例子,我刚刚花了一个下午在 1 级图书馆做这件事,看看它做了多少工作。我最终更改了 1300 多行代码。 2级库会更差。
那么,还有其他选择吗?
【问题讨论】: