美文网首页ios 知识点
iOS设计模式 ─── 行为拓展

iOS设计模式 ─── 行为拓展

作者: WellsCai | 来源:发表于2018-06-11 15:52 被阅读5次

行为拓展,也就是为对象添加功能。如果接触设计模式之前,我们为一个对象添加新功能也就是直接在类里面添加对应代码,继承或者写个分类(这其实就是装饰)。现在你有其他方法了(虽然我觉得责任链其实归结于对象解耦)。
对于这三种设计模式,都是在拓展对象行为的同时,对对象进行最少的修改甚至不作修改。(这个也是一个很重要的设计原则)
涉及到行为拓展的有以下三种设计模式:
① 访问者
② 装饰
③ 责任链

一. 访问者

在软件设计中,为了扩展类的功能而往一个类里塞进太多方法,类就会变得极为复杂。更好的做法是创建外部的类来拓展它,而对原始的代码不作太多改动。
访问者模式涉及两个关键角色:访问者和它访问的元素,元素可以是任何对象,但通常是“部分-整体”结构中的节点。如下类图所示,Visitor协议声明了两个很像的visit方法,用于访问和处理各种Element类型的ConcreteVisitor(1或2)实现了这一协议及抽象方法。visit操作定义了针对Element类型的适当操作。Client创建ConcreteVisit(1或2)的对象,并将其传给一个Element对象结构,Element对象结构中有个方法接受一般化的Visitor类型。继承Element的类中,所有的acceptVisitor:方法中的操作几乎一样,就是让Visitor对象访问发起调用的具体Element对象。实际使用的visit消息,定义在每个具体Element类中,这是具体Element类之间唯一不同点。每当把acceptVisitor:消息传给Element结构,这个消息就会被转发给每个节点。在运行时确定Element对象的实际类型,再根据实际类型决定该调用哪个visit方法。

访问者模式.png

举个例子🌰:以房屋承包商为例,Plumber(管道工)和Electrician(电工)是访问者。House是个复杂结构,包含有Fixable(可修理的)抽象物品,Contractor(承包商)可对其进行访问并维修。Plumber可以用其专有的visitPlumbing:操作访问House的Plumbing(管路)结构。同样的,电工可用他的visitElectrical:操作来访问Electrical(电路)结构。普通的Contractor好像既会修Plumbing又会修Electrical,实际上它把这些工作转包给能实际完成工作的人。所以,业主不必学习新的技能(修改现有的代码),只需要打开门让承包商进来,承包商(访问者)可以执行特定的技术性工作。

承包商和业主.png

访问者模式是拓展组合结构功能的一种强有力的方式。如果组合结构具有精心设计的基本操作,而且将来也不会变更,就可以用访问者模式,用各种不同用途的访问者,以同样的方式访问这个组合对象。访问者模式用尽可能少的修改,可以把组合结构与其他访问者类中的相关算法分离。
为什么访问者模式一直和组合绑在一起?在组合模式中,如果直接修改,就需要在组合的基接口,同时修改每个节点类。但对于访问者模式,通常就不再需要组合体类的接口了。如果使用的是范畴(分类),那我们针对不同的节点,就得创建对应的独立的分类。而使用访问者的话,只需要一个,它可以把针对所有节点的相关算法合并在移除。同样,如需再次拓展这些节点就需要修改已有的范畴或创建新的范畴(这样其实与直接修改相比差别不大)。很多情况下,使用范畴不比访问者好多少(如果是非组合情况下,使用范畴还是相当方便)。
当然,访问者模式也有个显著缺点,访问者与目标类耦合在一起。如果访问者需要支持新的类,访问者的父类和子类都要修改,才能反映新的功能(所以这边也要看是不是经常在目标类家族中添加新类)。

在以下场景,需要考虑访问者模式:

  • 一个复杂的对象结构包含很多其他对象,它们有不同的接口(比如组合体),但是想对这些对象实施一些依赖于其具体类型的操作。
  • 需要对一个组合结构中的对象进行不相关的操作,但是不想让这些操作“污染”这些对象的类。可以将相关的操作集中起来,定义在一个访问者类中,并在需要访问者中定义的操作时使用它。
  • 定义复杂结构的类很少作修改,但经常需要向其添加新的操作。

二. 装饰

对比访问者模式,装饰是通过从外部进行“装饰”,就像照片外面加了相框一样。装饰模式是动态地给一个对象添加一些额外的职责,就拓展功能来说,装饰模式相比生成子类更为灵活。
标准的装饰器模式包括一个抽象Component父类,它为其他具体组件(component)声明了一些操作。抽象的Component类可被细化为另一个叫做Decorator的抽象类。Decorator包含另一个Component的引用。ConcreteDecorator为其他Component或Decorator定义了几个拓展行为,并且会在自己的操作中执行内嵌的Component操作。
Compont定义了一些抽象操作,其具体类将进行重载以实现自己特定的操作。Decorator是一个抽象类,它将一个Component(或Decorator)内嵌到Decorator对象,定义了扩展这个Component的实例的“装饰性”行为。默认的operation方法只是向内嵌的Component发送一个消息。ConcreteDecoratorA和ConcreteDecoratorB重载父类的operation,通过super把自己增加的行为拓展给Component的operation。(如果只需要向Component添加一种职责,可以省掉抽象的Decorator类)

