Java模式设计之单例模式

在Java程序设计中经常会用到单例模式,但是很多时候程序员却不知道什么时候该使用单例模式,或者怎么使用单例模式。

  我们总结分析单例模式的时候,了解到单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个事例;三是它必须自行向整个系统提供这个实例。在下面的对象图中,有一个"单例对象",而"客户甲"、"客户乙"和"客户丙"是单例对象的三个客户对象。可以看到,所有的客户对象共享一个单例对象。而且从单例对象到自身的连接线可以看出,单例对象持有对自己的引用。

  单例模式在资源管理器中应用。

  一些资源管理器常常设计成单例模式。

  在计算机系统中,需要管理的资源包括软件外部资源,譬如每台计算机可以有若干个打印机,但只能有一个Printer Spooler, 以避免两个打印作业同时输出到打印机中。每台计算机可以有若干传真卡,但是只应该有一个软件负责管理传真卡,以避免出现两份传真作业同时传到传真卡中的情况。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。

  需要管理的资源包括软件内部资源,譬如,大多数的软件都有一个(甚至多个)属性(properties)文件存放系统配置。这样的系统应当由一个对象来管理一个属性文件。

  需要管理的软件内部资源也包括譬如负责记录网站来访人数的部件,记录软件系统内部事件、出错信息的部件,或是对系统的表现进行检查的部件等。这些部件都必须集中管理,不可政出多头。

  这些资源管理器构件必须只有一个实例,这是其一;它们必须自行初始化,这是其二;允许整个系统访问自己这是其三。因此,它们都满足单例模式的条件,是单例模式的应用。

  分析单例模式的结构。

  单例模式有以下的特点:

  1、单例类只可有一个实例。

  2、单例类必须自己创建自己这惟一的实例。

  3、单例类必须给所有其他对象提供这一实例。虽然单例模式中的单例类被限定只能有一个实例,但是单例模式和单例类可以很容易被推广到任意且有限多个实例的情况,这时候称它为多例模式(Multiton Pattern) 和多例类(Multiton Class)。

  由于Java 语言的特点,使得单例模式在Java 语言的实现上有自己的特点。这些特点主要表现在单例类如何将自己实例化上。

  饿汉式单例类

  这是在Java 语言里实现得最为简便的单例类,下面所示的类图描述了一个饿汉式单例类的典型实现。

  从图中可以看出,此类已经自已将自己实例化。

  代码清单1:饿汉式单例类


public class EagerSingleton

{

private static final EagerSingleton m_instance =

new EagerSingleton();

/**

* 私有的默认构造子

*/

private EagerSingleton() { }

/**

* 静态工厂方法

*/

public static EagerSingleton getInstance()

{

return m_instance;

}

}

  读者可以看出,在这个类被加载时,静态变量m_instance 会被初始化,此时类的私有构造子会被调用。这时候,单例类的惟一实例就被创建出来了。

  Java 语言中单例类的一个最重要的特点是类的构造子是私有的,从而避免外界利用构造子直接创建出任意多的实例。值得指出的是,由于构造子是私有的,因此,此类不能被继承。

 懒汉式单例类

  与饿汉式单例类相同之处是,类的构造子是私有的。与饿汉式单例类不同的是,懒汉式单例类在第一次被引用时将自己实例化。如果加载器是静态的,那么在懒汉式单例类被加载时不会将自己实例化。如下图所示,类图中给出了一个典型的饿汉式单例类实现。

  代码清单2:懒汉式单例类


package com.javapatterns.singleton.demos;

public class LazySingleton

