面向对象编程:
1、很容易导致出现像上帝类(God classes - 承担着很多subclasses需要的重要高层级代码的所有责任),Blobs(有过多职权的classes),Lava Flow(因为含有太多的非法代码导致任何人都不敢碰的classes)等等这些种反面模式(anti patterns)。
2、我们经常要忍受着愈加繁杂和庞大的继承体系来获得代码的可重用性,而且随着继承层次的增加,代码的复杂性会加速增长,随之而来的bug也会越来越难以发现。这时我们可能需要依靠设计模式来找回我们的思路,然而大多数设计模式只能帮助你理顺你的代码结构,却在同时更加加深了你的代码的复杂度。
面向协议编程:
swift:协议+协议扩张,即可实现继承的功能。
这是一个能展示(之前的方式)有多残暴的例子:
1
2
3
4
5
6
7
8
|
class PresentErrorViewController: UIViewController {
var errorViewIsShowing: Bool = false
func presentError(message: String = “Error!", withArrow shouldShowArrow: Bool = false , backgroundColor: UIColor = ColorSalmon, withSize size: CGSize = CGSizeZero, canDismissByTappingAnywhere canDismiss: Bool = true ) {
//写下了复杂的,脆弱的代码
}
}
//说一下,有100个class继承了这个class
EveryViewControllerInApp: PresentErrorViewController {}
|
随着项目的进行事情马上变的明了:并不是每一个UIViewController需要这个error逻辑,或是真的需要这个class所提供的每一个功能。我团队里任何一个人都可以轻易的在这个superclass里改点儿什么,从而影响整个app。这就让代码变得脆弱。还使得代码呈现出了多态。当本应该是由子类决定它自己的行为,这里的superclass却给帮着决定了。下面是在swift 2.0中我们如何用POP来更好的构建这段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
protocol ErrorPopoverRenderer {
func presentError(message: String, withArrow shouldShowArrow: Bool, backgroundColor: UIColor, withSize size: CGSize, canDismissByTappingAnywhere canDismiss: Bool)
}
extension UIViewController: ErrorPopoverRenderer { //使所有遵从于ErrorPopoverRenderer协议的UIViewController具有一个presentError的默认实现
func presentError(message: String, withArrow shouldShowArrow: Bool, backgroundColor: UIColor, withSize size: CGSize, canDismissByTappingAnywhere canDismiss: Bool) {
//加上呈现error视图的默认实现
}
}
class KrakenViewController: UIViewController, ErrorPopoverRenderer { //Drop the God class and make KrakenViewController conform to the new ErrorPopoverRenderer Protocol.
func methodThatHasAnError() {
//…
//抛出error,原因是Kraken海妖今天吃人会感到不适。
presentError( /*blah blah blah 好多参数*/ )
}
}
|
看,这里发生了很炫酷的事情。我们不仅消除了上帝类的存在,还让代码更加的模块化并增强了它的扩展性。通过创建一个 ErrorPopoverRenderer协议,就会让任何遵循了该协议的class具有呈现出一个ErrorView的能力。还不止这些,我们的KrakenViewController class不用必须实现presentError这个函数,因为我们扩展了UIViewController,让它提供了一个默认实现。
唉不过等下!这有个问题!我们每次想要呈现一个ErrorView的时候都必须要去实现每一个参数。这就有点儿让人不爽了,因为我们不能在protocol协议函数声明中为参数提供默认值。
我还挺喜欢这些参数的!更糟的是在让代码更具模块化特征的过程中我们引入了复杂度。还是继续吧,用swift 2.0中新加的一个小妙招来多少的补偿一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
protocol ErrorPopoverRenderer {
func presentError()
}
extension ErrorPopoverRenderer where Self: UIViewController {
func presentError() {
//在这里加默认实现,并提供ErrorView的默认参数。
}
}
class KrakenViewController: UIViewController, ErrorPopoverRenderer {
func methodThatHasAnError() {
//…
//抛出error,原因是Kraken海妖今天吃人会感到不适。
presentError() //Woohoo! 没有参数了!我们现在有默认实现了!
}
}
|
好了,现在看起来已经很不错了。我们不仅消除了这些烦人的参数,还用swift 2.0的新特性在protocol的层级上用Self给了presentError一个默认实现。用Self意味着当且仅当协议的遵循者是继承自UIViewController的情况下,这个扩展才会有效。这就让我们能够把ErrorPopoverRenderer真的当做是一个UIViewController,而甚至不需要对后者做扩展!更棒的是,从现在开始,Swift的运行时是以静态调度而非动态调度去调用presentError()方法。大致的意思就是我们在函数调用点给presentError()方法增强了一点性能。
哎,不过还是有个问题。到这里我们POP的旅途暂时告一段落,但对它的完善依旧不会停止。我们的问题就是如果只想对一部分参数使用默认值,对剩下的不用默认值该怎么做?在这方面用POP的话基本帮不上什么忙,但是我们可以寻求另外一种方法。现在,我们使用VOP吧。
VALUE-ORIENTED PROGRAMMING
看到了吧,POP和VOP总是伴随出现。在上面的WWDC视频链接中,Crusty提出了一些大胆的论断:我们用struct和enum类型就可以做到一切class能做到的事。我很大程度上同意这点,但没这么极端。依我看,protocol本质上是把VOP粘合在一起的胶水,这点我和Crusty持相同态度。实际上既然我们说到了Swift的核心理念以及VOP,我想给你们看看从Andy Matuschak的精彩访谈中关于Swift中的VOP
的话题里面摘出来的一张极好的图:
能看出来Swift的标准库中,仅有的4个class,和余下的95个struct和enum的实例共同构建了Swift功能的核心。
Andy如此阐述道:用Swift编程的时候我们要去考虑用一层很薄的对象层,和一层很厚的值类型层。Class是有它们的地方,但是我想尽最大程度的去认为它们的位置只应该处于对象层中的一个很高的级别上,在这里通过操纵值类型层中的逻辑来管理各种行为。
"把逻辑和行为分开"——Andy Matuschak
和你所了解的一样,值类型被赋给一个变量或者常量,抑或是传给函数做参数时是它的值被拷贝的。这就让值类型在任何时候只有一个享有者,从而降低复杂度。和引用类型相反,在赋值过程中引用类型会有很多享有者,其中一部分你甚至都没意识到。在任何时间点使用引用的话会带来一些副作用:引用的享有者会捣蛋,在背后偷偷改变这个引用。Class = 高复杂度,值 = 低复杂度。
通过利用值类型的简约特性,咱们实现一下之前提过的默认参数的设计吧。我们用的是 Brian Gesiak的value options paradigm方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
struct Color {
let red: Double
let green: Double
let blue: Double
init(red: Double = 0.0, green: Double = 0.0, blue: Double = 0.0) {
self.red = red
self.green = green
self.blue = blue
}
}
struct ErrorOptions {
let message: String
let showArrow: Bool
let backgroundColor: UIColor
let size: CGSize
let canDismissByTap: Bool
init(message: String = "Error!" , shouldShowArrow: Bool = true , backgroundColor: Color = Color(), size: CGSize = CGSizeZero, canDismissByTappingAnywhere canDismiss: Bool = true ) {
self.message = message
self.showArrow = shouldShowArrow
self.backgroundColor = backgroundColor
self.size = size
self.canDismissByTap = canDismiss
}
}
|
使用上面的选项型struct(是值类型!)就使我们的POP带上了一些VOP的色彩,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
protocol ErrorPopoverRenderer {
func presentError(errorOptions: ErrorOptions)
}
extension ErrorPopoverRenderer where Self: UIViewController {
func presentError(errorOptions = ErrorOptions()) {
//在这里加默认实现,并提供ErrorView的默认参数。
}
}
class KrakenViewController: UIViewController, ErrorPopoverRenderer {
func failedToEatHuman() {
//…
//抛出error,原因是Kraken海妖今天吃人会感到不适。
presentError(ErrorOptions(message: "Oh noes! I didn't get to eat the Human!" , size: CGSize(width: 1000.0, height: 200.0))) //Woohoo! 没有参数了!我们现在有默认实现了!
}
}
|
如你所见,对于用view controller做error处理,我们给与它了一种完全抽象的,可伸缩的和模块化的方式,还不用强迫所有的view controller去继承一个上帝类。当你有一个具有不同功能的上帝类的时候,上面的例子尤其能帮到你。除此之外,用这种方式去实现类似上面error功能的其他功能时,你把实现该功能的代码放哪儿都行,不必做太多的重构或者改变代码框架。