类簇作为单例模式?

4

抱歉这篇文章有点长,但是它旨在记录我解决这个问题的过程。

我有一个关于Cocoa应用程序中共享对象的问题,需要不时更改,并且如何最好地存储它,以便从几个不同的位置访问。请耐心等待。

类实现

共享对象被实现为一个类簇(即 https://dev59.com/aXI-5IYBdhLWcg3wc4Cw#2459385),看起来像下面这样(请注意,Document 只是一个类名; 它不一定表明我的实际类所做的事情):

Document.h 中:

typedef enum {
    DocumentTypeA,
    DocumentTypeB
} DocumentType;

@interface Document : NSObject {}
- (Document *) initWithDocumentType:(NSUInteger)documentType;
- (void) methodA;
- (void) methodB;
@end

Document.m 中:
@interface DocumentA : Document

- (void) methodA;
- (void) methodB;

@end

@interface DocumentB : Document

- (void) methodA;
- (void) methodB;

@end

@implementation Document

- (Document *)initWithDocumentType:(NSUInteger)documentType;
{
    id instance = nil;
    switch (documentType) {
        case DocumentTypeA:
            instance = [[DocumentA alloc] init];
            break;
        case DocumentTypeB:
            instance = [[DocumentB alloc] init];
            break;
        default:
            break;
    }

    return instance;
}

- (void) methodA
{
    return nil;
}

- (void) methodB
{
    return nil;
}

@end

@implementation DocumentA

- (void) methodA
{
    // ...
}

- (void) methodB
{
    // ...
}

@end

@implementation DocumentB

- (void) methodA
{
    // ...
}

- (void) methodB
{
    // ...
}

@end

用户如何与文档交互

通过菜单项,用户可以随意在DocumentA和DocumentB之间切换。

“切换”发生时会发生什么

当用户从DocumentA切换到DocumentB时,我需要发生两件事:

  1. 我的主要NSViewController (MainViewController)需要能够使用新对象。
  2. 我的AppDelegate需要更新位于主窗口内容边框中的NSTextField。(值得一提的是,我只能在AppDelegate中为NSTextField分配一个outlet)

问题

我看到单例被提及得相当频繁,作为一种在不混乱AppDelegate的情况下拥有全局引用的方法(主要参考这里这里)。话虽如此,我没有看到关于覆盖这样一个单例的太多信息(在我们的情况下,当用户从DocumentA切换到DocumentB[或反之亦然]时,这个全局引用需要保存新对象)。我不是设计模式的专家,但我确实记得听说单例不应该被销毁和重新创建...
所以,鉴于所有这些,这里是我的问题:
  1. 你将如何存储我的类簇(以便 MainViewControllerAppDelegate 可以适当地访问它)?
  2. 如果我让 MainViewController(大量使用 Document)和 AppDelegate(管理主窗口[因此,我的 NSTextField])都了解 Document,是否会混淆问题?

如果我对这个问题的思考方式不正确,请随时告诉我;我希望这个实现尽可能正交和正确。

谢谢!


状态更新 #1

感谢 @JackyBoy 的建议,这是我采取的路线:

  • Document 是那个在“切换”时通过传递新创建的实例来“通知” AppDelegateMainViewController 的。
  • 无论何时,AppDelegateMainViewController 都可以通过 Singleton 实例更新 Document 对象。

这是我的新文件(简化后,让大家看到问题的核心):

Document.h 中:

#import <Foundation/Foundation.h>
@class AppDelegate;
@class MainViewController;

typedef enum {
    DocumentTypeA,
    DocumentTypeB
} DocumentType;

@interface Document : NSObject

@property (weak, nonatomic) MainViewController *mainViewControllerRef;
@property (weak, nonatomic) AppDelegate *appDelegateRef;

+ (Document *)sharedInstance;
- (id)initWithParser:(NSUInteger)parserType;

@end

Document.m 中:
#import "AppDelegate.h"
#import "Document.h"
#import "MainViewController.h"

@interface DocumentA : Document

// ...

@end

@interface DocumentB : Document

// ...

@end

@implementation Document

@synthesize appDelegateRef;
@synthesize mainViewControllerRef;

+ (Document *)sharedInstance
{
    static XParser *globalInstance;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        // By default, I return a DocumentA object (for no particular reason).
        globalInstance = [[self alloc] initWithDocumentType:DocumentA];
    });

    return globalInstance;
}

