Design Pattern in MATLAB (2): 太多if?Strategy!

我們的俄羅斯實習生想要在清明節的時候開始環遊地球,他的預算是他實習時賺到的工資再加上路上湊集的慈善基金。他每到一個地方都會選擇一家當地的旅行社,旅行社通常有兩種套餐,豪華套餐,和經濟套餐。同時,他所用的信用卡有消費抽獎環節,每次消費都返現1-2000元不等。

他想模擬下一路上的行程,於是他迅速寫了如下程序:

function aroundTheWorld%% Destinations CityList = {Beijing,Moscow,NYC,London};totalBudget = 2000;for i = 1:numel(CityList) fprintf(Im traveling to %s
,CityList{i}); % 買機票 if totalBudget > 1000 plan = GaoFuShuai; else plan = Diaosi; end fprintf(I will buy tickets using %s plan
,plan); if strcmp(plan, GaoFuShuai) tixCost = 1000; elseif strcmp(plan, DiaoSi) tixCost = 100; end totalBudget = totalBudget - tixCost; income = randi(2000,1); %消費返現 totalBudget = totalBudget + income; % 住旅館 if totalBudget > 1000 plan = GaoFuShuai; else plan = Diaosi; end fprintf(I will book hotel using %s plan
,plan); if strcmp(plan, GaoFuShuai) hotelCost = 500; elseif strcmp(plan, DiaoSi) hotelCost = 50; end % 花錢買票 totalBudget = totalBudget - hotelCost; income = randi(2000,1); %消費返現 totalBudget = totalBudget + income;end

運行效果如下:

>> travelDemoIm traveling to BeijingI will buy tickets using GaoFuShuai planI will book hotel using GaoFuShuai planIm traveling to MoscowI will buy tickets using GaoFuShuai planI will book hotel using Diaosi planIm traveling to NYCI will buy tickets using Diaosi planI will book hotel using Diaosi planIm traveling to LondonI will buy tickets using GaoFuShuai planI will book hotel using GaoFuShuai plan

%%OK v0.1開發完成

實習生寫完程序後,很快就發現plan可以抽象成一個類,因為以後plan可能不僅包括了機票,旅店,還可以有餐飲的選擇。於是他創建了一個GFSPlan和一個DSPlan,和一個抽象的TravelPlan類,定義如下

classdef(Abstract) TravelPlan < handle methods(Abstract) getAirTicketCost(obj); getHotelCost(obj); endend

classdef GFSPlan < TravelPlan methods function cost = getAirTicketCost(~) cost = 1000; end function cost = getHotelCost(~) cost = 500; end endend

classdef DSPlan < TravelPlan methods function cost = getAirTicketCost(~) cost = 100; end function cost = getHotelCost(~) cost = 50; end endend

接下來就是把原來的程序改寫了

function aroundTheWorld%% Destinations CityList = {Beijing,Moscow,NYC,London};totalBudget = 2000;for i = 1:numel(CityList) fprintf(Im traveling to %s
,CityList{i}); % 買機票 plan = choosePlan(totalBudget); fprintf(I will buy tickets using %s
,class(plan)); totalBudget = totalBudget - plan.getAirTicketCost; income = randi(2000,1); %消費返現 totalBudget = totalBudget + income; % 住旅館 plan = choosePlan(totalBudget); fprintf(I will book hotel using %s
,class(plan)); % 花錢買票 totalBudget = totalBudget - plan.getHotelCost(); income = randi(2000,1); %消費返現 totalBudget = totalBudget + income;end % --------- Helper -------------%function plan = chooseBudget(totalBudget)if totalBudget > 1000 plan = GFSPlan;else plan = DSPlan; end

現在我們在算機票錢和旅館錢的時候就不需要再進行判斷了。

%%OK, v0.2開發完畢

我相信很多人和我一樣,都覺得這個時候程序就完美了,程序從可擴展性還有維護性上來說,都無可挑剔了。可是作為我們合格的實習生,myc發現了一個問題。

在chooseBudget函數中,我們每次都會新建一個新的Object, 在當前的問題條件下這樣做好像沒有什麼問題。但每次都新建一個Object意味著我們丟失了原有Object的信息。考慮以下情形

TravelPlan其實是有積分制度的,每次使用都會增加一定積分,當然豪華套餐和經濟套餐的消費積分程度不同。

如果將以上條件加入到問題中,該如何解決呢?

除此之外,還有個更重要的問題,就是plan的類型是會變的!一會是GFSPlan, 一會是DSPlan, 總覺得在以後會有問題。這該怎麼解決?

答案是,無法優雅地解決。其實這個問題在C++或是Java中會更明顯,因為在Runtime的時候你無法更改plan的類型。Strategy Pattern就是為了解決這類問題而誕生的。

還記得我們的核心思想嗎?

加一層抽象,如果一層不能解決問題,加兩層

我們只需要再加一層抽象類就可以完美地解決這個問題,我們定義這個抽象類如下

classdef Context < handle properties(Access = private) myPlan end methods function obj = Context(aPlan) obj.setPlan(aPlan); end function setPlan(obj, aPlan) assert(isa(aPlan,TravelPlan)); obj.myPlan = aPlan; end function cost = getAirTicketCost(obj) cost = obj.myPlan.getAirTicket(); end function cost = getHotelCost(obj) cost = obj.myPlan.getHotelCost(); end endend

接下來,只要改主程序即可

function aroundTheWorld%% Destinations CityList = {Beijing,Moscow,NYC,London};totalBudget = 2000;gfs = GFSPlan;ds = DSPlan;context = Context(gfs);for i = 1:numel(CityList) fprintf(Im traveling to %s
,CityList{i}); % 買機票 if totalBudget > 1000 context.setPlan(gfs); else context.setPlan(ds); end fprintf(I will buy tickets using %s
,class(plan)); totalBudget = totalBudget - context.getAirTicketCost; income = randi(2000,1); %消費返現 totalBudget = totalBudget + income; % 住旅館 if totalBudget > 1000 context.setPlan(GFSPlan); else context.setPlan(DSPlan); end fprintf(I will book hotel using %s
,class(plan)); % 花錢買票 totalBudget = totalBudget - context.getHotelCost; income = randi(2000,1); %消費返現 totalBudget = totalBudget + income;end

看出和v0.2的區別了嗎?作為演算法的執行者,context在整個程序中並沒有變換自己的類型,而且gfs和ds也沒有被消滅或是重新創建,這意味著你可以保留很多信息(比如積分)。

在實際項目中,Strategy Pattern的好處是用戶可以只做一次選擇(if totalBudget > 1000),而不用擔心後續的固定操作(getAirTicketCost和getHotelCost),並且可以隨時隨地地切換演算法(setPlan)。

加入新的strategy非常簡單,只要新寫一個繼承父類(TravelPlan)的子類即可。同樣,對於需要編譯的語言來說,加入新的strategy不需要編譯其他子類。

要注意的是,Context類是不需要知道有多少個TravelPlan子類的,他只需要接受一個是TravelPlan類對象就可以。但是用戶必須知道所有他需要的TravelPlan子類,這樣才能在運行時進行setPlan。

~v1.0開發完畢


推薦閱讀:

學3d max 要有什麼基礎?
為什麼反病毒軟體對比瀏覽器等軟體反而更容易被獲得漏洞並利用?
rails, django, phoenix,你們錯了
請問軟體開發與編程對電腦有什麼要求嗎?
有哪些硬體限制對軟體設計造成影響的例子?

TAG:MATLAB | 软件设计 |