更好的閱讀體驗請點擊 原文
從面相對象說起
面向對象的程序設計
(Object-Oriented Programming,簡記為OOP)這個概念大家都有所耳聞,目前(2017.12),在Tiobe世界語言排行榜上排前十的語言中,C語言和Assembly language(匯編)外的八種語言均原生支持面向對象的程序設計
。
怎么判斷一種編程語言是否支持OOP呢?看看這門語言是否支持類(class)、對象(object)、封裝(encapsulation)、繼承(inheritance)等功能和特性,支持這些就可以進行面向對象編程。拿Objective-C(OC)來說,類就是Class
,對象就是instance
,萬物的基類是NSObject
,這些東西在C語言里并不存在,是OC使用C語言的結構體(struct)抽象出來的產物。
我們從Objective-C的名字上也能看出一些端倪,直譯過來是對象化的C語言
,當然不僅是OC,排行榜前十中的C++同樣是C語言的一個超集;C#和Java同樣屬于類C語言,把面向對象做的更加徹底;PHP雖然是腳本語言,其解釋器是使用C語言寫的;而我們常說的Python,其全稱則是CPython,也是用C語言實現的解釋器,當然Python解釋器也有Java和C#實現的版本。
為什么C語言,比其他語言顯得更底層呢?接觸過的朋友相信都有很深的體會,C語言的程序,是在和圖靈機硬件打交道,變量、數組、結構體,聲明在堆內存就要為其分配內存空間大小,分配了內存,就要手動回收;數組還要區分靜態和動態,每塊數據占幾個字節,躺在內存的什么位置,一切都按編程人員的安排。所以有人說C語言就是一個高級匯編,想起來確實有一分道理(笑)。但在智能手機、移動計算機計算能力大大提升的今天,計算資源早已不是通用編程首先考慮的問題,相比于C語言強迫編程人員從機器的角度設計程序,抽象程度更高的OOP才更接近人腦的思維方式,才更適合提高軟件工程師的編程效率。
即使如此,仍有一部分人至今站在OOP的對立面,從代碼復雜度、建模能力要求等方面提出異議,堅持寫C++、Python、PHP的時候不構造類,寫純過程的程序。但其實,這些自稱為原C黨的朋友,并不能說自己沒有使用OOP,因為這些語言中變量,跟C語言中的變量,有本質的不同。
就用字符串
和數組
來舉例子,C語言是沒有string類型的,只有字符數組,用\0
來標記字符串結束;而其他語言中的string則是早已封裝好的字符串類(Class),用起來跟整型無異。
C語言中字符串和數字變量聲明
char name[] = "Tom\0";
int age = 12;
Python中字符串和數字變量聲明
name = "Tom"
age = 12
C++中字符串和數字變量聲明
string name = "Tom";
int age = 12;
我們在Python和C++中使用字符串,早已不是在直接與設備內存打交道,而C語言中的“字符串”還停留在只是內存中的一段連續空間的階段。
再來看一看數組,C++雖然也支持C的數組,但我想對比的其實是C++標準庫中的向量(Vector),以及Python中的鏈表(List),這些高級容器同樣是基于OOP理念設計的類,仍只有C語言的數組內容直接映射在內存上。
所以即使你不構造Class,在C++、Python、PHP中仍在使用對象和實例的OOP特性,即使開發的是線性程序。
徹底的OOP
經常會看到有人抱怨Java把面向對象的理念做的太過頭,C#作為Java的仿制品,也同樣逃脫不了被詬病的現實,但其穩定性也是有口皆碑。然而真正把OOP理念實現的徹頭徹尾徹徹底底的,反而是最早的OOP語言之一的Smarttalk,讓我先看一段Samrttalk的代碼
Transcript show: 'Hello world'
這是Smarttalk版本的Hello world
程序,Transcript
是Squeak(這是Smalltalk語言的一種版本實現)環境里,把信息顯示到屏幕上的一個對象。這段代碼是用冒號給這個對象發送了一個消息(Message),如果給這段代碼加上一對中括號,是不是像極了Ojective-C,沒錯,因為OC就是參考Smarttalk設計的Runtime。
同樣,Samrttalk也支持中括號的寫法,我們可以把上面的一段代碼段落,賦值給一個變量:
t := [ Transcript show: 'Hello world']
這個t變量,其實是一個閉包(BlockClosure)對象,相同的概念在C++ 11標準里才出現,相比之下Smarttalk的設計理念真的很前衛。而OC作為Smarttalk的追隨者,更是擁有NSOperation類來實現閉包,相比之下,block并不是基于OOP的設計。
C++的blcok和ObjC的NSOperation,這里block寫法OC同樣支持
void hello = ^ {
NSLog(@"hello world");
};
hello();
NSBlockOperation* block = [NSBlockOperation blockOperationWithBlock:^{
// 做一些操作
}];
[[NSOperationQueue mainQueue] addOperation:block];
要注意的是NSBlockOperation
是在OC支持block以后才出現的類,在此之前要使用NSOpertaion,我們需要繼承NSOpertaion類,并重寫這個類的-(void)main
方法,這無疑是一件十分繁瑣的事。
一切皆對象
OC作為Smarttalk的追隨者,在OOP的理念上是要強于C++、Python和PHP的,interface
、implementation
、getter
、setter
的接口設計,和Java、C#相互參考,水平相近,但仍比Smarttalk和Ruby略遜一籌。
熟悉Cocoa框架的朋友都知道,UI繪制框架CoreGraphic
中仍然要使用大量的CG開頭的C語言函數,點、線、面的容器,依舊是CGPoint,CGSize,CGRect這些C語言結構體;數字變量依然是int、NSInteger、NSNumber(數字類)混著用,相互轉換忙的不亦樂乎。當然這一切在OC支持字面量特性(Literals)以后有了好轉:
//通過@符號直接把普通變量轉換為數字對象
NSNumber *myIntegerNumber = @8;
//轉回來
NSInteger customNumber = [myIntegerNumber integerValue];
相比之下,Smarttalk和Ruby做的更徹底,更好用,下面是用Smarttalk重復輸出十次Hello world
的代碼,給數字10發timesRepeat
消息,重復消息參數中的閉包:
10 timesRepeat: [Transcript show: 'Hello world']
為什么整數類要設計這么方法呢?因為Smarttalk中并沒有循環語法,甚至其他語言常見的條件語句if/else在Smarttalk中都是不存在的,而都是使用OOP的理念實現,有興趣了解更多關于Smarttalk的內容,請來這里。
給對象發消息是更符合人類思維模式的設計
這里我們從繼承Smarttalk理念的Ruby說起,雖然其使用點語法替代了冒號,但仍能看出Ruby中的數字類型,就是數字對象。
//將數字對象102轉換成字符串對象
102.to_s
用Smarttalk實現則是
102 printString
相比之下Python則像是一個作者對OOP還處于感性認知階段設計出來的語言,所以會設計出len()、map()、fliter()這種C語言函數風格的接口,例如我在OC中我們獲取數組的長度使用count屬性,使用點語法或者中括號消息都可以獲取(關于OC中的點語法和中括號語法我們后面再聊)
NSArray* a = @[@(1),@(2),@(3)];
a.count;
[a count];
這很面向對象,因為我們要獲取數量的主體數組實例a,發消息讓他返回長度很符合人類的思維邏輯。同樣的我們看看Ruby,也是一樣的操作
a = [1,2,3]
a.length
a.size
然而當我使用第一次寫Python代碼的時候,我經歷了很多人都遇到過的情況,不知道字符串或者數組如何獲取長度。因為Python中string和list都沒有length、size、count、len等屬性和方法,然后我們發現Python提供了一個len()方法獲取序列長度,這個方法接受一切的對象作為參數。
a = [1,2,3]
len(a)
s = "123"
len(s)
針對這個問題,有一部分人認為不是問題,他們說做OOP不要太教條主義,len在前在后能有很大差別么?我想說真的是有的,這個看似簡單的前后問題,其實影響了實際的編程體驗,就是是否基于對象思考問題的體驗。
一方面,len()方法像一個憑空存在的方法,不依賴于任何類和對象,也不是依附于某個模塊,知道它存在,才會去使用它,同樣的還有Python中的type()、map()方法等。另一方面,這一類方法到底可以用于什么類型的對象,開發者心里也沒底,必須對照接口標明的參數類型使用。
這一切無疑不利于程序開發的思維連貫性,有朋友可能覺得我說的言過其實,我這里舉一個例子大家體會一下何為思維連貫性。
需求是將一段英文字符串的單詞逆序,How are you
處理成you are How
。
我們用OC實現如下:
#import <Foundation/Foundation.h>
NSString* reverse(NSString* text) {
NSArray *words = [text componentsSeparatedByString:@" "];
NSArray *reversed = [[words reverseObjectEnumerator] allObjects];
return [reversed componentsJoinedByString:@" "];
}
Python實現為
def reverse(text):
a = text.split(' ')
a.reverse()
return ' '.join(a)
Ruby的實現為
def reverse(string)
return string.split.reverse.join(' ')
end
觀察出來區別了了吧,重要的不是Ruby只用了一行代碼,而是Ruby相比于OC和Python,省去了很多中間變量,別看只是一點點節省,其實省去我們實際開發中很大一部分無用工作。當然,OC可以通過括號多層嵌套連貫起來寫,也能達到同樣的效果,但我們并不推薦這樣做,因為OC的方法名偏長,如果縮進不當,會讓代碼更難理解。
相比之下,Python的接口設計更滑稽一些,首先在Ruby中
array.reverse!
array.reverse
是兩個不同的方法,前者只逆轉array,沒有返回值。后者則返回一個新的逆轉數組對象,Python沒有類似設計。
其次Ruby和OC都將join方法設計在array類里,唯獨Python將其設為字符串類型的方法,導致了Python沒法連貫地將中間參數略去。