設(shè)計模式之模板方法模式和策略模式

概述

我們知道挪捕,OOP三個基本特征是:封裝粗梭、繼承、多態(tài)级零。通過繼承断医,我們可以基于差異編程,也就是說奏纪,對于一個滿足我們大部分需求的類鉴嗤,可以創(chuàng)建它的一個子類并只改變我們不期望的那部分。但是在實際使用中序调,繼承很容易被過度使用醉锅,并且過度使用的代價是比較高的,所以我們減少了繼承的使用发绢,使用組合或委托代替

優(yōu)先使用對象組合而不是類繼承

在本文中硬耍,我們會分別介紹模板方法模式策略模式,這兩個模式分別使用了繼承和委托兩種方式边酒。這兩種模式解決的問題是類似的经柴,經(jīng)常可以互換使用墩朦,它們都可以分離通用的算法和具體的上下文坯认。比如我們有一個通用的算法,算法有不同的實現(xiàn)方式,為了遵循依賴倒置原則牛哺,我們希望算法不依賴于具體實現(xiàn)陋气。

本文冒泡排序法來進行舉例說明:

/**
 * @author HansChen
 */
public class Sorter {

    /**
     * 冒泡排序
     */
    public int sort(int[] array) {
        int operations = 0;
        if (array.length <= 1) {
            return operations;
        }

        for (int i = 0; i < array.length - 1; i++) {
            for (int j = 0; j < array.length - i - 1; j++) {
                operations++;
                if (needSwap(array, j)) {
                    swap(array, j);
                }
            }
        }

        return operations;
    }

    /**
     * @return 是否需要交換數(shù)組中 index 和 index+1 元素
     */
    private boolean needSwap(int[] array, int index) {
        return array[index] > array[index + 1];
    }

    /**
     * 交換array數(shù)組中的 index 和 index+1 元素
     */
    private void swap(int[] array, int index) {
        int temp = array[index];
        array[index] = array[index + 1];
        array[index + 1] = temp;
    }
}

這是我們實現(xiàn)的冒泡排序算法,這個sort方法可以對int數(shù)組進行排序荆隘。但我們發(fā)現(xiàn)恩伺,這種寫法的擴展性是不強的,如果我們要實現(xiàn)double數(shù)組排序呢椰拒?如果我們需要排序的是一個對象數(shù)組晶渠?難道需要各自定義一個方法嗎?如果它們都使用冒泡排序算法燃观,那么sort的算法邏輯肯定是相似的褒脯,有沒有一種方法能讓這個算法邏輯復(fù)用呢?下面用模板方法模式和策略模式對它進行改造

模板方法模式

模板方法模式:定義一個算法的骨架缆毁,將骨架中的特定步驟延遲到子類中番川。模板方法模式使得子類可以不改變算法的結(jié)構(gòu)即可重新定義該算法的某些特定步驟

下圖是用模板方法模式對冒泡排序重構(gòu)后的結(jié)構(gòu)圖:


2019-9-2-11-29-33.png

首先,我們在BubbleSorter的sort方法中定義算法骨架脊框,再定義一些延遲到子類中的抽象方法:

/**
 * @author HansChen
 */
public abstract class BubbleSorter<T> {

    /**
     * 冒泡排序
     */
    public int sort(T array) {

        setArray(array);
        int length = getLength();

        int operations = 0;
        if (length <= 1) {
            return operations;
        }

        for (int i = 0; i < length - 1; i++) {
            for (int j = 0; j < length - i - 1; j++) {
                operations++;
                if (needSwap(j)) {
                    swap(j);
                }
            }
        }

        return operations;
    }

    /**
     * 初始化排序數(shù)組
     */
    protected abstract void setArray(T array);

    /**
     * @return 返回數(shù)組長度
     */
    protected abstract int getLength();

    /**
     * @return 是否需要交換數(shù)組中 index 和 index+1 元素
     */
    protected abstract boolean needSwap(int index);

    /**
     * 交換array數(shù)組中的 index 和 index+1 元素
     */
    protected abstract void swap(int index);
}

有了BubbleSorter類颁督,我們就可以創(chuàng)建任意不同類型的對象排序的簡單派生類,比如創(chuàng)建IntBubbleSorter去排序整型數(shù)組:

public class IntBubbleSorter extends BubbleSorter<int[]> {

    private int[] array;

    @Override
    protected void setArray(int[] array) {
        this.array = array;
    }

    @Override
    protected int getLength() {
        return array == null ? 0 : array.length;
    }

    @Override
    protected boolean needSwap(int index) {
        return array != null && (array[index] > array[index + 1]);
    }

    @Override
    protected void swap(int index) {
        int temp = array[index];
        array[index] = array[index + 1];
        array[index + 1] = temp;
    }
}

再比如創(chuàng)建DoubleBubbleSorter去排序雙精度型數(shù)組:

public class DoubleBubbleSorter extends BubbleSorter<double[]> {

    private double[] array;

    @Override
    protected void setArray(double[] array) {
        this.array = array;
    }

    @Override
    protected int getLength() {
        return array == null ? 0 : array.length;
    }