{

private static LazySingleton

m_instance = null;

/**

* 私有的默认构造子,保证外界无法直接实例化

*/

private LazySingleton() { }

/**

* 静态工厂方法,返还此类的惟一实例

*/

synchronized public static LazySingleton

getInstance()

{

if (m_instance == null)

{

m_instance = new LazySingleton();

}

return m_instance;

}  }

  同样,由于构造子是私有的,因此,此类不能被继承。饿汉式单例类在自己被加载时就将自己实例化。即便加载器是静态的,在饿汉式单例类被加载时仍会将自己实例化。单从资源利用效率角度来讲,这个比懒汉式单例类稍差些。

  从速度和反应时间角度来讲,则比懒汉式单例类稍好些。然而,懒汉式单例类在实例化时, 必须处理好在多个线程同时首次引用此类时的访问限制问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费时间。这意味着出现多线程同时首次引用此类的机率变得较大。

  饿汉式单例类可以在Java 语言内实现, 但不易在C++ 内实现,因为静态初始化在C++ 里没有固定的顺序,因而静态的m_instance 变量的初始化与类的加载顺序没有保证,可能会出问题。这就是为什么GoF 在提出单例类的概念时,举的例子是懒汉式的。他们的书影响之大,以致Java 语言中单例类的例子也大多是懒汉式的。实际上,本书认为饿汉式单例类更符合Java 语言本身的特点。

  使用单例模式必要条件:

  下面我们探讨一下使用单例模式的必要条件。

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

  例子一

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

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

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

  例子二

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

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

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

  下面简单说一下,笔者现在学习安卓,写了一个很小的音乐播放播放器,自己写了一个application。Application和Actovotu,Service一样是android框架的一个系统组件,当android程序启动时系统会创建一个 application对象,用来存储系统的一些信息。通常我们是不需要指定一个Application的,这时系统会自动帮我们创建,如果需要创建自己的Application,也很简单创建一个类继承 Application并在manifest的application标签中进行注册(只需要给Application标签增加个name属性把自己的 Application的名字定入即可)


package com.frewen.ttplayer;

importjava.util.ArrayList;

importjava.util.LinkedList;

importjava.util.List;

importandroid.app.Activity;

importandroid.app.Application;

importandroid.content.Context;

importandroid.graphics.Bitmap;

importandroid.graphics.BitmapFactory;

importandroid.media.MediaPlayer;

importandroid.util.Log;

import com.frewen.ttplayer.entry.Music;

import com.frewen.ttplayer.entry.impl.Musicdata;

import com.frewen.ttplayer.util.MusicPreference;

public classMyApplication extends Application {

public static MediaPlayer mediaPlayer;

public static MusicPreferencemusicPreference;

public static ArrayList<Music> musics= new ArrayList<Music>();

public static boolean isStart = false;

public List<Activity> activityList =new LinkedList<Activity>();

public static Bitmap bitmap_l;

public static Bitmap bitmap_s;

public static MyApplication instance;

public ArrayList<Music> getMusics() {

return musics;

}

public static Context context;

@Override

public void onCreate() {

super.onCreate();

context = getApplicationContext();

new Thread(new Runnable() {

@Override

public void run() {

setMusics(Musicdata.getMultiDatas(context));

bitmap_l = BitmapFactory.decodeResource(context.getResources(),

R.drawable.default_bg_l);

bitmap_s =BitmapFactory.decodeResource(context.getResources(),

R.drawable.default_bg_s);

}

}).start();

mediaPlayer = new MediaPlayer();

musicPreference = new MusicPreference(context);

}

public void setMusics(ArrayList<Music>ms) {

musics.clear();

musics = ms;

Log.i("test", "列表长度" + this.musics.size());

}

public MyApplication() {

}

// 单例模式中获取唯一的MyApplication实例

public static MyApplication getInstance() {

if (null == instance) {

instance = new MyApplication();

}

return instance;

}

// 添加Activity到容器中

public void addActivity(Activity activity) {

activityList.add(activity);

}

// 遍历所有Activity并finish

public void exit() {

for (Activity activity : activityList) {

activity.finish();

}

System.exit(0);

}

/**

* 向musics集合中追加一组miusic信息

*

*@param musics

*/

public void append(ArrayList<Music>musics) {

if (musics != null) {

this.musics.addAll(musics);

}

}

public void append(Music music) {

if (music != null) {

this.musics.add(music);

}

}

}

  android系统会为每个程序运行时创建一个Application类的对象且仅创建一个,所以Application可以说是单例 (singleton)模式的一个类.application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。因为它是全局的单例的,所以在不同的Activity,Service中获得的对象都是同一个对象。所以通过Application来进行一些,数据传递,数据共享等,数据缓存等操作。

最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-08-03 07:35:50

Java模式设计之单例模式的相关文章

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

作为对象的创建模式[GOF95], 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. 注:本文乃阎宏博士的<Java与模式>一书的第十五章. 引言 单例模式的要点 单例单例 显然单例模式的要点有三个:一是某各类只能有一个实例:二是它必须自行创建这个事例:三是它必须自行向整个系统提供这个实例.在下面的对象图中,有一个"单例对象",而"客户甲"."客户乙" 和"客户丙"是单例

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

