作者:ray1689718授权本站转载。
-
原作者:Hector Matos
-
原发表日期:2015-07-13
Swift的核心@H_502_20@
我们可以通过等式的传递性来理解swift:
-
Swift的核心是面向协议的编程。
-
面向协议的编程的核心是抽象(abstraction)和简化(simplicity)。
-
所以swift的核心就是抽象和简化。
你可能对我的标题感到诧异。我并不是说子类没有价值,尤其在使用单一继承(single inheritance)的情况下,类和子类当然是强有力的工具。然而我想说的是,iOS日常开发的问题是对类和继承的过度使用。作为面向对象的编程者(object-oriented programmer,后面统一替换为OOP编程者;object-oriented programming后面统一简写为OOP)我们总是会自然的倾向于使用引用类型和类去解决问题,但是我个人还是认为应该反过来,倾向于用值类型代替引用类型。我们还是要去写模块化的,可伸缩的并且可重用的代码,这一点不会变。swift中强大的值类型就可以帮我们实现此目的,且不需要对引用类型有过强的依赖。我认为不仅面向协议的编程(protocol oriented programming,后统一替换为POP)可以帮我们实现这点,另外2种编程类型也可以,且都具有抽象和简化的核心思想,这两种分别是:面向值的编程(value-oriented programming,后面统一替换成VOP)和函数式编程(functional programming)。
先说清楚,我绝不是这些种编程类型(POP,VOP和函数式编程)的专家。和你一样,从MMM时代(manual memory management - 手动内存管理)开始我就是一个OOP编程者。通过自学,从开始我就很重视值抽象(value abstraction)和简化的思想。我都没有意识到自己是一个倾向于函数式编程(functional programming)的OOP编程者,而且很多时候用的都是VOP和POP的思路。这可能是我为什么在第一天就兴高采烈的加入了swift的浪潮之中的原因。在WWDC的一整周里,swift的核心理念与我认为的该怎样去编程是如此之契合,这个感受一直充斥在我脑海中。通过这篇文章,我希望能帮助你(OOP的编程者)打开思路,去考虑该如何用更加Non-OOP(非OOP)的方式去解决问题。
OOP的问题(和我不得不去学它的原因)@H_502_20@
我会是第一个跳出来说的:不用OOP的话做出iOS应用很难。Cocoa的核心就是OOP。没有OOP的话你根本写不出来一个iOS应用。有时候我会幻想这不是真的。如果你有不同观点,赶快证明我是错的吧。我真的需要这样,求你了,证明我是错的吧!
不管怎么样,你总会遇到必须用对象、用引用类型解决问题的时候,然后由于Cocoa的规定而被迫使用类(classes)。这种情况下你碰到的问题都是我们大家熟知并热爱的:
-
传递class的实例这个做法好像总是有种不可思议的能力:你想用一个实例的时候,让这个实例的状态(state)和你所期望的不一样。(这是由于可变状态(mutable state)导致,你这个对象的另一个享有者在它觉得合理的时候能够改变此对象的属性。)
-
如果不用多继承的话,从一个很棒的class派生出子类从而获得它的扩展功能妨碍了你使用另外一些很棒的class的更多更能,而且还增加了复杂性。(举例来说,试着去把2个UITextField的子类结合起来,生成一个拥有这2者特性的超级UITextField吧。)
-
上面一条的另外一个问题是会引出意外行为(unexpected behavior)。如果你遇见了类似上面一条所描述的情况,你就陷入到了一个依赖问题中:你连接了2个superclass各自的特性,对于其中一个superclass的一处改动可能会给另外一个superclass带来不良影响。这就是被周知的class之间紧耦合(tight coupling)所带来的问题。
-
单元测试中的mocking。有些classes在系统中的环境状态下耦合过于紧密,想完全测试这些classes就需要你创建每个class的假表象。我都不用告诉你本质上你并没有真正的测试了这个class,你不过是在假装测试它。这里就不提很多Mocking的库是用运行时的小把戏来造一个假的class了。
-
并发(Concurrency)问题。这和上面提到的可变状态是伴随出现的。你从多个线程中同时改变一个引用就会引起这个问题,在运行时使对象之间的同步发生异常,这点也真的不用和你说了。
-
很容易导致出现像上帝类(God classes - 承担着很多subclasses需要的重要高层级代码的所有责任),Blobs(有过多职权的classes),Lava Flow(因为含有太多的非法代码导致任何人都不敢碰的classes)等等这些种反面模式(anti patterns)。
POP 面向协议的编程@H_502_20@
陷入OOP的反面模式特别容易。多半时间我们(包括我)就是太懒而不愿意去点File>New File。结果是在现有class的基础上添加一个函数是如此轻松,我们就不愿意从零开始建一个新的class了。如果你一直这么干,而且一直非常懒的从一个"很重要"的class派生subclass的话,你就把上帝类/死星类给弄出来了。实际上我之前就这么干过:我给一个app里的每个view Controller都加了能呈现一个指向navigationController的navigationBar的error view的功能。唉,我可真蠢。直到要改动那个Error上帝类行为的时候,我不得不把整个app都改一遍。这不是聪明的做法,你真应该看看那些bug。
一个掌管一切的class = bug满天飞
如果使用了POP,这个Error上帝类很大程度上就能很容易的抽象出来,以后改进它也方便。(顺便说下如果你想学POP,我极力推荐你去看这个视频)。想想就会觉得好笑,因为在这个视频中Apple自己都说:
"从一个protocol开始,别从class开始。"——Dave Abrahams: 毁你三观教授
这是一个能展示(之前的方式)有多残暴的例子:
1
2
3
4
5
6
7
8
|
classPresentErrorViewController:UIViewController{
var
errorViewIsShowing:Bool=
false
funcpresentError(message:String=“Error!",withArrowshouldShowArrow:Bool=
false
,backgroundColor:UIColor=ColorSalmon,withSizesize:CGSize=CGSizeZero,canDismissByTappingAnywherecanDismiss:Bool=
true
){
//写下了复杂的,脆弱的代码
}
}
//说一下,有100个class继承了这个class
EveryViewControllerInApp:PresentErrorViewController{}
|
随着项目的进行事情马上变的明了:并不是每一个UIViewController需要这个error逻辑,或是真的需要这个class所提供的每一个功能。我团队里任何一个人都可以轻易的在这个superclass里改点儿什么,从而影响整个app。这就让代码变得脆弱。还使得代码呈现出了多态。当本应该是由子类决定它自己的行为,这里的superclass却给帮着决定了。下面是在swift 2.0中我们如何用POP来更好的构建这段代码:
protocolErrorPopoverRenderer{
funcpresentError(message:String,withArrowshouldShowArrow:Bool,backgroundColor:UIColor,withSizesize:CGSize,canDismissByTappingAnywherecanDismiss:Bool)
}
extensionUIViewController:ErrorPopoverRenderer{
//使所有遵从于ErrorPopoverRenderer协议的UIViewController具有一个presentError的默认实现
}
}
classKrakenViewController:UIViewController,ErrorPopoverRenderer{
//DroptheGodclassandmakeKrakenViewControllerconformtothenewErrorPopoverRendererProtocol.
funcmethodThatHasAnError(){
//…
//抛出error,原因是Kraken海妖今天吃人会感到不适。
}
}
|
看,这里发生了很炫酷的事情。我们不仅消除了上帝类的存在,还让代码更加的模块化并增强了它的扩展性。通过创建一个 ErrorPopoverRenderer协议,就会让任何遵循了该协议的class具有呈现出一个ErrorView的能力。还不止这些,我们的KrakenViewController class不用必须实现presentError这个函数,因为我们扩展了UIViewController,让它提供了一个默认实现。
唉不过等下!这有个问题!我们每次想要呈现一个ErrorView的时候都必须要去实现每一个参数。这就有点儿让人不爽了,因为我们不能在protocol协议函数声明中为参数提供默认值。
我还挺喜欢这些参数的!更糟的是在让代码更具模块化特征的过程中我们引入了复杂度。还是继续吧,用swift 2.0中新加的一个小妙招来多少的补偿一下:
structColor{
letred:Double
letgreen:Double
letblue:Double
init(red:Double=0.0,green:Double=0.0,blue:Double=0.0){
self.red=red
self.green=green
self.blue=blue
}
}
structErrorOptions{
letmessage:String
letshowArrow:Bool
letbackgroundColor:UIColor
letsize:CGSize
letcanDismissByTap:Bool
init(message:String=
"Error!"
){
self.message=message
self.showArrow=shouldShowArrow
self.backgroundColor=backgroundColor
self.size=size
self.canDismissByTap=canDismiss
}
使用上面的选项型struct(是值类型!)就使我们的POP带上了一些VOP的色彩,如下:
|