主頁 > 軟體設計 > 優惠券的工廠與策略模式實作方案

優惠券的工廠與策略模式實作方案

2022-07-13 20:25:40 軟體設計

 

真正開發中使用最頻繁的模式基本就是【策略】和【工廠】這個兩個模式,

按照"國際慣例"先引入些模式的概念和示例,(示例參考Head First,但是力求比它講的簡潔且清晰)

之后在詳細講解優惠券的設計和模式應用,

 

所有面向物件入門的時候都是以人、動物為示例,講解什么是【繼承】等相關概念,這個是符合直覺的,

但是在實際應用中,繼承用到的地方有限,它有它的問題,它是一種【強耦合】方式,一般使用【策略模式】【裝飾模式】代替繼承,

 

以鴨子動物設計為例,講解繼承方式存在哪些問題:

 

 

所有鴨子都有quack和swim能力,所以超類實作這兩個功能,

display是抽象方法,每個子類鴨子自己負責實作自己的display功能,

這樣很好的使用了父類繼承能【復用】的特性,

(符合直覺的第一想法,而且還是面向物件學習的不錯的情況)

 

 

有些功能很好界定,有些功能很“尷尬”,例如fly功能,

fly不能加在超類上,因為不是所有鴨子都有fly功能,

如果加在超類上就導致所有的子類都要實作或者繼承這個可能不適用的方法,

而且也不是所有鴨子都會quack(例如木頭玩具鴨子),那些沒有quack的鴨子,同樣要實作或繼承quack,

想利用繼承來達到代碼復用的目的有以下問題:

  1. 同樣的display功能代碼在子類中重復,代碼沒有【復用】,
  2. 這些子類鴨子的display、fly代碼是寫死的,想運行時候修改很難,
  3. 由于每個display功能分散在不同的子類鴨子中,很難知道全部的行為,
  4. 我們修改了父類會導致牽一發而動全身,所有鴨子都受到了影響,同時我們修改某個相同型別display行為的時候,需要每個鴨子去找該相同代碼進行修改,

 

設計升級:

 

通過介面的形式,讓“某些”(而非全部)鴨子型別可飛可叫,

誰有需要誰就去實作相應的介面,

例如:你可以飛你就實作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
public class Bduck extends Duck{

    public Bduck(){
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    public void setFlyBehavior( FlyBehavior fb){
        flyBehavior = fb;
    }
    
    public void setQuackBehavior( QuackBehavior qb){
        quackBehavior = qb;
    }
    
    public void display(){
        System.out.println("i am Bduck");
    }
    
}
Bduck
public class Test{

    public static void main(String[] args){
        Duck d = new Bduck();
        d.performFly();
        d.setFlyBehavior(new FlyRocketPowered());
        d.performFly();
    }
}
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()的方式呼叫即可,方便、簡潔又安全,

懶漢式單例
單例實作模式

工廠封裝的好處:

  1. 可能很多地方都需要新建pizza物件,如果有pizza種類增刪或改變,我們只需要修改simplePizzaFactory這一個地方,【避免多處修改】,
    有時新建物件沒一行代碼那么簡單,比如連接池這種物件,集中管理很重要,
  2. createPizza方法可以是static的,好處是不需要實體化物件就可以使用,缺點是不能通過繼承來改變創建方法的行為,
  3. 工廠模式讓我們實作了【依賴倒置】,以前雖然已經面向介面編程,但是我們始終要new出具體實作類,一旦new出了具體實作類,
    雖然是面向介面編程,但是相當于和具體實作系結死了,運行時無法改變的,
    有了工廠,我們高層組建現在只依賴介面或者抽象類,底層實作類也是依賴介面或者抽象類,不依賴具體的實作類,具體實作類可以運行時通過傳參由工廠動態產生,

工廠封裝的缺點:

