优惠券的工厂与策略模式实现方案
🚀 優質資源分享 🚀
| 🧡 Python實戰微信訂餐小程序 🧡 | 進階級 | 本課程是python flask+微信小程序的完美結合,從項目搭建到騰訊云部署上線,打造一個全棧訂餐系統。 |
| 💛Python量化交易實戰💛 | 入門級 | 手把手帶你打造一個易擴展、更安全、效率更高的量化交易系統 |
真正開發中使用最頻繁的模式基本就是【策略】和【工廠】這個兩個模式。
按照"國際慣例"先引入些模式的概念和示例。(示例參考Head First,但是力求比它講的簡潔且清晰)
之后在詳細講解優惠券的設計和模式應用。
所有面向對象入門的時候都是以人、動物為示例。講解什么是【繼承】等相關概念。這個是符合直覺的。
但是在實際應用中,繼承用到的地方有限,它有它的問題,它是一種【強耦合】方式,一般使用【策略模式】【裝飾模式】代替繼承。
以鴨子動物設計為例,講解繼承方式存在哪些問題:
所有鴨子都有quack和swim能力,所以超類實現這兩個功能。
display是抽象方法,每個子類鴨子自己負責實現自己的display功能。
這樣很好的使用了父類繼承能【復用】的特性。
(符合直覺的第一想法,而且還是面向對象學習的不錯的情況)
有些功能很好界定,有些功能很“尷尬”,例如fly功能。
fly不能加在超類上,因為不是所有鴨子都有fly功能。
如果加在超類上就導致所有的子類都要實現或者繼承這個可能不適用的方法。
而且也不是所有鴨子都會quack(例如木頭玩具鴨子),那些沒有quack的鴨子,同樣要實現或繼承quack。
想利用繼承來達到代碼復用的目的有以下問題:
設計升級:
通過接口的形式,讓“某些”(而非全部)鴨子類型可飛可叫。
誰有需要誰就去實現相應的接口。
例如:你可以飛你就實現flyable接口,你不能飛,你就什么都不做。
通過接口的形式解決了部分問題,因為不是所有子類鴨子都具有fly,和quack行為。沒必要繼承或實現自己不適用的功能。
但是代碼無法【復用】的問題還是存在。
我們每個子類中都維護了display,quack功能,可能很多子類的功能都是一致的,沒有復用起來,修改一類相同行為,要每個類去找,逐個修改。
同時這些代碼都散在每個實現類中,不知道全部的行為。
設計思路與原則:
軟件項目唯一的共性:【需求不斷變化】
我們要做的就是【識別變化】【隔離變化】,每次迭代或者需求變化的時候,修改范圍可控,模塊之間【松耦合】。
主要最好不要動到那些成熟的已經經過測試和生產驗證的代碼,盡量遵循【開閉原則】。
是否進行隔離有個【單一職責】原則判斷,如果兩個模塊修改的原因是不同的,彼此的修改不一定牽涉到對方的修改。那他們應該隔離。
所謂隔離即代表,他們代碼在不同方法中、或在不同類中、或者不同服務模塊中、甚至是不同系統中。
示例中,每個鴨子的fly和quack會隨著鴨子的不同而不同。我們建立兩組類,一組和fly相關,一組和quack相關。
fly類里面有各種fly的實現方式。例如:用翅膀飛是一個實現類。用火箭飛是另外一個實現類。
這樣對于使用翅膀飛的一類鴨子,我想辦法把相應的fly類給到它,就實現了fly方法的【復用】和【集中管理】
下面我們要解決的就是如何將這個用翅膀飛的實現類“給到”這個具體的鴨子類。
插播一條概念:
【針對接口編程】
什么是接口?
接口就是約定好的規范、口令、圖紙。
就好比,各個地方的人,都聽得懂“滾”這個語言接口命令,也有相應的實現。 大家雖然各不相同、想法各異、體能差異。
但是聽到你跟他說“滾”,大家都會執行邁腿這個動作,根據人種不同,有的地方人可能邁腿上步揍你,有的地方的人是邁腿跑路。
這種不同人種的不同反應方式,我們稱為【多態】。
雖然語言接口相同,都是一個“滾”的語音輸入。但是具體實現類不同,反應也是不同的。
例如:電腦主板上有很多接口,這些接口是有明文規定,例如電壓、時序、通訊協議、功能等的。
這些就是規范。你按照這個規范走,就能拿到規范定義的結果和返回。
不同的內存廠商都有自己的內存條。他們的內存芯片、板子方案都是不同的,但是他們的插槽是相同的,他們都是實現了內存接口規范。
電腦只要按照內存接口規范,發出同樣的指令。任何廠商的內存條都能進行存儲操作。
以前經常聽說一句話,一流公司定規范,二流公司做產品。
其實規范就是接口,大公司定義實現方案和方案要實現的接口,其他公司根據自己的原材料實現這些接口,這個產品就落地了。
所謂【要針對接口編程,不要針對實現編程】
你學習如何讓一個人滾,一定要學習普通話,因為大多數地方的人都能聽懂,只不過反應不同。
如果你針對某個特定的人群學習,那你這個技能就限定在少數人上,例如閩南語只有福建那塊的人能聽懂。
再比如,你這個電腦主板內存接口是針對三星獨家的開發的,指令也只有三星認識,其他品牌的內存條甚至都插不上去。
這樣的主板誰會買,綁死在三星上,他說漲價你就要掏錢。不然整個電腦都不能運行。
針對接口實現的板子。我可以換同樣接口的國產便宜的內存。還是那句“又不是不能用,李姐萬歲”。
解釋完概念,我們看編程上如何應用。
我們以一個人的一天活動為例子。
class PersonDayAct{DayAct act = new 碼農();act.dayAct();act.nightAct(); } act.dayAct();act.nightAct(); 我們都用的接口方法,都是使用接口在編程。好處是如果我們想打印富二代的一天。DayAct act = new 富二代(); 只需要修改這一行代碼即可。 通過多態,我們就能打印富二代的一天活動。而且這個new操作,我們能通過稍后的工廠模式代替。如果以后要打印其他人的一天活動。我們只要新建新的實現類即可。不需要改動以前寫好的經過測試的代碼。符合【開閉原則】講完【面向接口編程】,我們繼續講如何完善鴨子示例。替代繼承的方式就是【組合】,多用組合,少用繼承。“有一個”比“是一個”更好,每一個鴨子都有一個FlyBehavior和一個QuackBehavior,好將飛行和咕咕叫委托給他們處理。鴨子的行為不是繼承來的,而是和“適當”的對象“組合”而來。組合的好處:
1.將一類行為封裝成類2.運行時動態改變行為。
public abstract class Duck{FlyBehavior flyBehavior;QuackBehavior quackBehavior;public Duck(){}public abstract void dispaly();public void performQuack(){quackBehavior.quack();}public void performFly(){flyBehavior.fly();}public void swim(){System.out.println("all ducks float,even decoys");} }Duck
Bduck
Test
總結:
策略模式:定義算法族,分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化獨立于使用算法的客戶。
解釋:示例中鴨子的飛行就有不同的策略,有的用翅膀飛,有的用火箭飛。
不同的人對于“滾”這個指令也有自己不同的應對策略,有的是跑,有的是上前揍你。
而這些策略是可以【復用】和【統一管理】的。我們通過【組合】的方式,將策略“放入”到類中,運行時可以更換不同策略。
而不是通過繼承來獲得這個行為。組合比繼承更加靈活,和方便。
但是策略模式還留下了一個問題就是,如何“放入”這個策略對象到類中,如果是new對象的形式,這個就和new的那個策略綁定死了。
我們希望的是,在程序運行過程中,通過輸入參數的不同,動態組合不同的實現類。從而實現不同的行為。
例如:我們通過優惠券的類型字段獲取不同的優惠券實現類。有的是滿減,有的是折扣,但是程序不關心這些類型。
他只要將價格計算委托到不同的策略上計算出最終價格即可。
簡單工廠模式:
工廠的職責就是新建產品。
以下單匹薩為例。pizza接口定義了pizza的制作方法。不同種類的pizza負責各自的實現,不同pizza有的烤的時間長,有的切的塊小。
以下是典型的面向接口編程,甚至還有點策略模式的味道。
| |
Pizza orderPizza(String type){Pizza pizza;if(type.equals("cheese")){pizza = new CheesePizza();}else if(type.equals("greek")){pizza = new GreekPizza();}else if(type.equals("pepperoni")){pizza = new PepperoniPizza();}pizza.prepare();pizza.babke();pizza.cut();pizza.box();return pizza; }|
唯一的問題是,如果我pizza的種類有了增刪,我需要修改if-else這塊代碼。這個就違反了【開閉原則】
我們應該將變化的地方【隔離變化】。
簡單工廠:
|
public class PizzaStore{SimplePizzaFactory factory;public PizzaStore(SimplePizzaFactory factory){this.factory = factory;}Pizza orderPizza(String type){Pizza pizza = factory.createPizza(type);pizza.prepare();pizza.babke();pizza.cut();pizza.box();return pizza;} }|
public class SimplePizzaFactory{public Pizza createPizza(String type){Pizza pizza;if(type.equals("cheese")){pizza = new CheesePizza();}else if(type.equals("greek")){pizza = new GreekPizza();}else if(type.equals("pepperoni")){pizza = new PepperoniPizza();}return pizza;} }|
simplePizzaFactory就干一件事,就是新建比薩。
對于需要單例的我們可以選用單例模式:
1.單例模式的餓漢式[可用] public class Singleton {private static Singleton instance=new Singleton();private Singleton(){};public static Singleton getInstance(){return instance;} } 訪問方式Singleton instance = Singleton.getInstance();2.單例模式懶漢式雙重校驗鎖[推薦用] class?Singleton{private?volatile?static?Singleton instance =?null;private?Singleton() {}public?static?Singleton getInstance() {if(instance==null) {synchronized?(Singleton.class) {if(instance==null)instance =?new?Singleton();}}return?instance;} } 訪問方式Singleton instance = Singleton.getInstance();3.內部類[推薦用]public class Singleton{private Singleton() {};private static class SingletonHolder{private static Singleton instance=new Singleton();} public static Singleton getInstance(){return SingletonHolder.instance;} }訪問方式Singleton instance = Singleton.getInstance();需要實例化時,調用getInstance方法,才會裝載SingletonHolder類,從而完成Singleton的實例化。4.枚舉形式 public enum Singleton {INSTANCE;public void doSomething() {System.out.println("doSomething");}} 調用方法:public class Main {public static void main(String[] args) {Singleton.INSTANCE.doSomething();}}直接通過Singleton.INSTANCE.doSomething()的方式調用即可。方便、簡潔又安全。懶漢式單例單例實現模式
工廠封裝的好處:
工廠封裝的缺點:
為了遵守【開閉原則】,有兩種方式:升級簡單工廠、工廠方法模式。
升級簡單工廠:
工廠也可以是一個接口或者抽象類,我們工廠也可能有很多種實現方式。
我們先實現了一種AStyleSimplePizzaFactory,如果后續需求變更,pizza種類有添加,我們可以在新建一個BStyleSimplePizzaFactory。
你可以認為這是一種分類方式。例如在中國,豆腐腦廠家。南方和北方都是生產豆腐腦,但是一個甜口一個咸口。
| pizza店可以按照風味分類:
| 交通工具也可以通過類型分類:
|
| 其實你也可以不按照這個分類。就是簡單工廠,里面通過if-else判斷,建造不同風味的pizza也沒問題。同樣,你也可以把交通工具不按照“海陸空”方式分類。我就在簡單工廠中,返回不同類型的交通工具實例。完全沒毛病。
but,但是。。。。按照項目進程,我們不能預測后續要添加多少需求,我們只能按照已知先寫了一個版本,真的后續添加了產品或者邏輯,我們不修改以前的代碼。我們只能新加工廠和實現類。就是為了符合【開閉原則】你可以認為一期只有AStyleSimplePizzaFactory,隨著項目迭代,各種B、C工廠都出來了。
個人以為:大部分項目開始完全沒有必要使用這么復雜的簡單工廠,【簡單軟件有簡單軟件的設計】,后續迭代去修改工廠類,或者有需求之后慢慢演進到這種升級版的簡單工廠才是正途。
老法師都是想著簡潔高效,新手才想著一定要高級有逼格。 |
交通工具工廠 |
工廠方法模式:
|
?
? |
調用的時候即:
PizzaStore store = new AStylePizzaStore(); store.orderPizza("cheese");|
|
?
?
工廠方法模式:定義一個創建對象的接口,但是由子類決定要實例化類時哪一個。工廠方法讓類把實例化推遲到子類。
? | ?工廠方法示例:
|
工廠方法好處:
1.將很多方法和流程固化在父類中,有利于標準化操作,將產品的實現和使用【解耦】。
2.當我們新增產品的時候,或者產品有其他風格和實現時,我們能根據【開閉原則】,新加新的子類即可。
3.工廠方法可以不是抽象的,相當于給了一個默認的實現方式。
工廠方法的缺點:
1.隨著業務增長,可能子類越來越多,難于管理(有抽象工廠管理)。
2.無論是簡單工廠升級版,還是工廠方法。我們很多時候升級不是非黑即白,用新工廠代替舊工廠那么簡單,或者新工廠就舊工廠各管各的,而是兩個工廠同時存在。
例如:我原來要做甜豆花,現在有要做咸豆花,但是主體業務邏輯不動。如果是新加一個子類。我們如何動態的指定工廠呢?在搞一個工廠的工廠嗎?突然感覺簡單工廠YYDS了。
其實我們還是要分清,這個新的產品添加,是原來的業務邏輯不動,還是原來的業務邏輯代碼需要變動。
如果原來的主邏輯代碼不動,我們應該需要修改if-else的,因為本質是參數有增加。
如果是拓展的,我們應該是要新建子類,然后拓展新加的代碼使用新加的子類。
至于什么時候用接口,什么時候用抽象類:
假如這個概念在我們腦子是確確實實存在的,就用抽象類?;蛘吣阌锌蓮陀玫姆椒ㄏM宇惱^承直接用。假如這個概念只是某些方面的特性:比如會飛的,會跑的,就用接口假如兩個概念模糊的時候,不知道選擇哪個的時候,就用接口,原因是java是單繼承,多接口實現,這個繼承能力很寶貴,從實現了這個接口后,還能從其它的抽象類繼承,更靈活。
抽象工廠:
為了控制工廠子類的數量。不必給每一個產品分配一個工廠類??梢詫a品分組,每組中的不同產品有同一個工廠類的不同方法來創建。
這個和簡單工廠的升級版本很像。但是注意抽象工廠是一個工廠生成不同的東西。是按照系列生產。
我們裝備美式裝備,里面是含有手槍、大炮等一系列的。
我們裝備德式裝備,里面又是一套手槍、大炮、汽車等。
//交通工具 public abstract class Vehicle {//實現由子類決定public abstract void run(); } //食物 public abstract class Food {public abstract void printName(); } //武器 public abstract class Weapon {//public abstract void shoot(); }產品接口
工廠
產品
測試
抽象工廠類圖:
抽象工廠允許客戶使用抽象接口來創建一組相關的產品,而不需要關心實際產出的具體產品是什么。
這樣客戶從具體的產品中【解耦】
抽象工廠的createProductA這種方法看起來很像工廠方法。父類定義,子類實現。
總結:
簡單工廠:唯一工廠類,一個產品抽象類,工廠類的創建方法依據入參判斷并創建具體產品對象。
工廠方法:多個工廠類,一個產品抽象類,利用多態創建不同的產品對象,避免了大量的if-else判斷。
抽象工廠:多個工廠類,多個產品抽象類,產品子類分組,同一個工廠實現類創建同組中的不同產品,減少了工廠子類的數量。
實際應用舉例:
策略和工廠應用的范圍實在太頻繁了,不用特別舉例子。
以優惠券為例。
優惠券分類型:滿減券、折扣券、等等。這些券類型就是決定了算價格的時候如何核銷。這就是一個策略。和不同的鴨子怎么飛是一樣道理。
同樣優惠券還有適用范圍。到底適用于那些商品、門店、等等。
優惠券有很多投放,這個投放可能在很多渠道和活動是共享的。例如:A券就投放100張,在主頁活動中心、線下掃碼同時領取。領完為止。
思路:
優惠券最主要的:優惠方式及計算、有效期方式及計算、適用范圍及計算。
將優惠打折方式作為一種策略。組合到優惠券的屬性中。就如同鴨子組合了一個飛行的策略。
同理優惠券有效期計算,有的是立即生效,有的是固定時間生效等。
優惠券適用范圍目前只有默認方式。
通過簡單參數化工廠:
通過券類型code來獲取不同打折優惠策略實例,
通過券validity_type獲取不同有效期計算的策略實例。
適用范圍,目前只有默認計算方式。無須參數化工廠。
氣氛都哄到這了,就順道講下剩下的兩種創建型模式:原型模式、建造者模式。
原型模式:
|
?
? |
Shape
public class Rectangle extends Shape {public Rectangle(){type = "Rectangle";}@Overridepublic void draw() {System.out.println("Inside Rectangle::draw() method.");} }public class Square extends Shape {public Square(){type = "Square";}@Overridepublic void draw() {System.out.println("Inside Square::draw() method.");} }public class Circle extends Shape {public Circle(){type = "Circle";}@Overridepublic void draw() {System.out.println("Inside Circle::draw() method.");} }ConcreteShape
public class ShapeCache {private static Hashtable shapeMap = new Hashtable();public static Shape getShape(String shapeId) {Shape cachedShape = shapeMap.get(shapeId);return (Shape) cachedShape.clone();}// 對每種形狀都運行數據庫查詢,并創建該形狀// shapeMap.put(shapeKey, shape);// 例如,我們要添加三種形狀public static void loadCache() {Circle circle = new Circle();circle.setId("1");shapeMap.put(circle.getId(),circle);Square square = new Square();square.setId("2");shapeMap.put(square.getId(),square);Rectangle rectangle = new Rectangle();rectangle.setId("3");shapeMap.put(rectangle.getId(),rectangle);} }ShapeCache
public class PrototypePatternDemo {public static void main(String[] args) {ShapeCache.loadCache();Shape clonedShape = (Shape) ShapeCache.getShape("1");System.out.println("Shape : " + clonedShape.getType()); Shape clonedShape2 = (Shape) ShapeCache.getShape("2");System.out.println("Shape : " + clonedShape2.getType()); Shape clonedShape3 = (Shape) ShapeCache.getShape("3");System.out.println("Shape : " + clonedShape3.getType()); } }Test
? |
原型模式,顧名思義,給你個原型,你根據原型能獲得大量相同或相似的對象,該步驟通過克隆對象完成。
對于高凈值,創建過程極其復雜的對象,可以使用這種模式大量建造,不用重新new,那樣效率太差。
(1)淺克隆
在淺克隆中,如果原型對象的成員亦量是8大基本數據類型(byte、short、int、long、float、double、char、boolean、除這8種,全部是引用類型,尤其String 底層是字符數組,不是基本數據類型)將復制一份給克降對象,如果原型對象的成員變量是引用類型(如類、接口、數組等復雜數據類型),則將引用對象的地址復制一份給克降對象,也就是說,原型對象和克隆對象的成員變量指向相同的內存地址。簡單來說,在淺克隆中,當原型對象被復制時,只復制它本身和其中包含的值類型的成員變量,而引用類型的成員變量并沒有復制。
示例:
org.springframework.beans.BeanUtils.copyProperties(source,target);
(2)深克隆
在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都將復制一份給克隆對象,深克隆將原型對象的所有引用對象也復制一份給克隆對象。簡單來說,在深克隆中,除了對象本身被復制外,對象所包含的所有成員變量也將被復制。
示例:org.apache.commons.lang3.SerializationUtils.clone(source);
建造者模式:
|
?
? |
Product
abstract class Builder {//創建產品對象protected Product product = new Product();public abstract void buildPartA();public abstract void buildPartB();public abstract void buildPartC();//返回產品對象public Product getResult() {return product;} }Builder
public class ConcreteBuilder extends Builder {public void buildPartA() {product.setPartA("建造 PartA");}public void buildPartB() {product.setPartB("建造 PartB");}public void buildPartC() {product.setPartC("建造 PartC");} }ConcreteBuilder
class Director {private Builder builder;public Director(Builder builder) {this.builder = builder;}//產品構建與組裝方法public Product construct() {builder.buildPartA();builder.buildPartB();builder.buildPartC();return builder.getResult();} }Director
public class Client {public static void main(String[] args) {Builder builder = new ConcreteBuilder();Director director = new Director(builder);Product product = director.construct();product.show();} }Client
? |
建造者模式,主要針對對象建造過程復雜,一般由很多子部件按一定步驟組合而成。產品的組成部分是不變的,但是每部分都是可以靈活選擇的。
例如:我們攢電腦的時候,都是將各種部件的要求告訴組裝店,電腦組成就那些,但是硬盤,cpu可以有很多種,他幫我們組裝好電腦(然后就被坑了。。。。)
本文來自博客園,作者:wanglifeng,轉載請注明原文鏈接:https://blog.csdn.net/wanglifeng717/p/16339222.html
總結
以上是生活随笔為你收集整理的优惠券的工厂与策略模式实现方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2003服务器系统驱动精灵,万能驱动助理
- 下一篇: Qt实现 QQ好友列表QToolBox