iOS 设计模式之二十四(访问者模式)

一、概念

1、访问者模式的动机

​ 虽然“看病难,看病贵”,但是一旦身体有恙,还是要主动去医院检查的,不要硬抗。医院一般的处理流程:医生开具处方单,划价人员拿到处方单之后根据药品名称和数量计算总价,药房工作人员根据药品名称和数量准备药品。我们可以将处方单看成一个药品信息的集合,里面包含了一种或多种不同类型的药品信息,不同类型的工作人员(如划价人员和药房工作人员)在操作同一个药品信息集合时将提供不同的处理方式,而且可能还会增加新类型的工作人员来操作处方单。

​ 在软件开发中,有时候我们也需要处理像处方单这样的集合对象结构,在该对象结构中存储了多个不同类型的对象信息,而且对同一对象结构中的元素的操作方式并不唯一,有一种模式可以满足上述要求,其模式动机就是以不同的方式操作复杂对象结构,这就是访问者模式

2、访问者模式的定义

访问者模式(Visitor Pattern):提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。

​ 访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分。在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为“对象结构”,访问者通过遍历对象结构实现对其中存储的元素的逐个操作。

3、访问者模式的5个角色

1)Vistor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。

​ 这些访问方法的命名一般有两种方式:一种是直接在方法名中标明待访问元素对象的具体类型,如visitElementA(ElementA elementA),还有一种是统一取名为visit(),通过参数类型的不同来定义一系列重载的visit()方法。

2)ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。

3)Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。

4)ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。

5)ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,比如一个List对象或一个Set对象。

4、结构图
访问者模式

二、示例

​ 本Demo以部门与员工为例:

1)首先创建Employee类,有一个accept()方法,表示抽象元素;

2)然后创建FulltimeEmployee和PartTimeEmployee类,继承自Employee类,实现accept()方法,表示具体元素;

3)然后创建Department类,有两个visit()方法,表示抽象访问者;

4)然后创建ITDepartment和HRDepartment类,继承自Department类,表示具体访问者;

5)最后创建EmployeeList类,内部有个Array用来存储Employee对象,表示对象结构。

具体代码如下:

Employee类:

@class Department;
// 员工类:抽象元素类
@interface Employee : NSObject
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *workTime;
- (instancetype)initWithName:(NSString *)name workTime:(NSString *)workTime;
- (void)accept:(Department *)department; //接受一个抽象访问者访问
@end

@implementation Employee
- (instancetype)initWithName:(NSString *)name workTime:(NSString *)workTime {
    self = [super init];
    if (self) {
        _name = name;
        _workTime = workTime;
    }
    return self;
}

- (void)accept:(Department *)department {}
@end

FulltimeEmployee和PartTimeEmployee类:

// FulltimeEmployee 全职员工类:具体元素类
@interface FulltimeEmployee : Employee
@end
@implementation FulltimeEmployee
- (void)accept:(Department *)department {
    [department visitFulltimeEmployee:self]; // 调用访问者的访问方法
}
@end

// PartTimeEmployee 兼职员工类:具体元素类
@interface PartTimeEmployee : Employee
@end
@implementation PartTimeEmployee
- (void)accept:(Department *)department {
    [department visitPartTimeEmployee:self];
}
@end

Department类:

// 部门类:抽象访问者类
@interface Department : NSObject
// OC只有Override重写,不能Overload重载,所以这里命名方法不同
// 声明一组访问方法,用于访问不同类型的具体元素
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee;
- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee;
@end

@implementation Department
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee {}
- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee {}
@end

ITDepartment和HRDepartment类:

// ITDepartment IT部门类:具体访问者类
@interface ITDepartment : Department
@end
@implementation ITDepartment
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee {
    NSLog(@"IT部门-全职:姓名%@, %@", employee.name, employee.workTime);
}

- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee {
    NSLog(@"IT部门-兼职:姓名%@, %@", employee.name, employee.workTime);
}
@end