  1. 如果有pizza種類增刪或改變,雖然只要修改一處,避免了多處修改,但是還是要修改簡單工廠的if-else,還是有違【開閉原則】,

 

為了遵守【開閉原則】,有兩種方式:升級簡單工廠、工廠方法模式,

升級簡單工廠:

工廠也可以是一個介面或者抽象類,我們工廠也可能有很多種實作方式,

我們先實作了一種AStyleSimplePizzaFactory,如果后續需求變更,pizza種類有添加,我們可以在新建一個BStyleSimplePizzaFactory,

你可以認為這是一種分類方式,例如在中國,豆腐腦廠家,南方和北方都是生產豆腐腦,但是一個甜口一個咸口,

 

pizza店可以按照風味分類:

 

 

交通工具也可以通過型別分類:

其實你也可以不按照這個分類,
就是簡單工廠,里面通過if-else判斷,建造不同風味的pizza也沒問題,
同樣,你也可以把交通工具不按照“海陸空”方式分類,
我就在簡單工廠中,回傳不同型別的交通工具實體,完全沒毛病,

but,但是,,,,
按照專案行程,我們不能預測后續要添加多少需求,我們只能按照已知先寫了一個版本,
真的后續添加了產品或者邏輯,我們不修改以前的代碼,我們只能新加工廠和實作類,
就是為了符合【開閉原則】

你可以認為一期只有AStyleSimplePizzaFactory,隨著專案迭代,各種B、C工廠都出來了,

個人以為:
大部分專案開始完全沒有必要使用這么復雜的簡單工廠,【簡單軟體有簡單軟體的設計】,
后續迭代去修改工廠類,或者有需求之后慢慢演進到這種升級版的簡單工廠才是正途,

老法師都是想著簡潔高效,新手才想著一定要高級有逼格,

public interface Moveable {
    void run();
}

public class Car implements Moveable{
    @Override
    public void run() {
        System.out.println("driving.....");
    }
}

public class Plane implements Moveable{
@Override
    public void run() {
        System.out.println("flying...");
    }
}
//交通工具工廠
public abstract class VehicleFactory {
//具體生成什么交通工具由子類決定,這里是抽象的,
    public abstract Moveable create();
}

//Car工廠類
public class CarFactory extends VehicleFactory{
    
    @Override
    public Moveable create() {
        //單例、多例、條件檢查自己控制
        return new Car();
    }
}
//飛機工廠類
public class PlaneFactory extends VehicleFactory {
    
    @Override
    public Moveable create() {
        //單例、多例、條件檢查自己控制
        return new Plane();
    }
}

public class Test{
    public static void main(String[] args){

        VehicleFactory factory = new PlaneFactory();
    Moveable m = factory.create();
    m.run();

    //換成Car工廠
    factory = new CarFactory();
    m = factory.create();
    m.run();

    }    
}

交通工具工廠
交通工具工廠

 

工廠方法模式:

 

 

public abstract class PizzaStore{
    
    public Pizza orderPizza(String type){
        Pizza pizza;
        
        pizza = createPizza(type);
        
        pizza.prepare();
        pziza.bake();
        pizza.cut();
        pizza.box();
        
        return pizza;
    }
    
    abstract Pizza createPizza(String type);
}

public class AStylePizzaStore extends PizzaStore{
     public Pizza createPizza(String type){
        if(type.equals("chesse")){
            pizza = new AStyleChessePizza();
        }else if(type.equals("peperoni")){
            pizza = new AStylePepperoniPizza();
        }
    }
}


呼叫的時候即:

PizzaStore  store = new AStylePizzaStore();
store.orderPizza("cheese");
 

 

 

工廠方法模式:
定義一個創建物件的介面,但是由子類決定要實體化類時哪一個,
工廠方法讓類把實體化推遲到子類,

 

 工廠方法示例:

 

 

 

 

工廠方法好處:

1.將很多方法和流程固化在父類中,有利于標準化操作,將產品的實作和使用【解耦】,

2.當我們新增產品的時候,或者產品有其他風格和實作時,我們能根據【開閉原則】,新加新的子類即可,

3.工廠方法可以不是抽象的,相當于給了一個默認的實作方式,

工廠方法的缺點:

1.隨著業務增長,可能子類越來越多,難于管理(有抽象工廠管理),

2.無論是簡單工廠升級版,還是工廠方法,我們很多時候升級不是非黑即白,用新工廠代替舊工廠那么簡單,或者新工廠就舊工廠各管各的,而是兩個工廠同時存在,

例如:我原來要做甜豆花,現在有要做咸豆花,但是主體業務邏輯不動,如果是新加一個子類,
我們如何動態的指定工廠呢?在搞一個工廠的工廠嗎?突然感覺簡單工廠YYDS了,

其實我們還是要分清,這個新的產品添加,是原來的業務邏輯不動,還是原來的業務邏輯代碼需要變動,

如果原來的主邏輯代碼不動,我們應該需要修改if-else的,因為本質是引數有增加,

如果是拓展的,我們應該是要新建子類,然后拓展新加的代碼使用新加的子類,

 

 

至于什么時候用介面,什么時候用抽象類:

假如這個概念在我們腦子是確確實實存在的,就用抽象類,或者你有可復用的方法希望子類繼承直接用,
假如這個概念只是某些方面的特性:比如會飛的,會跑的,就用介面
假如兩個概念模糊的時候,不知道選擇哪個的時候,就用介面,原因是java是單繼承,多介面實作,這個繼承能力很寶貴,從實作了這個介面后,還能從其它的抽象類繼承,更靈活,

 

 抽象工廠:

為了控制工廠子類的數量,不必給每一個產品分配一個工廠類,可以將產品分組,每組中的不同產品有同一個工廠類的不同方法來創建,

 這個和簡單工廠的升級版本很像,但是注意抽象工廠是一個工廠生成不同的東西,是按照系列生產,

我們裝備美式裝備,里面是含有手槍、大炮等一系列的,

我們裝備德式裝備,里面又是一套手槍、大炮、汽車等,

 

 

 

//交通工具
public abstract class Vehicle {
    //實作由子類決定
    public abstract void run();
}
//食物
public abstract class Food {
    public abstract void printName();
}
//武器
public abstract class Weapon {
    //
    public abstract void shoot();
}
產品介面
//抽象工廠
public abstract class AbstractFactory {
    //生產 交通工具
    public abstract Vehicle createVehicle();
    //生產 武器
    public abstract Weapon createWeapon();
    //生產食物
    public abstract Food createFood();
}

//哈利波特的魔法工廠
public class MagicFactory extends AbstractFactory {
//交通工具:掃把
    public Vehicle createVehicle(){
        return new Broom();
    }
    
    //武器:魔法棒
    public Weapon createWeapon(){
        return new MagicStick();
    }
    //食物:毒蘑菇
    public Food createFood(){
        return new MushRoom();
    }
}

//默認的工廠
public class DefaultFactory extends AbstractFactory{
@Override
    public Food createFood() {
        return new Apple();
    }
@Override
    public Vehicle createVehicle() {
        return new Car();
    }
@Override
    public Weapon createWeapon() {
        return new AK47();
    }
}
工廠
public class Car extends Vehicle{
    @Override
    public void run() {
        System.out.println("冒著煙奔跑中...");
    }
}
//掃帚
public class Broom extends Vehicle{
@Override
    public void run() {
        System.out.println("掃帚搖著尾巴呼呼呼...");
    }
}
//食物:毒蘑菇
public class MushRoom extends Food {
@Override
    public void printName() {
        System.out.println("mushroom");
    }
}
public class Apple extends Food {
    @Override
    public void printName() {
        System.out.println("apple");
    }
}
public class AK47 extends Weapon{
public void shoot(){
        System.out.println("噠噠噠....");
    }
}
//武器:魔法棒
public class MagicStick extends Weapon {
    @Override
    public void shoot() {
        System.out.println("fire hu hu hu ...");
    }
}
產品
//換一個工廠,只需要改動這一處,就可以了,換一個工廠,就把生產的系列產品都換了
        AbstractFactory factory =  new DefaultFactory(); //new DefaultFactory();
        //換一個工廠
        Vehicle vehicle = factory.createVehicle();
        vehicle.run();
        Weapon weapon = factory.createWeapon();
        weapon.shoot();
        Food food = factory.createFood();
        food.printName();
測驗

抽象工廠類圖:

 

 

 

 抽象工廠允許客戶使用抽象介面來創建一組相關的產品,而不需要關心實際產出的具體產品是什么,

這樣客戶從具體的產品中【解耦】

 抽象工廠的createProductA這種方法看起來很像工廠方法,父類定義,子類實作,

 

總結:

簡單工廠:唯一工廠類,一個產品抽象類,工廠類的創建方法依據入參判斷并創建具體產品物件,

工廠方法:多個工廠類,一個產品抽象類,利用多型創建不同的產品物件,避免了大量的if-else判斷,

抽象工廠:多個工廠類,多個產品抽象類,產品子類分組,同一個工廠實作類創建同組中的不同產品,減少了工廠子類的數量,

 

插播一條訊息:

和工廠方法非常像的一個模式不要搞混,就是模板方法模式,他們的思想本質是一樣的,

模板方法模式:在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中,模板方法使得子類可以在不改變

演算法結構的情況下,重新定義演算法中一些步驟,

abstract class AbstractClass{
    final void templateMethod(){
        primitiveOperation1();
        primitiveOperation2();
        concreteOperation();
        hook();
    }
    abstract void primitiveOperation1();
    abstract void primitiveOperation2();
    
    final void concreteOperation(){
        //具體實作
    }
    
    void hook(){
        
    }

}

hook方法是一個具體方法,但是什么事情都不做或者默認實作,

這種方法我們稱為hook,

 

子類視情況要不要覆寫它們,

鉤子的存在可以讓子類有能力對演算法的不同點進行掛鉤,

要不要掛鉤由子類決定,

 

 

 

public abstract class CaffeineBeverage{
    
    final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }
    
    abstract void brew();
    
    abstract void addCondiments();
    
    void boilWater(){
        System.out.println("Boiling water");
    }
    
    void pourInCup(){
        System.out.println("Pouring into cup");
    }

}
CaffeineBeverage
public class Tea extends CaffeineBeverage{
    public void brew(){
        System.out.println("Steep the tea");
    }
    public void addCondiments(){
        System.out.println("Adding Lemon");
    }
}
Tea
public class Coffee extends CaffeineBeverage{
    public void brew(){
        System.out.println("Dripping Coffee ");
    }
    public void addCondiments(){
        System.out.println("Adding Sugar and Milk");
    }
}
Coffee

 

鉤函式示例:

public abstract class CaffeineBeverageWithHook{
    void prepareRecipe(){
        boilWater();
        brew();
        poureInCup();
        if( customerWantsCondiments() ){
            addCondiments();
        }
    }
    abstract void brew();
    
    abstract void addCondiments();
    
    void boilWater(){
        System.out.println("Boiling water");
    }
    void pourInCup(){
        System.outn.println("Pouring into cup");
    }
    
    //這就是鉤子,子類可以覆寫這個方法,但不見得要這么做,
    boolean customerWantsCondiments(){
        return true;
    }
}

鉤子控制了咖啡因飲料是否執行某部分演算法,
public class CoffeeWithHook extends CaffeineBeverageWithHook{
    public void brew(){
        System.out.println("Dripping Coffee through filter");
    }
    public void addCondiments(){
        System.out.println("Adding sugar and milk");
    }
    
    //覆寫這個鉤子,提供自己功能,讓用戶自己輸入是否添加調料
    public boolean customerWantsCondiments(){
        String answer = getUserInput();
        
        if(answer.toLowerCase().startWith("y")){
            return true;
        }else{
            return false;
        }
    }
    
    private String getUserInput(){
        ...........
    }
}
模板方法和鉤子的使用實際體現了【好萊塢原則】:你別調我,我會去呼叫你的,

我們允許底層組件將自己掛鉤到系統上,但是高層組建會決定什么時候和怎樣使用這些底層組建,

依賴倒置原則讓我們盡量依賴抽象,避免依賴具體實作,

好萊塢原則是在創建框架或組件上的一種技巧,好讓底層組建能夠被掛鉤進行計算,不會讓高層組建依賴底層組建,

CaffeineBeverageWithHook 是我們的高層組建,他能夠控制沖泡的演算法,只有需要子類實作某個演算法時,才會呼叫子類,
客戶端代碼也只依賴抽象類,而不依賴具體的Tea和coffee,減少了整個系統的依賴,
不存在子類直接呼叫抽象類的情況,【避免依賴腐敗】,你依賴我,我依賴你,最后搞不清,
鉤函式示例

問題:

什么時候使用鉤子,什么時候使用抽象方法;

1.子類必須提供某個方法或者步驟實作時,使用抽象方法

2.如果演算法部分是可選的,就用鉤子;如果是鉤子,不是強制子類要進行覆寫什么的,有默認實作,

 

鉤子的真正目的:

1.可以讓子類實作演算法中可選的部分;演算法中有寫步驟是可選的,可以將這些部分實作成鉤子,而不是抽象方法讓子類強制重寫,減輕子類的負擔,

2.鉤子能夠有機會對模板方法中某些即將發生的步驟作出反應,

 

鉤子的概念:

其實鉤子來源于英文詞Hook,在windows系統中,一切皆訊息,比如按了一下鍵盤,也是一個訊息,Hook的意思是勾住,也就是在訊息過去之前,可以先把訊息勾住,不讓其傳遞,你可以優先處理,也即這項技術就是提供了一個入口,能夠針對不同的訊息或者API在執行前,先執行你的操作,你的操作也稱為「鉤子函式」,所以,有的時候程式員在討論的時候,也經常會說,可以先hook住,在處理,也即在執行某某操作之前,優先處理一下

 

在模板方法模式中,由于面向物件的多型性,子類物件在運行時將覆寫父類物件,子類中定義的方法也將覆寫父類中定義的方法,因此程式在運行時,具體子類的基本方法將覆寫父類中定義的基本方法,子類的鉤子方法也將覆寫父類的鉤子方法,從而可以通過在子類中實作的鉤子方法對父類方法的執行進行約束,實作子類對父類行為的反向控制,

 

 

模板方法模式示例:

陣列排序有那么一點模板方法的意思,Array.sort()方法無法設計一個類繼承Java陣列,而sort()希望能夠適用所有陣列,

所以定義了一個靜態方法,而由被排序物件內的每個元素自行提供比較大小的演算法部分,

符合模板方法的思想,

public class Duck implements Comparable<Duck> {
    String name;
    int weight;
  
    public Duck(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }
 
    public String toString() {
        return name + " weighs " + weight;
    }
  
    public int compareTo(Duck otherDuck) {
 
  
        if (this.weight < otherDuck.weight) {
            return -1;
        } else if (this.weight == otherDuck.weight) {
            return 0;
        } else { // this.weight > otherDuck.weight
            return 1;
        }
    }
}
Duck
public class DuckSortTestDrive {

    public static void main(String[] args) {
        Duck[] ducks = { 
                        new Duck("Daffy", 8), 
                        new Duck("Dewey", 2),
                        new Duck("Howard", 7),
                        new Duck("Louie", 2),
                        new Duck("Donald", 10), 
                        new Duck("Huey", 2)
         };

        System.out.println("Before sorting:");
        display(ducks);

        Arrays.sort(ducks);
 
        System.out.println("\nAfter sorting:");
        display(ducks);
    }

    public static void display(Duck[] ducks) {
        for (Duck d : ducks) {
            System.out.println(d);
        }
    }
}
DuckSortTestDrive

 

策略 為了封裝可互換的行為,然后使用委托來決定要采用哪一個行為
工廠方法 又子類決定實體化哪個類
模板方法 子類決定如何實作演算法中的某些步驟,是一種代碼復用的重要技巧,

 

實際應用舉例:

策略和工廠應用的范圍實在太頻繁了,不用特別舉例子,

以優惠券為例,

 

 優惠券分型別:滿減券、折扣券、等等,這些券型別就是決定了算價格的時候如何核銷,這就是一個策略,和不同的鴨子怎么飛是一樣道理,

同樣優惠券還有適用范圍,到底適用于那些商品、門店、等等,

優惠券有很多投放,這個投放可能在很多渠道和活動是共享的,例如:A券就投放100張,在主頁活動中心、線下掃碼同時領取,領完為止,

 

思路:

優惠券最主要的:優惠方式及計算、有效期方式及計算、適用范圍及計算,

將優惠打折方式作為一種策略,組合到優惠券的屬性中,就如同鴨子組合了一個飛行的策略,

同理優惠券有效期計算,有的是立即生效,有的是固定時間生效等,

優惠券適用范圍目前只有默認方式,

通過簡單引數化工廠:

通過券型別code來獲取不同打折優惠策略實體,

通過券validity_type獲取不同有效期計算的策略實體,

適用范圍,目前只有默認計算方式,無須引數化工廠,

 

 

 

氣氛都哄到這了,就順道講下剩下的兩種創建型模式:原型模式、建造者模式,

原型模式:

 

 

public abstract class Shape implements Cloneable {
   
   private String id;
   protected String type;
   
   abstract void draw();
   
   public String getType(){
      return type;
   }
   
   public String getId() {
      return id;
   }
   
   public void setId(String id) {
      this.id = id;
   }
   
   public Object clone() {
      Object clone = null;
      try {
         // 淺拷貝    
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      return clone;
   }
}
Shape
public class Rectangle extends Shape {
 
   public Rectangle(){
     type = "Rectangle";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Rectangle::draw() method.");
   }
}

public class Square extends Shape {
 
   public Square(){
     type = "Square";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}

public class Circle extends Shape {
 
   public Circle(){
     type = "Circle";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}
ConcreteShape
public class ShapeCache {
    
   private static Hashtable<String, Shape> shapeMap 
      = new Hashtable<String, Shape>();
 
   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);

 

 

建造者模式:

 

 

class Product {
    private String partA;
    private String partB;
    private String partC;

    public void setPartA(String partA) {
        this.partA = partA;
    }

    public void setPartB(String partB) {
        this.partB = partB;
    }

    public void setPartC(String partC) {
        this.partC = partC;
    }

    public void show() {
        //顯示產品的特性
    }
}
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://www.cnblogs.com/wanglifeng717/p/16339222.html

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/498979.html

標籤:設計模式

上一篇:LSP原則是什么

下一篇:通用樹形結構的迭代與組合模式實作方案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more