项目中的单身人士太多了 - 这是一种不好的做法吗?

时间:2011-12-09 03:47:44

标签: class design-patterns architecture singleton anti-patterns

在几乎每个项目中,我都创建了一些实现Singleton模式的类。例如,数据管理器 - 如果有一些工作与文件系统,数据加载器 - 如果应用程序连接到互联网,不同的资源管理器等。有时有多达5-7这样的类,我开始觉得我在做有问题。单独使用Singleton模式是不是很糟糕?

4 个答案:

答案 0 :(得分:4)

单身人士不是必然问题 - 拥有一个属于其领域专家的单个对象可能非常有用 - 但明确将单身人士编码到对象中几乎总是一个坏主意(和经常做得很糟糕)。事实证明,最好将应用程序的配置(包括具有单例实例化的类的决定)留给专门用于其的单独层,例如Spring for Java。像这样,管理层可以管理什么是单例,什么是特定上下文中的单例(例如,在会话的范围内)以及总是需要重新制造的内容。这使您可以专注于编写业务逻辑。

有关为什么单身人士可能成为问题以及为什么他们应该通过管理/配置成为单身人士的示例,请考虑管理与数据库的连接的类。您可能会说一个单身人士的经典案例。你也是对的,直到你发现你的应用程序已经发展到必须将连接集成到两个数据库(它发生了!)然后你必须解开整个混乱。如果你已经你的代码不知道它是否与单身人士打交道,那么你很有可能通过重新配置来处理它们;有些类会连接到一个数据库,有些类会连接到另一个数据库,但是他们只会轻松地继续使用它。 (任何需要两者的东西......好吧,这就是我说“绝佳机会”的原因。)

当您为代码编写测试时,另一个可以解决Singletons问题的例子。 (您确实在编写测试,是吗?)显式单例非常难以测试,因为它们很难配置且难以隔离。你不能在测试之间正确地拆掉他们,因为这意味着他们有很多。如果您的应用仅通过配置使用单例,则测试配置可以轻松更改,您可以更轻松地进行测试。

答案 1 :(得分:1)

关于这个话题有很多讨论。对于某些人来说,Singleton被认为是anti-pattern。然而,它可以非常有用,但它似乎比看起来更难以正确实现。在某些情况下,它的使用是合理的,例如,对于整个应用程序共有的数据具有唯一的入口点 - 并且其中存在棘手的部分,它是伪装的全局状态,当且仅当数据是不可变的时才安全使用。

另外,JEE6是Java企业版的最新版本,现在包含support作为EJB组件的Singleton模式。但是考虑一下,它花了六个标准的修订版来最终完成它!

答案 2 :(得分:1)

由于TDD取而代之,程序员们了解到测试单例是一种痛苦,并开始避免它。通过注入所需的类/资源(例如连接管理器,资源管理器等)可以实现相同的效果。这样,在测试环境中,这些类可以简单地被模拟,注入和覆盖测试。

另一方面,在某些情况下,只使用单身似乎是正确的方法 - 确保只存在一个实例。即它对连接池很有用,它保证当时只存在一个实例,并且不存在泄漏。 (注意:并非所有单例模式的实现都能真正提供这一点。在Java中,正确的方法是使用枚举 - 因为语言本身确保了它的唯一性。)

总之,我的回答是肯定的,使用过多的单身人士是不好的。请考虑使用DI原则。

答案 3 :(得分:0)

关于单身人士是一个反模式的有趣文章,虽然我不太同意。我从来没有使用单例作为一个独立的解决方案,我总是将它与factory模式相结合,我认为这可以忽略状态和封装的论点。

单例/工厂解决方案的一个很好的例子是数据库类。您可能有多个数据库,每个数据库都需要自己的连接,但您不希望每次调用都实例化并创建新连接。您希望回收共享连接以避免“太多连接”地雷。

有些事情:

/**
 * Database manager for handling database related stuff
 * Does use Zend_Db and Zend_Config
 */
class Database_Manager
{
    protected static $dbos = array(); // Protected scope enforces encapsulation

    public static function getDbo($name)
    {
        // Init
        $hash = md5($name);

        // Attempt to use singleton     
        if (array_key_exists($hash, self::$dbos)) {
            return self::$dbos[$hash];
        }

        // Your db connection settings are set here either via
        // switch/case based on name, or loaded from a config file (yaml, xml, etc)
        $dbConnectionParams = array('your', 'connection', 'settings');

        $config = new Zend_Config($dbConnectionParams);

        $dbo = Zend_Db::factory($config->database);

        // Adding to singleton so can be referenced in future calls
        self::$dbos[$hash] = $dbo;

        return $dbo;
}

在此示例中,工厂确保封装,而单例则回收已经实例化的数据库对象。

在一天结束时,这取决于你和你想要支持的事情。