Java模式设计之单例模式(二)

 

登记式单例类

  登记式单例类是GoF 为了克服饿汉式单例类及懒汉式单例类均不可继承的缺点而设计的。本书把他们的例子翻译为Java 语言,并将它自己实例化的方式从懒汉式改为饿汉式。只是它的子类实例化的方式只能是懒汉式的,这是无法改变的。如下图所示是登记式单例类的一个例子,图中的关系线表明,此类已将自己实例化。

  

  代码清单3:登记式单例类

  import java.util.HashMap;public class RegSingleton { static private HashMap m_registry = new HashMap();static { RegSingleton x = new RegSingleton();m_registry.put( x.getClass()。getName() , x);} /** * 保护的默认构造子*/ protected RegSingleton() {} /** * 静态工厂方法,返还此类惟一的实例*/ static public RegSingleton getInstance(String name)

  { if (name == null)

  { name = "com.javapatterns.singleton.demos.RegSingleton";} if (m_registry.get(name) == null)

  { try { m_registry.put( name,Class.forName(name)。newInstance() ) ;} catch(Exception e)

  { System.out.println("Error happened.");} return (RegSingleton) (m_registry.get(name) );} /** * 一个示意性的商业方法*/ public String about()

  { return "Hello, I am RegSingleton.";}它的子类RegSingletonChild 需要父类的帮助才能实例化。下图所示是登记式单例类子类的一个例子。图中的关系表明,此类是由父类将子类实例化的。

  

  下面是子类的源代码。

  代码清单4:登记式单例类的子类

  import java.util.HashMap;public class RegSingletonChild extends RegSingleton { public RegSingletonChild() {} /** * 静态工厂方法*/ static public RegSingletonChild getInstance()

  { return (RegSingletonChild)

  RegSingleton.getInstance("com.javapatterns.singleton.demos.RegSingletonChild" );} /** * 一个示意性的商业方法*/ public String about()

  { return "Hello, I am RegSingletonChild.";}

  在GoF 原始的例子中,并没有getInstance()方法,这样得到子类必须调用的getInstance(String name)方法并传入子类的名字,因此很不方便。本章在登记式单例类子类的例子里,加入了getInstance()方法,这样做的好处是RegSingletonChild 可以通过这个方法,返还自已的实例。而这样做的缺点是,由于数据类型不同,无法在RegSingleton 提供这样一个方法。由于子类必须允许父类以构造子调用产生实例,因此,它的构造子必须是公开的。这样一来,就等于允许了以这样方式产生实例而不在父类的登记中。这是登记式单例类的一个缺点。

  GoF 曾指出,由于父类的实例必须存在才可能有子类的实例,这在有些情况下是一个浪费。这是登记式单例类的另一个缺点。

  登记式单例类

  登记式单例类是 GoF 为了克服饿汉式单例类及懒汉式单例类均不可继承的缺点而设计的。本书把他们的例子翻译为Java 语言,并将它自己实例化的方式从懒汉式改为饿汉式。只是它的子类实例化的方式只能是懒汉式的,这是无法改变的。如下图所示是登记式单例类的一个例子,图中的关系线表明,此类已将自己实例化。

  

  代码清单3:登记式单例类

  import java.util.HashMap;public class RegSingleton { static private HashMap m_registry = new HashMap();static { RegSingleton x = new RegSingleton();m_registry.put( x.getClass()。getName() , x);} /** * 保护的默认构造子*/ protected RegSingleton() {} /** * 静态工厂方法,返还此类惟一的实例*/ static public RegSingleton getInstance(String name)

  { if (name == null)

  { name = "com.javapatterns.singleton.demos.RegSingleton";} if (m_registry.get(name) == null)

  { try { m_registry.put( name,Class.forName(name)。newInstance() ) ;} catch(Exception e)

  { System.out.println("Error happened.");} return (RegSingleton)(m_registry.get(name) );} /** * 一个示意性的商业方法*/ public String about()

  { return "Hello, I am RegSingleton.";}它的子类RegSingletonChild 需要父类的帮助才能实例化。下图所示是登记式单例类子类的一个例子。图中的关系表明,此类是由父类将子类实例化的。

  

  下面是子类的源代码。

  代码清单4:登记式单例类的子类

  import java.util.HashMap;public class RegSingletonChild extends RegSingleton { public RegSingletonChild() {} /** * 静态工厂方法*/ static public RegSingletonChild getInstance()

  { return (RegSingletonChild)

  RegSingleton.getInstance("com.javapatterns.singleton.demos.RegSingletonChild" );} /** * 一个示意性的商业方法*/ public String about()

  { return "Hello, I am RegSingletonChild.";}

  在GoF 原始的例子中,并没有getInstance() 方法,这样得到子类必须调用的getInstance(String name)方法并传入子类的名字,因此很不方便。本章在登记式单例类子类的例子里,加入了getInstance()方法,这样做的好处是RegSingletonChild 可以通过这个方法,返还自已的实例。而这样做的缺点是,由于数据类型不同,无法在RegSingleton 提供这样一个方法。由于子类必须允许父类以构造子调用产生实例,因此,它的构造子必须是公开的。这样一来,就等于允许了以这样方式产生实例而不在父类的登记中。这是登记式单例类的一个缺点。

  GoF 曾指出,由于父类的实例必须存在才可能有子类的实例,这在有些情况下是一个浪费。这是登记式单例类的另一个缺点。
  在什么情况下使用单例模式

  使用单例模式的条件

  使用单例模式有一个很重要的必要条件:

  在一个系统要求一个类只有一个实例时才应当使用单例模式。反过来说,如果一个类可以有几个实例共存,那么就没有必要使用单例类。但是有经验的读者可能会看到很多不当地使用单例模式的例子,可见做到上面这一点并不容易,下面就是一些这样的情况。

  例子一

  问:我的一个系统需要一些"全程"变量。学习了单例模式后,我发现可以使用一个单例类盛放所有的"全程"变量。请问这样做对吗?

  答:这样做是违背单例模式的用意的。单例模式只应当在有真正的"单一实例"的需求时才可使用。

  一个设计得当的系统不应当有所谓的"全程"变量,这些变量应当放到它们所描述的实体所对应的类中去。将这些变量从它们所描述的实体类中抽出来, 放到一个不相干的单例类中去,会使得这些变量产生错误的依赖关系和耦合关系。

  例子二

  问:我的一个系统需要管理与数据库的连接。学习了单例模式后,我发现可以使用一个单例类包装一个Connection 对象,并在finalize()方法中关闭这个Connection 对象。这样的话,在这个单例类的实例没有被人引用时,这个finalize()对象就会被调用,因此,Connection 对象就会被释放。这多妙啊。

  答:这样做是不恰当的。除非有单一实例的需求,不然不要使用单例模式。在这里Connection 对象可以同时有几个实例共存,不需要是单一实例。

  单例模式有很多的错误使用案例都与此例子相似,它们都是试图使用单例模式管理共享资源的生命周期,这是不恰当的。

  单例类的状态

  有状态的单例类

  一个单例类可以是有状态的(stateful),一个有状态的单例对象一般也是可变(mutable) 单例对象。

  有状态的可变的单例对象常常当做状态库(repositary)使用。比如一个单例对象可以持有一个int 类型的属性,用来给一个系统提供一个数值惟一的序列号码,作为某个贩卖系统的账单号码。当然,一个单例类可以持有一个聚集,从而允许存储多个状态。

  没有状态的单例类

  另一方面,单例类也可以是没有状态的(stateless),仅用做提供工具性函数的对象。既然是为了提供工具性函数,也就没有必要创建多个实例,因此使用单例模式很合适。一个没有状态的单例类也就是不变(Immutable) 单例类; 关于不变模式,读者可以参见本书的"不变(Immutable )模式"一章。

  多个JVM 系统的分散式系统

  EJB 容器有能力将一个EJB 的实例跨过几个JVM 调用。由于单例对象不是EJB,因此,单例类局限于某一个JVM 中。换言之,如果EJB 在跨过JVM 后仍然需要引用同一个单例类的话,这个单例类就会在数个JVM 中被实例化,造成多个单例对象的实例出现。一个J2EE应用系统可能分布在数个JVM 中,这时候不一定需要EJB 就能造成多个单例类的实例出现在不同JVM 中的情况。

  如果这个单例类是没有状态的,那么就没有问题。因为没有状态的对象是没有区别的。但是如果这个单例类是有状态的,那么问题就来了。举例来说,如果一个单例对象可以持有一个int 类型的属性,用来给一个系统提供一个数值惟一的序列号码,作为某个贩卖系统的账单号码的话,用户会看到同一个号码出现好几次。

  在任何使用了EJB、RMI 和JINI 技术的分散式系统中,应当避免使用有状态的单例模式。

  多个类加载器

  同一个JVM 中会有多个类加载器,当两个类加载器同时加载同一个类时,会出现两个实例。在很多J2EE 服务器允许同一个服务器内有几个Servlet 引擎时,每一个引擎都有独立的类加载器,经有不同的类加载器加载的对象之间是绝缘的。

  比如一个J2EE 系统所在的J2EE 服务器中有两个Servlet 引擎:一个作为内网给公司的网站管理人员使用;另一个给公司的外部客户使用。两者共享同一个数据库,两个系统都需要调用同一个单例类。如果这个单例类是有状态的单例类的话,那么内网和外网用户看到的单例对象的状态就会不同。除非系统有协调机制,不然在这种情况下应当尽量避免使用有状态的单例类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值