iOS 设计模式的应用 ⑭ 装饰模式

前言

    通常,我们拍照的时候,不会去想将来如何装饰它。比方说,后来冲印了照片,决定把它放入一个特殊玻璃制成的精美相框中。以后也可以把同一张照片放在别的相框里。虽然换了相框,但是照片没有受到影响,因为只是往照片添加了东西,而并没有改变它。在面向对象软件中,我们借用了类似的思想,向对象添加行为,而不破坏其原有风格,因此增加了的对象是同一个类的加强版。我们把这个设计模式叫做装饰模式

什么是装饰模式

    装饰模式是指动态地给一个对象添加一些额外的职责,同时又不改变其结构。就扩展功能来说,装饰模式相比生成子类更为灵活。

装饰模式的类图.png

    标准的装饰模式中 Component 定义了一些抽象操作,其实体类将进行重载以实现自己特定的操作。抽象的 Component 类可被细化为另一个叫做 Decorator 的抽象类。 Decorator 包含了 另一个 Component 的引用,定义了扩展这个 Component的实例的装饰性行为。 ConcreteDecorator 为其它 ComponentDecorator 定义了几个扩展行为,并且会在自己的操作中执行内嵌的 Component 操作。

什么时候使用装饰模式

  • 想要在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责
  • 扩展一个类的功能,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀,在不想增加很多子类的情况下扩展类。需要动态增加功能,动态撤销。

装饰模式的优缺点

装饰模式的优点

装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

装饰模式的缺点

多层装饰比较复杂

Cocoa 中的装饰模式

范畴

    Cocoa 框架本身利用范畴 (category)的方式实现了装饰模式,通过它可以向类添加行为,而不必进行子类化。通过范畴添加的方法对类原有的方法没有不良影响。范畴中的方法成为了类的一部分,并可由子类继承。然而这并不是一种严格的实现,它实现了模式的意图,但却是一种辩题。由范畴添加的行为是编译时绑定的,而且范畴实际上没有封装被扩展的类的实例。

装饰模式的实现

标准装饰模式的实现

通过装饰模式为图形创建滤镜处理,首先需要使用一种 Component 接口使得 UIImage 和这些滤镜连接起来,任何具体的 ImageComponent 和装饰器都可以处理这些调用。

@protocol ImageComponent 

// We will intercept these
// UIImage methods and add
// additional behavior
@optional
- (void) drawAsPatternInRect:(CGRect)rect; 
- (void) drawAtPoint:(CGPoint)point;
- (void) drawAtPoint:(CGPoint)point blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
- (void) drawInRect:(CGRect)rect;
- (void) drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;

@end

接着通过创建 UIImage 的范畴遵守 ImageComponent,由于协议中定义的方法,UIImage 已经实现,所以不必再范畴中实现

@interface UIImage (ImageComponent) 

@end

创建抽象的 ImageFilter 装饰类,ImageFilterapply方法让具体滤镜子类向 _component 增加额外行为,通过 forwardingTargetForSelector: 让子类返回代替的接收器处理 aSelector

@interface ImageFilter : NSObject 
{
  @private
  id  _component;
}

@property (nonatomic, strong) id  component;

- (void) apply;
- (id) initWithImageComponent:(id ) component;
- (id) forwardingTargetForSelector:(SEL)aSelector;

/*
// overridden methods in UIImage APIs
- (void) drawAsPatternInRect:(CGRect)rect; 
- (void) drawAtPoint:(CGPoint)point;
- (void) drawAtPoint:(CGPoint)point blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
- (void) drawInRect:(CGRect)rect;
- (void) drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha;
 */

@end
@implementation ImageFilter

@synthesize component=_component;


- (id) initWithImageComponent:(id ) component
{
  if (self = [super init])
  {
    // save an ImageComponent
    [self setComponent:component];
  }
  
  return self;
}

- (void) apply
{
  // should be overridden by subclasses
  // to apply real filters
}

- (id) forwardingTargetForSelector:(SEL)aSelector
{
  NSString *selectorName = NSStringFromSelector(aSelector);
  if ([selectorName hasPrefix:@"draw"])
  {
    [self apply];
  }
  
  return component_;
}

/*
- (void) drawAsPatternInRect:(CGRect)rect
{
  [self apply];
  [_component drawAsPatternInRect:rect];
}

- (void) drawAtPoint:(CGPoint)point
{
  [self apply];
  [_component drawAtPoint:point];
}

- (void) drawAtPoint:(CGPoint)point blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha
{
  [self apply];
  [_component drawAtPoint:point
                blendMode:blendMode
                    alpha:alpha];
}

- (void) drawInRect:(CGRect)rect
{
  [self apply];
  [_component drawInRect:rect];
}

- (void) drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha
{
  [self apply];
  [_component drawInRect:rect
               blendMode:blendMode
                   alpha:alpha];
}
*/

@end

ImageTransformFilterImageShadowFilter 专注于通过重载 apply方法提供自己的滤镜算法。

