要做到「化繁為簡」,中心思想便在於「捕捉事物的共通性,界定事物的相異性」。我們所要面臨任何的事物或概念都有共通處,只不過程度各有高低不同。而在我們所開發的軟體系統中,也處處充滿著既有共通處卻又有相異處的多種組成。對軟體系統的設計者來說,必須要試著找出整個系統中,那些也許顯而易見、但或許幽晦不明的共通性及相異性,才真正能夠達到「化繁為簡」的目標。
我們繼續以上一回所提到的快速排序演算法為例。整數的排序、浮點數的排序、字串的排序,甚至給定世界中任何一組城市名稱,同時依照它們的緯度高低來排序,這些排序的應用之間可以有很強烈的共通部份,也就是排序的演算法本身,然而在具備共通性的情況下,這些應用之間又有相異之處,也就是被排序的型別不同,更精確來說,倘若能夠捕捉出事物的共通性、界定事物的相異性,便能夠以這共通性為核心,搭配各種不同的型別加以驅動。
如果沒有試著去找出各種應用的快速排序之間的共通性,我們也許會針對每一種型別都分別撰寫一份程式碼。但是,如果可以發覺它們之間的共通性,我們會明白,其實這些不同的實作,形式都很相像,唯一的差別在於每種實作比較大小的方式不同,所以我們因為找出了共通性,所以也才能夠進一步找出它們相異性。就像是我們常常會把掛在嘴邊的這個名詞「核心」,但什麼才是核心?一堆不同概念之間的共通概念就是核心概念。相同的核心,包覆以不同的外殼,就會對外呈現出不同的具體展現。
|
不同概念之間的共通概念就是核心概念。相同的核心,包覆以不同的外殼,就會對外呈現出不同的具體展現。 |
現在坊間有許多不同的網路相簿服務,也許剛開始,你的開發目標,只想快速下載單一個特定的相簿中的所有相片,所以你可能選擇寫死(hardcode)擷取相簿的方法。
首先,你的程式會有一個輸入值,是相簿的啟始網址,接著你取回該網址的網頁HTML內容,然後依據該相簿網站的特性,解析出網頁中指向每張相片的網址,接著把這些網址都記錄下來。由於相簿服務多半提供單一本相簿多頁呈現的方式,所以在找出該頁的每張相片的網址後,你還得進一步找出下一頁的網址,以便讓你的下載程式能夠知道接下來該到那個位址,繼續去解析相片的網址,並且持續找出每頁中所顯示的相片,直到沒有下一頁為止。當所有相片的網址都被找出來之後,你的下載程式便能夠逐一依據網址,下載每張相片,並儲存在本地端的檔案系統之中。
假設這個程式一直都運作的很好,但有一天,基於某個原因,你必須為這個相簿快速下載軟體提供新的來源,也就是,要能額外再下載另一個網路相簿服務上的相簿。你當然也可以重複上述的步驟,重新再打造一個專門用來下載這擴增的網路相簿上的相片。可是,那些時時把「捕捉事物共通性」放在心上的設計者或程式員,就會注意到一件事──下載這兩個網路相簿服務的動作之間,其實有很強烈的共通性。對下載的應用來說,它們這兩個服務除了網頁的長相不同之外,其他都完全相同。所以,我們可以試著整理出它們之間的共通性,設計出一個核心的行為:
1. 擷取相簿的第n(初始值為1)頁
2. 解析出該相簿頁上的所有相片網址,並加以記錄
3. 解析該相簿頁指向下一頁的網址(如果有的話)
4. 如果尚有下一頁時,回到步驟1,反之繼續下一步驟
5. 逐一將所有記錄下來的相片網址下載下來,並予以儲存
上述的行為,便是下載兩種網路相簿中的相片的共通行為,當我們整理出這樣子的行為時,意謂著我們透過「捕捉事物的共通性」找出了它們的核心。接著,在這個共通的核心之下,我們也同時「界定出事物的相異性」。
對下載這兩種網路相簿而言,不同之處在於:一、解析出該相簿頁上所有相片網址的具體方法不同。二、解析出該相簿頁上指向下一頁網址的具體方法不同。這兩個具體的方法之所以相異,是因為不同的相簿服務,它們的HTML網頁的寫法不同所引起。假若,我們用C語言來實作這個下載的功能,仿照qsort()的寫法,我們大概會定出下述的一個函式:
void download_album(URL *start_url, int (*fun_find_photo_url)(char *, URL **), URL* (*fun_find_next_page_url)(char *)); |
透過C語言的函式指標,我們將展現差異性的兩件事情,予以參數化,一如qsort()將決定兩值究竟孰大孰小,交由參數中的函式指標來決定一樣。fun_find_photo_url函式指標所指向的函式,會收到代表網頁內容的char*,並且應該要回傳究竟在這個網頁中找出了多少個相片的網址,並且將它記錄在傳入的URL **參數裡。fun_find_next_page_url函式指標所指向的函式,會收到代表網頁內容的char*,並且應該要在能找到下一頁網址的情況下,回傳下一頁的網址。
對呼叫download_album()這個函式的呼叫者而言,它只需要傳入相簿的啟始位址,然後了解從一個網頁中找出所有相片的網址,以及從一個網頁中找到下一頁網址的方法。下載不同的相簿當然會傳入不同的網址,而後兩個參數則是完全取決於要下載的相簿網站究竟是這兩個網站中的那一個。對於這兩個網站的下載而言,共通處我們實作了download_album(),而相異處,我們可以分別實作:
int find_photo_url_album_a(char *, URL **); URL * find_next_page_url_album_a(char *); int find_photo_url_album_b(char *, URL **); URL * find_next_page_url_album_b(char *); |
這麼一來,我們就建立起一個相簿下載的「核心」。它萃取出各網路相簿下載時的共通工作,並且將各網路相簿下載時的差異予以參數化。日後,也許你會想要增加對第三個、第四種網路相簿的下載功能,只要這些相簿彼此之間的差異所在不變,那麼你就只需要增加find_photo_url_album_c()、find_photo_url_album_d()、……之類的函式,核心的部份完全不需要更動。
從這個稍微複雜的例子中,你應該就可以明顯感受到這麼做的效益。透過這樣子的做法,即使我們的需求變得愈來愈多,但情況並沒有因此而變得更複雜。每多一本相簿,只需要增加一點點程式碼,而且架構十分清楚,也容易擴充。這正是「執簡御繁」的效果所在。下一回,我們將從程式語言的機制中,進一步探討協助程式員「執簡御繁」的各種做法。
不同概念之間的共通概念就是核心概念。相同的核心,包覆以不同的外殼,就會對外呈現出不同的具體展現。
《作者簡介》王建興
清華大學資訊工程系的博士研究生,研究興趣包括電腦網路、點對點網路、分散式網路管理、以及行動式代理人,專長則是Internet應用系統的開發。曾參與過的開發專案性質十分廣泛而且不同,從ERP、PC GAME到P2P網路電話都在他的涉獵範圍之內。
相關閱讀:
程式設計心法:化繁為簡,執簡御繁(上)
熱門新聞
2024-11-18
2024-11-20
2024-11-12
2024-11-15
2024-11-15
2024-11-19