【问题标题】:Decorator pattern in Objective-CObjective-C 中的装饰器模式
【发布时间】:2010-11-30 09:49:22
【问题描述】:

我正在考虑使用decorator pattern 来扩展 UIKit 类的功能。我面临的问题是,从我在其他语言中看到的示例中,该模式迫使我复制装饰对象的界面。以下是我如何看待该模式的实现:

// 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

总而言之,问题在于装饰对象方法的重复,即使我不需要在那里更改任何内容。有没有更简单的方法来传递那些我不需要“覆盖”的方法?我可以为此使用 NSProxy 之类的东西吗?

编辑:这对我来说主要是一个理论问题,因为我意识到装饰器模式不是我解决实际问题所需要的。不过,我以后可能会再次使用它,所以我仍然对您的回答非常感兴趣。

【问题讨论】:

    标签: iphone objective-c design-patterns decorator


    【解决方案1】:

    是的,可以使用NSProxy 在 Objective-C 中实现装饰器模式。您必须实现 methodSignatureForSelector: 和 forwardInvocation: to forward messages 到装饰对象。

    但我不认为这对于您在这里尝试做的事情是一个很好的解决方案;发送调用非常costly 并且不用于这样的目的 - 当然有更好的方法来实现您想要做的事情,例如使用类别(或者可能是方法调配)。

    【讨论】:

    • 我认为 swizzling 是在 Objective-C 中做装饰器的正确方法。
    【解决方案2】:

    您在界面中的评论:

    // all methods of UIScrollView need to be duplicated here -- this is the problem
    

    不正确。即使重新实现它们,也不必在接口中重新声明从超类继承的方法。这不是 C++。

    但是,您必须在实例变量中创建重定向到滚动视图的所有方法的实现。可能有一种聪明的方法可以截获到子类实例的消息并自动将它们转发到实例变量,但我想不出它会是什么。

    正如其他人所说,请考虑改用 UIScrollView 类别。类别不是装饰器的直接替代品,因为实现类别将为 UIScrollView 的所有实例提供您的自定义方法,但这可能不是问题。

    更新

    您可以使用-forwardingTargetForSelector: 将您尚未实现的消息重定向到装饰对象。但是请注意,这比直接实现每个方法或使用类别要慢。

    【讨论】:

    • 你是对的。我更新了代码。当然,问题仍然存在。
    • 如果您需要覆盖原始类中的方法,则类别将无法正常工作。主要是因为如果需要,您不能调用原始实现。见here。我认为forwardingTargetForSelector: 是您想要的。
    【解决方案3】:

    我相信您想要的是创建一个类别。类别允许您向现有类(包括核心类)添加其他方法。

    iOS Reference Library: Category

    【讨论】:

    • 第二个。这也是我的第一个想法。 Objective C 中的类别是非常有用的东西。
    • Categories 擅长添加额外的方法,但你不能使用它们来扩展覆盖方法的功能。因此它们不能替代装饰器模式。我还意识到我误解了该模式,因为我不应该使用它来向装饰对象添加新方法。我将编辑代码示例。
    【解决方案4】:

    这(仅)不是无聊的方法编码问题,而是对象的重复问题,即将创建两个 UIScrollView 对象,装饰器和装饰对象。有时当您确实需要两个不同的对象及其数据时,这可能很有用,但其中一个是主对象,我称之为“廉价装饰器”。但是如果装饰器本身不需要并使用它自己的成员,我称之为“肮脏的装饰器”(不用担心,你并不孤单,我也写了几次;-)纯装饰器将实现相同的接口( Java)分别。类别 (Obj-C) 喜欢装饰对象。

    也许将 UIScrollView 子类化并覆盖您感兴趣的那些方法就足够了。或者像 Matthew 建议的类别。

    干杯

    【讨论】:

      【解决方案5】:

      如果您添加的只是额外的方法,请使用类别 只需导入您的标题并在任何 UIScrollView 上调用该方法

      @interface UIScrollView (Additions)
      -(void)doFancyStuff;
      @end
      
      @implementation UIScrollView (Additions)
      -(void)doFancyStuff
      {
           //your code
      }
      @end
      

      即使你真的想继承它,你也会这样做

      @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
      

      【讨论】:

        【解决方案6】:

        我认为这接近于装饰者模式:

        用法:

        DecoratorSample* _decorator = [[DecoratorSample alloc] init];
        _decorator.scrollView = self.tableView; // or any other class derived from UIScrollView
        [_decorator.scrollView addSubview:_decorator];
        [_decorator release];
        

        这样,tableView 将继续响应它的 TableView 事件以及来自 _decorator 的 ScrollView 事件

        提醒:_decorator 可以派生自任何实现 UIScrollViewDelegate 的类

        我认为这类似于 UINavigationController 或 UITabbarController,在我看来,它们非常接近装饰器模式。可能你不能对所有类型的对象执行此操作,但它确实适用于 UIView 类型的东西。还可以查看“Cocoa Design Patterns”一书,了解一些美味的 Cocoa 图案。不幸的是,我发现的关于装饰器的东西很少。

        @interface DecoratorSample : UIScrollView <UIScrollViewDelegate> 
        {
            UIScrollView* _scrollView;
            id<UIScrollViewDelegate> _forwardDelegate;    
        }
        
        @property (nonatomic,assign) id<UIScrollViewDelegate> forwardDelegate;
        @property (nonatomic,retain) UIScrollView* scrollView;
        
        @end
        

        和:

        @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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-02-04
          • 2014-10-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-19
          • 1970-01-01
          相关资源
          最近更新 更多