1. 程式人生 > >Visitor(訪問者模式)

Visitor(訪問者模式)

定義:the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures

表示一個作用於某物件結構中的各元素操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。

問題:現在 我要寫兩個類,OperateA和OperateB,這兩個類都有兩個變數a和b,同時提供一個相同的方法 add,在OperateA中add單純的返回a+b,在OperateB中,返回的是a*10+b(沒什麼意思,就是為了區別兩個add函式)。

好了,如果你面相物件的思想牛B一點,你可以讓他們共同繼承自一個叫做Operate的抽象類,然後這個抽象類有一個抽象方法add,程式碼如下:

#import <Foundation/Foundation.h>

@interface Operate : NSObject

@property (nonatomic) int a;
@property (nonatomic) int b;

-(int) add;

@end
這是一個抽象類,add是一個抽象方法 不需要實現

然後讓OperateA和OperateB繼承Operate

#import "Operate.h"

@interface OperateA : Operate

@end
#import "OperateA.h"

@implementation OperateA

-(int) add
{
    return self.a+self.b;
}

@end
以上是OperateA的程式碼
#import "Operate.h"

@interface OperateB : Operate

@end
#import "OperateB.h"

@implementation OperateB

-(int) add
{
    return self.a*10+self.b;
}

@end
好了,以上程式碼在我看來已經是不錯的了,但是 你發現了沒有 如果我這時候需要為OperateA和OperateB新增一個sub函式。。。你要怎麼辦?去修改3個類。。這是非常糟糕的一件事情。

於是,你可能會想到策略模式,沒錯 策略模式確實是一個不錯的解決方法,但其實,對於我而言,策略模式每次都要讓呼叫者自己選擇演算法,而我的OperateA和OperateB的演算法都是固定的。

所以 我們思考一個新的解決方法,按照策略模式的思路 我們需要吧add這個方法抽調出來,變成一個Add類,該類提供兩個方法 calWithMethodA以及calWithMehodB,然後讓Add繼承一個叫做Calculate的抽象函式(因為以後可能還會有Sub的加入),變成這樣

#import "Operate.h"
@class Operate; //類的提前宣告

@interface Calculate : NSObject

-(int)calWithMethodA:(Operate*)op;

-(int)calWithMethodB:(Operate*)op;

@end

注意 Calculate是一個抽象函式,兩個calWithMethod方法都是抽象方法,讓Add類繼承自Calculate,並且去實現這2個方法

#import "Calculate.h"

@interface Add : Calculate

@end
#import "Add.h"

@implementation Add

-(int) calWithMethodA:(Operate*)op
{
    return op.a+op.b;
}

-(int) calWithMethodB:(Operate*)op
{
    return op.a*10+op.b;
}

@end
好了 這時候,已經按照策略模式吧所謂的演算法分離出來了,那原來的Operate類又該怎麼修改呢?我們把原來的add方法刪除,改成一個叫做calWithCalculate的方法,然後讓cal方法去呼叫對應的演算法,變成這樣
#import <Foundation/Foundation.h>
#import "Calculate.h"

@class Calculate;   //可能需要使用類的提前宣告,沒有測試過這裡

@interface Operate : NSObject

@property (nonatomic) int a;
@property (nonatomic) int b;

-(void) calWithCalculate:(Calculate*)cal;

@end

然後 OperateA同樣繼承自Operate類,變成這樣
#import "Operate.h"

@interface OperateA : Operate

@end

#import "OperateA.h"

@implementation OperateA

-(void) calWithCalculate:(Calculate*)cal
{
    [cal calWithMethodA:self];
}

@end

OperateB其實一樣,只是呼叫變成了calWithMethodB
#import "OperateB.h"

@implementation OperateB

-(void) calWithCalculate:(Calculate*)cal
{
    [cal calWithMethodB:self];
}

@end

注意 這裡的訣竅就在於無論是Operate還是Calculate提供的抽象方法 對外的引數介面都是父類的指標,這樣具體方法的實現中會根據對應的指標去尋找對應的子類例項

好了 這樣 我們的重構已經結束了,如果需要一個Sub方法,只需要在寫一個Sub類,繼承自Calculate,然後實現

-(int)calWithMethodA:(Operate*)op;

-(int)calWithMethodB:(Operate*)op;

這兩個方法即可,這樣就完成了對演算法的擴張,同時滿足了OCP原則。

當然,真正的Visitor設計模式還提供了一個Context(相當於呼叫環境),讓我們新建一個Context類,去呼叫這個OperateA和B吧

#import <Foundation/Foundation.h>
#import "Operate.h"
#import "Calculate.h"

@interface Context : NSObject

-(void) addOperate:(Operate*)op;
-(void) removeOperate:(Operate*)op;
-(void) calWithCalculate:(Calculate*)cal;

@end

#import "Context.h"

@implementation Context
{
    NSMutableArray* array;
}

-(void) addOperate:(Operate*)op
{
    [array addObject:op];
}

-(void) removeOperate:(Operate*)op
{
    [array removeObject:op];
}

-(void) calWithCalculate:(Calculate*)cal
{
    for (Operate* op in array)
    {
        [op calWithCalculate:cal];
    }
}

@end

好了 這就是整個Visitor模式了,客戶端呼叫的時候 只需要這樣
    Context* context=[[Context alloc] init];
    OperateA* opA=[[OperateA alloc] init];
    OperateB* opB=[[OperateB alloc] init];
    
    [context addOperate:opA];
    [context addOperate:opB];
    Add* add=[[Add alloc] init];
    [context calWithCalculate:add];

然後 如果你需要新增一個對於OperateA與OperateB的sub方法,也只需要新建一個Sub類,去繼承Calculate方法即可,不在需要修改原來已經封裝好了的OperateA與OperateB了。

我們回過頭看看定義

表示一個作用於某物件結構中的各元素操作(OperateA與OperateB)。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作(新增Sub方法)。

一切看起來似乎都很完美,但是發現了沒有,如果現在出現一個OperateC怎麼辦?我需要一個calWithMethodC,這樣 我需要修改好多類才能完成達到這個目的。是的沒錯,這就是Visitor的缺點,一定要記住,在你使用Visitor之前,OperateA和OperateB一定是固定不變的,這可能也是Visitor受到限制的原因之一

所以 GOF四人中有一個就說 大多數的情況你不需要使用Visitor模式,但是當你真正需要它的時候,代表你真正需要它了。。。

好了 Visitor可能是所有設計模式中最難的一個了,理解這個設計模式需要有一定的耐心,當然 我發現如果你把它的思想和策略模式結合起來似乎變得很簡單了,因為他們都是把具體的演算法抽象了出來,不同的地方可能在於 Visitor是針對多個物件的一種抽象,而策略模式是針對一個物件的演算法抽象

總之 你會發現,設計模式都是把具體的,可能以後會變得方法抽象出一個具體的抽象類,這樣以後擴充套件的時候,新建一個類去實現這些抽象函式,這樣做到了OCP原則。所以 其實 設計模式並不是那麼可怕,具體知道了為什麼他要這麼做 就顯得不是那麼困難了。

原諒我不習慣使用UML圖,因為我每次閱讀的時候都不愛看那個圖。。。簡直就是越看越看不懂