第 21 章 定義過程

2018-02-24 15:51 更新

像App Inventor這類的編程語言通常會提供一組基本的內(nèi)置功能,對于app inventor來說,就是一組基本塊。編程語言還提供一種功能擴展的方法,即,向語言中添加新的子程序(塊)。【在計算機科學中,子程序(英語:Subroutine, procedure, function, routine, method, subprogram),是一個大型程序中的某一部份代碼,由一個或多個語句塊組成。它負責完成某項特定任務,而且與其他代碼相比,具備相對的獨立性?!g者注】在App Inventor中,通過定義過程(procedure),即,命名一些順序執(zhí)行的塊,來實現(xiàn)功能的擴展。應用中可以像調(diào)用App Inventor中的預定義塊一樣,調(diào)用這些過程。本章中你將看到,創(chuàng)建這樣抽象的過程的能力對于解決復雜問題是非常重要的,這是創(chuàng)建真正好應用的基石。

{%}

當家長對孩子說“睡覺前去刷牙”時,他們的實際含義是“從架子上拿起牙刷牙膏,向牙刷上擠一點牙膏,在每顆牙齒上刷10秒鐘(哈哈?。保鹊?。“刷牙”就是一種抽象:為一系列的低級指令起一個公認的名稱。此處,家長要求孩子完成他們已經(jīng)認可了的“刷牙”的一系列指令。

你也可以在編程中創(chuàng)建這樣的有名字的一系列指令,有些編程語言稱之為函數(shù)(function)或子程序(subprogram),在App Inventor中,被稱為過程(procedure)。過程就是一組順序執(zhí)行的有名字的塊,在應用中可以隨時隨地調(diào)用它。

圖21-1就是一個過程的例子,它的功能是以英里為單位,計算兩個GPS坐標之間的距離。

{%}

圖 21-1 計算兩點間距離的過程

不必急于探究這個過程中的內(nèi)部構(gòu)件,只要知道對于你所使用的編程語言來說,這樣的過程擴展了它的功能。如果每個家長每天晚上都要向他的孩子解釋“刷牙”的步驟,那么這個孩子到了五年級可能還是不會刷牙。說“刷牙”是一種更有效的方式,而且每個人都會在睡覺之前去刷牙。

同樣的道理,在設計或編寫一個大型應用時,一旦定義好了distanceBetweenPoints這個過程,你就會忽略它的內(nèi)部實現(xiàn)細節(jié),而只是簡單地使用(或調(diào)用)它的名字。這種抽象能力對于解決大型問題來說是至關(guān)重要的,可以將大型的軟件項目分解成若干個便于管理的代碼塊。

過程還可以有助于減少錯誤,因為它們可以省去很多冗余的代碼:只要在一處定義了過程,應用中就可以隨處調(diào)用它。因此,假如應用中要計算你的當前位置與其他10個點之間的最近距離,你不必拷貝粘貼10次圖21-1中的塊,相反,你只需要定義這個過程,并在需要時調(diào)用它即可。此外,那種拷貝粘貼塊的方法還非常容易引入錯誤,因為一旦你想修改程序,就必須找到所有的拷貝,并逐個以相同的方式修改它們。想象一下,你試圖在一個有1000行或塊的代碼中,找到5-10個曾經(jīng)粘貼過的代碼塊!與其被迫地拷貝粘貼這寫塊,不如用過程在一處將代碼塊封裝起來。

最后,過程將有助于建立代碼庫,讓這些代碼在其他應用中可以被重用。即便是創(chuàng)建一個非常具體的應用,有經(jīng)驗的程序員總會在必要時設法考慮重用其他應用中的部分代碼。有些程序員從未創(chuàng)建過應用,他們只是專注與創(chuàng)建可重用的代碼庫,以便其他程序員以此來創(chuàng)建他們自己的應用。

消除冗余

看一下圖21-2中的代碼塊,能否發(fā)現(xiàn)其中的冗余。

{%}

{%}

{%}

圖 21-2 "隨手記"應用中的冗余代碼

這里的冗余代碼指與foreach塊有關(guān)(實際上是整個foreach塊以及它上面的"set NotesLabel.Text to"塊),例子中的三個foreach的作用都是顯示筆記列表,只是使用的場合有所不同:當添加新項、刪除某一項,以及應用啟動從數(shù)據(jù)庫加載列表時。

作為一個有經(jīng)驗的程序員,一旦看到這樣的代碼,腦子里會立即敲響警鐘,甚至不必等到開始拷貝粘貼第一段程序中的代碼,他們知道最好是將這些冗余的代碼封裝在一個過程里,這樣既保證程序有很好的可讀性,也可以使后來的修改變得容易。

因此,有經(jīng)驗的程序員會創(chuàng)建一個過程,將冗余代碼塊放在其中,并在原來使用冗余代碼的地方調(diào)用這一過程。應用的執(zhí)行結(jié)果完全一樣,但更易于維護,也讓其他程序員更容易地加以利用。這種代碼(塊)的重新整理的過程成為重構(gòu)。

定義過程

我們來創(chuàng)建一個過程,實現(xiàn)圖21-2中那些冗余代碼的功能。在App Inventor中,定義過程幾乎與定義變量一樣簡單:從Procedures抽屜中拖出一個“to procedure”塊或“to procedure result”塊。如果過程需要通過計算返回一個結(jié)果,則使用后者(我們將在本章稍后的部分討論它)。

在拖出“to procedure”塊后,可以修改過程名稱:點擊默認名稱“procedure”并輸入新名稱。由于冗余代碼塊的作用是顯示筆記列表,因此重構(gòu)時將過程名設為“displayList”,如圖21-3所示。

{%}

圖 21-3a 點擊默認名稱“procedure”

{%}

圖 21-3b 將過程名改為“displayList”

下一步是向過程中添加塊,此時就用現(xiàn)有的冗余塊,將它們從事件處理程序中拖出并放在displayList塊中,如圖21-4所示。

{%}

圖 21-4 封裝了冗余代碼的過程displayList

現(xiàn)在我們可以用過程來顯示筆記列表了,在應用的任何一處,都可以很容易地調(diào)用它。

調(diào)用過程

像“displayList”和“刷牙”這樣的過程是一個包含了某種功能的實體,它們只有在被調(diào)用時,才能體現(xiàn)出這種功能。因此,以上我們只是創(chuàng)建了過程,卻并沒有調(diào)用它。調(diào)用它意味著要運行它,或者說來實現(xiàn)它。

在App Inventor中,可以從Procedures抽屜中拖出一個以“call”開頭的塊來調(diào)用一個過程。每當定義了一個新的過程,procedures抽屜中就會顯示一個新的塊,即定義一個過程,就是向Procedures抽屜中添加一個新塊,如圖21-5所示。

{%}

圖 21-5 定義好一個過程后,Procedures抽屜中就會出現(xiàn)一個新的“call” 塊

你一直都在用“call”塊來調(diào)用App Inventor中的預定義函數(shù),如Ball.MoveTo以及Texting.SendMessage。當你定義了一個過程,就相當于創(chuàng)建了自己的塊,也相當于你擴展了App Inventor語言,新的“call”塊讓你可以使用自己的創(chuàng)造。

在“隨手記”的例子中,三次拖出“call displayList”塊來取代三個事件處理程序中的冗余代碼,如,ListPicker1.AfterPicking事件處理程序(刪除一條筆記)修改的結(jié)果如圖21-6所示。

{%}

圖 21-6 使用“call displayList”來調(diào)用放在過程中的那些塊

程序計數(shù)器

要理解“call”塊的運行機制,要想象應用中有一個指針,它隨著塊的運行而移動。在計算機科學中,這個指針被稱作程序計數(shù)器。

程序計數(shù)器隨著事件處理程序中的塊的運行而移動,當它遇到一個“call”塊時,它會跳到所遇到的過程中,并開始隨著過程中的塊的執(zhí)行而移動,;當過程執(zhí)行完成,程序計數(shù)器再跳回到此前的位置(“call”塊處),并從此處開始繼續(xù)移動。以“隨手記”為例,“remove list item”塊執(zhí)行完成后,程序計數(shù)器跳到displayList過程中,并隨過程中的塊(設置NotesLabel.Text屬性為空,以及foreach循環(huán))移動;最后程序計數(shù)器在回到TinyDB1.StoreValue塊。

為過程添加參數(shù)

過程displayList將冗余代碼重整到一處,這使得程序更加容易理解,你可以在更高層次上理解這些事件處理程序,而忽略掉如何顯示列表的細節(jié)。這樣做的另一個好處是,如果想要修改列表的顯示方式,就只需修改一處代碼(而不是三處)。

就過程的通用性而言,displayList是有局限的,因為該過程是針對特定的列表(notes)而設定的,而且用指定的label(NotesLabel)來顯示列表內(nèi)容,它不能用于顯示其他列表,比如應用的用戶列表,因為過程中的要素定義的過于具體。

App Inventor以及其他編程語言都提供了一種稱為參數(shù)的機制,用于構(gòu)造更為通用的過程。過程為了實現(xiàn)它的預設功能所必須的信息就由參數(shù)來提供,以睡前刷牙為例,有可能將牙膏的類型和刷牙時間設定為刷牙過程的參數(shù)。

通過點擊過程塊左上角的藍色標記,就可以為過程設定參數(shù)。對于displayList過程,我們定義了一個名為“l(fā)ist”的參數(shù),如圖21-7所示。

{%}

圖 21-7 在過程中引入了list作為參數(shù)

即使是定義了參數(shù),但foreach塊中仍然直接引用特定列表“notes”(插入到foreach塊的“in list”插槽中)。而我們希望在過程中使用我們傳遞的參數(shù)list,因此將對“global notes”的引用替換成對“get list”的引用。如圖21-8所示。

{%}

圖 21-8 現(xiàn)在foreach中使用了傳遞來的參數(shù)“l(fā)ist”

新版本的過程更加通用:在調(diào)用displayList時,無論傳入什么樣的列表,displayList都能顯示它。在向過程添加參數(shù)時,App Inventor會自動為“call”塊添加一個對應的插槽,因此當displayList添加了參數(shù)list之后,“call displayList”塊就變成圖21-9中的樣子。

{%}

圖 21-9 現(xiàn)在調(diào)用displayList時,需要指明要顯示的列表

過程定義中引入的參數(shù)list被稱為“形式參數(shù)”,而“call”塊中與之相對應的插槽被稱為“實際參數(shù)”。當在應用中的某處調(diào)用過程時,必須為過程中的每個“形式參數(shù)”提供一個“實際參數(shù)”。

對于“隨手記”的應用來說,將列表“notes”作為實際參數(shù)添加到“call”塊的list插槽中。ListPicker.AfterSelection的修改結(jié)果如圖21-10所示。

{%}

圖 21-10 在調(diào)用displayList時,將notes作為實際參數(shù)傳入

現(xiàn)在當displayList被調(diào)用時,列表notes被傳遞到過程中,來取代形式參數(shù)list。此時,程序計數(shù)器隨著過程中的每個塊的運行,它的指向是參數(shù)list,而實際上處理的是變量notes。

由于有了參數(shù),過程displayList可以用于處理任何列表,而不僅僅是notes。例如,如果“隨手記”應用可以在一組用戶中共享,而你想查看一下用戶列表,就可以調(diào)用displayList并傳入userList參數(shù)。如圖21-11所示。

{%}

圖 21-11 過程displayList可用于顯示任何列表,而不僅僅是notes

過程的返回值

關(guān)于過程displayList的可重用性,還有一個問題需要討論——你能猜到是什么嗎?如前所述,它可以顯示任何數(shù)據(jù)列表,但也只能在標簽NotesLabel中顯示。如果你想用其他的界面元素(如另一個label)來顯示列表(如userList),該如何是好呢?

一個方法就是重構(gòu)過程——將它的功能從“用指定label顯示列表”改為“只返回一個文本對象,它可以被顯示在任何地方”。為此,需要使用“procedure result”塊來取代“procedure”塊,如圖21-12所示。

{%}

圖 21-12 “procedure result”塊

你會發(fā)現(xiàn)與“procedure”塊相比,“procedure result”塊的底部有一個額外的插槽,將一個變量放入插槽,這個變量將被返回給調(diào)用者。因此,正如調(diào)用者可以向過程以參數(shù)的方式傳入數(shù)據(jù)一樣,過程也可以以值得方式將數(shù)據(jù)返回給調(diào)用者。

圖21-13顯示了上述過程的改寫版本,現(xiàn)在使用的是“procedure result”塊。注意,由于過程的作用變了,因此名稱也由displayList改為convertListToText(將列表轉(zhuǎn)換為文本)。

{%}

圖 21-13 過程convertListToText返回一個文本對象,調(diào)用者可以將其放在任何一個label中

在圖21-13所示的塊中,變量text用來保存foreach循環(huán)中通過遍歷列表而生成的文本。用text變量取代之前使用的過于具體的NotesLabel組件。在foreach執(zhí)行完畢后,變量text包含了列表中的所有項,而且項之間以換行符“\n”分隔(即“item1\nitem2\nite3”)。最后,將變量text插入return插槽,返回給調(diào)用者。

在定義“procedure result”時,與“procedure”相比,對應的“call”塊看起來略有不同,如圖21-14中所做的比較。

{%}

圖 21-14 下面的有返回值的“call”必須插入到某個插槽中

不同的是在“call convertListToText”塊的左側(cè)有一個插頭,這是因為當“call”塊運行時,過程在執(zhí)行一系列指令后將向“call”塊返回一個值,必須有某個插槽可以接收這個返回值。

在這種情況下,調(diào)用塊“call convertListToText”的返回值可以插入到任何一個label的Text屬性中,以notes列表為例,需要顯示列表的三個事件處理程序都可以調(diào)用這一過程,如圖21-15所示。

{%}

圖 21-15 將列表notes的內(nèi)容轉(zhuǎn)換為文本,并用NotesLabel顯示出來

更重要的是,由于過程的定義更具通用性,不需要引用任何特定list或label,因此應用中可以使用convertListToText在任何一個label蒸南瓜顯示任何一個列表。像圖21-16中的例子那樣。

{%}

圖 21-16 這一過程再也不必與一個特定的Label組件捆綁在一起

在應用中重用塊

通過過程的方式實現(xiàn)代碼的重用不必只限于單獨的應用,有許多過程,如convertListToText,可以用在你創(chuàng)建的任何應用中。事實上,有許多組織和編程社區(qū)都在為他們感興趣的領(lǐng)域創(chuàng)建過程代碼庫,例如動畫過程的代碼庫。

通常編程語言會提供一個“import(導入)”功能,可以在任何應用中引入其他的代碼庫。App Inventor目前沒有這項功能,不過正在開發(fā)之中。同時,也可以在一個特定的“庫應用”中創(chuàng)建一些過程,并復制該應用的代碼,作為一個新建項目的基礎代碼。

第二個例子:求兩點間距離

在displayList(convertListToText)例子中,我們將過程定義描述為一種消除冗余代碼的方法:你開始寫代碼,隨后發(fā)現(xiàn)代碼存在冗余,于是整理代碼消除冗余。無論如何,一個軟件的開發(fā)人員或開發(fā)團隊在應用開發(fā)的初期都會創(chuàng)建很多過程,同時也考慮到要重用部分代碼。這樣的規(guī)劃可以在項目過程中節(jié)省大量時間。

考慮一項應用:確定離某人當前位置最近的本地醫(yī)院,某些東西在緊急情況下會派上用場的。以下是這個應用的高層設計描述:

應用啟動時,以英里為單位計算兩點之間的距離,起點是當前所在位置,終點是發(fā)現(xiàn)的第一家醫(yī)院。然后再尋找第二家醫(yī)院,以此類推。在求得若干個距離后,判斷最短距離的醫(yī)院,并顯示它所在位置的地址。

從以上描述中,你能斷定應用中需要什么樣的過程嗎?

通常,一段描述中的動詞提示了所需的過程。重讀一遍描述,正如“等等”所提示的,這是另一個線索。這種情況下,“求出兩點之間的距離”與“判斷這些距離中最短的”成為兩個必需的過程。

現(xiàn)在考慮設計一個過程distanceBetweenPoints(兩點間距離)。在設計過程時,首先要確定過程的輸入及輸出:調(diào)用者需要向過程傳遞實現(xiàn)過程的功能所需的參數(shù),而過程要向調(diào)用者返回執(zhí)行結(jié)果。在這里,調(diào)用者需要向過程傳遞兩個點的經(jīng)度及緯度值,如圖21-17所示;而過程的任務是以英里為單位返回兩點之間的距離。

{%}

圖 21-17 調(diào)用者想過程傳遞了4個參數(shù),并收到一個距離

圖21-18中顯示了我們在本章開始時提到的那個過程,使用公式求得兩個GPS坐標點之間的近似英里數(shù)。

{%}

圖 21-18 過程distanceBetweenPoints

圖21-19顯示了對上述過程的兩次調(diào)用,每次都會求出當前位置與指定醫(yī)院之間的距離。

{%}

圖 21-19 兩次調(diào)用distanceBetweenPoints過程

第一次調(diào)用中,起點為用戶當前所在位置的LocationSensor(位置傳感器)讀數(shù),終點是St. Mary's hospital(圣瑪利亞醫(yī)院),計算的結(jié)果保存在變量distanceStMarys中;第二次調(diào)用也類似,只是將終點的數(shù)據(jù)改為CPMC Hospital(加州太平洋醫(yī)療中心醫(yī)院)的經(jīng)緯度。

接下來程序比較兩個距離并返回最近的醫(yī)院。但是如果還有更多的醫(yī)院,那就需要在一個距離列表中進行比較,并找到最小值。依你所學,你能寫出這個過程嗎?將其命名為findMinimum,接受一個數(shù)值列表作為參數(shù),并返回最短距離在列表中的索引值。

小結(jié)

像App Inventor這樣的編程語言提供了一個內(nèi)置功能的基本集,而過程是一種新功能的提取,它擴充了app inventor語言。App Inventor不提供顯示列表的塊,于是由你來做;那么是否需要一個計算兩個GPS坐標間距離的塊呢?答案是靠我們自己來創(chuàng)造。

想要建造大型的、可維護的軟件,以及在解決復雜問題時免于不斷地糾纏于細節(jié)之中,則定義高級過程的能力是至關(guān)重要的。過程是將代碼塊封裝起來,并起一個名字。在編寫過程時,你會關(guān)注這些塊的細節(jié),但對程序的其他部分而言,這個過程只是一個抽象的名字,你可以在更高層次上來引用它。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號