設計模式學習總結(一)

學習設計模式本來是我在找工作時用來復習的Java基礎。參考了《Android 源碼設計模式解析與實戰》一書和其他博主的博客,再加上自己的理解和總結,就有了這篇博客。

設計模式可以說是和每一個開發者都密切相關,比如像一個安卓開發程序員,使用RecyclerView的setAdapternotifidatasetchange這兩個方法就用到了適配器模式和觀察者模式,同時如果想成為一個架構師,這些設計模式的知識也會讓你搭建框架的時候得心應手。

目前設計模式為23種,這些是目前公認的,我的理解是:如果自己有更好的想法也可以寫出更好的設計模式,就像mvc,mvp這些,都是別人參悟出來的一些架構方式,后來被人公認才廣泛流行起來,當然設計模式也不是都一定是完美的,每個設計模式都是優缺點并存。

本文將介紹部分設計模式:

  • 構造者模式
  • 工廠模式
  • 責任鏈模式
  • 觀察者模式
  • 原型模式
  • 代理模式
  • 單例模式
  • 狀態模式
  • 策略模式
  • 適配器模式

其他的設計模式等學習完之后,再去補充一篇博客(二)即可。

1、構造者模式

如果用過Retorfit、OKHttp等對以下代碼一定不會陌生。

    public void init() {
        // 初始化okhttp
        OkHttpClient client = new OkHttpClient.Builder()
                .build();

        // 初始化Retrofit
        retrofit = new Retrofit.Builder()
                .client(client)
                .baseUrl(Request.HOST)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

Retrofit的初始化就是一個典型的構造者模式。

1.1、構造者模式的定義

將一個復雜對象的構建和它的初始化進行分離,使得同樣的構建過程可以創建不同的表示。

1.2、用構造者模式實現ImageLoader。

首先先簡單分析下自己如果要實現一個ImageLoader需要具備那部分。


ImageLoader的結構

示例只對ImageLoader需要的緩存機制和下載器做一些配置。
ImageLoader是一個高度拓展的一個圖片框架,最為突出的一點就是,ImageLoader中的組件很大部分是可以自己定制的,只要符合ImageLoader定義的接口規范即可。
定義一個ImageLoaderConfig來作為配置ImageLoader參數的類。

package com.axe.builder.imageloader;

/**
 * 圖片下載框架的配置
 * 
 * @author 11373
 *
 */
public class ImageLoaderConfig {

    /**
     * 使用內存緩存
     */
    public boolean useMemoryCache = false;

    /**
     * 使用自定義的內存緩存
     */
    public boolean useCustomMemoryCache = false;

    /**
     * 使用磁盤緩存
     */
    public boolean useDiskCache = false;

    /**
     * 使用自定義的磁盤緩存
     */
    public boolean useCustomDiskCache = false;

    /**
     * 是否使用自定義的下載器
     */
    public boolean useCustomDownLoader = false;

    /**
     * 內存緩存
     */
    public Cache memoryCache;

    /**
     * 磁盤緩存
     */
    public Cache diskCache;

    /**
     * 下載器
     */
    public DownLoader downLoader;

    /**
     * 構造者
     * 
     * @author 11373
     *
     */
    public static class Builder {

        /**
         * 內存緩存
         */
        public Cache memoryCache;

        /**
         * 磁盤緩存
         */
        public Cache diskCache;

        /**
         * 下載器
         */
        public DownLoader downLoader;

        public boolean useMemoryCache = false;

        public boolean useDiskCache = false;

        public Builder setDownLoader(DownLoader downLoader) {
            this.downLoader = downLoader;
            return this;
        }

        public Builder setMemoryCache(Cache memoryCache) {
            this.memoryCache = memoryCache;
            return this;
        }

        public Builder setDiskCache(Cache diskCache) {
            this.diskCache = diskCache;
            return this;
        }

        public Builder userMemoryCache(boolean flag) {
            this.useMemoryCache = flag;
            return this;
        }

        public Builder useDiskCache(boolean flag) {
            this.useDiskCache = flag;
            return this;
        }

        public void applyConfig(ImageLoaderConfig config) {

            if (useMemoryCache) {
                config.useMemoryCache = true;
                if (memoryCache != null) {
                    config.memoryCache = memoryCache;
                } else {
                    // 如果外部沒有傳入自己的內存緩存策略,則使用默認的
                    config.memoryCache = new MemoryCache();
                }
            }

            if (useDiskCache) {
                config.useDiskCache = true;
                if (diskCache != null) {
                    config.diskCache = diskCache;
                } else {
                    // 如果外部沒有傳入自己的內存緩存策略,則使用默認的
                    config.diskCache = new DiskCache();
                }
            }

            if (downLoader != null) {
                config.downLoader = downLoader;
                config.useCustomDownLoader = true;
            } else {
                // 如果外部沒有傳入自己的內存緩存,則使用默認的
                config.downLoader = new DeFaultDownLoader();
            }
        }

        public ImageLoaderConfig create() {
            ImageLoaderConfig config = new ImageLoaderConfig();
            applyConfig(config);
            return config;
        }
    }

}

而ImageLoader這個類只用于下載圖片,它的核心方法是displayImage,然后他的所有配置都來自ImageLoaderConfig:

package com.axe.builder.imageloader;

/**
 * 圖片下載器
 * 
 * @author 11373
 *
 */
public class ImageLoader {

    private ImageLoaderConfig config;

    private static ImageLoader imageLoader;

    private ImageLoader() {
    }

    public static ImageLoader getInstance() {
        if (imageLoader == null) {
            synchronized (ImageLoader.class) {
                if (imageLoader == null) {
                    imageLoader = new ImageLoader();
                }
            }
        }
        return imageLoader;
    }

    // 初始化
    public void init(ImageLoaderConfig config) {
        this.config = config;
    }

    public void displayImage(String url, ImageView image) {
        Bitmap bitmap = null;
        // 如果設置了內存緩存,就使用內存緩存。
        if (bitmap == null && config.useMemoryCache) {
            bitmap = config.memoryCache.getBitmap(url);
        }
        // 設置了磁盤緩存,就是使用磁盤緩存。
        if (bitmap == null && config.useDiskCache) {
            bitmap = config.diskCache.getBitmap(url);
        }
        // 如果內存和磁盤都找不到圖片,那就去下載
        if (bitmap == null) {
            config.downLoader.downloadImage(url);
        }
        image.setBitmap(bitmap);
    }
}

初始化和使用:

public class BuilderMain {

    public static void main(String[] args) {
        ImageLoaderConfig config = new Builder()
                .setDiskCache(new AxeDiskCache())
                .setMemoryCache(new AxeMemoryCache())
                .useDiskCache(true)
                .userMemoryCache(true)
                .setDownLoader(new AxeImageLoader())
                .create();
        ImageLoader loader = ImageLoader.getInstance();
        loader.init(config);
        // 使用
        loader.displayImage("", null);
    }
}
1.3、構造者模式的優缺點

優點:良好的封裝性,使用構造者模式可以使客戶端不必知道產品內部的組成細節。構造者獨立存在,便于拓展。
缺點:會產生多余的構造者對象,消耗內存。

2、工廠模式

2.1、工廠方法模式

這個簡單的來說,就是面向對象的多態實現,建一個創建對象的接口,具體創建什么接口由子類自己決定。就相當于一個有一個創建汽車的工廠,具體有寶馬汽車工廠和奔馳汽車工廠實現了它,具體是造出寶馬車還是奔馳車由這兩個方法決定。

  • 創建一個汽車的實現類,封裝了汽車的公共方法,開車。
public interface Car {
    void drive();
}
public class BenziCar implements Car {
    @Override
    public void drive() {
        System.out.println("駕駛奔馳車");
    }
}
public class BWMCar implements Car{
    @Override
    public void drive() {
        System.out.println("寶馬車在駕駛");
    }
}

  • 創建一個公共的方法,都有一個公共的屬性就是創建汽車。然后奔馳工廠和寶馬工廠都會繼承它,并且都有一個公共的方法,那就是創建汽車,也就是都有一個創建對象的方法。
public interface Factory {
    Car createCar();
}
public class BenziFactory implements Factory{
    @Override
    public Car createCar() {
        return new BenziCar();
    }
}
public class BWMCarFactory implements Factory {
    @Override
    public Car createCar() {
        return new BWMCar();
    }
}

實際使用:

public class FactoryMain {
    public static void main(String[] args) {
        Factory benziFactory = new BenziFactory();
        benziFactory.createCar().drive();
        
        Factory bwmFactory = new BWMCarFactory();
        bwmFactory.createCar().drive();
    }
}
駕駛奔馳車
寶馬車在駕駛

工廠方法模式的優缺點

  • 優點:符合開放封閉原則。新增產品時,只需增加相應的具體產品類和相應的工廠子類即可;符合單一職責原則。每個具體工廠類只負責創建對應的產品。(本段文字來自博客:Android的設計模式-工廠方法模式
  • 缺點:一個具體工廠只能創建一種具體產品;增加新產品時,還需增加相應的工廠類,系統類的個數將成對增加,增加了系統的復雜度和性能開銷;引入的抽象類也會導致類結構的復雜化。(本段文字來自博客:Android的設計模式-工廠方法模式
2.2、簡單工廠模式

繼續2.1的例子,我們將所有的實現的汽車工廠變成一個工廠,這個工廠可以生成各種不同的汽車。修改汽車工廠的基類如下:

public interface Factory2 {
    Car createCar(String name);
}

我們只要傳入我們想生成的汽車名字,我們即可生成改汽車的對象。
實現類如下:

public class CarFactory implements Factory2 {
    @Override
    public Car createCar(String name) {
        if ("benzi".equals(name)) {
            return new BenziCar();
        } else if ("bwm".equals(name)) {
            return new BWMCar();
        }
        return null;
    }
}

實際使用時:

    Factory2 factory2 = new CarFactory();
    factory2.createCar("benzi").drive();
    factory2.createCar("bwm").drive();

還有一種用方式的去實現這個公用的工廠:

public class CarFactory2 {
    public static <T extends Car> T createCar(Class<T> clz) {
        Car car = null;
        try {
            car = (Car) Class.forName(clz.getName()).newInstance();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        return (T) car;
    }
}

實際使用時:

CarFactory2 factory3 = new CarFactory2();
factory3.createCar(BenziCar.class).drive();
factory3.createCar(BWMCar.class).drive();

簡單工廠模式的優缺點

  • 優點:代碼解耦,創建實例的工作與使用實例的工作分開,使用者不必關心類對象如何創建。
  • 缺點:從第一種實現方式就可以看到,會增加很多的if判斷,當要產生的對象變多的時候不便于維護;違背開放封閉原則,若需添加新產品則必須修改工廠類邏輯,會造成工廠邏輯過于復雜;簡單工廠模式使用了靜態工廠方法,因此靜態方法不能被繼承和重寫;工廠類包含了所有實例(產品)的創建邏輯,若工廠類出錯,則會造成整個系統都會會受到影響。(本段文字來自博客:Android的設計模式-工廠方法模式
2.3、抽象工廠模式

現在造一輛車的一些零件肯定是由各種不同的廠商生產的。寶馬汽車和奔馳汽車的引擎和輪子也不是同一個廠商生產的,那么如果將所有的這些零件工廠也抽象出來,代碼表示如下:
零件的接口:

public interface Engine {
    void getEngine();
}

public interface Wheel {
    void getWheel();
}

零件的實現類:

public class AKMWheel implements Wheel{
    @Override
    public void getWheel() {
        System.out.println("AKM輪子");
    }
}

public class M416Wheel implements Wheel{
    @Override
    public void getWheel() {
        System.out.println("M426輪子");
    }
}

public class M24Engine implements Engine {
    @Override
    public void getEngine() {
        System.out.println("M24引擎");
    }
}
public class AWMEngine implements Engine {
    @Override
    public void getEngine() {
        System.out.println("AWM引擎");
    }
}

創建汽車的接口:

public interface Factory3 {
    Wheel createWheel();
    Engine createEngine();
}

寶馬汽車和奔馳汽車的具體實現:

public class BenziCarFactory implements Factory3 {
    @Override
    public Wheel createWheel() {
        return new AKMWheel();
    }
    @Override
    public Engine createEngine() {
        return new M24Engine();
    }
}
public class BWMCarsFactory implements Factory3{
    @Override
    public Wheel createWheel() {
        return new M416Wheel();
    }

    @Override
    public Engine createEngine() {
        return new AWMEngine();
    }
}

具體使用:

    System.out.println("創建寶馬車");
    Factory3 bwFactory = new BWMCarsFactory();
    bwFactory.createEngine().getEngine();
    bwFactory.createWheel().getWheel();
    System.out.println("創建奔馳車");
    Factory3 bzFactory = new BenziCarFactory();
    bzFactory.createEngine().getEngine();
    bzFactory.createWheel().getWheel();
創建寶馬車
AWM引擎
M426輪子
創建奔馳車
M24引擎
AKM輪子

抽象工廠模式的優缺點

  • 優點:代碼解耦,創建實例的工作與使用實例的工作分開,使用者不必關心類對象如何創建。
  • 缺點: 如果增加新的產品,則修改抽象工廠和所有的具體工廠,違反了開放封閉原則(本段文字來自博客:Android的設計模式-工廠方法模式

3、策略模式

繼續用英雄聯盟來舉例。

在這個游戲中獲取賞金的方式有很多種,比如:擊殺野怪、擊殺英雄、擊殺小兵等等。如果讓我寫一個計算賞金的方法,我會怎么寫呢。

package com.axe.strategy;

/**
 * 賞金類
 * 
 * @author 11373
 *
 */
public class Bounty {

    /**
     * 小兵
     */
    public static final int TYPE_BATMAM = 0;

    /**
     * 英雄
     */
    public static final int TYPE_HERO = 1;

    /**
     * 野怪
     */
    public static final int TYPE_MONSTER = 2;

    /**
     * 大龍
     */
    public static final int TYPE_DRAGON = 3;

    /**
     * 獲取賞金的方法
     * 
     * @param type  類型
     * @param count 數量
     * @return
     */
    public int killGetMoney(int type, int count) {
        int sum = 0;
        if (type == TYPE_BATMAM) {
            sum += getBatmanMoney(count);
        } else if (type == TYPE_HERO) {
            sum += getHeroMoney(count);
        } else if (type == TYPE_MONSTER) {
            sum += getMonsterMoney(count);
        } else if (type == TYPE_DRAGON) {
            sum += getDragonMoney(count);
        }
        return sum;
    }

    /**
     * 獲取小兵的錢
     * 
     * @param count
     * @return
     */
    private int getBatmanMoney(int count) {
        return count * 30;
    }

    /**
     * 獲取英雄的錢
     * 
     * @param count
     * @return
     */
    private int getHeroMoney(int count) {
        return count * 300;
    }

    /**
     * 獲取野怪的錢
     * 
     * @param count
     * @return
     */
    private int getMonsterMoney(int count) {
        return count * 50;
    }
    
    /**
     * 獲取大龍的錢
     * @param count
     * @return
     */
    private int getDragonMoney(int count) {
        return count * 500;
    }

}

在實際使用的時候:

Bounty bounty = new Bounty();
int getMoney = bounty.killGetMoney(Bounty.TYPE_BATMAM, 10) + bounty.killGetMoney(Bounty.TYPE_HERO, 1)
            + bounty.killGetMoney(Bounty.TYPE_MONSTER, 6);

看起來問題不大,也比較簡潔。但是如果這個是一個真實的英雄聯盟中的例子,那么情況會有這么簡單嗎?比如:英雄聯盟中的擊殺野怪,野怪有很多種,獲得的賞金也不一樣;擊殺英雄的時候,不一定就是300個金幣。還有某些輔助裝備會導致金幣不一樣。而且這里覆蓋的擊殺種類肯定不止這么多。比如推掉防御塔也有金幣,推掉水晶也有金幣等等。如果按照所有的情況去計算的話,Bounty 這個類將會非常龐大,里面的邏輯判斷也會非常多。

    /**
     * 獲取賞金的方法
     * 
     * @param type  類型
     * @param count 數量
     * @return
     */
    public int killGetMoney(int type, int count) {
        int sum = 0;
        if (type == TYPE_BATMAM) {
            sum += getBatmanMoney(count);
        } else if (type == TYPE_HERO) {
            sum += getHeroMoney(count);
        } else if (type == TYPE_MONSTER) {
            sum += getMonsterMoney(count);
        } else if (type == TYPE_DRAGON) {
            sum += getDragonMoney(count);
        }
        return sum;
    }

這個方法的if嵌套層數會非常多。if條件太多維護起來絕對是痛苦,并且會帶來更多的錯誤!如果使用策略模式去優化的話,如何去寫呢?

  • 定義要給獲取賞金的規則
/**
 * 獲取賞金的規則
 * @author 11373
 *
 */
public interface GetMoney {
    public int getMoney(int count);
}

  • 將每種類型的獲取賞金的方法繼承該接口
/**
 * 小兵賞金計算器
 * @author 11373
 *
 */
public class BatmanCalculater implements GetMoney {

    @Override
    public int getMoney(int count) {
        return count * 30;
    }

}
  • 定義一個類去控制賞金獲取的方式
/**
 * 賞金計算器
 * 
 * @author 11373
 *
 */
public class BountyCalculater {
    private GetMoney getMoney;

    public int getBountyMoney(int count) {
        return getMoney.getMoney(count);
    }

    public void setGetMoney(GetMoney getMoney) {
        this.getMoney = getMoney;
    }
}

實際的使用情況。遇到不同的獲取賞金的情況時,只需要替換不同的計算規則即可。

// 使用策略模式
        int sum = 0;
        BountyCalculater calculater = new BountyCalculater();

        calculater.setGetMoney(new BatmanCalculater());
        sum += calculater.getBountyMoney(10);

        calculater.setGetMoney(new HeroMoneyCalculater());
        sum += calculater.getBountyMoney(5);

        calculater.setGetMoney(new DragonCalculater());
        sum += calculater.getBountyMoney(1);

        calculater.setGetMoney(new MonsterCalculater());
        sum += calculater.getBountyMoney(34);
        System.out.println(sum);

現在看來應該是增加了不少的類,這個就是策略模式的一個劣勢。
但是如果從類的單一性原則和產品可維護階段來說,就會感覺這個策略模式的妙用之處。

  • 類的單一性原則
    每個種類的計算規則都單獨封裝成一套計算方法,修改了某一套計算方法不會對其他的計算規則產生影響。
  • 可維護和拓展
    假如要新增一個計算規則,只需要繼承GetMoney即可,不會對其他的計算規則產生任何的影響。
3.1、策略模式的優缺點

優點:結構清晰明了,使用簡單直觀;耦合度比較低,便于拓展;
缺點:隨著策略的增多,策略的子類也會增多。

4、單例模式

這種是比較常見的模式了,如果是封裝什么網絡請求框架、圖片請求框架,在整個app只需要一個全局對象的時候都會用到這個模式。很多博客應該已經把這個東西介紹得很清楚,單例模式的意義就是——讓整個程序中只有唯一的一個對象。

4.1、餓漢模式
/**
 * Created by Axe on 2017/8/29.
 * <p>
 * 單例模式 - 餓漢模式
 *  最簡單的單例模式,具有單利模式的所有特征
 *  缺點:
 *  1、當類加載時就會初始化成員變量,可能會浪費資源。
 *  2、在多線程情況下,不安全,無法保證對象唯一。
 */

public class HungryModeSingleton {

    private static final HungryModeSingleton singleton = new HungryModeSingleton();

    private HungryModeSingleton() {
    }

    public static HungryModeSingleton getInstance() {
        return singleton;
    }
}
4.2、懶漢模式
/**
 * Created by Axe on 2017/8/29. 
 * 1、懶漢模式只有調用的時候才初始化,節省了開支。
 * 2、懶漢模式保證了線程安全
 * 缺點:
 * 每次初始化會進行同步,會消耗不必要的資源
 */

public class LazyModeSingleton {

    private static LazyModeSingleton singleton;

    private LazyModeSingleton() {
    }

    public static synchronized LazyModeSingleton getInstance() {
        if (singleton == null) {
            singleton = new LazyModeSingleton();
        }
        return singleton;
    }

}

4.3、雙層檢測通道模式(線程安全)
/**
 * Created by Axe on 2017/8/29.
 * <p>
 * Double Check Lock (雙層檢查同步模式)
 */

public class DCLSingleton {
    private static DCLSingleton singleton = null;

    private DCLSingleton() {
    }

    public static DCLSingleton getInstance() {
        if (singleton == null) {
            synchronized (DCLSingleton.class) {
                if (singleton == null) {
                    singleton = new DCLSingleton();
                }
            }
        }
        return singleton;
    }
}
4.4、內部類模式
/**
 * Created by Axe on 2017/8/29.
 * <p>
 * 靜態內部類單例模式
 */

public class StaticInnerClassSingleton {

    private StaticInnerClassSingleton() {
    }

    public static StaticInnerClassSingleton getInstance() {
        return SingletonHolder.singleton;
    }

    private static class SingletonHolder {
        private static final StaticInnerClassSingleton singleton = new StaticInnerClassSingleton();
    }
}

##### 4.5單例模式的優缺點
優點:整個內存中只有一個對象,減少了內存消耗,減少了系統性能的開銷。
缺點:持有Context時,可能導致內存泄漏;擴展比較困難,只能修改源代碼來修改拓展。

5、狀態模式

一看到狀態模式,就能想到,這個肯定是和”狀態“有關。
繼續用打英雄聯盟舉例(今天和它杠上了):

現在生活中有一個這樣的情況,如果要打英雄聯盟,那電腦就必須開機,此時的電腦的狀態為NO,如果電腦沒開機就無法玩英雄聯盟,此時的電腦狀態為OFF。如果現在去寫一個玩英雄聯盟但是依賴電腦狀態的類,如果電腦開機就可以玩游戲,電腦沒有開機就不玩游戲,并提示開機才能玩游戲,那該如何寫呢?
在沒有學習狀態模式的時候,我是這么寫的:

public class PlayGameController implements IComputerActivity {

    /**
     * 表示關機狀態
     */
    private static final int OFF = 0;

    /**
     * 表示開機狀態
     */
    private static final int NO = 1;

    private int state = 0;

    public void setSate(int state) {
        this.state = state;
    }

    @Override
    public void playGame() {
        if (state == OFF) {
            System.out.println("請先開機再打游戲");
        } else {
            System.out.println("正在打游戲");
        }

    }

    @Override
    public void watchMovie() {
        if (state == OFF) {
            System.out.println("請先開機再看電影");
        } else {
            System.out.println("正在看電影");
        }
    }
}

這里定義了電腦的一些行為接口IComputerActivity ,這些電腦暫時有兩個方法:playGame和watchMovie。
打游戲和看電影都必須在開機之后才能執行的操作,所以這里在這兩個方法中都加了判斷電腦是不是開機的狀態:

        if (state == OFF) {
            System.out.println("請先開機再打游戲");
        } else {
            System.out.println("正在打游戲");
        }

在狀態比較少和電腦的行為比較少時問題不大。但是假如電腦的狀態變多,這個if條件將會變得很繁瑣,比如if判斷會變成這樣:

        if (state == OFF) {
            System.out.println("請先開機再打游戲");
        } else  if(state == xxx){
            System.out.println("正在打游戲");
        }else if(state == rrr){
         }
        ... ... 此處省略若干if條件

假如電腦的行為不僅僅是玩游戲和看電影,還有數個行為的話,那這些if判斷每個行為中都要寫一遍。重復的if判斷維護起來也非常麻煩,也更加容易出錯。那有沒有辦法讓這些行為能單獨處理,一個類只處理一個狀態?

那么用狀態模式來重構這些代碼。
1、電腦開機狀態的處理:

public class PowerNoState implements IComputerActivity{

    @Override
    public void playGame() {
        System.out.println("正在打英雄聯盟");
    }

    @Override
    public void watchMovie() {
        System.out.println("正在看火影忍者");
    }
}

2、電腦關機狀態的行為處理:

/**
 * 狀態模式 :當電腦電源關閉之后的操作
 * @author 11373
 *
 */
public class PowerOffState implements IComputerActivity{

    @Override
    public void playGame() {
        System.out.println("請開機玩游戲");
    }

    @Override
    public void watchMovie() {
        System.out.println("請開機看電影");
    }

}

3、然后定義好電腦電源

public interface PowerController {
    public void powerOn();
    public void powerOff();
}

當調用powerOn時我們就初始化PowerNoState,當調用powerOff就初始化PowerOffState這樣

5.1、狀態模式的優缺點

優點:將每一個狀態單獨封裝成子類,便于維護和拓展;能減少過多的條件語句,使結構更加清晰,提高代碼的維護性。

缺點:當狀態很多時,必然會增加狀態子類的個數。

6、觀察者模式

如果用過RxJava就會接觸到觀察者模式了。定義對象的一種一多的依賴關系,則所有的依賴于它的對象都會得到通知并且自動更新。
舉一個游戲中的簡單的例子,比如在英雄聯盟中,易大師穿著復活甲被殺死了,這個時候蓋倫和艾希都在等待易大師復活,再對他進行攻擊。
那么,這里的觀察者就是蓋倫和艾希。他們有一個共同的行為就是打擊的操作。

/**
 * 觀察者
 * @author 11373
 *
 */
public interface Observer {
    /**
     * 每一個觀察者都有一個攻擊的方法
     * @param name
     */
    public void hit(String name);
}

那易大師能被其他人觀察到它的狀態,并當他發生狀態改變的時候進行改變。

/**
 * 被觀察類
 * 
 * @author 11373
 *
 */
public interface Obserable {

        //提供的能被觀察者觀察到的方法
    public void addObserver(Observer observer);
    public void removeObserver(Observer observer);

      // 英雄復活的方法
    public void resurgence(String name);
}

蓋倫的實現:

/**
 * 觀察者實現類
 * @author 11373
 *
 */
public class GaLenObserverImpl implements Observer{

    @Override
    public void hit(String name) {
        System.out.println("我是蓋倫,"+name+"復活了,快打他");
    }

}

易大師的實現:

/**
 * 易大師,被觀察者
 * 
 * @author 11373
 *
 */
public class YiObserableImpl implements Obserable {

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

    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        if (observers.contains(observer)) {
            observers.remove(observer);
        }
    }

    @Override
    public void resurgence(String name) {
        for (Observer observer : observers) {
            observer.hit(name);
        }
    }
}

最后的執行效果:

    public static void main(String[] args) {
        Observer aich = new AichObserverlmpl();
        Observer galen = new GaLenObserverImpl();
        Obserable yi = new YiObserableImpl();
        yi.addObserver(aich);
        yi.addObserver(galen);
// 易大師復活的行為
        yi.resurgence("易大師");
    }
我是艾希易大師復活了,快打他
我是蓋倫易大師復活了,快打他

6.1、觀察者模式的優缺點

優點:觀察者對象和被觀察者對象解耦,雙方依賴都依賴抽象,而不是依賴具體對象。

缺點:依賴關系并未完全解除,抽象主題任然依賴抽象觀察者;使用觀察者模式時需要考慮一下開發效率和運行效率的問題,程序中包括一個被觀察者、多個觀察者,開發、調試等內容會比較復雜,而且在Java中消息的通知一般是順序執行,那么一個觀察者卡頓,會影響整體的執行效率,在這種情況下,一般會采用異步實現。

7、代理模式

在玩游戲的時候,通常有這種情況發生。有些人玩游戲技術并不咋地,但是段位卻老高。以上就是常見的代理模式啦!

把以上的情況變成代碼該如何表示呢?
定義玩家接口:

 * 游戲玩家接口
 * 
 * @author 11373
 *
 */
public interface IPlay {

    /**
     * 登錄游戲
     */
    void loginGame();

    /**
     * 打游戲
     */
    void play();

    /**
     * 贏得比賽
     */
    void winGame();

}

定義普通玩家接口:

/**
 * 游戲玩家類,實際要贏得游戲的玩家
 * @author 11373
 *
 */
public class GamePlayer implements IPlay{

    @Override
    public void loginGame() {
        System.out.println("游戲玩家登錄游戲");
    }

    @Override
    public void play() {
        System.out.println("游戲玩家開始打游戲");
    }

    @Override
    public void winGame() {
        System.out.println("游戲玩家贏得了比賽");
    }

}

定義代打接口:

/**
 * 靠代打游戲生存的游戲代打
 * 
 * @author 11373
 *
 */
public class PlayerProxy implements IPlay {

    private IPlay player;

    public void setIPlyer(IPlay player) {
        this.player = player;
    }

    @Override
    public void loginGame() {
        player.loginGame();
    }

    @Override
    public void play() {
        player.play();
    }

    @Override
    public void winGame() {
        player.winGame();
    }

}

實際操作:

    public static void main(String[] args) {
        
        // 實際要打游戲的游戲玩家
        GamePlayer axeChen = new GamePlayer();
        
        // 游戲代打
        PlayerProxy gameProxy = new PlayerProxy();
        
        // 游戲代打知道要為誰代打游戲
        gameProxy.setIPlyer(axeChen);
        
        // 代打開始登錄游戲
        gameProxy.loginGame();
        // 代打開始打游戲
        gameProxy.play();
        // 代打贏得了比賽
        gameProxy.winGame();
    }
7.1、代理模式的優缺點

推薦看下這篇博客的總結,http://www.lxweimin.com/p/a0e687e0904f

8、適配器模式

適配器模式在安卓開發經常可以見到,RecyclerView的setAdapter就是典型的適配器模式,將數據源傳入,然后再適配不同的UI布局。
一個簡單的例子去說明適配器模式:
比如生活中的手機電源,我們的電源通常是220v,但是手機上能接受的電源只有5V。這個是我們通常有一個手機的充電適配器去將220v的電壓轉為5v。接下來把這種情況變成代碼。

定義適配器接口:

public interface Adapter {
    public int getVolt5();
}

電源實體類(實際輸出電源):

public class Power {
    public int get220v() {
        return 220;
    }
}

電源適配器類,關鍵的適配操作:

public class PhoneAdapter implements Adapter {

    private Power power;

    public PhoneAdapter(Power power) {
        this.power = power;
    }

    @Override
    public int getVolt5() {
        return 5;
    }
}

代碼測試:

      Power power = new Power();
        System.out.println("電源電壓:"+power.get220v());
        PhoneAdapter adapter = new PhoneAdapter(power);
        System.out.println("通過是配置適配之后的電壓:"+adapter.getVolt5());

輸出結果:

電源電壓:220
通過是配置適配之后的電壓:5

以上的代碼就是將輸入的電壓220v通過適配器轉化成5v。RecyclerView的Adapter就是將數據源傳入適配器(adapter)中,然后去適配不同的布局。
當然這邊還有一種類適配器模式這邊這邊簡單提下:

public class PhoneAdapter2 extends Power implements Adapter {

    @Override
    public int getVolt5() {
        System.out.println("電源電壓:"+get220v());
        System.out.println("經過適配器適配后的電壓:"+5);
        return 5;
    }
}

這邊是用適配器繼承數據源,同時實現適配器的接口。它的優勢是無需持有數據源對象,只需繼承數據源對象。

8.1、適配器模式的優缺點

優點:提高了類的復用性,適配器能讓一個類有更廣泛的用途;提高了靈活性,更換適配器就能達到不同的效果。不用時也可以隨時刪掉適配器,對原系統沒影響。

缺點:過多的使用適配器,會讓系統非常零亂,不易整體進行把握。明明調用A接口,卻被適配成B接口。

9、責任鏈模式

以生活中的一個例子來解釋責任鏈模式。比如公司的一個員工購買了一個辦公用品,一共花費5000元,這個時候該員工去找部門經理審批報銷,部門經理一看5000元已經大于他能審批的金額,于是就交給總監去審批。總監一看5000元也大于他能報銷的金額,于是就交給老板去審批,老板能報銷員工10w以內的金額,于是老板審批通過,同意了報銷。

以上就是一個簡單的責任鏈模式的例子,他的定義為:****

9.1、用代碼來解釋報銷的案例

所有領導的相同點抽象,他們都能報銷,有報銷金額的范圍等等。

public abstract class Leader {

    /**
     * 下一個執行者
     */
    public Leader nextHanlder;

    /**
     * 自身能夠處理的最少金額
     * 
     * @return
     */
    public abstract int limit();

    /**
     * 報銷金額的方法
     * 
     * @param money
     */
    public abstract void handle(int money);

    /**
     * 控制責任鏈的條件
     * 
     * @param money
     */
    public final void handleRequest(int money) {
        if (money <= limit()) {
            handle(money);
        } else {
            nextHanlder.handleRequest(money);
        }
    }
}

然后部門經理,總監,CEO都實現了這些方法。

/**
 * 經理級別最多報銷1000
 * 
 * @author 11373
 *
 */
public class Manager extends Leader {

    @Override
    public int limit() {
        return 1000;
    }
    @Override
    public void handle(int money) {
        System.out.println("經理正在處理報銷金額:"+money);
    }
}

public class CTO extends Leader{

    @Override
    public int limit() {
        return 5000;
    }

    @Override
    public void handle(int money) {
        System.out.println("CTO正在處理報銷金額:"+money);
    }

}


public class CEO extends Leader{

    @Override
    public int limit() {
        return 100000;
    }

    @Override
    public void handle(int money) {
        System.out.println("CEO正在處理報銷金額:"+money);
    }

}

最后測試:

    public static void main(String[] args) {
        CEO ceo = new CEO();
        CTO cto = new CTO();
        Manager manager = new Manager();
        manager.nextHanlder = cto;
        cto.nextHanlder = ceo;
        manager.handleRequest(5000);
    }

這里的執行結果是,經理無法報銷,CTO能報銷
于是CTO就報銷了這筆金額。

CTO正在處理報銷金額:5000
9.2、責任鏈模式的優缺點

優點:請求者和處理者關系解耦,處理者比較好的擴展。
缺點:處理者太多會影響性能,特別是循環遞歸的時候。

10、原型模式

原型模式的核心為clone方法,涉及java中的深拷貝和淺拷貝的知識。
這邊關于深拷貝和淺拷貝的東西,涉及的東西比較多,這里就直接引用別人的博客吧.
http://www.lxweimin.com/p/6d1333917ae5

參考書籍:《Android源碼設計模式,解析與實戰》
參考博客:http://www.lxweimin.com/p/bf92927c9d22
感謝博主四月葡萄的博客,他寫的博客總結得比較好,建議去看看!我很多地方也是引用他寫的鏈接。
代碼地址:https://github.com/AxeChen/DesignMode

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。