内容概览
- 前言
- 设计信息流
- 定义明确的责任
- 用不变性进行简化
前言
大多数大型应用的架构都会经历从简单到复杂的过程。
随着业务逻辑不断增多,应用需要处理的任务也会越来越繁杂,各种bug也会显现出来。
如果应用没有采用合适的软件架构和模式,问题将变得更加棘手。
你对此是否深有体会?







针对这些问题,我们需要对架构进行不断地探索,因此我们对架构的理解也会更加深入。
渐渐地,我们对架构的要求会越来越高。然而,往往我们的能力没有和我们的品位一起提升。


架构不是教条主义,它需要我们的洞察力!
设计信息流


真实数据 和 派生数据


派生数据的特点:
- 根据输入数据计算得出
- 当输入数据变化时,需要重新计算
- 很像缓存
示例:
这是一个很常见的需求。
在界面放置一个UITextView,旁边有一个UILabel作为UITextView的文字计数器。
当UITextView的文本发生变化时,我们会在代理方法中更新UILabel的character count。

如果通过代码来直接更新UITextView的文本,是不会有代理方法被调用的。
这时候可能会出现什么问题?
如果,此时只是通过代码来更新UITextView的文本,character count将得不到更新:


所以,我们需要通过model来直接更新TextView的文本和character count,因为实际的数据在model处。

如果用户输入了新的值,model也需要被更新。

但是,如果只是这样做的话,我们就丢失了原始数据。
假如,当前页面是主界面列表中某一条记录的详情页,用户通过点击主界面列表跳转到当前页面。如果我们直接对原始的模型进行修改,这是不合理的。
所以,合理的设计应该是这样的:

总结一下设计信息流的过程:
- 找到真实数据在哪里
- 找到真实数据和派生数据的联系
- 更新真实数据
定义明确的责任


对输入数据进行校验是软件开发中常见的情况,尤其是对注册、登录模块的输入数据进行校验。

常见的验证流程:
- 对每个输入框进行验证
- 当所有输入框的验证都通过时,将按钮设为可用状态。

全部输入框的验证流程:
- 检查用户名输入框
- 检查密码输入框
- 检查第二个密码输入框的值是否和第一个相同
-
检查邮箱输入框
某个输入框的验证流程:
- 创建正则表达式
- 查找匹配的用户名
- 如果有匹配项,用户名有效
- 否则,在输入框提示错误

在这个过程中,我们还需要判定nil:


然后,我们可能会写出这样的代码:
NSString *username = [self.usernameField text];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@“[a-zA-Z0-9_]{6,}” options:0 error:nil];
NSRange result = [regex rangeOfFirstMatchInString:username options:NSMatchingAnchored range:NSMakeRange(0, [username length])];
if (username && result.location == NSNotFound) {
allValid = NO;
[self.usernameField setBackgroundColor:[UIColor redColor]];
} else {
if (!username) {
anyNil = YES;
}
[self.usernameField setBackgroundColor:[UIColor whiteColor]]];
}
请注意,验证过程直接对输入框的背景色进行了更改,这是不合理的。

而且,验证过程被定义在了ViewController中,这也是不合理的。
如果有多个页面需要进行相同的校验,一定会出现这样的情况:

所以,我们应该将验证过程独立出来:

验证过程的特征:
- 接收输入数据
- 检查输入数据是否有效
- 如果无效,给出原因
根据以上特征,定义一个验证器协议:
protocol Validator {
validateWithError(error: NSErrorPointer) -> Bool
}
这样定义有以下优势:
- 由协议的遵守者来定义输入
- 可以由小的验证来构建大的验证
- 可以组合
- 同样适用于ObjC
然后,定义用户名验证器:
class UsernameValidator: Validator {
var input: NSString?
func validateWithError(error: NSErrorPointer) -> Bool {
let regex = NSRegularExpression(pattern: ...)
...
}
}
然后,定义密码验证器:
class PasswordValidator: Validator {
var input: NSString?
...
}
class SetPasswordValidator: Validator {
let firstPasswordValidator = PasswordValidator()
let secondPasswordValidator = PasswordValidator()
...
}
然后,只需要组合前面的验证器就可以轻松定义注册验证器:
class SignUpValidator: Validator {
let usernameValidator = UsernameValidator()
let setPasswordValidator = SetPasswordValidator()
let emailAddressValidator = EmailAddressValidator()
...
}
用不变性进行简化
假设有三个实例 A, B, C:

当你需要把A中的值赋给B时,如果这个值为引用类型,那么A仍然指向了这个值。

如果,B对这个值进行修改,A也会受到影响。

如果再将这个引用值赋给C,情况将会变得更加复杂。

如果使用值类型,而不是引用类型呢?

把A中的值赋给B。

B如果改变这个值也不会影响A。

Swift中的 struct
是很好用的值类型,它有以下特征:
- 可选择的可变性(mutating 关键字)
- 传递的是值,而不是引用 (Copy-On-Write 写时复制)
参考内容:
Advanced iOS Application Architecture and Patterns
转载请注明出处,谢谢~
网友评论