絕對不是沒有關係。
說個冷笑話。
說白了一切都是記憶體的管理應用。
Day 2中的說明是為了「好懂」,都只是種粗略的邏輯與概念,嚴格說起來並不夠專業嚴謹正確,因為我希望讓大家知道要有能力從管理記憶體的觀點去思考,而不是只是寫著一行又一行的指令去指望編譯器可以正確編譯、Virtual Machine也能正常按照編譯出來的指令去配置記憶體,結果程式產生了「非邏輯性的錯誤」(這通常就是記憶體配置出問題)後又完全不知道怎麼辦。
本篇說明是為了「好懂」,都只是種粗略的邏輯與概念,嚴格說起來並不夠專業嚴謹正確。
所以繼續用同樣的方式去解釋...什麼是函數?
雖然程式碼就是一行一行的指令,電腦會從每個區段的開始,一行又一行的執行、直到區段結束為止,但在記憶體中,程式並不是用「一行又一行的程式碼」這種方式儲存在記憶體中。
每支程式在讀取並匯入記憶體中時,電腦會去判斷程式的結構、去知道這支程式有哪些「函數」,這些函數會被整理在程式的最上層。
00001000,00001100,00000010,00000000,
這行是表示從記憶體00001000到00001100記錄著這支程式所有的函數清單(00000010),程式編號為00000000。
接下來開始就是從記憶體00001000到00001100之間(一共八個記憶體欄位)所記錄的資料。
00010000,00010100,00000011,00000001,
00010101,00011001,00000011,00000010,
第一行總共用了四個記憶體欄位,它表示從記憶體00010000到00010100之間紀錄了一隻函數的功能(00000011),而這支函數的編號(不是名稱)是00000001。
第一行同樣也總共用了四個記憶體欄位,它表示從記憶體00010101到00011001之間紀錄了一隻函數的功能(00000011),而這支函數的編號(不是名稱)是00000002。
如果程式執行時要使用到特定一支函數,就需要用函數的編號去記憶體00001000到00001100之間找到函數對應的區段,然後再去把函數的真正內容找出來執行。
(注意!接下來的內容會陳述一種程式編寫技巧的利弊,但這種利弊是我從習慣物件導向的角度去思考,其實解決它的辦法並不一定要使用物件導向,解決方法很多,只是我只懂物件導向而已。(XD))
但這種機制也是歷史悠久,當時的程式也都比較簡單,當函數規模擴張到幾千幾萬時,明顯的問題就出來了:找函數會吃掉很多效能。
所以這時候就有物件導向的概念了,只要在「程式」和「函數清單」之間增加一層「物件」,就可以將幾千幾萬支函數做分散了。
舉例:以往,一支程式裡的函數可能包含各種功能,例如UI、例如檔案、例如網路傳輸,但如果將它分散成UI物件、檔案物件、網路傳輸物件,那就可以將所有函數分散到這幾支物件中。
這時候回頭看前面的記憶體規劃方式就會有完全不同的意義。
00001000,00001100,00000010,00000000,
這行會變成表示從記憶體00001000到00001100記錄著這支程式所有的物件清單(00000010),程式編號為00000000。
接下來開始就是從記憶體00001000到00001100之間(一共八個記憶體欄位)所記錄的資料。
00010000,00010100,00000011,00000001,
00010101,00011001,00000011,00000010,
第一行總共用了四個記憶體欄位,它表示從記憶體00010000到00010100之間紀錄了一隻物件的函數(00000011),而這支物件的編號(不是名稱)是00000001。
第一行同樣也總共用了四個記憶體欄位,它表示從記憶體00010101到00011001之間紀錄了一隻物件的函數(00000011),而這支物件的編號(不是名稱)是00000002。
函數的部分不重複描述。
但這樣做的問題也很明顯,就是以後使用一支函數不能只使用一個函數的編號,必須要同時使用物件的編號。
這可能會導致所有的程式碼在各方面都變大,存在硬碟中佔掉的空間、存在記憶體中的空間、實際執行需要的CPU效能......(當整支程式在功能一模一樣的情況下時,使用物件導向卻只有一個物件編寫出來的程式效能會明顯差於不使用物件導向所編寫出來的程式。)
「既然這樣,為什麼我們還要使用物件導向?」
因為程式不是只靠函數構成,還有資料。
假設我們有ㄧ張圖片要顯示在「桌面」上,「桌面」需要具備以下幾組資料去記憶這張圖片:圖片的檔案位置,圖片的畫質,圖片的位置。
資料 檔案位置1;
資料 畫質1;
資料 座標1;
(注意!這不是正式的Flutter程式。)
如果有三張圖片,就要將以上資料乘以三。(如果知道什麼叫Array/陣列/矩陣的人可能會直覺認為我接下來講的東西太冗長。)
資料 檔案位置1, 檔案位置2, 檔案位置3;
資料 畫質1, 畫質2, 畫質3;
資料 座標1, 座標2,座標3;
(注意!這也不是正式的Flutter程式。)
接下來會發生一些奇妙的狀況,例如:我要讓圖片可以「旋轉」,那就需要新增「角度」這個資料,圖片如果有一張,我就要增加一次,三張就要增加三次。
但如果使用物件導向,設計程式時就不是去思考「資料要設置幾份」這樣的問題,而是可以將圖片視為一個設計程式時使用的「物件」就好了。
「我現在要開始設計一個名為圖片的物件,」工程師可以在程式碼內使用這樣的指令,接著要開始決定這個「物件」的內容,「我希望物件圖片包含了檔案位置、畫質、顯示位置、顯示角度等資料。」
物件 圖片{
資料 檔案位置1;
資料 畫質1;
資料 座標1;
}
(注意!這依然不是正式的Flutter程式。)
可以說物件是種「資料包」,雖然是「包」,但在使用時可以像使用原本最基礎的資料(文字或數值)一樣,「我要在這裡設置一張圖片資料的欄位」這樣就會有一組「檔案位置、畫質、顯示位置、顯示角度」,如果「我要在這裡設置兩張圖片資料的欄位」這樣就會有兩組「檔案位置、畫質、顯示位置、顯示角度」。
資料 圖片1, 圖片2, 圖片3;
(注意!...應該不用再說了。)
而且未來不管是新增了多少種資料,例如「我希望圖片旋轉的數度有快慢差異」,那只需要在圖片物件中新增一次速度,就不需要在修改和擴充資料數目。
就設計上的意義來說,物件導向很快、很貼近人類思考的模式,比起用0/1設計指令、用記憶體規劃的方式設計程序,物件導向幾乎可以描繪所有人類感官能夠體驗的事物。
物件導向讓程式設計師用接近人類思考習慣和感官體驗的方式去設計程式。
紙上談兵夠了。回頭實際看看物件導向到底怎麼使用吧?
因為Widget就是種物件,所以再回來看一下SDK提供的基本程式(但這次我有做一點小修正)...
void main() {
runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
));
}
runApp這個函數會負責顯示Widget,而MaterialApp就是個Widget。
MaterialApp這個Widget奇妙的地方在於「它完全不包含任何實際用於顯示的UI元件」,它提供的是一種「環境」。
因為手機APP就跟視窗系統一樣,可以由數個介面組成一支程式,每個介面就像個視窗,可以放大、或縮小回到桌面,而既然是同一支APP,那每個介面就會希望有一些統一的屬性,例如「primarySwatch: Colors.blue」就將標題的背景統一定義為藍色。
仔細看一下...MaterialApp和runApp後面都連結了「()」,這是否表示MaterialApp是個函數?
對一半也錯一半。
如果去用物件導向的字面意義「物件」這種概念去理解它,這地方很容易混淆,可以試著釐清它,但也可以選擇不要、用直覺跟習慣使用物件導向。
像上面的「設計圖片物件」的例子中有提到「我要在這裡設置一張圖片資料的欄位」,這件事情要怎麼做?
void main() {
String titleString = 'Flutter Demo Home Page';
MaterialApp ma = MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: titleString),
);
runApp(ma);
}
「String」是種資料,「String titleString = ....」這段指令的目的是「設置一組資料並將它命名為titleString,可以用來儲存文字。」
在Flutter這樣的程式語言中,設置資料原則上都需要指定資料的種類,文字就是種資料的種類,可以用來儲存APP標題、或是圖片路徑;還有數值,數值比較五花八門,因為數值的種類是以位元長度來分類,未來再介紹。
物件一旦設計好後,或是匯入了某些library跟project並取得使用裡面套件的能力後,物件也可以是種能使用的資料種類。像MaterialApp就是,使用物件的名稱做為資料種類,「MaterialApp ma = ...」這樣的指令目的就是「設置一組資料並將它命名為ma,可以用來儲存MaterialApp物件資料。」
但文字資料只要使用兩個「'」、中間夾入文字,就可以完成設置一組文字資料,但物件為何卻要使用函數?
「物件導向」雖然說是用「物件」概念設計程式,但其實目的也是在管理記憶體,所以設置物件資料在概念上應該比較接制執行一連串的功能,所以它是個函數,但這種用來設置物件資料的函數有個特殊的名稱叫做「建構子 constructor」。
正確來說,一個名為MaterialApp的物件、同時有個名為MaterialApp的建構子,它在程式碼上會長這個樣子。
class MaterialApp {
MaterialApp(){
}
}
注意,這是範例,實際上MaterialApp的內容遠比這個複雜許多,但重點在「建構子」上。
使用建構子可以產生物件資料,有資料就可以執行功能來進行「運算」,像使用runApp這個功能來運算MaterialApp需要顯示出什麼樣的畫面......