更好的閱讀體驗請點擊 原文
從面相對象說起
面向?qū)ο蟮某绦蛟O(shè)計
(Object-Oriented Programming搀罢,簡記為OOP)這個概念大家都有所耳聞妒蛇,目前(2017.12),在Tiobe世界語言排行榜上排前十的語言中抄瓦,C語言和Assembly language(匯編)外的八種語言均原生支持面向?qū)ο蟮某绦蛟O(shè)計
捣郊。
怎么判斷一種編程語言是否支持OOP呢箱季?看看這門語言是否支持類(class)、對象(object)、封裝(encapsulation)、繼承(inheritance)等功能和特性怪与,支持這些就可以進(jìn)行面向?qū)ο缶幊獭D肙bjective-C(OC)來說缅疟,類就是Class
分别,對象就是instance
,萬物的基類是NSObject
存淫,這些東西在C語言里并不存在耘斩,是OC使用C語言的結(jié)構(gòu)體(struct)抽象出來的產(chǎn)物。
我們從Objective-C的名字上也能看出一些端倪纫雁,直譯過來是對象化的C語言
煌往,當(dāng)然不僅是OC,排行榜前十中的C++同樣是C語言的一個超集轧邪;C#和Java同樣屬于類C語言刽脖,把面向?qū)ο笞龅母訌氐祝籔HP雖然是腳本語言忌愚,其解釋器是使用C語言寫的曲管;而我們常說的Python,其全稱則是CPython硕糊,也是用C語言實現(xiàn)的解釋器院水,當(dāng)然Python解釋器也有Java和C#實現(xiàn)的版本腊徙。
為什么C語言,比其他語言顯得更底層呢檬某?接觸過的朋友相信都有很深的體會撬腾,C語言的程序,是在和圖靈機(jī)硬件打交道恢恼,變量民傻、數(shù)組、結(jié)構(gòu)體场斑,聲明在堆內(nèi)存就要為其分配內(nèi)存空間大小漓踢,分配了內(nèi)存,就要手動回收漏隐;數(shù)組還要區(qū)分靜態(tài)和動態(tài)喧半,每塊數(shù)據(jù)占幾個字節(jié),躺在內(nèi)存的什么位置青责,一切都按編程人員的安排挺据。所以有人說C語言就是一個高級匯編,想起來確實有一分道理(笑)爽柒。但在智能手機(jī)吴菠、移動計算機(jī)計算能力大大提升的今天,計算資源早已不是通用編程首先考慮的問題浩村,相比于C語言強(qiáng)迫編程人員從機(jī)器的角度設(shè)計程序,抽象程度更高的OOP才更接近人腦的思維方式占哟,才更適合提高軟件工程師的編程效率心墅。
即使如此,仍有一部分人至今站在OOP的對立面榨乎,從代碼復(fù)雜度怎燥、建模能力要求等方面提出異議,堅持寫C++蜜暑、Python铐姚、PHP的時候不構(gòu)造類,寫純過程的程序肛捍。但其實隐绵,這些自稱為原C黨的朋友,并不能說自己沒有使用OOP拙毫,因為這些語言中變量依许,跟C語言中的變量,有本質(zhì)的不同缀蹄。
就用字符串
和數(shù)組
來舉例子峭跳,C語言是沒有string類型的膘婶,只有字符數(shù)組,用\0
來標(biāo)記字符串結(jié)束蛀醉;而其他語言中的string則是早已封裝好的字符串類(Class)悬襟,用起來跟整型無異。
C語言中字符串和數(shù)字變量聲明
char name[] = "Tom\0";
int age = 12;
Python中字符串和數(shù)字變量聲明
name = "Tom"
age = 12
C++中字符串和數(shù)字變量聲明
string name = "Tom";
int age = 12;
我們在Python和C++中使用字符串拯刁,早已不是在直接與設(shè)備內(nèi)存打交道脊岳,而C語言中的“字符串”還停留在只是內(nèi)存中的一段連續(xù)空間的階段。
再來看一看數(shù)組筛璧,C++雖然也支持C的數(shù)組逸绎,但我想對比的其實是C++標(biāo)準(zhǔn)庫中的向量(Vector),以及Python中的鏈表(List)夭谤,這些高級容器同樣是基于OOP理念設(shè)計的類棺牧,仍只有C語言的數(shù)組內(nèi)容直接映射在內(nèi)存上。
所以即使你不構(gòu)造Class朗儒,在C++颊乘、Python、PHP中仍在使用對象和實例的OOP特性醉锄,即使開發(fā)的是線性程序乏悄。
徹底的OOP
經(jīng)常會看到有人抱怨Java把面向?qū)ο蟮睦砟钭龅奶^頭,C#作為Java的仿制品恳不,也同樣逃脫不了被詬病的現(xiàn)實檩小,但其穩(wěn)定性也是有口皆碑。然而真正把OOP理念實現(xiàn)的徹頭徹尾徹徹底底的烟勋,反而是最早的OOP語言之一的Smarttalk规求,讓我先看一段Samrttalk的代碼
Transcript show: 'Hello world'
這是Smarttalk版本的Hello world
程序,Transcript
是Squeak(這是Smalltalk語言的一種版本實現(xiàn))環(huán)境里卵惦,把信息顯示到屏幕上的一個對象阻肿。這段代碼是用冒號給這個對象發(fā)送了一個消息(Message),如果給這段代碼加上一對中括號沮尿,是不是像極了Ojective-C丛塌,沒錯,因為OC就是參考Smarttalk設(shè)計的Runtime畜疾。
同樣赴邻,Samrttalk也支持中括號的寫法,我們可以把上面的一段代碼段落庸疾,賦值給一個變量:
t := [ Transcript show: 'Hello world']
這個t變量乍楚,其實是一個閉包(BlockClosure)對象,相同的概念在C++ 11標(biāo)準(zhǔn)里才出現(xiàn)届慈,相比之下Smarttalk的設(shè)計理念真的很前衛(wèi)徒溪。而OC作為Smarttalk的追隨者忿偷,更是擁有NSOperation類來實現(xiàn)閉包,相比之下臊泌,block并不是基于OOP的設(shè)計鲤桥。
C++的blcok和ObjC的NSOperation,這里block寫法OC同樣支持
void hello = ^ {
NSLog(@"hello world");
};
hello();
NSBlockOperation* block = [NSBlockOperation blockOperationWithBlock:^{
// 做一些操作
}];
[[NSOperationQueue mainQueue] addOperation:block];
要注意的是NSBlockOperation
是在OC支持block以后才出現(xiàn)的類渠概,在此之前要使用NSOpertaion茶凳,我們需要繼承NSOpertaion類,并重寫這個類的-(void)main
方法播揪,這無疑是一件十分繁瑣的事贮喧。
一切皆對象
OC作為Smarttalk的追隨者,在OOP的理念上是要強(qiáng)于C++猪狈、Python和PHP的箱沦,interface
、implementation
雇庙、getter
谓形、setter
的接口設(shè)計,和Java疆前、C#相互參考寒跳,水平相近,但仍比Smarttalk和Ruby略遜一籌竹椒。
熟悉Cocoa框架的朋友都知道童太,UI繪制框架CoreGraphic
中仍然要使用大量的CG開頭的C語言函數(shù),點胸完、線康愤、面的容器,依舊是CGPoint舶吗,CGSize,CGRect這些C語言結(jié)構(gòu)體择膝;數(shù)字變量依然是int誓琼、NSInteger、NSNumber(數(shù)字類)混著用肴捉,相互轉(zhuǎn)換忙的不亦樂乎腹侣。當(dāng)然這一切在OC支持字面量特性(Literals)以后有了好轉(zhuǎn):
//通過@符號直接把普通變量轉(zhuǎn)換為數(shù)字對象
NSNumber *myIntegerNumber = @8;
//轉(zhuǎn)回來
NSInteger customNumber = [myIntegerNumber integerValue];
相比之下,Smarttalk和Ruby做的更徹底齿穗,更好用傲隶,下面是用Smarttalk重復(fù)輸出十次Hello world
的代碼,給數(shù)字10發(fā)timesRepeat
消息窃页,重復(fù)消息參數(shù)中的閉包:
10 timesRepeat: [Transcript show: 'Hello world']
為什么整數(shù)類要設(shè)計這么方法呢跺株?因為Smarttalk中并沒有循環(huán)語法复濒,甚至其他語言常見的條件語句if/else在Smarttalk中都是不存在的,而都是使用OOP的理念實現(xiàn)乒省,有興趣了解更多關(guān)于Smarttalk的內(nèi)容巧颈,請來這里。
給對象發(fā)消息是更符合人類思維模式的設(shè)計
這里我們從繼承Smarttalk理念的Ruby說起袖扛,雖然其使用點語法替代了冒號砸泛,但仍能看出Ruby中的數(shù)字類型,就是數(shù)字對象蛆封。
//將數(shù)字對象102轉(zhuǎn)換成字符串對象
102.to_s
用Smarttalk實現(xiàn)則是
102 printString
相比之下Python則像是一個作者對OOP還處于感性認(rèn)知階段設(shè)計出來的語言唇礁,所以會設(shè)計出len()、map()惨篱、fliter()這種C語言函數(shù)風(fēng)格的接口盏筐,例如我在OC中我們獲取數(shù)組的長度使用count屬性,使用點語法或者中括號消息都可以獲榷噬摺(關(guān)于OC中的點語法和中括號語法我們后面再聊)
NSArray* a = @[@(1),@(2),@(3)];
a.count;
[a count];
這很面向?qū)ο蠡希驗槲覀円@取數(shù)量的主體數(shù)組實例a,發(fā)消息讓他返回長度很符合人類的思維邏輯绣夺。同樣的我們看看Ruby吏奸,也是一樣的操作
a = [1,2,3]
a.length
a.size
然而當(dāng)我使用第一次寫Python代碼的時候,我經(jīng)歷了很多人都遇到過的情況陶耍,不知道字符串或者數(shù)組如何獲取長度奋蔚。因為Python中string和list都沒有l(wèi)ength、size烈钞、count泊碑、len等屬性和方法,然后我們發(fā)現(xiàn)Python提供了一個len()方法獲取序列長度毯欣,這個方法接受一切的對象作為參數(shù)。
a = [1,2,3]
len(a)
s = "123"
len(s)
針對這個問題酗钞,有一部分人認(rèn)為不是問題腹忽,他們說做OOP不要太教條主義,len在前在后能有很大差別么砚作?我想說真的是有的窘奏,這個看似簡單的前后問題,其實影響了實際的編程體驗葫录,就是是否基于對象思考問題的體驗着裹。
一方面,len()方法像一個憑空存在的方法米同,不依賴于任何類和對象骇扇,也不是依附于某個模塊摔竿,知道它存在,才會去使用它匠题,同樣的還有Python中的type()拯坟、map()方法等。另一方面韭山,這一類方法到底可以用于什么類型的對象郁季,開發(fā)者心里也沒底,必須對照接口標(biāo)明的參數(shù)類型使用钱磅。
這一切無疑不利于程序開發(fā)的思維連貫性梦裂,有朋友可能覺得我說的言過其實,我這里舉一個例子大家體會一下何為思維連貫性盖淡。
需求是將一段英文字符串的單詞逆序年柠,How are you
處理成you are How
。
我們用OC實現(xiàn)如下:
#import <Foundation/Foundation.h>
NSString* reverse(NSString* text) {
NSArray *words = [text componentsSeparatedByString:@" "];
NSArray *reversed = [[words reverseObjectEnumerator] allObjects];
return [reversed componentsJoinedByString:@" "];
}
Python實現(xiàn)為
def reverse(text):
a = text.split(' ')
a.reverse()
return ' '.join(a)
Ruby的實現(xiàn)為
def reverse(string)
return string.split.reverse.join(' ')
end
觀察出來區(qū)別了了吧褪迟,重要的不是Ruby只用了一行代碼冗恨,而是Ruby相比于OC和Python,省去了很多中間變量味赃,別看只是一點點節(jié)省掀抹,其實省去我們實際開發(fā)中很大一部分無用工作。當(dāng)然心俗,OC可以通過括號多層嵌套連貫起來寫傲武,也能達(dá)到同樣的效果,但我們并不推薦這樣做城榛,因為OC的方法名偏長揪利,如果縮進(jìn)不當(dāng),會讓代碼更難理解狠持。
相比之下疟位,Python的接口設(shè)計更滑稽一些,首先在Ruby中
array.reverse!
array.reverse
是兩個不同的方法喘垂,前者只逆轉(zhuǎn)array献汗,沒有返回值。后者則返回一個新的逆轉(zhuǎn)數(shù)組對象王污,Python沒有類似設(shè)計。
其次Ruby和OC都將join方法設(shè)計在array類里楚午,唯獨Python將其設(shè)為字符串類型的方法昭齐,導(dǎo)致了Python沒法連貫地將中間參數(shù)略去。