// HRDepartment 人力资源部类:具体访问者类
@interface HRDepartment : Department
@end
@implementation HRDepartment
// 实现人力资源部对全职员工的访问
- (void)visitFulltimeEmployee:(FulltimeEmployee *)employee {
    NSLog(@"HR部门-全职:姓名%@, %@", employee.name, employee.workTime);
}

// 实现人力资源部对兼职员工的访问
- (void)visitPartTimeEmployee:(PartTimeEmployee *)employee {
    NSLog(@"HR部门-兼职:姓名%@, %@", employee.name, employee.workTime);
}
@end

EmployeeList类:

// 员工列表类:对象结构
@interface EmployeeList : NSObject
- (void)add:(Employee *)employee;
- (void)remove:(Employee *)employee;
- (void)accept:(Department *)department;
@end

@interface EmployeeList ()
@property(nonatomic, strong) NSMutableArray *list; //定义一个集合用于存储员工对象
@end
@implementation EmployeeList
- (instancetype)init
{
    self = [super init];
    if (self) {
        _list = [NSMutableArray array];
    }
    return self;
}

- (void)add:(Employee *)employee {
    [self.list addObject:employee];
}

- (void)remove:(Employee *)employee {
    if ([self.list containsObject:employee]) {
        [self.list removeObject:employee];
    }
}

- (void)accept:(Department *)department {
    // 遍历访问员工集合中的每一个员工对象
    for (Employee *employee in self.list) {
        [employee accept:department];
    }
}
@end

运行代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Employee *zhangSan = [[FulltimeEmployee alloc] initWithName:@"张三" workTime:@"朝九晚五"];
    Employee *liSi = [[PartTimeEmployee alloc] initWithName:@"李四" workTime:@"苦逼的997"];
    Employee *xiaoHong = [[FulltimeEmployee alloc] initWithName:@"小红" workTime:@"朝九晚五"];
    Employee *xiaoLi = [[PartTimeEmployee alloc] initWithName:@"小丽" workTime:@"苦逼的996"];
    
    EmployeeList *list = [EmployeeList new];
    [list add:zhangSan];
    [list add:liSi];
    [list add:xiaoHong];
    [list add:xiaoLi];
    
    ITDepartment *IT = [ITDepartment new];
    [list accept:IT];
    NSLog(@"--------------------------");
    
    // 访问者模式:在系统中增加一种新的访问者,无须修改源代码,只要增加一个新的具体访问者类即可,比如新增HR部门
    // 但是如果新增Employee类型,则必定要修改Department,增加访问新类型的方法
    HRDepartment *HR = [HRDepartment new];
    [list accept:HR];
}

打印结果:

IT部门-全职:姓名张三, 朝九晚五
IT部门-兼职:姓名李四, 苦逼的997
IT部门-全职:姓名小红, 朝九晚五
IT部门-兼职:姓名小丽, 苦逼的996
--------------------------
HR部门-全职:姓名张三, 朝九晚五
HR部门-兼职:姓名李四, 苦逼的997
HR部门-全职:姓名小红, 朝九晚五
HR部门-兼职:姓名小丽, 苦逼的996

三、总结

​ 由于访问者模式的使用条件较为苛刻,本身结构也较为复杂,因此在实际应用中使用频率不是特别高。当系统中存在一个较为复杂的对象结构,且不同访问者对其所采取的操作也不相同时,可以考虑使用访问者模式进行设计。在XML文档解析、编译器的设计、复杂集合对象的处理等领域访问者模式得到了一定的应用。

​ 访问者模式与抽象工厂模式类似,对“开闭原则”的支持具有倾斜性,可以很方便地添加新的访问者,但是添加新的元素较为麻烦。

1、优点

1、增加新的访问操作很方便。使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合“开闭原则”。

2、将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。

3、让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。

2、缺点

1、增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”的要求。

2、破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。

3、适用场景

1、一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
2、需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离

3、对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。

Demo地址:iOS-Design-Patterns

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容