@interface ImageTransformFilter : ImageFilter
{
  @private
  CGAffineTransform _transform;
}

@property (nonatomic, assign) CGAffineTransform transform;

- (id) initWithImageComponent:(id )component 
                    transform:(CGAffineTransform)transform;
- (void) apply;

@end
@implementation ImageTransformFilter

@synthesize transform=_transform;


- (id) initWithImageComponent:(id )component 
                    transform:(CGAffineTransform)transform
{
  if (self = [super initWithImageComponent:component])
  {
    [self setTransform:transform];
  }
  
  return self;
}

- (void) apply
{
  CGContextRef context = UIGraphicsGetCurrentContext();
  
  // setup transformation
  CGContextConcatCTM(context, _transform);
}

@end
@interface ImageShadowFilter : ImageFilter
{

}

- (void) apply;

@end

@implementation ImageShadowFilter

- (void) apply
{
  CGContextRef context = UIGraphicsGetCurrentContext();
  
  // set up shadow
  CGSize offset = CGSizeMake (-25,  15);
  CGContextSetShadow(context, offset, 20.0);
}

@end

创建一个 DecoratorView,实现 Image 的绘制调用滤镜处理


@interface DecoratorView : UIView 
{
  @private
  UIImage *_image;
}

@property (nonatomic, strong) UIImage *image;

@end

最后为图片增加滤镜

// load the original image
UIImage *image = [UIImage imageNamed:@"Image.png"];
// create a transformation
CGAffineTransform rotateTransform = CGAffineTransformMakeRotation(-M_PI / 4.0);
CGAffineTransform translateTransform = CGAffineTransformMakeTranslation(-image.size.width / 2.0, 
                                                                        image.size.height / 8.0);
CGAffineTransform finalTransform = CGAffineTransformConcat(rotateTransform, translateTransform);

// a true subclass approach
id  transformedImage = [[ImageTransformFilter alloc] initWithImageComponent:image
                                                                                   transform:finalTransform];
id  finalImage = [[ImageShadowFilter alloc] initWithImageComponent:transformedImage]; 
DecoratorView *decoratorView = [[DecoratorView alloc] initWithFrame:[self.view bounds];
[decoratorView setImage:finalImage];
[self.view addSubview:decoratorView];

使用范畴的方式实现装饰

在使用范畴的方式中,只需要向 UIImage 类中添加滤镜,构成范畴,首先创建BaseFilter ,与 ImageFilter 类似定义几个基本的二维绘图操作。


@interface UIImage (BaseFilter)

- (CGContextRef) beginContext;
- (UIImage *) getImageFromCurrentImageContext;
- (void) endContext;

@end
@implementation UIImage (BaseFilter)

- (CGContextRef) beginContext
{
  // Create a graphics context with the target size
  // On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions 
  // to take the scale into consideration
  // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
  CGSize size = [self size];
  if (NULL != UIGraphicsBeginImageContextWithOptions)
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
  else
    UIGraphicsBeginImageContext(size);
  
  CGContextRef context = UIGraphicsGetCurrentContext();
  
  return context;
}

- (UIImage *) getImageFromCurrentImageContext
{
  [self drawAtPoint:CGPointZero];
  
  // Retrieve the UIImage from the current context
  UIImage *imageOut = UIGraphicsGetImageFromCurrentImageContext();
  
  return imageOut;
}

- (void) endContext
{
  UIGraphicsEndImageContext();
}

@end

创建 TransformShadow 范畴实现扩展行为

@interface UIImage (Transform)

- (UIImage *) imageWithTransform:(CGAffineTransform)transform;

@end
@implementation UIImage (Transform)

- (UIImage *) imageWithTransform:(CGAffineTransform)transform
{
  CGContextRef context = [self beginContext];
  
  // setup transformation
  CGContextConcatCTM(context, transform);
  
  // Draw the original image to the context
  UIImage *imageOut = [self getImageFromCurrentImageContext];
  
  [self endContext];
  
  return imageOut;
}

@end
@interface UIImage (Shadow)

- (UIImage *) imageWithDropShadow;

@end
- (UIImage *) imageWithDropShadow
{
  CGContextRef context = [self beginContext];
  
  // set up shadow
  CGSize offset = CGSizeMake (-25,  15);
  CGContextSetShadow(context, offset, 20.0);
  
  // Draw the original image to the context
  UIImage * imageOut = [self getImageFromCurrentImageContext];
  
  [self endContext];
  
  return imageOut;
}

@end

总结

    装饰模式动态地为对象附加额外的职责,为扩展功能提供了一种灵活的替代子类的方法。与子类化一样,装饰模式的改编允许在不修改现有代码的情况下合并新行为。装饰模式包装其行为扩展的类的对象。它们实现与它们包装的对象相同的接口,并在将任务委托给包装对象之前或之后添加自己的行为。装饰模式表达了类应该对扩展开放但对修改关闭的设计原则。

你可能感兴趣的:(iOS 设计模式的应用 ⑭ 装饰模式)