Java物件導向思維
- 軟體功能的分割、組合與整合
軟體設計就像是在做積木組合,簡略的來說大致上必須要先經過三道的加工程序才能組合出你所想要的成品。這三道程序分別是分析、切割、組合、整合。這就像我們在做積木玩具,一開始你會先有想要的成品,例如你想要做一艘船,那麼你必須要先開始進行分析一艘船需要有那些元素,最後你可能會有一張大略的草圖。接著你必須要將你的草圖做切割拆解動作,把一艘船分解成數個不同的機構組件。接著你開始拿著一些基本的積木方塊去完成分解出來的各個不同的模組。最終在拿著所有完成的模組進行整合的動作,把各個不同的模組整合成一艘船。一但你完成了第一艘船,你想在做第二艘船,只需要在重新組裝當初設計的拆解模組,這可以模組組合時搭上不同顏色的積木與元素,很快的你就可以在重新完成一艘外觀看起來不同,但本質結構類似的船。
基本上分析得到的成品草稿的就等同於你的應用程式,拆解出的機構模組就等同於你的設計模式(Design Pattern),而基本的積木元件就是Java的函式庫。所以同樣一個應用程式,每個人的拆解與組合的模式都不同,並沒有絕對的好與壞。今天你把每個模組拆的很細,這代表你可以有很大的變化空間(因為模組多且小,可以各別客製化變更的變多就多)。但是相對的你必須要花費更多的時間與精力來進行整合的工作,因為每個模組都被切分的很細,你必需撰寫更多的程式碼來結合它們(因為有更多的接合部份要處理,你得花費更久的時間去組裝)。而正也剛好反應出了物件導向設計的優點與缺點,物件導向的好處是在結構化、模組化、可重複利用,但這都是必須要花費時間代表來換取的,並不是所的專案都一定要使用物件導向去設計,而所使用的程度也要有所限制,不能一味的只為了物件導向而去做。必須在權衡專案未來的變更性、生命週期、擴充需求、人力資源..種種因素後,再去決定你的切割模組要切的多細。像是一些生命週期極短且不具有延續性的產品,或許連切割都不必,直接使用原始元件去打造成品還比較有效率。
- 綜合練習:名片卡列印工具
以下為一個名片卡列印工具,此程式己經經歷過了分析、切割、組合的動作,你可以試著使用這些組合好的元件,去整合出不同的名片列印器。另外你可以試著去變更切割,將元件在切的更細,接著在重新進行組合和整合的動作,並思考開發的流程有何不同之處?
【範例程式】切割出的片範本-列印介面
package com.ittraining.oop.concept.card; |
---|
【範例程式】切割出的片範本-名片印表機抽像介面
package com.ittraining.oop.concept.card; |
---|
【範例程式】組合實作完成的模組-個人資料
package com.ittraining.oop.concept.card; |
---|
【範例程式】組合實作完成的模組-公司資料
package com.ittraining.oop.concept.card; |
---|
【範例程式】組合實作完成的模組-名片列印器
package com.ittraining.oop.concept.card; |
---|
【範例程式】進行整合應用-主程式
package com.ittraining.oop.concept.card; |
---|
【運行結果】
- 類別的設計思維
在先前的章節中,我們有提到類別為物件的範本,類別是存在於磁碟中的檔案,而物件則是類別載入進記憶體後的實體。因此同一個類別可以被多次載入到記憶體中,形成許多不同的實體物件,物件之間的資料可以是完全獨立的,也可以是共享的(static靜態)。接下來我們要來討論的是類別是如何被設計出來的。首先我們要先了解類別、物件的組成結構關係(如下圖所示)。
圖:類別、物件之組成與運作關係圖
要了解類別如何設計前,我們必須先清濋的了解物件主要的工作模式,如此我們才能往回推,去思考要如何開發適合的類別。由上圖我們可以看到,物件內部主要就是由資料與函式所組成。函式最主要的目的就是去操作處理這些資料,並將計算的結果回傳給呼叫者。
資料的來源有兩條路線;
- 建構函式帶入(在建構物件時傳入)。
- 從函式呼叫時傳入(從函式的傳入參數中帶入)。
資料的種類也可以區分為二種:
- 內部資料 (建構物件時於建構式帶入的資料,利用成員變數參照)
- 外部資料(函式呼叫時從外部傳入的資料,區域變數參照)
外部資料也可以變成內部資料,只要在函式呼叫時指派一個成員變數去參照指向函式參數所帶入的資料位址。因此在設計類別時必須要先從內部資料定義開始思考,以下是基本設計類別的思考方向。
- 你必須要知道你要處理的資料有那些,接著將這些資料加以分類
- 相同類型的資料基本上可以成為一個基礎類別。
- 設計類別的建構式,將資料透過建構式帶入進物件中。
- 宣告成員變數,將成員變數指向建構式帶入的資料。
- 開始設計函式,將內部資料加以處理後在輸出結果。
- 將這一群設計好的基礎類別在整理分類。
- 接著重新設計更大的類別,建構式中帶入其它先前設計好的物件資料。
- 重覆4的動作,接著到設計函式時,可以接收從其它物件中帶入的外部資料,並將內部與外部的資料加以運算處理後輸出結果。
以上為Button UP,由底層往上堆疊功能的設計模式,另外也有Top Down的設計模式(從從大的架構在往下長出實作內容)。若你是剛進入物件導向領域的初學者,建議可以先從Button UP的方法去思考設計。因為Top Down必須要使用抽像的思考模式,先定義抽像的介面與類別,在去實作內容。這必須要等到你看的程式夠多、設計模式了解的更透徹時,自然可以轉換以更抽像的方法去思考程式的設計。
- 綜合練習:線上購物系統
在此我們將利用一個簡易的線上購物系統,利用上述的Button UP的類別設計思維方式去進行類別的設計。以下範例將只會進行二階段的設計思考,請在試著思考若要在將此功能做的更容易被使用,每個類別之間的資料還有那些可以如何被整合處理。
【範例程式】商品類別
package com.ittraining.oop.concept.shopping; |
---|
【範例程式】購買人類別
package com.ittraining.oop.concept.shopping; |
---|
【範例程式】結帳系統類別
package com.ittraining.oop.concept.shopping; |
---|
【範例程式】主程式-進行功能驗證
package com.ittraining.oop.concept.shopping; |
---|
【運行結果】
- 如何進行抽像化
抽像化最主要的目的在於定義一個通用的操作範本,就像是打造一組模具,之後的組件都可以利用此模組去生產,而模組只定義了外形並不限定你填入的原料性質。所以你可以利用同一套模具,生產出許多不同材質、不同顏色的元件,但其外形都是相同的。也因為外形相同所以你可以很輕易的舊產品中的某一塊組件拆下來,換上不同材質或顏色的組件。
- 如何進行抽像化
如何去進行抽像化?一般來說抽像化的方式與類別的設計都有二種思考方式。分別是Button UP與Top Down。Button UP的方式進行抽像化主要是先完成實作的類別,在從類別中抽取出相同的介面,而其它的物件將盡可能的都使用介面去進行操作。因此使用抽像介面與抽像類別可降低物件與物件之間的結合性(相依性)、讓類別可以當做零組件重複利用。
- 如何選擇進行抽像化的類別
由於抽像化的主要用意在於降低物件與物件之間的結合性,因此我們必須選擇物件之間結合性較高的類別來進行抽像化,才能有效的降低整體物件的結合性。那麼那些物件是具有高度結合性(相依性)的呢? 這從UML類別圖中可以看的出來,一個同時被許多其它物件所參照引用的類別,在UML類別圖中將會有許多物件關聯線與其相接(聚合關係、結合關係、委任關係、繼承關係)。若該類別所提供的功能過多,關聯過於複雜,那麼你可能分割設計數個不同抽像介面,並使用重構中,類別的擷取技巧,將該類別做適當的切割。
- 綜合練習:對線上購物系統新增功能並進行抽像化
在此我們將上一節中練習的購物系統應用程式進行抽像化的練習,增加一個Market類別,用來存放所有的上架商品,用戶將直接透過Market取得商品,管理者可以透過Market上架商品。為提供用戶與管理者一致性通用的存取Market方法,所以將Market類別進行抽像化提取出Store抽像介面。
對Market抽像化的好處在於你可以實作許多不同儲存結構的類別,只要該類別實作了Store的函式,那麼就可以直接替換掉舊的Market類別,改使用新的儲存結構。另外也提供了未來擴充上便利性,在將來如果Market需要另外開設許多不同的分店,而每個分度都有不同的管理者與販賣的商品,那麼你可以為不同的分店實作不同的類別,只要每個類別都有實作Store就可以被系統所使用。
【範例程式】商店抽像介面-Store
package com.ittraining.oop.concept.shopping.abstractsample; |
---|
【範例程式】商店實作-Market
package com.ittraining.oop.concept.shopping.abstractsample; |
---|
【範例程式】購買人類別
package com.ittraining.oop.concept.shopping.abstractsample; |
---|
【範例程式】結帳系統類別
package com.ittraining.oop.concept.shopping.abstractsample; |
---|
【範例程式】主程式-進行功能驗證
package com.ittraining.oop.concept.shopping.abstractsample; |
---|
【運行結果】