单例,应用程序委托和顶层数据

转载 2012年03月21日 12:13:48

原文地址:http://blog.csdn.net/kmyhy/article/details/7026511

如果你的某个类需要实现单例模式,那么应该在哪里实现?你应该如何管理和控制它?不同的实现方式有不同的优缺点。

全局变量简介

它们令人害怕

全局变量对于老练的程序员来说是令人不愉快的东西。他们认为,如果程序中充斥着全局变量(本来应该是局部变量)是一种结构上的失败,程序将完全不受控制。

本文将全面介绍全局变量的声明和使用。

它们必不可少

事实上,程序中需要这些全局状态,全局变量必不可少。如果变量符合下列条件,则它应当是一个全局变量:

  1. 没有任何对象拥有该对象,需要管理它或者要对它负责;
  2. 在整个程序中,该对象只有一个;
  3. 它不是常量(比如字符串或者数字)。

 如果这些全都符合,那么你应该使用全局变量。

如果你还不明白,那么于此相反的情况(不是全局变量)应该是:

  1. 接受对象管理的成员变量;
  2. 接受对象管理的集合的成员(们);
  3. 一个#define宏或者常量(常量属于编译器状态,而不是代码)

在Cocoa中,它们并不是真正的全局变量

事实上,我将提到的这些在Cocoa中所谓的全局变量并不是纯粹标准C中的全局变量,但在Cocoa中,我们完全可以用这些方法作为全局变量使用。

我将提到顶级成员对象(即application delegate的成员),以及单例对象。

并解释,为什么它们会被认为是全局对象:

  • 应用程序对象是最先构造的对象,而其它对象按照不同层级先后依次构造,因此顶级对象的作用域是整个应用程序(就像一个全局对象)。应用程序委托被认为是应用程序对象的衍生(对于你永远不重载application类时尤其如此)。
  • 一个单例对象只能被分配一次(并且不可被删除)——因此它是类的唯一的全局实例。单例对象其实是以全局变量的形式储存的,它们永远不会用Objective C的方式访问(它们是以类方法的方式访问的),至少在实现中提供一些抽象方法。

应用程序委托和应用程序控制器

Cocoa程序员应知道,MainMenu.xib文件中创建了一个应用程序委托对象:

对于iPhoneSDK,则在MainWindow.xib文件。

在applicationDidFinishLaunching方法中,可以初始化一个可全局访问的变量:

 

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification

{

    myGlobalObject = [[MyGlobalObject alloc] init];

}

假设myGlobalObject有一个getter方法,通过这种方式你就可以访问这个对象:

 

[[NSApp delegate] myGlobalObject]

 

或者(iPhone中):

 

[[UIApplication sharedApplication].delegate myGlobalObject]

 

由于delegate返回的是id类型(而不是你真实的delegate的类型),你需要把myGlobalObject的声明为属性,并个且把delegate对象用括号括住并转换为你真正的应用程序委托类,比如:

 

((MyAppDelegate *)[UIApplication sharedApplication].delegate).myGlobalObject

 

当然,我看到有人在应用程序委托头文件种使用宏来定义委托对象:

 

#define UIAppDelegate \

    ((MyAppDelegate *)[UIApplication sharedApplication].delegate)

 

然后用:

 

UIAppDelegate.myGlobalObject

 

来访问顶层对象(记住import 该头文件).

 

使用应用程序委托的弊处

上面的做法是可行的,但在我的程序中,我从不在应用程序委托中做除了这些以外的事情:

  • NSApplication委托方法(从applicationDidFinishLaunching:方法直到application的finalize方法)
  • 不在windows菜单条中的菜单事件处理(例如“Preferences”菜单)

使用你的AppDelegate对象去管理你的全局对象是件糟糕的事情,因为你容易把太多的东西放到顶层对象中去,AppDelegate会因此庞大、结构紊乱。这有违设计模式,被称作Big Ball of Mud(一团乱泥)。

它有损程序的结构表现在两方面。首先是封装。AppDelegate只应该关注AppDelegate的内容(比如NSApplication对象和相关状态)。如果把其他对象相关的数据放到其中,就会强行干扰其他对象的自我控制。

其次是关注分离。保管、管理程序中非应用程序相关的变量不是AppDelegate的职责。一个设计良好的程序应该将类组织在完全分离、自包含的对象内,把任何对象都放到AppDelegate中违反了这一思维。

 

Cocoa的单例

要实现封装,应该把全局数据创建到类中作为单独的模块。通过单例达到这一目的。

Apple有一个最基本的单例实现方式: Creating a Singleton Instance.

我个人习惯于把这些单例方法放到宏中,你可以从这里下载到我的宏定义文件:

//

//  SynthesizeSingleton.h

//  CocoaWithLove

//

//  Created by Matt Gallagher on 20/10/08.

//  Copyright 2009 Matt Gallagher. All rights reserved.

//

//  Permission is given to use this source code file without charge in any

//  project, commercial or otherwise, entirely at your risk, with the condition

//  that any redistribution (in part or whole) of source code must retain

//  this copyright and permission notice. Attribution in compiled projects is

