I was thinking of using the decorator pattern to extend the functionality of a UIKit class. The problem that I'm facing is that from examples I've seen in other languages the pattern forces me to duplicate the decorated object's interface. Here's how I would see the pattern implemented:
// Inheritance used for compile time check only
@interface UIScrollViewDecorator : UIScrollView
{
UIScrollview *decoratedScrollView;
}
- (id)initWithScrollView:(UISCrollView*)scrollView;
@end
@implementation UIScrollViewDecorator
- (id)initWithScrollView:(UISCrollView*)scrollView
{
self = [super init];
if(self != nil)
{
decoratedScrollView = [scrollView retain];
// maybe set up some custom controls on decoratedScrollView
}
}
// all methods for UIScrollView need to be manually passed to the decorated object
// -- this is the problem
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
// use "overwritten methods" to mess with the input for the decorated scroll view
// most of the time though I have no need to make any adjustments here; I still need
// to pass all these messages through so that outside users can interact with me just
// like with a real UIScrollView
[decoratedScrollView scrollRectToVisible:rect animated:animated];
}
@end
So to summarize, the problem is the duplication of the decorated object's method, even when I don't need to alter anything there. Is there a simpler way to pass along those methods that I don't need to "overwrite"? Could I use something like a NSProxy for this?
EDIT: This has become mostly a theoretical problem for me since I realized the decorator pattern is not what I needed to solve my actual problem. However, since I may come to use it again in the future, I'm still very interested in your answers.
6 Answers 6
Yes, it is possible to implement the Decorator pattern in Objective-C with an NSProxy. You would have to implement methodSignatureForSelector: and forwardInvocation: to forward messages to the decorated object.
But I don't think that this would be a good solution for what you try to do here; sending invocations is very costly and is not intended for such a purpose - there are certainly better ways to achieve what you want to do, e.g. with Categories (or maybe method swizzling).
-
I think swizzling is the proper way to do decorators in Objective-C.Mihai Damian– Mihai Damian2013年08月05日 12:26:29 +00:00Commented Aug 5, 2013 at 12:26
Your comment in the interface:
// all methods of UIScrollView need to be duplicated here -- this is the problem
is incorrect. You do not have to redeclare methods inherited from the superclass in the interface even if you are re-implementing them. This is not C++.
You do, however, have to create implementations of all the methods that redirect to the scroll view in the instance variable. There may be a clever way to intercept the messages to instances of your subclass and automatically forward them to the instance variable, but I can't think what it would be off the top of my head.
As others have said, consider using a category of UIScrollView instead. Categories are not a direct replacement for decorators since implementing a category will give all instances of UIScrollView your custom method, but that probably won't be a problem.
Update
You can use -forwardingTargetForSelector:
to redirect messages you haven't implemented to the decorated object. However, be aware that this will be slower than implementing each method directly or using categories.
-
You are correct. I updated the code. The problem remains the same, of course.Mihai Damian– Mihai Damian2010年11月30日 10:42:50 +00:00Commented Nov 30, 2010 at 10:42
-
A category won't really work if you need to override a method in the original class. Mainly because you can't call the original implementation if you need to. See here. I think
forwardingTargetForSelector:
is what you want.bcattle– bcattle2015年02月03日 22:31:55 +00:00Commented Feb 3, 2015 at 22:31
I believe what you want is to create a Category. Categories allow you to add additional methods to an existing class, including to core classes.
-
2nd this. It was my first thought as well. Categories in Objective C are very useful things.drekka– drekka2010年11月30日 10:13:54 +00:00Commented Nov 30, 2010 at 10:13
-
1Categories are good at adding additional methods, but you can't use them to extend the functionality of overwritten methods. Therefore they're not a substitute for the decorator pattern. I also realized that I misunderstood the pattern as I should not be using it to add new methods to the decorated object. I'll edit the code example.Mihai Damian– Mihai Damian2010年11月30日 10:19:48 +00:00Commented Nov 30, 2010 at 10:19
it is not (only) a problem of boring method coding but of duplication of the object i.e. there will be created two UIScrollView objects, decorator and decorated object. This might be useful sometimes when you really need two different objects and their data, but one of them is the master, I call this a "cheap decorator". But if the decorator itself does not need and use its own members I call it a "dirty decorator" (no worries, you are not alone and I dit it as well a few times ;-) The pure decorator will implement the same interface (Java) resp. category (Obj-C) like the decorated object.
Maybe it is sufficient to subclass UIScrolView an overwrite those methods you are interested in. Or categories like Matthew suggested.
Cheers
Kay
If all you are adding is extra methods go with categories Just import your header and call the method on any UIScrollView
@interface UIScrollView (Additions)
-(void)doFancyStuff;
@end
@implementation UIScrollView (Additions)
-(void)doFancyStuff
{
//your code
}
@end
Even if you really wanted to subclass it you would do it this way
@interface MyCustomScrollVoew : UIScrollView
{
}
- (id)init;
@end
@implementation MyCustomScrollVoew
- (id)init
{
if (self = [super init])
{
//do some fancy stuff here
}
return self;
}
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated
{
[super scrollRectToVisible:rect animated:animated];
//do some extra stuff here
//by removing the first line you can simply override the method
}
@end
I think this comes close to the decorator pattern:
USAGE:
DecoratorSample* _decorator = [[DecoratorSample alloc] init];
_decorator.scrollView = self.tableView; // or any other class derived from UIScrollView
[_decorator.scrollView addSubview:_decorator];
[_decorator release];
In this way the tableView will keep responding to it's TableView events and additionally the ScrollView events from _decorator
MENTION: the _decorator can be derived from any class that implements the UIScrollViewDelegate
I think this is similar to UINavigationController or UITabbarController which, in my opinion, come very close to the decorator pattern. Probably you cannot do this with all type of objects but it does work with UIView type of stuff. Also check the book "Cocoa Desgn Patterns" for some yummy Cocoa patterns. Unfortunately what I have found regarding decorator is little.
@interface DecoratorSample : UIScrollView <UIScrollViewDelegate>
{
UIScrollView* _scrollView;
id<UIScrollViewDelegate> _forwardDelegate;
}
@property (nonatomic,assign) id<UIScrollViewDelegate> forwardDelegate;
@property (nonatomic,retain) UIScrollView* scrollView;
@end
and:
@implementation DecoratorSample
@synthesize forwardDelegate = _forwardDelegate;
- (void)dealloc
{
[_scrollView release];
}
- (void) setScrollView:(UIScrollView*)scrollView
{
[_scrollView release];
_scrollView = scrollView;
[_scrollView retain];
_forwardDelegate = _scrollView.delegate;
_scrollView.delegate = self;
}
- (UIScrollView*) scrollView
{
return _scrollView;
}
#pragma UIScrollView implementation
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
if (_forwardDelegate != nil && [_forwardDelegate respondsToSelector:@selector(scrollViewWillBeginDragging:)])
{
[_forwardDelegate scrollViewWillBeginDragging:scrollView];
}
//do something else
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (_forwardDelegate != nil && [_forwardDelegate respondsToSelector:@selector (scrollViewDidScroll:)])
{
[_forwardDelegate scrollViewDidScroll:scrollView];
}
//do something else
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (_forwardDelegate != nil && [_forwardDelegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)])
{
[_forwardDelegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
}
//do something else
}
@end
Explore related questions
See similar questions with these tags.