2010-11-30 14 views
9

Estaba pensando en usar el decorator pattern para extender la funcionalidad de una clase UIKit. El problema que estoy enfrentando es que a partir de ejemplos que he visto en otros idiomas, el patrón me obliga a duplicar la interfaz del objeto decorado. Así es como me gustaría ver el patrón implementado:Patrón de decorador en Objective-C

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

Entonces, para resumir, el problema es la duplicación del método del objeto decorado, incluso cuando no es necesario que cambie nada allí. ¿Hay alguna manera más sencilla de transmitir esos métodos que no necesito "sobreescribir"? ¿Podría usar algo como un NSProxy para esto?

EDITAR: Esto se ha convertido principalmente en un problema teórico para mí, ya que me di cuenta de que el patrón de decorador no es lo que necesitaba para resolver mi problema real. Sin embargo, dado que puedo volver a usarlo en el futuro, todavía estoy muy interesado en sus respuestas.

Respuesta

4

Sí, es posible implementar el patrón Decorator en Objective-C con NSProxy.Debería implementar methodSignatureForSelector: y forwardInvocation: al forward messages en el objeto decorado.

Pero no creo que esta sea una buena solución para lo que intenta hacer aquí; el envío de invocaciones es muy costly y no está destinado a tal fin; sin duda hay mejores formas de lograr lo que desea hacer, p. con categorías (o tal vez método swizzling).

+0

Creo que el swizzling es la forma correcta de hacer decoradores en Objective-C. –

2

Creo que lo que quieres es crear una categoría. Las categorías le permiten agregar métodos adicionales a una clase existente, incluidas las clases principales.

iOS Reference Library: Category

+0

segundo esto. Fue mi primer pensamiento también. Las categorías en el Objetivo C son cosas muy útiles. – drekka

+1

Las categorías son buenas para agregar métodos adicionales, pero no puede usarlas para extender la funcionalidad de los métodos sobrescritos. Por lo tanto, no son un sustituto del patrón de decorador. También me di cuenta de que malinterpreté el patrón ya que no debería usarlo para agregar nuevos métodos al objeto decorado. Voy a editar el ejemplo de código. –

2

es no (sólo) un problema de codificación método aburrido, pero de la duplicación del objeto es decir, no se crearán dos objetos UIScrollView, decorador y objeto decorado. Esto puede ser útil a veces cuando realmente necesita dos objetos diferentes y sus datos, pero uno de ellos es el maestro, yo lo llamo un "decorador barato". Pero si el decorador en sí mismo no necesita y usa sus propios miembros, lo llamo "decorador sucio" (no se preocupe, no está solo y lo hago algunas veces ;-) El decorador puro implementará la misma interfaz (Java) resp. categoría (Obj-C) como el objeto decorado.

Quizás es suficiente con que la subclase UIScrolView sobrescriba los métodos que le interesen. O categorías como Matthew sugirió.

Saludos

Kay

3

su comentario en la interfaz:

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

es incorrecto. No es necesario redeclarar los métodos heredados de la superclase en la interfaz, incluso si los está implementando de nuevo. Esto no es C++.

Sin embargo, tiene que crear implementaciones de todos los métodos que redirigen a la vista de desplazamiento en la variable de instancia. Puede haber una manera inteligente de interceptar los mensajes a las instancias de su subclase y reenviarlos automáticamente a la variable de instancia, pero no puedo pensar qué sería lo que me gustaría tener en mente.

Como han dicho otros, considere usar una categoría de UIScrollView en su lugar. Las categorías no son un reemplazo directo para los decoradores ya que la implementación de una categoría le dará a todas las instancias de UIScrollView su método personalizado, pero eso probablemente no será un problema.

actualización

Puede utilizar -forwardingTargetForSelector: para redirigir los mensajes que no han aplicado al objeto decorado. Sin embargo, tenga en cuenta que esto será más lento que la implementación de cada método directamente o mediante el uso de categorías.

+0

Estás en lo correcto. Actualicé el código. El problema sigue siendo el mismo, por supuesto. –

+0

Una categoría no funcionará realmente si necesita anular un método en la clase original. Principalmente porque no puedes llamar a la implementación original si es necesario. Ver [aquí] (https://www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html). Creo que 'forwardingTargetForSelector:' es lo que quieres. – bcattle

1

Si todo va a añadir es métodos adicionales llevar por categorías Sólo tiene que importar encabezado y llama al método en cualquier UIScrollView

@interface UIScrollView (Additions) 
-(void)doFancyStuff; 
@end 

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

Incluso si realmente quería hacerla una subclase que lo haría de esta manera

@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 
0

creo que esto se acerca a la decorator:

USO:

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

De esta manera el tableView mantendrá responder a ella es eventos TableView y, además, los eventos ScrollView de _decorator

citar: la _decorator se puede derivar de cualquier clase que implementa la UIScrollViewDelegate

Creo que este es similar a UINavigationController o UITabbarController que, en mi opinión, se acerca mucho al patrón de decorador. Probablemente no puedas hacer esto con todo tipo de objetos, pero sí funciona con el tipo de cosas de UIView. También consulte el libro "Cocoa Desgn Patterns" para conocer los deliciosos patrones de cacao. Lamentablemente, lo que he encontrado con respecto al decorador es poco.

@interface DecoratorSample : UIScrollView <UIScrollViewDelegate> 
{ 
    UIScrollView* _scrollView; 
    id<UIScrollViewDelegate> _forwardDelegate;  
} 

@property (nonatomic,assign) id<UIScrollViewDelegate> forwardDelegate; 
@property (nonatomic,retain) UIScrollView* scrollView; 

@end 

y:

@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 
Cuestiones relacionadas