【问题标题】:Global vs Singleton in .NET.NET 中的全局与单例
【发布时间】:2009-02-02 17:23:00
【问题描述】:

我这里有一个很常见的情况。多年来我一直没有发现我正在做的事情是否符合行业标准。考虑一个连接到数据库的应用程序,但是连接字符串而不是存储在某些文件/设置中作为命令行参数传递在启动时或在应用程序启动时浏览到数据库。

那么有必要将该连接字符串保存在应用程序范围内的某个位置。我见过的最常见的方法是使用 get/set 方法保存连接字符串的模块或全局类。我会这样做的另一种方法是使用单例。 我的 DAL 可以在需要时通过 GetConnectionString 方法访问连接字符串。

有更好的方法吗?

更新:我没有配置文件,即使有,我也需要在应用程序实例的生命周期内读取一次连接字符串。您能否详细说明“将其注入任何类”部分

【问题讨论】:

    标签: .net singleton global


    【解决方案1】:

    在一般的全局状态中,无论是全局类还是单例,都应尽可能避免。

    理想的解决方案是让您的应用程序从配置中加载连接字符串并将inject 加载到任何需要它的类中。根据应用程序的大小,IoC 容器(如 UnityCastle Windsor)会有所帮助,但肯定不是解决方案的必需部分。

    如果这不是一个选项,并且您被困在维护全局状态(由于现有代码库或其他原因),我不知道您建议的两种方法之间存在巨大差异。

    更新: 澄清一下,暂时忘记关于 IoC 容器的所有内容,“Inject”只是“作为参数传入”的一种奇特方式(对于类的构造函数,或通过属性,或其他方式)。

    与其让数据访问类必须请求连接字符串(来自某种全局或单例),不如通过构造函数或属性传入。

    更新 #2:我认为对于这种方法的含义仍有一些误解。

    这基本上归结为您的数据访问类是否如下所示:

    public class DataAccessClass
    {
        public DataAccessClass()
        {
            _connString = SomeStaticThing.GetConnectionString();
        }
    }
    

    public class DataAccessClass
    {
        public DataAccessClass(string connString)
        {
            _connString = connString;
        }
    }
    

    Thesearticles(事实上,该博客中的许多文章)详细说明了后者优于前者的许多原因(尤其是因为前者几乎不可能进行单元测试)。

    是的,在某个地方,首先必须有一些静态人员负责获取连接字符串,但关键是您对静态方法的依赖仅限于此一个位置(这很可能是您启动应用程序过程中的 Main 方法),而不是遍布整个代码库。

    【讨论】:

    • @DannySmurf 我不同意......当然在一个小项目中涉及一个 IoC 容器是矫枉过正的,但只是重构您的数据访问类以将连接字符串作为参数?怎么设计过度了?
    • @[John Price]:这在理论上听起来不错,但我不知道有谁实际上是这样做的——你呢?如果是这样,你是怎么做的,特别是。因为内置的 ADO 类不直接支持它(有些假设配置文件连接字符串,这使得它更加困难)
    • 另外,“传入”的东西在哪里得到连接字符串?也许来自...静态类?全部的。该死的。矫枉过正。
    • @John Price:IoC 的东西是过度设计,而不是配置文件解决方案。我对此的问题是,即使是针对一个相当简单的问题提及复杂的东西,闻起来就像火车一样对项目产生影响的设计模式到遗忘。
    • @DannySmurf 我想我看不出我上面的第二个示例(它使用 IoC 方法)比我的第一个示例更复杂;这只是一种不同的方式来处理数据如何到达它需要的地方,并且不需要任何额外的代码。 “IoC”不一定意味着“IoC 框架”。
    【解决方案2】:

    很高兴看到这么简单的问题有这么多创造性的解决方案;-)

    显着事实,来自 OP 问题:

    1. 连接字符串在命令行中传递
    2. 许多其他组件可能需要使用连接字符串

    所以,没有办法绕过使用静态元素;无论是全局变量(在 .NET 中很难做到,真的,没有封闭类)、静态类还是单例都无关紧要。最短路径解决方案是由处理命令行的 Program 类初始化的静态类。

    所有其他解决方案仍然需要对传入的连接字符串进行静态访问,尽管它们可能会将其隐藏在一个或多个间接层之后。

    我并不是说你不想用更花哨的东西来修饰基本解决方案,但这不是必需的,它并没有消除连接字符串的基本静态/全局性质as描述

    【讨论】:

    • +1。不喜欢装点门面,尤其是像应用偏好这样简单的东西。在字符串存储之上增加复杂性(和间接性,以隐藏一个解决方案实际上只是一个全局的事实)只是愚蠢的。
    【解决方案3】:

    如果您要存储的只是一个字符串(可能还有一堆其他全局应用程序设置),我会使用带有一堆属性的静态类来保存所有这些。单例需要更多的代码和维护工作,但在这种情况下这是不必要的工作,因为您的属性类不会做任何事情;拿着东西就行了。

    【讨论】:

    • 我认为这不应该被投票否决。他有一个很好的观点,因为简单的静态数据可能比单例更可取。当然,我们大多数人会走得更远,完全删除全局,但就单例和全局之间的选择而言,我同意这一点。
    • 您已根据问题回答,但实际上这两种解决方案都不是最好的/更好的。
    • +1 考虑到问题的情况,这个答案是正确的
    【解决方案4】:

    当然有办法。首先,快速了解dependency injection 和类似技术,并阅读有关service layer 的信息,因为这正是您需要的。

    就服务层而言,您需要某种配置服务,这将是您的应用程序的一部分将查询配置信息的模块 - 连接字符串、URI 等:

    interface IConfigurationService
        string ConnectionString
        bool IntegratedSecurity
        bool EncryptProfiles
    

    您的系统中只有一个 IConfigurationService 实例应该很简单,但这是通过选择的 DI 容器实现的,而不是单例模式。

    接下来,您的所有 DAL 服务都将获得对 IConfigurationService 的引用:

    class FooDal : IFooDal
        IConfigurationService ConfigurationService
    

    以便他们现在可以使用IConfigurationService.ConnectionString 来获取连接字符串。

    【讨论】:

    • 老兄。他询问全局或单例是否会更好地保持连接字符串。我无法想象如何添加“配置服务”以及它自己无用的接口,总共增加两层复杂性可能是存储字符串的正确方法。 -1.
    • erm... 在您的应用程序中拥有单个 DI 容器与拥有全局数据或将全局数据包装在单个对象中没有真正的区别 - 也称为单例 :) 它确实让我感到惊讶开发人员如何为简单的任务想出各种复杂的解决方案。
    【解决方案5】:

    这取决于。

    请记住,单例:

    • 强制该类的一个实例将存在,并且
    • 提供全球访问权限

    全局只提供全局访问,但不保证实例化的数量。

    最后的选择当然是使用局部变量。也就是说,将配置信息作为参数传递给构造函数或任何需要它的地方。

    在前两者之间进行选择应该有点容易。 如果该类的两个实例存在,那会是一场灾难吗? 我会说可能不会。我可能想实例化我自己的配置类来为应用程序的一个特定组件提供单独的选项,或者初始化我的单元测试。

    在极少数情况下,您需要保证只存在一个实例。因此,全局可能比单例更好。

    本地和全局之间的选择比较棘手。全局可变状态通常最好避免。 不可变状态的问题较少,不会导致与全局可变状态相同的同步/并发/可伸缩性问题。

    但通常,最好将其作为局部变量传递给需要它的组件。这可以手动完成,只需将其传递给需要 DB 访问的对象的构造函数,或者在某种程度上可以使用 IoC 容器自动完成。

    但无论如何,如果它不是全局的,那么您的代码就会变得更通用,更可移植。如果它对代码工作必须存在的类和全局数据具有隐藏的依赖关系,那么它就不能轻易地用于其他项目,或者如果你过多地重构整个代码库。 p>

    单元测试也变得更加困难,因为代码甚至无法编译,除非存在一些我们理想情况下希望从测试中忽略的外部数据。

    【讨论】:

      【解决方案6】:

      正如@Anton 所说,您需要有一个接口来公开类似

      的内容
      interface IConfigurationService
          string ConnectionString
      

      然后,每当您的某个类需要您的连接字符串时,您就可以在包含有效字符串的构造时为其提供 IConfigurationService 的实现。您需要在应用程序启动时(可能是您获取数据库地址的时间)找到一个有效的位置来创建您的实现,并将其传递给需要它的类。

      尽管与单例或全局相比,这似乎需要做很多工作,但这会降低代码中的耦合度,从而提高可重用性并使单元测试更容易,并且一旦你说服自己全局(大部分)是邪恶的,这将非常简单: )

      如前所述,有 IoC 容器可以提供框架来执行此操作,但在您的情况下这可能有点过头了,而且在将工作交给“魔术盒”之前自己使用该模式也不错

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多