    @Override
    protected boolean needSwap(int index) {
        return array != null && (array[index] > array[index + 1]);
    }

    @Override
    protected void swap(int index) {
        double temp = array[index];
        array[index] = array[index + 1];
        array[index + 1] = temp;
    }
}

甚至我們不僅限于對數(shù)組排序浇雹,還可以對List集合排序沉御,比如創(chuàng)建IntegerListBubbleSorter對List集合進行冒泡排序:

public class IntegerListBubbleSorter extends BubbleSorter<List<Integer>> {

    private List<Integer> list;

    @Override
    protected void setArray(List<Integer> list) {
        this.list = list;
    }

    @Override
    protected int getLength() {
        return list == null ? 0 : list.size();
    }

    @Override
    protected boolean needSwap(int index) {
        return list != null && (list.get(index) > list.get(index + 1));
    }

    @Override
    protected void swap(int index) {
        int temp = list.get(index);
        list.set(index, list.get(index + 1));
        list.set(index + 1, temp);
    }
}

定義上述類之后,我們看下怎么使用上面的類:

public class Test {

    public static void main(String[] args) {
    
        //對整型數(shù)組排序
        int[] intArray = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
        int operations = new IntBubbleSorter().sort(intArray);
        System.out.println("[Template Method] operations:" + operations + ", array:" + Arrays.toString(intArray));

        //對double數(shù)組排序
        double[] doubleArray = {9.9, 8.8, 7.7, 6.6, 5.5, 4.4, 3.3, 2.2, 1.1, 0.0};
        operations = new DoubleBubbleSorter().sort(doubleArray);
        System.out.println("[Template Method] operations:" + operations + ", array:" + Arrays.toString(doubleArray));

        //對List集合排序
        List<Integer> list = Arrays.asList(9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
        operations = new IntegerListBubbleSorter().sort(list);
        System.out.println("[Template Method] operations:" + operations + ", list:" + list.toString());
    }
}

模板方法模式展示了經(jīng)典重用的一種形式昭灵,通用算法被放在基類中吠裆,通過繼承在不同的子類中實現(xiàn)該通用算法。我們通過定義通用類BubbleSorter烂完,把冒泡排序的算法骨架放在基類试疙,然后實現(xiàn)不同的子類分別對int數(shù)組、double數(shù)組抠蚣、List集合進行排序祝旷。但這樣是有代價的,因為繼承是非常強的關(guān)系嘶窄,派生類不可避免地與基類綁定在一起了缓屠。但如果我現(xiàn)在需要用快速排序而不是冒泡排序來進行排序,但快速排序卻沒有辦法重用setArray护侮、getLengthneedSwapswap方法了储耐。不過羊初,策略模式提供了另一種可選的方案

策略模式

策略模式屬于對象的行為模式。其用意是針對一組算法,將每一個算法封裝到具有共同接口的獨立的類中长赞,從而使得它們可以相互替換晦攒,下面用策略模式對冒泡排序進行重構(gòu)

下圖是用策略模式對冒泡排序重構(gòu)后的結(jié)構(gòu)圖:


2019-9-2-11-35-32.png

首先定義一個BubbleSorter類,它持有一個抽象策略接口:

public class BubbleSorter<T> {

    /**
     * 抽象策略接口得哆,可以有不同的實現(xiàn)
     */
    private SortHandler<T> sortHandler;

    public BubbleSorter(SortHandler<T> sortHandler) {
        this.sortHandler = sortHandler;
    }

    /**
     * 冒泡排序
     */
    public int sort(T array) {

        sortHandler.setArray(array);
        int length = sortHandler.getLength();

        int operations = 0;
        if (length <= 1) {
            return operations;
        }

        for (int i = 0; i < length - 1; i++) {
            for (int j = 0; j < length - i - 1; j++) {
                operations++;
                if (sortHandler.needSwap(j)) {
                    sortHandler.swap(j);
                }
            }
        }

        return operations;
    }
}

定義抽象策略接口:

public interface SortHandler<T> {

    /**
     * 初始化排序數(shù)組
     */
    void setArray(T array);

    /**
     * @return 返回數(shù)組長度
     */
    int getLength();

    /**
     * @return 是否需要交換數(shù)組中 index 和 index+1 元素
     */
    boolean needSwap(int index);

    /**
     * 交換array數(shù)組中的 index 和 index+1 元素
     */
    void swap(int index);
}

創(chuàng)建具體的策略類IntSortHandler對整型數(shù)組進行操作:

public class IntSortHandler implements SortHandler<int[]> {

    private int[] array;

    @Override
    public void setArray(int[] array) {
        this.array = array;
    }

    @Override
    public int getLength() {
        return array == null ? 0 : array.length;
    }

    @Override
    public boolean needSwap(int index) {
        return array != null && (array[index] > array[index + 1]);
    }

    @Override
    public void swap(int index) {
        int temp = array[index];
        array[index] = array[index + 1];
        array[index + 1] = temp;
    }
}

創(chuàng)建具體的策略類DoubleSortHandler對雙精度型數(shù)組進行操作:

public class DoubleSortHandler implements SortHandler<double[]> {

    private double[] array;