- (id)initWithDocumentType:(NSUInteger)documentType
{
    Document *instance = nil;
    switch (parserType) {
        case DocumentTypeA:
            instance = [[DocumentA alloc] init];
            break;
        case DocumentTypeB:
            instance = [[DocumentB alloc] init];
            break;
        default:
            break;
    }

    // QUESTION: Is this right? Do I have to store these references
    // every time a new document type is initialized?    
    self.appDelegateRef = (AppDelegate *)[NSApp delegate];
    self.mainViewControllerRef = self.appDelegateRef.mainViewController;

    [self.appDelegateRef parserSwitchedWithParser:instance];
    [self.mainViewControllerRef parserSwitchedWithParser:instance];

    return instance;
}

@end

@implementation Xparser_NSXML

// ...

@end

@implementation DocumentA

// ...

@end

我应该担心 Document 知道 AppDelegateMainViewController 的存在吗?此外,当 Document 对象更新时,是否应该担心它重新通知两个 AppDelegateMainViewController (即使其中一个是发起更新的)?
一如既往,感谢大家对我的支持,帮助我寻找最佳实现方案。 :)

更新 #2

来自@Caleb的评论帮助我理解,在这个特定的问题中,基于NSNotification的设置会更加灵活。

谢谢大家!

2个回答

1
我记得听说单例不应该被销毁和重建...... 嗯,你可以在其中拥有引用,因此实际上并没有“销毁”单例,而是他所指向的对象。我倾向于将应用程序委托留给没有应用逻辑的地方,所以通常将其放在其他地方。在你的情况下,由于你需要从不同的地方访问某些东西,拥有一个是有道理的。关于集群,你仍然可以拥有它,你只需要求单例访问它,并返回相应的对象,如下所示:
Document *myDocument = [[MySingleton defaultManager] createObjectWithType:aType];

你会从中获得以下好处:
  1. 你可以从应用程序的任何地方访问集群

  2. 你可以解耦,只有一个实体知道你的集群。

  3. 在单例中,你可以引用你的AppDelegate并与之交互。

  4. 在单例中,你可以引用正在使用的对象(文档A、文档B)。

还有一件事,我建议将集群访问方法作为类方法(而不是实例方法)。


谢谢您的意见。我仍然遇到一个问题:在这种设置下,当用户从“文档A”切换到“文档B”时,AppDelegateMainViewController都需要初始化集群的新版本。 当然,我可以控制它,但是如果需要通知DocumentADocumentB之间的更改,则两个类都需要被通知,这似乎有点笨拙。 我想AppDelegate可以将其DocumentA / DocumentB实例传递给MainViewController? - ABach
明白了,非常感谢。如果您不介意的话,我打算试一试并提供有关我的问题的状态更新;我真的很感激您能告诉我是否考虑得正确。 - ABach
ABAch两件事情:1)您应该只在“-(id)initWithDocumentType:(NSUInteger)documentType”中进行必要的初始化,例如“-(id)initWithDocumentType:(NSUInteger)documentType withMainViewController:(MainViewController *)mainViewController withAppDelegate:(AppDelegate *)appDelegate”。之后,您可以使用一个方法,如:“-(void)switchDocumentWith:(Document *)aDocument” 并且在那里:[self.appDelegateRef parserSwitchedWithParser:instance]; [self.mainViewControllerRef parserSwitchedWithParser:instance]; - Rui Peres
明白了。感谢您一直以来的支持和帮助。目前来看,@Caleb 的回答在我的应用程序中更有意义。话虽如此,我仍然非常感激您的所有协助。 - ABach
@Caleb 的方法也是完全有效的选择。 - Rui Peres

1
我不认为这里需要共享对象,更不需要单例。你真的需要在任意时间从许多不同的对象中找到当前文档吗?似乎更像是您只有两个对象(应用程序委托和视图控制器)都需要知道当前文档的情况。通知提供了一种简单的方法来管理:每当发生切换时,您可以发布一个包含新文档的NSNotification。任何需要了解当前文档的对象都将注册“文档切换”通知,当通知到达时,它们可以将指向文档的指针存储在实例变量或属性中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接