2011-12-09 21 views
4

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

+0

代码中定义的单例总是错误的。单身人士在高层次文件中定义为“只能有一个此类的实例”是可以接受的。 – Raynos

回答

4

单身并不一定问题 - 有一个单一的对象,它是在其领域的专家是非常有用的 - 但明确地编码单岬到对象几乎总是一个坏主意(并且经常做得不好太)。事实证明,将应用程序的配置(包括对哪些类具有单例实例化的决定)保留到专门用于此的单独层(如Spring for Java)上会更好。像这样,管理层可以管理什么是单身人士,什么是特定背景下的单身人士(例如,在会话范围内)以及总是需要重新制造的单身人士。这使您可以专注于编写业务逻辑。

有关单例可能会出现问题的原因以及管理/配置应该是单例的原因的示例,请考虑管理与数据库的连接的类。你可能会说一个单例的经典案例。你也可以,直到你发现你的应用程序已经发展到必须将连接集成到两个数据库(它发生了!),然后你必须解决整个混乱。如果你已经保存了你的代码不知道它是否处理单例,你很有可能通过重新配置来处理它;一些课程将连接到一个数据库,另一些则连接到另一个数据库,但他们只会用最小的麻烦继续进行。 (任何事情都需要......嗯,这就是为什么我说“很好的机会”。)

当你为你的代码编写测试时,为什么Singletons可能会出现问题的另一个例子。 (你会写测试,是吗?)显式单例测试非常困难,因为它们很难配置并且很难分离。测试之间不能正确地撕下它们,因为这意味着它们中有很多。如果您的应用程序仅通过配置使用单身人士,测试配置可以轻松更改,并且您可以更轻松地完成测试。

1

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

正如旁注所示,Java企业版的最新版本JEE6现在包含用于Singleton模式的support作为EJB组件。但想一想,花了六个修订标准终于完成!

1

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

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

总之,我的回答是肯定的,使用太多的单例是不好的。考虑使用DI原理。

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; 
} 

在这个例子中,工厂确保封装,而单回收已实例的数据库对象。

在一天结束时,取决于您和您想要支持的道路。