一直以來都飽受公司APP客戶端關於各種計費點的折磨。一段時間內,同一應用或不同應用間接入多家的計費模式,然後需要在不同的計費間來回的跳轉,大大的增加了出錯的幾率,甚至有時候出現計費點錯亂的現象,基於這種困擾,一直以來都想封裝一套統一實現計費策略的代碼庫,最近正好有一套APP要實現微信,支付寶支付等計... ...
寫在之前
這周生活上出現了很多的不如意,從周一開始就覺得哪裡出現了問題,然後就是各種煩躁的情緒,後來事情還真是如預感的那樣發生了,很是心痛,但也無可奈何,希望大家都好好珍惜自己身邊的人:友人,親人,家人,願一切和睦。
正文
一直以來都飽受公司APP客戶端關於各種計費點的折磨。一段時間內,同一應用或不同應用間接入多家的計費模式,然後需要在不同的計費間來回的跳轉,大大的增加了出錯的幾率,甚至有時候出現計費點錯亂的現象,這就導致了工作效率的降低,而且做了大量的無用功,費時費力還沒有成果。
基於這種困擾,一直以來都想封裝一套統一實現計費策略的代碼庫,但是一直沒有著手去做。最近有一套APP要實現微信,支付寶支付等計費,那麼正好可以利用這個機會來實現一整套的計費點代碼庫。
而本篇博文的產生就是計費代碼庫的一個縮影,主要講解的是如何使用策略模式設計這種可以使用多種演算法來完成同樣的目標。
需求分析
下麵來簡單的看下需求:
本款APP要求能實現微信、支付寶等支付方式進行購買虛擬幣,購買完成後需上傳購買數量,同時要更新用戶所擁有的虛擬幣。
ok,我們來分析下需求,主要可以分三步:
首先,購買虛擬幣,這是一個動作,在這個動作中有可以分為多種形式,比如這裡的用微信和支付寶購買;
其次,購買完成後,要上傳服務端存儲用戶購買的虛擬幣數量;
最後是更新UI,顯示用戶目前擁有的虛擬幣。
由上面的分析,我們對需求有了很清晰的認識,這裡的主要任務是設計一個簡版的計費代碼庫,所以我們主要關心的也就是計費這一模塊,其他的部分我們並不能考慮到計費庫里,我相信大家都能明白,一個庫,乾好一件事即可。
說到計費,我們的需求中也就是購買虛擬幣,而購買虛擬幣這個行為可以是多種形式的,比如說使用微信,支付寶等,那麼各種支付方式是不是都是一樣的實現呢?
顯然不是,它們都有自己的實現方式,而且每一家也都有自己的計費流程等,所以我們的庫設計應該是可以符合多種實現的計費模式,並且能很好的實現擴展,而且各種實現可以互相替換,比如,購買虛擬幣能使用微信支付也能使用支付寶支付,這就實現了互相替換。那麼有沒有這麼一種實現方式呢?
顯然答案是有的,這就是我們要講的策略模式。
模式定義
那到底什麼是策略模式呢?
策略模式是定義了一族演算法,並分別將每組演算法封裝起來,使它們之間可以互相替換。演算法的變化獨立於使用演算法的用戶。
它主要使用在哪些場景呢?
①:同一種行為,多種實現。
②:安全地封裝多種同一類型的操作。
③:同一抽象類有多個子類,而又需要選擇具體子類操作。
由它的定義和使用場景,我們更加確信了我們的計費代碼庫使用這種模式是行得通的。
那麼,我們到底該怎麼使用策略模式進行設計我們的庫呢?
詳細設計
說道關於計費的問題,相信我們每個人都很清楚的瞭解,那就是需要有一個類似pay的方法專門用來進行支付,所以我們得有一個支付類,並且這個支付類帶有一個pay的支付方法,如下設計:
這裡我並沒有針對參數來進行詳細的構建,畢竟只是一個針對簡單的demo級別庫,主要還是用來學習策略模式的,所以,我這裡就只是簡單的設計一個String類型的url參數,並讓他返回一個String類型的結果,因為大多數網路請求都是返回的一個json串,便於解析。
可以看到我在這裡設計了一個基類BasePay,並帶有一個pay方法,接過相信支付功能的同學都應該知道,為了保證支付的安全性,很多支付渠道都會有一個預訂單的介面,在預訂單中返回一些重要的信息,併在支付介面中調用,例如微信支付就是這樣模式。
所以為了滿足這種需求,我們就需要有這麼一個預訂單的方法,那麼我們在我們的基類中添加上這種方法:
同樣的沒有做過多的參數考慮,只是讓他傳進一個String類型的URL地址。
ok,我們的基類已經設計出來了,那麼各種各種的支付都可以繼承此類來完成預訂單和支付的能力,那麼你覺得這樣的設計怎麼樣呢?
假如有一種支付是不需要預訂單呢?
這樣也很簡單,我就直接做一個空的實現。
沒錯,這種可以實現,那接入有100個支付,其中有80個沒有預訂單的實現呢?
難道你要做上80個空實現嗎?
顯然這是不可取的,那麼我們該怎麼做呢?
設計原則:保持共性,封裝變化
假如有80個支付渠道不需要預訂單,20個是需要預訂單的,由此可以看出這個預訂單的介面是變化的,而且剩下的20個需要預定單的每個實現也是不相同的,那麼我們根據面向對象的設計原則:把需要變化的部分給獨立的封裝起來,只保留相同的部分,那麼我們是不是可以把它給獨立的封裝起來以便應付隨時變化的實現呢?
同樣的道理支付介面的實現也是不盡相同的,是否也應該封裝起來呢?
由此想到了另外的一個設計原則。
設計原則:針對介面編程,而不是針對實現編程
針對介面編程,不針對具體的實現進行編程,這樣我們就可以區分不同的實現獨立的完成,同時每個支付的業務只面對介面,不用關心具體的實現是怎麼完成的:
由上圖所示,分別的定義了兩個介面PreOrderInterface和PayInterface,並分別包含一個方法,並且我們分別對這兩個介面做了兩個實現,分別是針對微信和支付寶的。
這樣一來的話,我們就把原來的BasePay裡面的兩個變化的方法分別的抽取出來封裝成介面,並根據不同的業務渠道進行實現。
那我們是不是可以直接用BasePay基類來實現這個兩個介面呢?
如果直接用BasePay來實現介面,那和不抽取變化封裝有什麼區別呢,所以我們不能直接用BasePay來或者是它的子類來直接的實現這兩個介面,那麼到底該怎麼運用這兩個介面呢?
由此我們想到了另外的一種設計原則:
設計原則:少用繼承,多用組合
可以使用組合的形式來添加對介面的運用,有了介面的引用,不僅可以包含了演算法的封裝,同時也可以在運行時動態的改變行為。
這樣我們就有了對這兩個介面的引用,那麼我們在定義兩個對介面調用的方法:
同時為了便於擴展,為了能在運行時改變我們的行為方式,我們可以用設置介面的方式來實現:
ok,這樣將會更加利於我們的擴展。下麵來看看完整的BasePay類吧:
這樣的話,當我們需要預訂單的時候直接的調用performPreOrder方法,在支付的時候調用performPay方法,可以完全的不用考慮它的直接實現到底是怎麼做的,我們只需要關心的就是調用的介面而已,從而降低了耦合性。
當然這隻是我們的基類,我們還需要它的具體實現類:
如果具體類有相同的功能,那麼可以在父類中定義一個抽象方法,同時改變父類為抽象類,那麼這個相同的功能就可以直接的在子類中實現,比如說要列印一個輸出支付結果的log日誌,那麼就可以分別在子類中完成。
ok,有了子類具體實現,那麼到底該怎麼使用呢:
其實也很簡單,既然父類已經有了對介面的引用,那麼我們可以在子類中對介面實例化:
這裡可以看出,當我們需要什麼的時候就進行什麼初始化,如果不需要,可以直接的不用管它。
那麼在來看看我們是怎麼使用的:
當我們確定要使用哪種支付方式時,可以利用面向對象多態的特性指定具體的子類,然後在子類中實例化父類對介面的具體引用實現,當使用支付介面時,可以直接的調用父類提供的支付方法完成支付。
父類只需要關心引用的介面,而不需要關心具體的介面實現,實現解耦;
子類只需要關係對父類引用介面的實例化,和所共有的特性實現,也不需要具體的實現;
對具體的實現進行了封裝,每種具體的實現類都實現介面,它只關註具體的實現,這樣就把整個變化封裝在特有的實現類中,通過介面獲取到對它的引用,那麼整個結構就顯得非常的清晰,各個模塊都只需要關註自己的領域,而且模塊間耦合性也降到最低。
把變化進行封裝,保持共性,通過介面制定具體的演算法,這就是我們的策略模式。
最後在來看下整個類圖:
ok,這樣我們就利用策略模式實現了一個簡單的計費代碼庫,每當有新的計費加入時,我們只需要添加具體的實現類和具體的支付類,並不需要修改原來的代碼,並且可以多方面復用。
到這裡基本的策略模式就講完了,計費代碼庫demo級也就出來了,當然它並不完善,只是關註策略模式的這一部分,其他的像網路請求,一些幫助類等都是需要封裝的,也不是這個主題所關註的,所以這就需要大家根據自己的需求來實現了。
好了,今天就講到這裡吧。
各位如果還有哪裡不明白的,或是我這裡講的還不夠透徹,亦或是講錯了的地方請留言指正,讓我們共同進步,謝謝
同時,請大家掃一掃關註我的微信公眾號,雖然寫的不是很勤,但是每一篇都有質量保證,讓您學習到真正的知識。