命令模式
命令模式将一个请求封装为一个对象。封装以后的请求会比原生的请求更加灵活,因为这些封装后的请求可以在多个对象之间传递,存储以便以后使用,还可以动态的修改,或者放进一个队列中。苹果通过
Target-Action
机制和
Invocation
实现命令模式。
你可以通过苹果的官方在线文档阅读更多关于
Target-Action
的内容,至于
Invocation
,它采用了
NSInvocation
类,这个类包含了一个目标对象,方法选择器,以及一些参数。这个对象可以动态的修改并且可以按需执行。实践中它是一个命令模式很好的例子。它解耦了发送对象和接受对象,并且可以保存一个或者多个请求。
如何使用命令模式
在你深入了解
invocation
之前,你需要首先来设置一个支持撤销操作的大体骨架。所以你需要定义一个
UIToolBar
和用作撤销堆栈的
NSMutableArray
。
在
ViewController.m
的扩展中,在你定义其它变量的地方定义如下的变量:
UIToolbar *toolbar;
// We will use this array as a stack to push and pop operation for the undo option
NSMutableArray *undoStack;
这里我们创建了包含新增按钮的工具栏,同时还创建了一个用作命令存储队列的数组。
在
viewDidLoad
方法的第二个注释之前,增加下面的代码:
toolbar = [[UIToolbar alloc] init];
UIBarButtonItem *undoItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemUndo target:self action:@selector(undoAction)];
undoItem.enabled = NO;
UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
UIBarButtonItem *delete = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteAlbum)];
[toolbar setItems:@[undoItem,space,delete]];
[self.view addSubview:toolbar];
undoStack = [[NSMutableArrayalloc] init];
上面的代码在工具栏上面增加了
2
个按钮和一个可变长度组件(
flexible space)
,它还创建了一个空的撤销操作栈,刚开始撤销按钮是不可用的,因为撤销栈是空的。
另外你可能注意到工具条没有使用
frame
来初始化,因为
viewDidLoad
不是决定
frame
大小最终的地方。
在
ViewController.m
中增加如下设置
frame
大小的代码:
- (void)viewWillLayoutSubviews
{
toolbar.frame = CGRectMake(0, self.view.frame.size.height-44, self.view.frame.size.width, 44);
dataTable.frame = CGRectMake(0, 130, self.view.frame.size.width, self.view.frame.size.height - 200);
}
你将还需要在
ViewController.m
中增加三个方法来管理专辑:增加,删除,撤销。
第一个方法是增加一个新的专辑:
- (void)addAlbum:(Album*)album atIndex:(int)index
{
[[LibraryAPI sharedInstance] addAlbum:album atIndex:index];
currentAlbumIndex = index;
[self reloadScroller];
}
在这里你增加专辑,并设置当前专辑索引,然后重新加载滚动视图。
接下来是删除方法:
- (void)deleteAlbum
{
// 1
Album *deletedAlbum = allAlbums[currentAlbumIndex];
// 2
NSMethodSignature *sig = [self methodSignatureForSelector:@selector(addAlbum:atIndex:)];
NSInvocation *undoAction = [NSInvocationinvocationWithMethodSignature:sig];
[undoAction setTarget:self];
[undoAction setSelector:@selector(addAlbum:atIndex:)];
[undoAction setArgument:&deletedAlbum atIndex:2];
[undoAction setArgument:¤tAlbumIndex atIndex:3];
[undoAction retainArguments];
// 3
[undoStack addObject:undoAction];
// 4
[[LibraryAPI sharedInstance] deleteAlbumAtIndex:currentAlbumIndex];
[self reloadScroller];
// 5
[toolbar.items[0] setEnabled:YES];
}
上面的代码中有一些新的激动人心的特性,所以下面我们就来考虑每个被标注了注释的地方:
1.
获取需要删除的专辑
2.
定义了一个类型为
NSMethodSignature
的对象去创建
NSInvocation,
它将用来撤销删除操作。
NSInvocation
需要知道三件事情:选择器(发送什么消息),目标对象(发送消息的对象),还有就是消息所需要的参数。在上面的例子中,消息是与删除方法相反的操作,因为当你想撤销删除的时候,你需要将刚删除的数据回加回去。
3.
创建了
undoAction
以后,你需要将其增加到
undoStack
中。撤销操作将被增加在数组的末尾。
4.
使用
LibraryAPI
删除专辑
,
然后重新加载滚动视图。
5.
因为在撤销栈中已经有了操作,你需要使得撤销按钮可用。
注意
:使用
NSInvocation
,你需要记住下面的几点:
1.参数必须以指针的形式传递.
2.参数从索引2开始,索引0,1为目标(target)和选择器(selector)保留。
3.如果参数有可能会被销毁,你需要调用retainArguments.
最后,增加下面的撤销方法:
- (void)undoAction
{
if (undoStack.count > 0)
{
NSInvocation *undoAction = [undoStack lastObject];
[undoStack removeLastObject];
[undoAction invoke];
}
if (undoStack.count == 0)
{
[toolbar.items[0] setEnabled:NO];
}
}
撤销操作弹出栈顶的
NSInvocation
对象,然后通过
invoke
调用它。这将调用你在原先删除专辑的时候创建的命令,将删除的专辑加回专辑列表。因为你已经删除了一个栈中的对象,所以你需要去检查栈是否为空,如果为空,也就意味着不需要进行撤销操作了,你这时候需要将撤销按钮设置为不可用。
构建并运行的你应用,测试撤销机制,删除一个或者多个专辑,然后点击撤销按钮看看效果:
这里你正好也可以测试我们对专辑数据的变更是不是已经被存储了以便可以在不同的会话间使用。现在,你删除一条数据,将应用发送到后台,然后终止应用,下次应用启动的时候应该不会显示删除的专辑了。
接下来做啥?
你可以从这里下载完整的工程源代码:
BlueLibrary-final
在本应用中,我们没有涉及到其它两个设计模式,但是我们还是要提一下它们:
Abstract Factory (aka Class Cluster)
and
Chain of Responsibility (aka Responder Chain)
.
你可以自由选择去阅读上面的两篇文字以扩展你对设计模式的认知范围。
在本指南中,你看到如何利用设计模式的威力以一种直接和松耦合的方式去解决复杂的任务。你已经学到了许多的设计模式以及
它们的概念:
单例模式,
MVC
模式,委托模式,协议,门面模式,观察者模式,备忘录模式以及命令模式。
你最终的代码是松耦合,可复用以及可读的。如果另外一个开发者阅读你的代码,他们会马上理解代码逻辑以及每个类都做了什么。
我们并不是说要在你写的每句代码中使用设计模式。相反,我们要清楚的意识到可以用设计模式解决一些特定的问题,特别是在设计之初。他们会让作为开发者的生涯更加轻松,同时你的代码也将变的更加漂亮。
原文出处:http://xmuzyq.iteye.com/blog/1942386