Basically I have a class hierarchy something like this:
NSObject
MySpecialController
MyExtraSpecialController
Each of these has an init method, and each implementation calls super first to let the superclass initialize itself first, all the way up the chain. For lack of better terminology I would say that each class "augments" its super class' behaviour.
But let's suppose I want to "replace" my super class's behaviour entirely (simply because I want to specialize it further for a particular app, but without cluttering the generic reusable super class. So it is assumed that I have intimate knowledge of the super class). The actual change that I want to do is replace a property with one of a more specific class type. To fully implement this I need the init method to instantiate an instance of widget of the appropriate class. So if I instantiate a MySpecialController, its widget property should be of type MySpecialWidget; but if I instantiate a MyExtraSpecialController, its widget should be of type 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
Now this works in the sense that MySpecialController works, and can be used by anyone with the public API. And MyExtraSpecialController also works, and follows proper separation of concerns as it assumed nothing about the superclass's behaviour. This is the type of subclass one would create of a framework or library class: robust and unassuming.
What actually happens though is that when I create a new instance of MyExtraSpecialController, its superclass first instantiates a MySpecialWidget, and then it immediately deallocates that instance and replaces it with an instance of MyExtraSpecialWidget. Sure this works, but since I DO have intimate knowledge of the superclass (which basically means that I know exactly what its init method does, so I can safely replace it without needing to call it first), I want to avoid this problem and only instantiate a single widget (it just so happens that creating a widget is really expensive and isn't premature optimization). So I want to replace super's implementation entirely so that it doesn't create a widget, and will replace everything else that it does based on my intimate knowledge, but, and this is key, I still want to call init further up the chain because I don't know what my replaced class' superclass' init method does (NSObject in this case), as this is a class I don't have intimate knowledge of.
The immediate solution that comes to mind is to use the Objective-C dynamic runtime to get hold of the grandparent instance, and just call its init (which will then take care of calling up the chain if it needs to), therewith bypassing super. But whenever I find myself about to do something like that I always wonder if there is a better approach altogether--conceptually speaking, i.e. to replace rather than augment a superclass' method. Is there?
You could remove the instantiation of self.widget from the init functions and implement a custom "lazy" getter function instead:
- (MySpecialWidget *)widget
{
if (_widget == nil) {
_wigdet = [MySpecialWidget new];
}
return _widget;
}
Then you can override this method in the subclass. The widget will be created on the first access to self.widget, and either the superclass or the subclass getter is called.
One easy way to solve this would be to create a hook for making the widget.
@implementation MySpecialController
-(id)init {
if (self = [super init]) {
self.widget = [self makeWidget];
}
}
- (MySpecialWidget*) makeWidget
{
[MySpecialWidget new];
@end
Then your subclass can override makeWidget to return a VerySpecialWidget. This makes sense when you don't want clients to know about these widgets.
In your scenario, it's possible that clients know something about the widgets -- e.g. they want a VerySpecialController in order to get a VerySpecialWidget. If that's the case, you might want to let the client pick the widget:
[MySpecialController initWith: [MyVerySpecialWidget new]];
If the widget is the primary force for making the subclass, either approach may eliminate the need to sprout the subclass in the first place.
The second approach has the additional advantage of making unit testing easier; you can build a MySpecialController and pass it a dummy, stub, or mock without any fuss:
[MySpecialController initWith: [MyTestObjectThatPretendsToBeAWidget new]];
But the first pattern is cleaner if the clients shouldn't know anything about widgets.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With