    @Override
    public void setArray(double[] array) {
        this.array = array;
    }

    @Override
    public int getLength() {
        return array == null ? 0 : array.length;
    }

    @Override
    public boolean needSwap(int index) {
        return array != null && (array[index] > array[index + 1]);
    }

    @Override
    public void swap(int index) {
        double temp = array[index];
        array[index] = array[index + 1];
        array[index + 1] = temp;
    }
}

創(chuàng)建具體的策略類IntegerListSortHandler對List集合進行操作:

public class IntegerListSortHandler implements SortHandler<List<Integer>> {

    private List<Integer> list;

    @Override
    public void setArray(List<Integer> list) {
        this.list = list;
    }

    @Override
    public int getLength() {
        return list == null ? 0 : list.size();
    }

    @Override
    public boolean needSwap(int index) {
        return list != null && (list.get(index) > list.get(index + 1));
    }

    @Override
    public void swap(int index) {
        int temp = list.get(index);
        list.set(index, list.get(index + 1));
        list.set(index + 1, temp);
    }
}

定義上述類之后脯颜,我們看下怎么使用策略模式

public class Test {

    public static void main(String[] args) {
        //對整型數(shù)組排序
        int[] intArray = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
        BubbleSorter<int[]> intBubbleSorter = new BubbleSorter<>(new IntSortHandler());
        int operations = intBubbleSorter.sort(intArray);
        System.out.println("[Strategy] operations:" + operations + ", array:" + Arrays.toString(intArray));
        
        //對double數(shù)組排序
        double[] doubleArray = {9.9, 8.8, 7.7, 6.6, 5.5, 4.4, 3.3, 2.2, 1.1, 0.0};
        BubbleSorter<double[]> doubleBubbleSorter = new BubbleSorter<>(new DoubleSortHandler());
        operations = doubleBubbleSorter.sort(doubleArray);
        System.out.println("[Strategy] operations:" + operations + ", array:" + Arrays.toString(doubleArray));

        //對List集合排序
        List<Integer> list = Arrays.asList(9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
        BubbleSorter<List<Integer>> integerListBubbleSorter = new BubbleSorter<>(new IntegerListSortHandler());
        operations = integerListBubbleSorter.sort(list);
        System.out.println("[Strategy] operations:" + operations + ", list:" + list);
    }
}

策略模式不是將通用方法放到基類中,而是把它放進BubbleSorter的sort方法中贩据,把排序算法中必須調(diào)用的抽象方法定義在SortHandler接口中栋操,從這個接口中派生出不同的子類。把派生出的子類傳給BubbleSorter后饱亮,sort方法就可以把具體工作委托給接口去完成矾芙。注意:SortHandler對BubbleSorter是一無所知的,它不依賴于冒泡排序的具體實現(xiàn)近上,這個和模板方法模式是不同的剔宪。如果其他排序算法也需要用到SortHandler,完全也可以在相關(guān)的排序算法中使用SortHandler

總結(jié)

模板方法模式和策略模式都可以用來分離高層的算法和低層的具體實現(xiàn)細節(jié)壹无,都允許高層的算法獨立于它的具體實現(xiàn)細節(jié)重用葱绒。但策略模式還有一個額外的好處就是允許具體實現(xiàn)細節(jié)獨立于高層的算法重用,但這也以一些額外的復(fù)雜性斗锭、內(nèi)存以及運行事件開銷作為代價

文中示例代碼下載:https://github.com/shensky711/awesome-demo/tree/master/Patterns

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末地淀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拒迅,更是在濱河造成了極大的恐慌骚秦,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件璧微,死亡現(xiàn)場離奇詭異作箍,居然都是意外死亡,警方通過查閱死者的電腦和手機前硫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門胞得,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人屹电,你說我怎么就攤上這事阶剑。” “怎么了危号?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵牧愁,是天一觀的道長。 經(jīng)常有香客問我外莲,道長猪半,這世上最難降的妖魔是什么兔朦? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮磨确,結(jié)果婚禮上沽甥,老公的妹妹穿的比我還像新娘。我一直安慰自己乏奥,他們只是感情好摆舟,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邓了,像睡著了一般恨诱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上驶悟,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天胡野,我揣著相機與錄音,去河邊找鬼痕鳍。 笑死硫豆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的笼呆。 我是一名探鬼主播熊响,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诗赌!你這毒婦竟也來了汗茄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤铭若,失蹤者是張志新(化名)和其女友劉穎洪碳,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叼屠,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡瞳腌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了镜雨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫂侍。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖荚坞,靈堂內(nèi)的尸體忽然破棺而出挑宠,到底是詐尸還是另有隱情,我是刑警寧澤颓影,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布各淀,位于F島的核電站,受9級特大地震影響诡挂,放射性物質(zhì)發(fā)生泄漏揪阿。R本人自食惡果不足惜疗我,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望南捂。 院中可真熱鬧,春花似錦旧找、人聲如沸溺健。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鞭缭。三九已至,卻和暖如春魏颓,著一層夾襖步出監(jiān)牢的瞬間岭辣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工甸饱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留沦童,地道東北人。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓叹话,卻偏偏與公主長得像偷遗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子驼壶,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

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