装饰器模式.png

举个例子🌰:为UIImage创建图像滤镜。装饰模式是向对象添加新的行为与职责的一种方式,它不改变任何现有的行为与接口。所以我们定义一个跟这个图像对象一样的类,但它包含对另一个图像对象的引用,增加这个图像对象的行为。
我们使用滤镜是就可以像下面这样使用:

// 普通图片 → 看成是Component
UIImage *image = [UIImage imageNamed:@"Image.png"];
// 变形滤镜 → 看成是ConcreteDecoratorA(为Component装饰)
id <ImageComponent> transformedImage = [[ImageTransformFilter alloc] initWithImageComponent:image transform:finalTransform];
// 阴影滤镜 → ConcreteDecoratorB (这边为一个Decorator装饰,也就是ConcreteDecoratorA)
id <ImageComponent> finalImage = [[ImageShadowFilter alloc] initWithImageComponent:transformedImage];

这两种滤镜都是继承于ImageFilter(ImageFilter内部的属性和方法如下,主要还是绑定Component和转发方法)。当使用finalImage时,会将finalImage画在view上,就会调用<ImageComponent>协议的draw方法,通过转发调用finalImage自身的apply方法,从而在Image的上下文上添加阴影。

@interface ImageFilter : NSObject <ImageComponent>

@property (nonatomic, retain) id <ImageComponent> component;
// 每个滤镜自己的执行方法(当做自己的策略)
- (void) apply;
// 初始化方法(绑定一个component)
- (id) initWithImageComponent:(id <ImageComponent>) component;
// 实现draw方法转发到apply
- (id) forwardingTargetForSelector:(SEL)aSelector;
对应滤镜的类图.png

装饰器是从外部改变内嵌的Component,或者说是改变对象的外表,每个节点也不知道谁在改变它,因为变化的部分是外面的类(比如滤镜类)。还有一种设计模式叫策略模式,是通过改变对象的“内容”,已滤镜为例,就好像直接在draw方法中改变,当然会注入对应的策略,所以说每个知道一组预定义的变更方式。两种模式的区别如下:

装饰与策略的异同.png

当然我们还可以使用范畴(也就是我们说的分类)来实现装饰模式。不过,在范畴方式中,滤镜是实例方法,而真正的子类方法(我们上面实现的)中是子类。真正子类方式的实现使用一种较为结构化的方式连接各种装饰器。范畴的方式更为简单和轻量,适合现有类只需要少量装饰器的应用程序。虽然范畴不同于实际的子类化,不能严格实现模式的原始风格,但它实现了解决同样问题的意图。实际的子类化虽然实现起来比较复杂,却更容易理解,图像的任何组合都能当做对象,我们可以动态应用或删除,而不影响UIImage原有行为的完整性。

在以下情况下,应该考虑使用装饰模式:

  • 想要在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 想要拓展一个类的行为却做不到。类定义可能被隐藏,无法进行子类化;或者对类的每个行为的拓展,为支持每种功能组合,将产生大量的子类。
  • 对类的职责的拓展是可选的。

三. 责任链

责任链模式与装饰模式在结构上有点类似,但是它实现不同的目的。
责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间发生耦合。此模式将这些对象连成一条链,并沿着这条链传递请求,知道有一个对象处理它位置。(通俗讲就是,“这个问题我不懂,也许你懂”,或者,“我的活干完了,现在该你了”)。
责任链中,对象引用了同一类型的另一个对象,形成一条链。链中每个对象实现了相同的方法,处理链中第一个对象发起的同一请求,如果第一个对象不知道如何处理请求,它就把请求传给下一个响应者(Successor)。
Handler 是上层抽象类,定义了处理请求handleRequest的方法。ConcreteHandler1和ConcreteHandler2实现了handleRequest方法,来处理它们认识的请求对象。Handler还有个Successor的实例(即下一个响应者)。消息从第一个Handler实例开始传递,如果这个实例不知道,就传给它的Successor(也是一个Handler实例),直到请求被传到链中的最后一个Handler。

责任链.png

举个例子🌰:以RPG游戏人物防御为例,防御道具可以是盾牌或铠甲,每种形式的防御只能应付一种特定的攻击。如果防御道具无法防御某种攻击,就会传给下一个道具,直到攻击到人物。
在游戏过程中,任何攻击处理程序都能在任何时间被添加或删除,而不会影响人物的其他行为(解耦的好处)。对此类设计,责任链模式是很自然的选择。否则,攻击处理程序的复杂组合会让人物的代码非常庞大,让处理程序的变更非常困难。

游戏中的责任链.png

之前说过,责任链模式在这一模块是行为拓展,它让链头的成员拥有了链的所有处理能力。同时消除耦合也是一个主要的功能,它让我们不用考虑要处理的请求是什么,要处理该请求的对象是哪个。如果不用责任链模式,得对这些一一去判断,让对象一一去对应请求。

在以下情况,需要考虑责任链模式:

  • 有多个对象可以处理请求,而处理程序只有在运行时才能确定。
  • 向一组对象发出请求,而不想显式指定处理请求的特定处理程序。

相关文章

网友评论

    本文标题:iOS设计模式 ─── 行为拓展

    本文链接:https://www.haomeiwen.com/subject/ewlssftx.html