不完全的单例类 什么是不完全的单例类 估计有些读者见过下面这样的"不完全"的单例类. 代码清单10:"不完全"单例类 package com.javapatterns.singleton.demos; public class LazySingleton { private static LazySingleton m_instance = null; /** * 公开的构造子,外界可以直接实例化 */ public LazySingleton() { } /**

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

在什么情况下使用单例模式 使用单例模式的条件 使用单例模式有一个很重要的必要条件: 在一个系统要求一个类只有一个实例时才应当使用单例模式.反过来说,如果一个类可以有几个实例共存,那么就没有必要使用单例类.但是有经验的读者可能会看到很多不当地使用单例模式的例子,可见做到上面这一点并不容易,下面就是一些这样的情况. 例子一 问:我的一个系统需要一些"全程"变量.学习了单例模式后,我发现可以使用一个单例类盛放所有的"全程"变量.请问这样做对吗? 答:这样做是违背单例模式的

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

一个实用的例子:属性管理器 什么是属性文件 这里给出一个读取属性(properties) 文件的单例类,作为单例模式的一个实用的例子.属性文件如同老式的视窗编程时的.ini 文件,用于存放系统的配置信息.配置信息在属性文件中以属性的方式存放,一个属性就是两个字符串组成的对子,其中一个字符串是键(key),另一个字符串是这个键的值(value). 大多数的系统都有一些配置常量,这些常量如果是存储在程序内部的,那么每一次修改这些常量都需要重新编译程序.将这些常量放在配置文件中,系统通过访问这个配置文

Java模式设计之多态模式与多语言支持

作为对象的创建模式,多态模式中的多态类可有多个实例:而且多态类必须自己创建.管理自己的实例,并向外界提供自己的实例.读者在阅读本文的时候,可以参考阅读笔者的<Java与模式>一书(刚由电子工业出版社出版)中的相关章节. 引言 一个真实的项目 这是一个真实的.面向全球消费者的华尔街金融网站项目的一部份.按照项目计划书,这个网站系统是要由数据库驱动的,并且要支持十九种不同的语言:而且在将来支持更多的语言.消费者在登录到系统上时可以选择自己所需要的语言,系统则根据用户的选择将网站的静态文字和动态文字

Java模式设计之数据访问对象模式

很多的J2EE应用程序需要使用持久性数据(数据库.文件等).不同的程序,持久性存储是各不相同的,并且用来访问这些不同的持久性存储机制的API也有很大的不同.如果应用程序要在不同的持久性存储间迁移,这些访问特定持久存储层的代码将面临重写. 如何解决这个问题?且看"DAO模式" 数据访问对象(Data Acess Object) 模式 一.环境 根据数据源不同,数据访问也不同.根据存储的类型(关系数据库.面向对象数据库.文件等等)和供应商实现不同,持久性存储(比如数据库)的访问差别也很大.

JAVA:将反射技术应用于工厂模式(Factory)和单例模式(Singleton)的简单代码

反射技术大量用于Java设计模式和框架技术,最常见的设计模式就是工厂模式(Factory)和单例模式(Singleton). 参考URL: http://blog.csdn.net/xiaohai798/article/details/11640427 用接口来沟通不同程序的开发进度,不必等上游程序写好代码之后,再注入后面流程的程序员.且在实现上,可以用配置文件灵活变更,而不用重编译整个项目. InterfaceTest.java: interface InterfaceTest { //基于接

学习笔记之JAVA图形设计卷I AWT——第3章 图 形

   学习笔记之JAVA图形设计卷I AWT--第3章 图 形 前时显示器坏了,file://写前言:我觉得写的不是学习笔记,倒象教程.我是想让有所获,故详细了点.注意1:在AWT中提供的用户接口构件(如按钮.列表.菜单.对话框等)不包含一些类似的纯粹的绘制图形的对象(如Line或Circle类) 详细意思:由于原始的AWT在设计时不允许纯粹的绘制图形的对象,那么Rectangle.Polygon和Point没有任何绘制图形的能力.换句话说,Rectangle.Polygon和Point不具备d

基础-JAVA课程设计实数计算器求指导思路

问题描述 JAVA课程设计实数计算器求指导思路 [问题描述]运用面向对象程序设计知识,利用Java语言设计和实现一个复数计算器.要求具备如下主要功能: (1)建立实数类.复数类 (2)实现实数.复数信息的初始化 (3)实现实数的加.减.乘.除.自增.自减.求平方.二次方根等操作 (4)实现复数的加.减.乘.除.取模.求平方.求共轭复数.求单个复数的向量角.求两个复数的夹角等运算 (5)实现实数.复数信息的输出 在实现过程中,需利用面向对象程序设计理论的基础知识,充分体现出Java语言关于类.继承