//  appreciated but not required.

//


#define SYNTHESIZE_SINGLETON_FOR_CLASS(classname) \

\

static classname *shared##classname = nil; \

\

+ (classname *)shared##classname \

{ \

@synchronized(self) \

{ \

if (shared##classname == nil) \

{ \

shared##classname = [[self alloc] init]; \

} \

} \

\

return shared##classname; \

} \

\

+ (id)allocWithZone:(NSZone *)zone \

{ \

@synchronized(self) \

{ \

if (shared##classname == nil) \

{ \

shared##classname = [super allocWithZone:zone]; \

return shared##classname; \

} \

} \

\

return nil; \

} \

\

- (id)copyWithZone:(NSZone *)zone \

{ \

return self; \

} \

\

- (id)retain \

{ \

return self; \

} \

\

- (NSUInteger)retainCount \

{ \

return NSUIntegerMax; \

} \

\

- (void)release \

{ \

} \

\

- (id)autorelease \

{ \

return self; \

}

如果你在类实现中导入了SynthesizeSingleton.h头文件,则你可以使用语句:

 

SYNTHESIZE_SINGLETON_FOR_CLASS(MyClassName);

 

将该语句置于@implementation MyClassName声明后,这样你的类自动会变成单例。

还需要加入这一句到MyClassName的头文件中:

 

+ (MyClassName *)sharedMyClassName;

 

这样当其他源文件导入MyClassName.h后才能找到单例的访问方法。

一旦类成为单例,你可以使用下面的语句访问单例对象:

 

[MyClassName sharedMyClassName];

 

注意:单例对象不需要显式地alloc和init(在第一此访问时会自动调用alloc和init),但如果你想实行初始化动作时仍然要实现默认的init方法。

单例的好处

设计良好的单例是分离的、自管理的对象。在AppDelegate中的变量与delegate对象毫无类似,一个单例只关注它自身的角色和职责。在Xcode中打开Mac OSX10.5文档,搜索shared开头的单词,可以看到苹果使用了单例模式创建“manager”对象,允许你get、set和操作程序中只创建一次的对象。

而且,单例通过方法进行访问,在某些实现中有一些抽象的实现——你可以从真单例模式转换为基于独立线程的实现,而不用改动接口部分。

结论

除非必要,不要使用全局变量。程序中绝大多数数据都有明显的非顶级对象的“父对象”。单例及顶级对象只应当包含真正属于顶级对象的数据。

Cocoa单例在有时候是有用的和有弹性的。你可以让AppDelegate保有顶级数据,但尽可能将范围限制于MainMenu.xib构造的对象。

创建单例winform应用程序

方法1: using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; ...

创建单例winform应用程序的一种更好的方式

我们经常会创建一些单例winform应用程,但如何保证单例,最常用的方法就是扫描进程,但这种方式缺点是显而易见的,这里介绍一种方式。 代码: using System; using System....

单例应用程序的实现

一些程序需要单实例运行,这里简单介绍下单实例的实现及相关问题。 首先定义单实例指的是整个操作系统中只运行一个应用程序的实例。当用户运行应用程序时先检查系统是否已经启动该应用程序,若启动则将自动弹出应...

Scala类的定义,主/辅构造器,以及方法中的变量定义,单例对象,伴生对象,Apply方法调用,应用程序对象

1. 类1.1. 类的定义package cn.toto.scala.day2/** * 在Scala中,类并不用声明为public类型的。 * Scala源文件中可以包含多个类,所有这些类都...

QT中实现程序只运行一个实例--应用程序的单例化

起因 最近想实现一个应用程序单例化的程序, 目前使QT运行一个实例有如下几种方式 1.QSharedMemory 使用共享内存,当第二个进程启动时,判断内存区...
  • gatieme
  • gatieme
  • 2016年01月31日 13:58
  • 3225

C# 软件下载插件,软件自动更新功能实现,通过cmd命令调用应用程序,应用程序实现单例启动

C# 软件下载插件,软件自动更新功能实现,通过cmd命令调用应用程序,应用程序实现单例启动...

iOS应用程序间共享数据

我们知道iOS由于沙盒的存在,应用程序不能越过自己的区域去访问别的存储空间的内容,不过可能有许多场景我们需要在应用程序之间共享数据,比如多个应用共用用户名密码进行登录等。虽然我们不能直接通过文件系统来...

[07] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序读取相关数据

[渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序读取相关数据 2014-05-08 18:24 by Bce, 1400 阅读, 1 评论...
  • Litt_J
  • Litt_J
  • 2014年12月29日 14:35
  • 978

PHP直接操作共享内存中的数据实现与其他应用程序共享信息

概述 共享内存是一种在相同机器中的应用程序之间交换数据的有效方式。一个进程可创建一个可供其他进程访问的内存段,只要它分配了正确的权限。每个内存段拥有一个惟一的 ID(称为 shmid),这个 ID ...
  • my_yang
  • my_yang
  • 2012年09月14日 10:31
  • 3389
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:单例,应用程序委托和顶层数据
举报原因:
原因补充:

(最多只允许输入30个字)