14

我一直想知道在什么情况下确实有必要在 Objective-C 中采用单例模式(例如,定义一个专用类并创建一个单一实例),而将类用作对象是行不通的。

特别是,我正在考虑以下解决方案:

  1. 定义和使用适当的类方法,而不是单例实例上的实例方法;
  2. 使用static变量(文件范围全局变量),而不是单例实例的实例变量;
  3. 注册为通知的观察者时使用类对象,而不是单例实例。尽管类对象本身就是一个 Objective-C 对象(对吗?),但这需要注册的通知处理程序是一个类方法;(这可能吗?)

例如,您可以将所有纹理创建/清理实现为同一类的类方法和静态变量(工厂模式加上一些资源管理) ,而不是拥有一个Texture类(模型对象)一个单例(资源管理器)。TextureManagerTexture

对这个设计有什么想法吗?

编辑: 现在我想到了,仍然在上面的Texture示例中,即使我将两个类分开(TextureTextureManager),我也必须在 A 之间进行选择。让经理成为单例,并使用实例方法或 B 操作它。让管理器成为无实例的辅助类。澄清:

Texture* myTexture = [[TextureManager defaultManager] textureWithName:@"TextureName"]; 
// (singleton, client uses instance methods)

相对

Texture* myTexture = [TextureManager textureWithName:@"TextureName"]; 
// (Class standing in for singleton, client uses class methods)

后者看起来更直接,更不繁琐/冗长,但我想知道哪种设计“更正确”。当然,如果需要,前者允许多个TextureManager实例(在我的情况下不是)。

4

3 回答 3

7

我一直在想同样的事情,我想我有一个答案给你。

这取决于你需要用它做什么。两者都不一定更“正确”。

如果您想了解我如何得出结论的详细信息,请继续阅读或向下滚动到tl;dr部分。

正如您所说,访问单例以让班级为您管理单例似乎(外部)不那么麻烦。本质上,您可以通过用初始化方法替换单例的工厂方法来做到这一点。查看Apple在这方面的文档,您可以看到他们在哪里显示了一种“共享”方法,该方法充当工厂根据需要生产单例。

static MyGizmoClass *sharedGizmoManager = nil;

+ (MyGizmoClass*)sharedManager
{
    if (sharedGizmoManager == nil) {
        sharedGizmoManager = [[super allocWithZone:NULL] init];
    }
    return sharedGizmoManager;
}

您可以使用 void 初始化程序替换该方法,而不是这样做:

+ (void)initializeMyGizmo
{
    if (sharedGizmoManager == nil) {
        sharedGizmoManager = [[super allocWithZone:NULL] init];
    }
    // whatever else needs to be done to the singleton to initialize it
}

然后使用类方法并允许MyGizmoClass管理对单例的更新,例如[MyGizmoClass setGizmoName:@"Gadget"].

注意:这种情况下,查看文件以查看属性的人会感到困惑.h,在这种情况下,他们可能会得出结论,他们应该自己创建对象的实例,或者能够在某些情况下访问单例形式或时尚。因此,如果您要走封装对单例的访问的路线,那么使用公共变量是不明智的。

到那时:

如果您确实仅通过类本身限制访问,您将丢失任何 getter 和 setter 或其他随属性一起提供的免费东西。这意味着,如果MyGizmoClass要将其作为模型的一部分,NSString *gizmoName您将被迫为此“属性”创建自定义 getter 和 setter,并将其作为 ivar 或属性保存在.m文件(即私有)的接口扩展中单例类,或作为相邻的静态变量。

所以这引出了一个问题(这也是让我首先思考的问题),我们是否应该完全包含该行static MyGizmoClass *sharedGizmoManager = nil;,或者我们是否可以完全取消内部接口扩展并替换我们想要限制访问的任何可能的 ivars 或属性static实施中的实施?

我已经回答了...

这取决于你需要用它做什么。

tl;博士

第一个场景

如果您曾经(即使是最微小的机会)需要将您的子类 TextureManager化或可以创建它的多个实例(使其不再是单例),最好坚持常规的 Apple 单例约定。

这包括多个“singleton”,其中您可能有几个 TextureManager预先配置了不同设置的s。

在这种情况下,您将根据需要(公开或私下)使用属性以及 ivars。您也可以混合使用 ivars 和静态变量,但您仍然需要TextureManager在实现内部有一个静态实例TextureManager

第二个场景

如果您需要一个实例并且 TextureManager它将完全独立运行而不会进一步混合,那么您可以在.m文件的实现中完全删除类的静态实例,并用其中的静态变量替换 ivars 和属性执行。

如果您将属性或设置存储在 CoreData 中并且只需要它们进行配置,这将很有用。

请记住,在这种情况下,您必须为静态变量创建所有 getter 和 setter,并且只能使用类方法访问它们(但这就是重点)。

其他有趣的东西

这个答案为何时以及如何调用“initializer”方法或创建单例的问题提供了一个有趣的解决方案。这可以与每个场景一起使用,以在第一个场景中初始化单例,或者在第二个场景中将默认值预加载到类级静态中。

如果您想在实现中坚持使用静态单例,您可以查看这篇文章,让您更好地了解单例的真正“全局范围”。

于 2014-01-23T05:25:07.070 回答
2

是的,你绝对可以在不需要单例的情况下创建一个 Texture 类。

单例可能不应该被创建和用作对象。

单例可用于许多重要的事情。
我当然不知道它们可以用来做什么,但我会告诉你我过去用它们做什么。

我通常在具有多个关卡的游戏(如愤怒的小鸟)中使用单例进行关卡导航。
通过关卡导航,我的意思是……当玩家在游戏中完成某个关卡时,我只需在单例上调用一个类方法并传入关卡编号,然后单例的类方法会确定下一个关卡(如果用户按下'下一级'按钮)。

于 2013-09-14T06:27:46.813 回答
2

我可以帮助你更好地理解 Singleton 类以及它何时适用。


模式:单例

Intent:强制一个类只能有一个实例,并使该实例可供任何其他对象访问。

动机:有时我们需要确保在我们的问题域中只存在一个特定类型的对象。示例:一个学生只带一个背包,可以装满书。我们不想将他与带有更多书籍的二级背包联系起来。

使用时

  • 只需要一个类的单个实例,并且该实例必须可以从代码中的不同对象访问。
  • 当您(可能)需要能够通过子类化该类来向该类添加更多功能时。

于 2013-09-14T07:59:05.673 回答