3

基本上我有一个像这样的类层次结构:

NSObject
  MySpecialController
    MyExtraSpecialController

它们中的每一个都有一个init方法,并且每个实现首先调用super以让超类首先初始化自己,一直到链上。由于缺乏更好的术语,我会说每个类都“增强”了它的超类的行为。

但是让我们假设我想完全“替换”我的超类的行为(仅仅是因为我想为特定的应用程序进一步专门化它,但又不会弄乱通用的可重用超类。所以假设我对超类有深入的了解)。我想要做的实际更改是将属性替换为更具体的类类型之一。为了完全实现这一点,我需要init实例化widget适当类的实例的方法。所以如果我实例化 a MySpecialController,它的widget属性应该是 type MySpecialWidget;但如果我实例化 a MyExtraSpecialController,它widget应该是类型MyExtraSpecialWidget

//MySpecialController:

@interface MySpecialController : NSObject

@property (strong, nonatomic) MySpecialWidget *widget;

@end

@implementation MySpecialController

-(id)init {
    if (self = [super init]) {
        self.widget = [MySpecialWidget new];
    }
}

@end


//MyExtraSpecialController:

@interface MyExtraSpecialController : MySpecialController

@property (strong, nonatomic) MyExtraSpecialWidget *widget;

@end

@implementation MyExtraSpecialController

-(id)init {
    if (self = [super init]) {
        self.widget = [MyExtraSpecialWidget new];
    }
}

@end

现在,这在某种意义上是MySpecialController有效的,并且可以被任何拥有公共 API 的人使用。而且MyExtraSpecialController也有效,并且遵循适当的关注点分离,因为它对超类的行为没有任何假设。这是为框架或库类创建的子类类型:健壮且不张扬。

但实际发生的是,当我创建 的新实例时MyExtraSpecialController,它的超类首先实例化 a MySpecialWidget,然后立即释放该实例并将其替换为 的实例MyExtraSpecialWidget。当然这可行,但由于我对超类有深入的了解(这基本上意味着我确切地知道它的init方法做了什么,所以我可以安全地替换它而无需先调用它),我想避免这个问题并且只实例化一个小部件(碰巧创建一个小部件真的很昂贵,而且不是过早的优化)。所以我想完全替换 super 的实现,这样它就不会创建一个小部件,并且会根据我的知识替换它所做的所有其他事情,但是,这是关键,我仍然想调用init更进一步,因为我不知道我替换的类的“超类”init方法做了什么(NSObject在这种情况下),因为这是一个我不熟悉的类。

想到的直接解决方案是使用 Objective-C 动态运行时来获取祖父实例,然后调用它init(如果需要,它将负责调用链),从而绕过 super。但是每当我发现自己要做类似的事情时,我总是想知道是否有更好的方法——从概念上讲,即替换而不是增加超类的方法。在那儿?

4

3 回答 3

3

self.widget您可以从init函数中删除实例化并实现自定义“惰性”getter 函数:

- (MySpecialWidget *)widget
{
     if (_widget == nil) {
         _wigdet = [MySpecialWidget new];
     }
     return _widget;
}

然后你可以在子类中重写这个方法。小部件将在第一次访问时创建self.widget,并且调用超类或子类 getter。

于 2013-03-08T10:51:13.397 回答
1

解决此问题的一种简单方法是创建一个用于制作小部件的钩子。

@implementation MySpecialController

-(id)init {
    if (self = [super init]) {
        self.widget = [self makeWidget];
    }
}

- (MySpecialWidget*) makeWidget
 {
     [MySpecialWidget new];
@end

然后您的子类可以覆盖makeWidget以返回一个VerySpecialWidget. 当您不希望客户知道这些小部件时,这是有道理的。

在您的场景中,客户可能对小部件有所了解——例如,他们想要一个 VerySpecialController 来获得一个 VerySpecialWidget。如果是这种情况,您可能希望让客户端选择小部件:

 [MySpecialController initWith: [MyVerySpecialWidget new]];

如果小部件是创建子类的主要力量,那么任何一种方法都可能首先消除生成子类的需要。

第二种方法具有使单元测试更容易的额外优势。您可以构建一个 MySpecialController 并将其传递给一个 dummy、stub 或 mock,而无需大惊小怪:

 [MySpecialController initWith: [MyTestObjectThatPretendsToBeAWidget new]];

但是如果客户不应该知道任何关于小部件的事情,那么第一种模式会更清晰。

于 2013-03-08T13:48:46.510 回答
0

一种方法是将实例方法添加-widgetClassMySpecialController

@implementation MySpecialController

- (id)init
{
    self = [super init];
    if (self) {
        self.widget = [[[self widgetClass] alloc] init];
    }
    return self;
}

- (id)widgetClass
{
    return [MySpecialWidget class];
}

//...

@end

并覆盖该方法MyExtraSpecialController

@implementation MyExtraSpecialController

- (id)widgetClass
{
    return [MyExtraSpecialWidget class];
}

//...

@end
于 2013-03-08T14:06:16.853 回答