需求分析
本披薩店訂單系統針對連鎖企業設計,涉及不同地區、不同風味的多個門店。
- 不同門店采用的原材料可以自定義。
- 門店將來可能會增加披薩的其他操作。
- 不同門店可以增加特色披薩。
- 點單時客戶可以高度自定義披薩、飲料。
- 因為是連鎖店,菜單價格需要統一控制。
- 門店將來可能會增加除了披薩、飲料外的其他類型餐點。
- 為滿足以上需求,所設計的系統具有彈性且必須充分符合OO原則,便於后續開發。
用例圖
基於以上需求分析,可以初步得到如下的用例圖:
從客戶和管理員兩個actor出發:
- 客戶點單的用例較為簡單,就是首先選擇披薩店,然后進行點單(包括點披薩和點飲料兩部分,由於需求6,后續可能會增加新類型的商品)
- 管理員管理披薩店,可以新增連鎖的披薩店,也可以修改現有披薩店的信息。管理的主要內容目前為對菜單的修改,包括新增菜單,新增可點的商品(若是新建連鎖店,則前兩步必須進行),以及修改現有的菜單。
類圖
為滿足以上需求,所設計的系統具有彈性且必須充分符合OO原則,便於后續開發。所以我們運用了很多經典的設計模式,下面將基於這些設計模式完成類圖的設計。
- 工廠模式。為滿足需求2,采用工廠模式設計門店。
根據依賴倒置原則,倒置設計思路,不從“頂端”的披薩店開始設計,而從披薩開始。首先抽象出一個Pizza類,再回頭思考如何設計PizzaStore類,這樣PizzaStore類就會依賴抽象的Pizza類,而不需要理會具體的Pizza類,從而使得具體不同種類的披薩和抽象的PizzaStore類都依賴於這個抽象的Pizza類,從而使得設計符合依賴倒置原則。而PizzaStore則通過工廠方法創建具體Pizza。工廠方法模式的類圖:
為了滿足需求1,再創建一個原料工廠,負責創建Pizza所需的面餅、醬料、芝士等原料,供制作Pizza時使用。Pizza的代碼利用相關的工廠生產原料,所生產的原料依賴所使用的工廠,Pizza類根本不關心這些原料,從而實現Pizza和具體原料的完全解耦。
因此整個工廠實際上是抽象工廠模式,允許披薩店使用抽象接口獲得一組相關產品(原料),從而使披薩店和原料解耦。
通過工廠模式,我們可以很容易地創建新的原料工廠和披薩店,且符合開閉原則和接口原則,只需要直接增加新的類,實現PizzaStore和PizzaIngredientFactory中的抽象方法即可,使得整個系統非常具有彈性。 - 裝飾者模式。為滿足需求3和需求4,可以用裝飾者模式負責創建自定義Pizza。由於需要自定義Pizza,涉及到屬性和價格的變化,為了滿足開閉原則,使用裝飾者模式是最佳選擇。
即每個裝飾的組件和基本的被裝飾的組件,均繼承自Pizza抽象類,並重寫其cost()和prepare()等方法,每個裝飾組件均有一個指針指向被裝飾者,從而使得這些方法可以先委托給被裝飾者,然后再調用自己的方法,從而實現動態地將責任附加到對象上,可以更彈性地擴展功能。 - 單件模式。為滿足需求5,需要使用單件模式,創建全局唯一的價目表對象。
因為全國連鎖需要保證價格統一,所以價目表只能有一個實例,否則會導致許多問題的產生(程序行為異常、資源使用過量)。雖然全局變量也可以實現這個功能,但是如果將對象賦值給一個全局變量,那么必須在程序一開始就創建好對象,如果這個對象非常耗費資源,而程序在這次的執行過程中又一直沒用到它,就會很浪費。而單件模式只有需要用到的時候才會創建對象。
以所有飲料及其配料為例,所有的cost()方法均調用Menu類中的getXXPrice()方法獲取價格。通過讓單件模式讓Menu類的對象在運行時只有一個,保證價格的統一性。
- 命令模式。考慮訂單的處理流程:首先顧客將訂單交給前台,然后前台轉交給對應的披薩店,然后披薩店按照訂單開始准備披薩,最后返回給顧客。在整個流程中,應該將發出請求的客戶方和接收與執行請求的披薩店解耦,這時可以采用命令模式。
本系統中,我們這樣運用了命令模式:首先客戶創建訂單對象Order;然后調用setOrder()方法將訂單對象存儲在調用者OrderHandler對象中;然后客戶要求調用者執行命令,即調用OrderHandler中的handleOrder()方法,該方法中又會調用Order的excute()方法,返回Pizza。而具體如何返回以及返回哪個店的Pizza,則通過Order的具體實現類重寫excute()方法實現。 - 適配器模式。為滿足需求6,且符合開閉原則,采用適配器模式,將所有的餐點通過Adapter轉換成Pizza類型。
圖9中可以看到OrderHandler中有一個printReceipt(List)方法,該方法通過傳入一個Pizza類型的列表打印菜單。若不采用適配器模式,則每次增加新增菜品時,均需要重新修改此處的代碼,增加對應新類型參數的方法,違背OO原則。
由於飲料沒有prepare()、bake()、cut()、box()方法,因此不用關心。而description()和cost()方法是飲料類有的,因此可以直接調用飲料類的這兩個方法,從而將飲料類轉換成了Pizza類。使用了適配器模式后,若后續增加新類型的餐點,只需要增加新的Adapter類將其與Pizza對接,而無需修改OrderHandler中的代碼,符合開閉原則。 - RESTFUL API設計模式 以及 前端控制器模式。在Controller層,采用restful的api設計。
- 觀察者模式。不同於傳統前端,我們小組的前端采用minecraft plugin的模式。為了與游戲交互,需要監聽游戲中產生的各種事件,而這就需要我們使用觀察者模式,通過注冊用於監聽的Listener類,即可實現監聽不同的事件,再去具體實現對應的業務邏輯。
例如OrderListener類中,通過監聽玩家的鼠標事件,實現打開菜單的邏輯。游戲的服務端作為Subject,帶有@EventHandler注解的方法成為了Observer,每當Subject中產生一個PlayerInteract事件時,就會將其“通知”給Observer,然后執行我們的業務代碼。
- 策略模式。游戲中的每個菜單窗口界面都是一個類,通過玩家的openInventory方法可以實現相互替換,這里即策略模式。
- 外觀模式。為了隱藏游戲系統的復雜性並便於客戶端調用,提供了一個系統的接口Player,便於操作玩家執行各種動作。
可以看到游戲系統非常復雜,但對外只需調用Player中的方法即可實現大多數功能(必要時可以直接調用系統內部的方法),所以外觀模式在通常情況下具有便捷性,而又不完全封裝,必要時可以直接調用內部系統的方法。
時序圖
根據兩個用例圖,可以畫出如下的時序圖:
-
用戶點單時序圖:
-
管理員管理的時序圖: