Head First設(shè)計模式讀書筆記


title: Head First設(shè)計模式
date: 2019/06/24 13:39


我們總是需要花費大量的時間在系統(tǒng)的維護和變化上,所以應(yīng)該注重于代碼的可維護性和高擴展性谭期。

一管引、策略模式

某公司做了一個鴨子的游戲,里面有各種各樣的鴨子班巩,于是開發(fā)者定義了下面這樣的一個超類

public abstract class Duck {

    /**
     * 叫
     */
    public void quack() {
        System.out.println("呱呱叫蔓罚!");
    }

    /**
     * 游泳
     */
    public void swim() {
        System.out.println("游泳中竿奏!");
    }

    /**
     * 外貌
     */
    public abstract void display();

    // ===========================

    /**
     * 增加一個飛行行為
     */
    public abstract void fly();
    
}

所有的子類鴨子繼承這個類训裆,實現(xiàn)display的方法體眶根;此時,公司需要增加一個飛行行為边琉,于是小明在上面的接口中增加了一個fly()抽象方法属百。

然后就會造成一個問題,就是所有的子類都要重新實現(xiàn)這個這個抽象方法(這個沒法解決)变姨,而且重復(fù)的代碼沒有辦法復(fù)用族扰。

思考:那怎么辦呢?

  1. 寫一個類讓子類繼承(Java不支持多繼承)
  2. 將變化的行為找出定欧,為該行為書寫接口和實現(xiàn)類渔呵,并使用復(fù)合代替繼承(√)

實現(xiàn):

設(shè)計原則1:找出易變的地方,將其獨立出來砍鸠,不要和不會變的代碼混合在一起扩氢。

quack() 嘎嘎叫、呱呱叫睦番、不會叫

swim() 都會游泳

display() 每種鴨子不同(不可復(fù)用类茂,不會改變

fly() 會飛耍属、不會飛

所以我們要將quack()和fly()方法抽取出來

設(shè)計原則2:變量的聲明類型應(yīng)該是超類型(接口托嚣、抽象類)

設(shè)計原則3:多用組合少用繼承巩检,如果不是特別確定是它的子類就不要用繼承

繼承關(guān)系:is-a 是一個

實現(xiàn)關(guān)系:has-a 有一個

/**
 * 飛行行為和他的子類們
 */
public interface FlyBehavior {
    void fly();
}

public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("使用羽毛飛!");
    }
}

public class FlyNoWay implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("不會飛示启!");
    }
}

/**
 * 鴨叫行為和它的子類們
 */
public interface QuackBehavior {
    void quack();
}

public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("呱呱叫兢哭!");
    }
}

/**
 * 嶄新的鴨子類
 */
public abstract class Duck {

    // 維持兩個行為的變量
    private FlyBehavior flyBehavior;

    private QuackBehavior quackBehavior;

    /**
     * 叫
     */
    public void quack() {
        quackBehavior.quack();
    }

    /**
     * 游泳
     */
    public void swim() {
        System.out.println("游泳中!");
    }

    /**
     * 外貌
     */
    public abstract void display();

    /**
     * 飛行
     */
    public void fly(){
        flyBehavior.fly();
    }

    public Duck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
        this.flyBehavior = flyBehavior;
        this.quackBehavior = quackBehavior;
    }

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }
}

/**
 * 野鴨(子類/客戶端)
 */
public class MallardDuck extends Duck {

    public MallardDuck() {
        super(new FlyWithWings(), new Quack());
    }

    public MallardDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
        super(flyBehavior, quackBehavior);
    }

    /**
     * 外貌
     */
    @Override
    public void display() {
        System.out.println("黃色的夫嗓!");
    }
}

/**
 * 客戶端調(diào)用
 */
public static void main(String[] args) {
    MallardDuck mallardDuck = new MallardDuck();

    mallardDuck.fly();
    mallardDuck.quack();

    // 動態(tài)改變飛行行為
    mallardDuck.setFlyBehavior(new FlyNoWay());
    mallardDuck.fly();
}

定義:

定義一系列的算法迟螺,把它們一個個封裝起來,讓它們可相互替換舍咖。

封裝:可以復(fù)用

相互替換:解決算法需要動態(tài)替換的場景(例如:商場有一個開關(guān)矩父,打開的話全場的商品7折,使用策略模式就可以動態(tài)切換算法)

image

使用場景:

  1. 某種行為/算法可以被很多地方用到
  2. 算法需要動態(tài)替換
  3. 多重判斷導(dǎo)致的代碼量龐大
  4. 對外隱藏代碼實現(xiàn)細(xì)節(jié)

其它參考文章

https://www.runoob.com/design-pattern/strategy-pattern.html

https://www.zhihu.com/question/31162942

二排霉、觀察者模式/發(fā)布訂閱模式

高內(nèi)聚 低耦合

高內(nèi)聚:模塊中的方法在許多方面都很類似即做相似事情的功能封裝在同一個模塊中窍株。

低耦合:模塊之間的依賴程度要盡可能小攻柠;通過一個簡單又穩(wěn)定的接口進行通信球订,不關(guān)心他是怎么實現(xiàn)的。

例如瑰钮,查詢審查任務(wù)冒滩,這個功能是ars系統(tǒng)的功能,但是數(shù)據(jù)在ams系統(tǒng)中浪谴,如果我們直接去他們的數(shù)據(jù)庫中去取开睡,那么我們就和ams系統(tǒng)耦合在一起了,如果以后不用ams系統(tǒng)了苟耻,我們的系統(tǒng)所有和審查任務(wù)有關(guān)的邏輯全部要修改篇恒。

一般而言高內(nèi)聚性代表低耦合性,反之亦然梁呈。

如果所有模塊的內(nèi)聚性強婚度,那么其它模塊引用的模塊就減少了。

已知有一個類WeatherData用來檢測氣象站的信息官卡,當(dāng)氣象測量更新蝗茁,我們要將布告板上的數(shù)值修改。

public class WeatherData {

    // 溫度
    public String getTemperature() {
        return null;
    }

    // 濕度
    public String getHumidity() {
        return null;
    }

    // 氣壓
    public String getPressure() {
        return null;
    }

    /**
     * 如果氣象測量更新會調(diào)用這個方法
     */
    public void measurementsChanged() {
        
    }
}

錯誤示范:

public void measurementsChanged() {
    String temperature = getTemperature();
    String humidity = getHumidity();
    String pressure = getPressure();

    // 問題:直接對具體對象進行編程寻咒,如果增加布告板則需要修改這部分代碼
    currentConditionsDisplay.update(temperature, humidity, pressure);
    statisticsDisplay.update(temperature, humidity, pressure);
    forecastDisplay.update(temperature, humidity, pressure);
}

設(shè)計原則4:對象之間應(yīng)該松耦合

定義:

觀察者模式定義了對象之間的一對多關(guān)系哮翘,當(dāng)對象改變狀態(tài),其它依賴者都會收到通知毛秘。

image

觀察者模式使主題和觀察者之間解耦(不知道彼此的實現(xiàn)細(xì)節(jié))

實現(xiàn):

1)自己實現(xiàn)

public interface Subject {

    /**
     * 注冊觀察者
     *
     * @param observer
     */
    void registerObserver(Observer observer);

    /**
     * 移除觀察者
     *
     * @param observer
     */
    void removeObserver(Observer observer);

    /**
     * 通知觀察者們
     */
    void notifyObservers();
}

public class WeatherData implements Subject {

    private float temperature;
    private float humidity;
    private float pressure;

    // 溫度
    public float getTemperature() {
        return temperature;
    }

    // 濕度
    public float getHumidity() {
        return humidity;
    }

    // 氣壓
    public float getPressure() {
        return pressure;
    }

    /**
     * 如果氣象測量更新會調(diào)用這個方法
     */
    public void measurementsChanged() {
        this.notifyObservers();
    }

    private List<Observer> observers = new ArrayList<>();

    /**
     * 注冊觀察者
     *
     * @param observer
     */
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    /**
     * 移除觀察者
     *
     * @param observer
     */
    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    /**
     * 通知觀察者們
     */
    @Override
    public void notifyObservers() {
        observers.forEach(observer -> observer.update(temperature,humidity,pressure));
    }
}

public interface Observer {

    void update(float temperature, float humidity, float pressure);
}

/**
 * 所有的布告板都要實現(xiàn)這個接口
 */
public interface DisplayElement {

    void display();
}

public class CurrentConditionsDisplay implements Observer,DisplayElement {

    private Subject subject;

    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Subject subject) {
        this.subject = subject;

        subject.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        // 獲取該布告板需要的數(shù)據(jù)
        this.temperature = temperature;
        this.humidity = humidity;

        this.display();
    }

    @Override
    public void display() {
        System.out.println("xxx");
    }

    public static void main(String[] args) {

        Subject subject = new WeatherData();

        Observer observer = new CurrentConditionsDisplay(subject);
        // 等待氣象信息改變就行了
    }
}

P59饭寺,學(xué)完MVC模式要看一下

2)使用JDK提供的

我們上面實現(xiàn)的方式是主題主動將信息推給我們的阻课,這樣就會導(dǎo)致,有些參數(shù)我們不需要艰匙,但是它還是給我們了限煞,JDK提供的觀察者為我們提供了“推”、“拉”兩種方式员凝。

public class WeatherData extends Observable {

    private float temperature;
    private float humidity;
    private float pressure;

    // 溫度
    public float getTemperature() {
        return temperature;
    }

    // 濕度
    public float getHumidity() {
        return humidity;
    }

    // 氣壓
    public float getPressure() {
        return pressure;
    }

    /**
     * 如果氣象測量更新會調(diào)用這個方法
     */
    public void measurementsChanged() {
        // 調(diào)用這個方法署驻,表示數(shù)據(jù)改變
        super.setChanged();

        // notifyObservers有兩個重載,一個是傳參的(推)健霹、一個是不傳參的(拉)
        // 我們在這就使用拉的方式旺上,所以需要提供上面的3個get方法。
        super.notifyObservers();
        // super.notifyObservers("xxx");
    }
}

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private Observable observable;

    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Observable observable) {
        this.observable = observable;

        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) o;

            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();

            this.display();
        }
    }

    @Override
    public void display() {
        System.out.println("xxx");
    }

    public static void main(String[] args) {

        Observable weatherData = new WeatherData();
        Observer observer = new CurrentConditionsDisplay(weatherData);
        // 等待氣象信息改變就行了
    }

}

上面演示了拉的方式糖埋,但在“推”的方式被認(rèn)為更正確宣吱。

JDK提供的觀察者缺點:

  1. Observable是一個類,而且它還沒有實現(xiàn)接口瞳别,所以限制了繼承
  2. 它將setChange()設(shè)置為protected的了征候,導(dǎo)致我們沒有辦法使用組合來解決多繼承。

所以最好實際使用的時候自己實現(xiàn)一套洒试,注意要考慮并發(fā)情況下偶倍奢。

使用場景

https://www.runoob.com/design-pattern/observer-pattern.html

三、裝飾者模式

/**
 * 飲料超類
 */
public abstract class Beverage {

    private String description;

    public String getDescription() {
        return description;
    }

    public Beverage(String description) {
        this.description = description;
    }

    /**
     * 獲取商品總價的方法
     */
    public abstract double cost();

}

星巴克中有很多的飲料垒棋,而且他們有的需要加一些配料卒煞,如果我們將飲料和一大堆配料進行排列組合的創(chuàng)建類的話就會造成類爆炸。

image

我們想一想叼架,有沒有別的解決辦法畔裕?

我們可以修改一下Beverage類,將每種配料在類中聲明一個boolean值乖订,cost()方法負(fù)責(zé)將所有配料的價格計算出來扮饶,子類只需要調(diào)用super.cost() + 該飲料的價格

上面的方式雖然看著沒有問題乍构,但是如果要增加一個配料的話就需要修改Beverage類的代碼甜无,這違反了開閉原則

設(shè)計原則5:對擴展是開放的哥遮,對修改關(guān)閉岂丘。

定義:

動態(tài)的將職責(zé)附加到對象上。如果要擴展功能眠饮,裝飾者提供了比繼承更有彈性的替代方案奥帘。

因為繼承是在編譯時期就確定好的中,而組合是在運行時動態(tài)的確定的仪召。

例如:如果我需要一個加摩卡和奶泡的咖啡寨蹋,一個加奶泡的牛奶

使用繼承:

飲料 <- 咖啡 <- 摩卡奶泡咖啡
<- 牛奶 <- 奶泡牛奶

使用組合:

飲料 <- 咖啡
<- 牛奶

奶泡松蒜、摩卡(有東西來我在加,只要他是飲料的子類就行)

image

實現(xiàn):

/**
 * 飲料超類
 */
public abstract class Beverage {

    private String description;

    public String getDescription() {
        return description;
    }

    public Beverage() {
    }

    public Beverage(String description) {
        this.description = description;
    }

    /**
     * 獲取商品總價的方法
     *
     * @return
     */
    public abstract double cost();

}

public class Espresso extends Beverage {

    /**
     * 獲取商品總價的方法
     *
     * @return
     */
    @Override
    public double cost() {
        return 1.99;
    }

    public Espresso() {
        super("濃縮咖啡");
    }
}

/**
 * 裝飾者的超類
 */
public abstract class CondimentDecorator extends Beverage {

    protected Beverage beverage;

    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }

    // 子類可以將父類的方法變成抽象方法
    public abstract String getDescription();
}

public class Mocha extends CondimentDecorator {

    public Mocha(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + "已旧,摩卡";
    }

    /**
     * 獲取商品總價的方法
     *
     * @return
     */
    @Override
    public double cost() {
        return beverage.cost() + 0.2;
    }

    public static void main(String[] args) {
        // 摩卡咖啡的描述和價格
        Beverage beverage = new Espresso();
        beverage = new Mocha(beverage);

        System.out.println("beverage.getDescription() = " + beverage.getDescription());
        System.out.println("beverage.cost() = " + beverage.cost());
    }
}

P98秸苗,學(xué)完工廠和生成器模式看看

什么時候使用裝飾者模式?

  1. 動態(tài)的為對象增加新的功能
  2. 擴展的功能對一系列類(繼承同一個接口/類)都可以復(fù)用的

https://www.runoob.com/design-pattern/decorator-pattern.html

四评姨、工廠模式

什么時候使用工廠模式难述?

  1. 創(chuàng)建對象的過程很復(fù)雜
  2. 多個地方再用

4.1 簡單工廠

封裝對象的創(chuàng)建過程萤晴,通過參數(shù)的不同創(chuàng)建不同的對象吐句。如果實現(xiàn)改變只需要改這個類就行了。

4.2 工廠方法模式

定義一個工廠接口店读,為每一個商品提供一個工廠嗦枢,我覺得書上寫的這種方式是一個特例:

public abstract class PizzaStore {

    abstract Pizza createPizza(String item);

    // -> 在這里,客戶端就是這個方法屯断,可以進行前置處理和后置處理
    // -> 而正常的工廠方法是沒有這個地方的
    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        System.out.println("--- Making a " + pizza.getName() + " ---");
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

其中Spring的也是用的這樣的方法:

public abstract class AbstractFactoryBean<T> implements FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {

    public final T getObject() throws Exception {
        if (this.isSingleton()) {
            return this.initialized ? this.singletonInstance : this.getEarlySingletonInstance();
        } else {
            return this.createInstance();
        }
    }

    protected abstract T createInstance() throws Exception;
}

4.3 抽象工廠方法

我感覺就是生產(chǎn)一個產(chǎn)品族

設(shè)計原則5:依賴倒置原則(讓高層組件依賴于底層組件的抽象)

image

五文虏、單例模式

六、命令模式

一個萬能遙控器要操控多個東西殖演,例如:開關(guān)燈氧秘、開關(guān)門。趴久。丸相。

但是有一個問題,那就是這些東西都不是一個類型的(沒有實現(xiàn)同一個接口)彼棍,我們就需要像下面這樣進行硬編碼:

public class NoCommandRemoteControl {

    public void onClick(int slot) {

        if (slot == 1) {
            Light light = new Light();
            light.on();
        } else if (xxx) {
            Door door = new Door();
            door.open();
        } ...
    }
}

這就導(dǎo)致了行為的發(fā)送者和接受者完全耦合起來了灭忠,我們需要的設(shè)計應(yīng)該是我們并不知道是誰給我們干活,只要有一個抽象就行了座硕。

定義:

將一個請求封裝為一個對象弛作,從而使我們可用不同的請求對客戶進行參數(shù)化;對請求排隊或者記錄請求日志华匾,以及支持可撤銷的操作映琳。命令模式是一種對象行為型模式,其別名為動作(Action)模式或事務(wù)(Transaction蜘拉,可撤回)模式萨西。

image

實例:

public interface Command {
    void execute();
}

/**
 * 開燈命令
 */
public class LightOnCommand implements Command {

    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }
}

/**
 * 管理命令的對象,命令的保存+執(zhí)行
 */
public class RemoteControl {

    Command[] commands;

    public RemoteControl() {
        // 遙控器上有14個按鈕
        commands = new Command[14];

        // 為了避免運行的時候判空诸尽,賦予默認(rèn)的什么都不干的命令
        Command noCommand = new NoCommand();
        for (int i = 0; i < 14; i++) {
            commands[i] = noCommand;
        }
    }

    /**
     * 用于客戶端動態(tài)添加/修改不同插槽的命令
     *
     * @param slot
     * @param command
     */
    public void setCommand(int slot, Command command) {
        commands[slot] = command;
    }

    /**
     * 當(dāng)按鈕按下時調(diào)用
     *
     * @param slot
     */
    public void pushDownButton(int slot) {
        // if (commands[slot] != null) {  使用NoCommand就可以避免判空原杂。
        commands[slot].execute();
    }


    // 客戶端
    public static void main(String[] args) {

        RemoteControl remoteControl = new RemoteControl();
        remoteControl.setCommand(1, new LightOnCommand(new Light()));   // 前兩句相當(dāng)于:Thread thread = new Thread(() -> {});
        // remoteControl.setCommand(2, new XXXCommand(new XXX()));
        // ...
        remoteControl.pushDownButton(1);    // 相當(dāng)于:thread.run()
    }
}

撤回命令和執(zhí)行宏命令,看書

使用場景:

  1. 系統(tǒng)需要將請求調(diào)用者和請求接收者解耦您机,使得調(diào)用者和接收者不直接交互穿肄。
  2. 系統(tǒng)需要在不同的時間指定請求年局、將請求排隊和執(zhí)行請求。(書上有咸产,沒看懂)
  3. 系統(tǒng)需要支持命令的撤銷(Undo)操作恢復(fù)(Redo)操作(書上說的日志記錄)矢否。
  4. 系統(tǒng)需要將一組操作組合在一起,即支持宏命令

策略模式與命令模式區(qū)別

策略模式是通過不同的算法做同一件事情:例如排序

而命令模式則是通過不同的命令做不同的事情脑溢,常含有(關(guān)聯(lián))接收者僵朗。

七、適配器模式和外觀模式

適配器模式:在不修改原有帶啊嗎的基礎(chǔ)上屑彻,讓原有代碼實現(xiàn)另一個接口(轉(zhuǎn)換原有類的類型)

前提:客戶是和接口綁定起來而不是實現(xiàn)验庙。

例如:Log4J等日志框架由于未實現(xiàn)相同接口,但又想要讓他們之間可以動態(tài)的切換社牲,于是使用了適配器模式粪薛。

分為類適配器(使用繼承)和對象適配器(使用組合)

外觀模式:

設(shè)計原則5:最少知識原則

不要讓太多的類耦合在一起,避免修改系統(tǒng)中的一部分會影響到其它部分搏恤。

在一個對象中违寿,我們只應(yīng)該調(diào)用以下范圍對象的方法:

  1. 對象本身的方法
  2. 作為方法參數(shù)傳遞進來的對象
  3. 當(dāng)前方法自己創(chuàng)建的對象
  4. 對象的組件(實例變量)
public class Emailer {
    
    public Emailer(Server server) {…}

    public void sendSupportEmail(String message, String toAddress) {
        EmailSystem emailSystem = server.getEmailSystem();
        String fromAddress = emailSystem.getFromAddress();
        emailSystem.getSenderSubsystem().send(fromAddress, toAddress, message);
    }

}

我們經(jīng)常會寫出這樣的代碼,有以下幾個問題:

1熟空、復(fù)雜而且看起來不必要藤巢。Emailer 與多個它可能**不是真的需要的 API 進行交互**,例如 EmailSystem息罗,我們獲取EmailSystem的目的是獲取Sender掂咒,那為啥不直接獲取Sender呢。

2阱当、**依賴于 Server 和 EmailSystem 的內(nèi)部結(jié)構(gòu)**俏扩,如果兩者之一進行了修改,則 Emailer 有可能被破壞弊添。

3录淡、不能重用。任何其他的 Server 實現(xiàn)也必須包含一個能返回 EmailSystem 的 API油坝。


// 減少依賴嫉戚,直接將sender傳入
public Emailer(Sender sender, String fromAddress) {…}

public void sendSupportEmail(String message, String toAddress) {
    sender.send(fromAddress, toAddress, message);
}


// 拒絕
public float getTemp() {
    // 如果Thermometer對象修改我們就要改
    Thermometer th = station.getThermometer();
    return th.getTemp();
}

public float getTemp() {
    return station.getTemp();
}

https://www.cnblogs.com/gaochundong/p/least_knowledge_principle.html

八、模板方法模式

咖啡和茶有著相同的沖泡過程澈圈,所以我們可以將它們抽取出來彬檀,讓他們共同繼承一個抽象類:

/**
 * 咖啡因飲料抽象類
 */
public abstract class CaffeineBeverage {

    /**
     * 準(zhǔn)備飲料(模板方法)
     */
    public final void prepareRecipe() {
        this.boilWater();
        this.brew();
        this.pourInCup();
        this.addCondiments();
    }

    // 燒熱水
    protected void boilWater() {
        System.out.println("燒熱水中~");
    }

    // 煮
    protected abstract void brew();

    // 飲料倒入杯中
    protected void pourInCup() {
        System.out.println("飲料倒入杯中~");
    }

    // 加調(diào)料
    protected abstract void addCondiments();

    // 鉤子函數(shù):在整個程序的生命周期中,提供一個可以在指定時機做某事的東西瞬女,例如AOP和析構(gòu)函數(shù)
    // 一般是空方法窍帝,子類可以選擇不重寫;在這里做的是判斷客戶是否加調(diào)料
    protected boolean customerWantsCondiments() {
        return false;
    }
}

public class Tea extends CaffeineBeverage {

    @Override
    protected void brew() {
        System.out.println("煮茶中~");
    }

    @Override
    protected void addCondiments() {
        System.out.println("加檸檬~");
    }

    public static void main(String[] args) {
        CaffeineBeverage caffeineBeverage = new Tea();
        caffeineBeverage.prepareRecipe();
    }

}

定義:

在一個方法中定義一個算法的骨架诽偷,部分方法交由子類實現(xiàn)坤学。

鉤子函數(shù)作用:

  1. 讓子類實現(xiàn)算法中可選的部分(如上)
  2. 讓子類有機會對模板方法中某些即將發(fā)生的步驟作出反應(yīng)(如析構(gòu)函數(shù))疯坤。

好萊塢原則:高層組件調(diào)用底層組件,底層組件不要調(diào)用高層組件

在模板方法中深浮,高層組件就是父類压怠,底層組件就是它的子類,整個流程中都是父類調(diào)用子類的東西飞苇。

也就是說菌瘫,在我們的開發(fā)中最好不要使用super.xxx()調(diào)用父類的東西,主要是為了避免高低組件之間有明顯的環(huán)狀依賴布卡。

策略模式與模板方法的區(qū)別雨让,能不能結(jié)合?

策略模式是將算法抽離出來(使他可以動態(tài)切換)羽利,并且實現(xiàn)了整個算法宫患,使用者(Duck)通過組合的方式進行調(diào)用。

模板方法模式是在父類定義好算法流程这弧,讓子類實現(xiàn)。

兩者不能結(jié)合虚汛,因為策略模式把算法抽離出來了匾浪,必須通過組合的方式才能提供功能,但是模板方法需要從父類繼承下來卷哩。而且策略的父類是策略不能讓具體的對象(Duck)繼承它(Duck不是策略的子類)

九蛋辈、迭代器和組合模式

迭代器模式定義:

這種模式用于順序訪問集合對象的元素,不需要知道集合對象的底層表示将谊。

使用:

public class Waitress {

    Menu pancakeHouseMenu;
    Menu dinerMenu;

    public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
        this.pancakeHouseMenu = pancakeHouseMenu;
        this.dinerMenu = dinerMenu;
    }

    public void printMenu() {
        Iterator pancakeIterator = pancakeHouseMenu.createIterator();
        Iterator dinerIterator = dinerMenu.createIterator();

        System.out.println("MENU\n----\nBREAKFAST");
        printMenu(pancakeIterator);
        System.out.println("\nLUNCH");
        printMenu(dinerIterator);
    }

    private void printMenu(Iterator<MenuItem> iterator) {
        while (iterator.hasNext()) {
            MenuItem menuItem = iterator.next();
            System.out.print(menuItem.getName() + ", ");
            System.out.print(menuItem.getPrice() + " -- ");
            System.out.println(menuItem.getDescription());
        }
    }


    public static void main(String[] args) {
        new Waitress(new DinerMenu(), new PancakeHouseMenu()).printMenu();
    }
}

現(xiàn)在晚餐菜單中想要再包含一個菜單冷溶,如下:

dinerMenu:[menuItem1, menuItem2, 甜點菜單]

由于類型不同,所以在使用的時候需要用instance of進行判斷類型尊浓,這樣就對客戶端(Waitress)不具備透明性了逞频,還是在面向具體的實現(xiàn)編程,所以我們需要采用組合模式栋齿;

設(shè)計原則8: 單一職責(zé)苗胀,一個類只負(fù)責(zé)一個職責(zé)

組合模式定義

將對象組合成屬性結(jié)構(gòu)來表現(xiàn)層次結(jié)構(gòu);它可以是客戶以一致的方式處理個別對象以及對象的組合瓦堵。

實現(xiàn):

/**
 * 菜單組件基协,菜單和菜單項都屬于菜單組件
 */
public class MenuComponent {

    // 增加子組件
    public void add(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    // 刪除子組件
    public void remove(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    // 獲取子組件
    public MenuComponent getChild(int i) {
        throw new UnsupportedOperationException();
    }

    // 獲取組件名
    public String getName() {
        throw new UnsupportedOperationException();
    }

    // 獲取組件描述
    public String getDescription() {
        throw new UnsupportedOperationException();
    }

    // 獲取組件價格
    public double getPrice() {
        throw new UnsupportedOperationException();
    }

    // 是否是蔬菜類菜品
    public boolean isVegetarian() {
        throw new UnsupportedOperationException();
    }

    // 打印菜單
    public void print() {
        throw new UnsupportedOperationException();
    }
}

public class Menu extends MenuComponent {

    private String name;
    private String description;

    // 里面可以裝菜單也可以裝菜單項
    private List<MenuComponent> menuComponents = new ArrayList<>();

    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public String getDescription() {
        return this.description;
    }

    // 菜單獨有
    @Override
    public void add(MenuComponent menuComponent) {
        menuComponents.add(menuComponent);
    }

    // 菜單獨有
    @Override
    public void remove(MenuComponent menuComponent) {
        menuComponents.remove(menuComponent);
    }

    // 菜單獨有
    @Override
    public MenuComponent getChild(int i) {
        return menuComponents.get(i);
    }

    @Override
    public void print() {
        for (MenuComponent menuComponent : menuComponents) {
            // 如果遇到menuComponent,相當(dāng)于遞歸調(diào)用當(dāng)前方法菇用。所以可以使用組合模式代替遞歸
            menuComponent.print();
        }
    }

    public Menu(String name, String description) {
        this.name = name;
        this.description = description;
    }
}

public class MenuItem extends MenuComponent {

    private String name;
    private String description;
    private boolean vegetarian;
    private double price;

    public MenuItem(String name, String description, boolean vegetarian, double price) {
        this.name = name;
        this.description = description;
        this.vegetarian = vegetarian;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    // 菜單項獨有
    public boolean isVegetarian() {
        return vegetarian;
    }

    // 菜單項獨有
    public double getPrice() {
        return price;
    }

    @Override
    public void print() {
        System.out.println(this);
    }

    @Override
    public String toString() {
        return "MenuItem{" +
                "name='" + name + '\'' +
                ", description='" + description + '\'' +
                ", vegetarian=" + vegetarian +
                ", price=" + price +
                '}';
    }
}

public class Waitress {

    private MenuComponent menuComponent;

    public Waitress(MenuComponent menuComponent) {
        this.menuComponent = menuComponent;
    }

    public void printMenu() {
        menuComponent.print();
    }

    public static void main(String[] args) {

        MenuComponent menu = new Menu("所有菜單", "所有菜單");

        // Menu可以修改成各個餐廳的子類(DinerMenu澜驮,PancakeHouseMenu)
        Menu menu1 = new Menu("早餐菜單", "早餐菜單");
        menu1.add(new MenuItem("沙拉", "沙拉", true, 12.5));

        Menu menu2 = new Menu("零食", "零食");
        menu2.add(new MenuItem("薯片", "薯片", false, 2.5));
        menu1.add(menu2);

        menu.add(menu1);

        /*
           所有菜單 -|
                   早餐菜單 -|
                          沙拉
                          零食菜單 -|
                                 薯片

         */
        new Waitress(menu).printMenu();
    }

}

組合模式違反了單一職責(zé),它既要管理層次結(jié)構(gòu)惋鸥,還要執(zhí)行菜單的操作杂穷。

組合模式舍棄了單一職責(zé)換取了透明性鹅龄,組件接口同時包含了管理子節(jié)點葉子節(jié)點的操作,客戶就可以將兩種節(jié)點類型(菜單亭畜、菜單項)一視同仁扮休。

組合模式可以在層次結(jié)構(gòu)已經(jīng)建立好了的時候代替遞歸。構(gòu)建的時候還是要使用遞歸的拴鸵。

十玷坠、狀態(tài)模式

在生命周期中,具有很多的狀態(tài)劲藐,我們就可以將狀態(tài)(變化的部分)抽取出來八堡。

定義:

允許對象在內(nèi)部狀態(tài)改變時改變它的行為(封裝狀態(tài)為一個類,動態(tài)切換聘芜,context類負(fù)責(zé)調(diào)用)兄渺。

實現(xiàn):

/**
 * 不同階段的狀態(tài)接口
 */
public interface State {

    /**
     * 投25美分
     */
    void insertQuarter();

    /**
     * 退出25美分
     */
    void ejectQuarter();

    /**
     * 轉(zhuǎn)動把手
     */
    void turnCrank();

    /**
     * 發(fā)放糖果
     */
    void dispense();

//    /**
//     * 添加糖果
//     */
//    void refill();
}

/**
 * 沒有25美分的狀態(tài)
 */
public class NoQuarterState implements State {
    GumballMachine gumballMachine;

    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    public void insertQuarter() {
        System.out.println("已經(jīng)插入25美分");
        // 修改糖果機的狀態(tài)為【已經(jīng)插入25美分】
        gumballMachine.setState(gumballMachine.getHasQuarterState());
    }

    public void ejectQuarter() {
        System.out.println("還沒插入25美分");
    }

    public void turnCrank() {
        System.out.println("還沒插入25美分");
    }

    public void dispense() {
        System.out.println("還沒插入25美分");
    }
}

public class GumballMachine {

    // 賣光狀態(tài)
    State soldOutState;
    // 沒有25美分狀態(tài)
    State noQuarterState;
    // 已有25美分狀態(tài)
    State hasQuarterState;
    // 出糖狀態(tài)
    State soldState;

    State state;
    int count = 0;

    public GumballMachine(int numberGumballs) {

        // 初始化糖果機的幾種狀態(tài)并設(shè)置初始狀態(tài)
        soldOutState = new SoldOutState(this);
        noQuarterState = new NoQuarterState(this);
        hasQuarterState = new HasQuarterState(this);
        soldState = new SoldState(this);

        this.count = numberGumballs;
        if (numberGumballs > 0) {
            state = noQuarterState;
        } else {
            state = soldOutState;
        }
    }

    public void insertQuarter() {
        state.insertQuarter();
    }

    public void ejectQuarter() {
        state.ejectQuarter();
    }

    public void turnCrank() {
        // 轉(zhuǎn)動
        state.turnCrank();
        // 出糖
        state.dispense();
    }

    void releaseBall() {
        System.out.println("A gumball comes rolling out the slot...");
        if (count != 0) {
            count = count - 1;
        }
    }

    int getCount() {
        return count;
    }

    void setState(State state) {
        this.state = state;
    }

    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    public static void main(String[] args) {
        GumballMachine gumballMachine = new GumballMachine(100);

        gumballMachine.insertQuarter();
        gumballMachine.turnCrank();
    }
}

上面的Demo吧狀態(tài)切換放到了狀態(tài)類中,也可以放到Context中進行控制轉(zhuǎn)換的流向汰现,一般狀態(tài)轉(zhuǎn)換固定時在Context中控制挂谍。

與策略模式區(qū)別:

狀態(tài)模式,在軟件運行過程中自動的按照規(guī)定的流程主動切換瞎饲,客戶端渾然不知口叙。

策略模式咳短,一般是客戶主動指定策略贞言。

可以這樣想,狀態(tài)對用戶不可見绳矩,內(nèi)部自己改變的驮捍,策略對用戶可見疟呐,而且是由客戶切換的。

而且狀態(tài)模式類似于一個流程性的東西东且。

十一启具、代理模式

作用:控制和管理訪問

定義:

為另一個對象提供一個替身或占位符以控制對這個對象的訪問。

MVC模式

書上寫的很好苇倡,看書

視圖:用來呈現(xiàn)模型

控制器:取得用戶輸入解讀輸入對模型的意思

模型:負(fù)責(zé)維護所有的數(shù)據(jù)富纸、狀態(tài)和應(yīng)用邏輯

控制器將視圖上的動作轉(zhuǎn)成模型上的動作;模型實現(xiàn)了應(yīng)用邏輯旨椒。

模式:在某情景(context)下晓褪,針對某問題的某種解決方案。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末综慎,一起剝皮案震驚了整個濱河市涣仿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖好港,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愉镰,死亡現(xiàn)場離奇詭異,居然都是意外死亡钧汹,警方通過查閱死者的電腦和手機丈探,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拔莱,“玉大人碗降,你說我怎么就攤上這事√燎兀” “怎么了讼渊?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長尊剔。 經(jīng)常有香客問我爪幻,道長,這世上最難降的妖魔是什么须误? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任挨稿,我火速辦了婚禮,結(jié)果婚禮上霹期,老公的妹妹穿的比我還像新娘叶组。我一直安慰自己,他們只是感情好历造,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著船庇,像睡著了一般吭产。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鸭轮,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天臣淤,我揣著相機與錄音,去河邊找鬼窃爷。 笑死邑蒋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的按厘。 我是一名探鬼主播医吊,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逮京!你這毒婦竟也來了卿堂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎草描,沒想到半個月后览绿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡穗慕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年饿敲,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逛绵。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡怀各,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出暑脆,到底是詐尸還是另有隱情渠啤,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布添吗,位于F島的核電站沥曹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碟联。R本人自食惡果不足惜妓美,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鲤孵。 院中可真熱鬧壶栋,春花似錦、人聲如沸普监。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凯正。三九已至毙玻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間廊散,已是汗流浹背桑滩。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留允睹,地道東北人运准。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像缭受,于是被迫代替她去往敵國和親胁澳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容