引言
在我們?nèi)粘i_發(fā)中,我們或多或少會(huì)有這樣的抱怨:
- 我擦, 為什么要換框架? 換這么多地方, 肯定會(huì)有大量的問題吧
- 唉,領(lǐng)導(dǎo)怎么想的,為什么又要改需求,萬一其他地方出一堆問題怎么辦?
- 哎呀我去,我改的不是這兒啊,為啥這個(gè)地方還報(bào)錯(cuò)了呢?
如果你有上面的抱怨,說明你該學(xué)習(xí)設(shè)計(jì)模式了少年。
上面很多問題,其實(shí)總結(jié)原因如下:
- 代碼冗余,混亂,理不清頭緒
- 耦合重, 出處相關(guān)
- 擴(kuò)展性差,擴(kuò)展起來很麻煩
- 可重用性低,到處存在相似的代碼
設(shè)計(jì)模式就是為了解決這些問題。如果你也遇到了上述問題其一或者若干,你還覺得設(shè)計(jì)模式和日常開發(fā)無關(guān)嗎?
今天我們講的設(shè)計(jì)模式,是策略模式。掌握了策略模式,想要切換框架? So Easy !!!
那么通過引入場景,我們一步步的來看,策略模式到底好在哪?為什么要使用設(shè)計(jì)模式?
開始啦!
近幾年聽說流行一種很好用的框架, 簡單易用,高效高能,聽說是叫Glide。 這是一種圖片加載框架,好處多多; 我相信這種聽說在多年前就有相似的場景,那就是UniverseImageLoader。
如果我們還停留在初級(jí)階段,工作經(jīng)驗(yàn)不足,那么我們是這么寫的
// 在AdapterA中,使用Glide加載圖片
Glide.with(this).load(url).into(iv);
// 在ActivityB中,使用Glide加載圖片
Glide.with(this).load(url).into(iv);
// 在Fragment中,使用Glide加載圖片
Glide.with(this).load(url).into(iv);
// Glide好用極了, 加載圖片真快,而且內(nèi)存管理好?。?!
有一天,老大來說,測試說了,圖片地址不能用的時(shí)候我們不能顯示空白啊,這樣太丑了,加個(gè)默認(rèn)圖片吧。我們想:這還不容易, Ctrl + H 搜一下Glide, 每個(gè)地方改一下唄! 然而改著改著,我們忽然發(fā)現(xiàn),總共有1000處使用了Glide, 我們真是欲哭無淚。這時(shí)有一定開發(fā)經(jīng)驗(yàn)的小王來了,用鄙視的眼光看著我們?!澳氵@個(gè)傻鳥”,你寫個(gè)GlideUtils, 直接調(diào)用GlideUtils不就行了。無論老大讓怎么改,我只改GlideUtils就行了。
我們?nèi)绔@至寶,重新改造了代碼
public class GlideUtils {
public static void display(Activity context, String url, ImageView target) {
Glide.with(context).load(url).into(target);
}
}
我們偷偷開心, 嘿嘿,這下再改的話,就不用加班了吧!后來我們需要添加一些個(gè)性化設(shè)置,我們在方法中需要添加一些變量處理,但是我們不想把所有的變量都設(shè)為靜態(tài)的,于是就有了
public class GlideUtils {
private static GlideUtils utils;
private GlideUtils() {
// 初始化
}
public static getUtils() {
if(utils == null) {
utils = new GlideUtils();
}
return utils;
}
public void display(Activity context, String url, ImageView target) {
Glide.with(context).load(url).into(target);
}
}
這樣我們在依賴外部資源的前提下,性能效率都提高了,還能做到按需初始化。我們覺得自己好棒, 成長真的很快。普及一下, 全靜態(tài)方法實(shí)現(xiàn)的工具類, 和單例模式的工具類,區(qū)別在哪?
1.靜態(tài)方法工具類是包含以下特征的靜態(tài)方法的集合,它的靜態(tài)方法是獨(dú)立的,無任何外部依賴的。
1.單例模式工具類是系統(tǒng)的, 依賴外部資源或初始化的。
我們需要初始化Glide配置,也需要依賴一定的外部資源,因此改為單例模式。 但是問題又來了,好多小伙伴都在改這個(gè)類,加入了好多不同的甚至重復(fù)的方法, 我們甚是苦惱, 最終我們決定通過接口來約束功能的實(shí)現(xiàn)。
public class GlideUtils implements IGlider{
private static GlideUtils utils;
private GlideUtils() {
// 初始化
}
public static getUtils() {
if(utils == null) {
utils = new GlideUtils();
}
return utils;
}
@Override
public void display(Activity context, String url, ImageView target) {
Glide.with(context).load(url).into(target);
}
}
public interface IGlider {
void display(Activity context, String url, ImageView target);
}
我們覺得很完美, 就像欣賞藝術(shù)品一樣看著這段代碼,內(nèi)心暗喜。這時(shí)領(lǐng)導(dǎo)來了,說小明啊, 這個(gè)最近出了新的圖片加載框架,那是相當(dāng)牛逼啊,要不你改下吧。 這時(shí)你有點(diǎn)小得意了,還好設(shè)計(jì)的好,改一個(gè)類就好了。打開編譯器,三下五除二改好了,你覺得So Easy, 老子真乃神人也。但是一運(yùn)行,不對啊,怎么好多地方都有問題。原來的代碼都刪除了,現(xiàn)在的也不行,如果是緊急狀態(tài),怎么辦?
接下來就是正題了,策略模式。
策略模式是什么?
策略模式是指有一定行動(dòng)內(nèi)容的相對穩(wěn)定的策略名稱。
優(yōu)點(diǎn):
1、 策略模式提供了管理相關(guān)的算法族的辦法。策略類的等級(jí)結(jié)構(gòu)定義了一個(gè)算法或行為族。恰當(dāng)使用繼承可以把公共的代碼轉(zhuǎn)移到父類里面,從而避免重復(fù)的代碼。
2、 策略模式提供了可以替換繼承關(guān)系的辦法。繼承可以處理多種算法或行為。如果不是用策略模式,那么使用算法或行為的環(huán)境類就可能會(huì)有一些子類,每一個(gè)子類提供一個(gè)不同的算法或行為。但是,這樣一來算法或行為的使用者就和算法或行為本身混在一起。決定使用哪一種算法或采取哪一種行為的邏輯就和算法或行為的邏輯混合在一起,從而不可能再獨(dú)立演化。繼承使得動(dòng)態(tài)改變算法或行為變得不可能。
3、 使用策略模式可以避免使用多重條件轉(zhuǎn)移語句。多重轉(zhuǎn)移語句不易維護(hù),它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統(tǒng)統(tǒng)列在一個(gè)多重轉(zhuǎn)移語句里面,比使用繼承的辦法還要原始和落后。
缺點(diǎn):
1、客戶端必須知道所有的策略類,并自行決定使用哪一個(gè)策略類。這就意味著客戶端必須理解這些算法的區(qū)別,以便適時(shí)選擇恰當(dāng)?shù)乃惴?。換言之,策略模式只適用于客戶端知道所有的算法或行為的情況。
2、 策略模式造成很多的策略類,每個(gè)具體策略類都會(huì)產(chǎn)生一個(gè)新類。有時(shí)候可以通過把依賴于環(huán)境的狀態(tài)保存到客戶端里面,而將策略類設(shè)計(jì)成可共享的,這樣策略類實(shí)例可以被不同客戶端使用。換言之,可以使用享元模式來減少對象的數(shù)量。
以上是百度百科的定義。 那么什么是策略模式? 相信大家都看過歷史,有一個(gè)場景大家也都熟悉。謀臣XX向主公獻(xiàn)計(jì),往往都會(huì)提供多個(gè)計(jì)謀, 讓主公根據(jù)自己的決定選擇一個(gè),并言明厲害,策略A乃上上之策, B乃上策, C乃下策。這就是策略。什么是策略模式呢? 我們從三要素來分析:
策略的目的是什么? 聯(lián)合孫權(quán),抵抗曹操。在程序中體現(xiàn)為接口。策略再多,但是實(shí)現(xiàn)的目的是一樣的
繼承目的的不同的策略,接口規(guī)范了策略的目的, 策略豐富了目的的達(dá)成方式。如投降曹操, 火燒赤壁, 蠻力抵抗
呈現(xiàn)策略并使用, 主公想要什么計(jì)策, 那么謀士要獻(xiàn)上什么策略,主公來使用
我們已找對象為例, 進(jìn)行說明。
- 我們的目的是什么? 找到一個(gè)合適的對象。定義一個(gè)接口,用來限定策略的執(zhí)行目標(biāo)。
/**
* 不同策略所需要達(dá)到的最終目標(biāo)
* @author qichunjie 2018/5/28
*/
public interface IStrategy {
/**
* 找媳婦
*/
public void findAWife();
}
- 目標(biāo)已確定,那么我們有什么策略呢? 1) 自由戀愛 2) 相親 3) 說媒 。 那么我們有了三個(gè)IStrategy的實(shí)現(xiàn)類
/**
* @author qichunjie 2018/5/28
*/
public class LianAiStrategy implements IStrategy {
@Override
public void findAWife() {
System.out.println("通過自由戀愛的方式,找到了媳婦兒");
}
}
/**
* @author qichunjie 2018/5/28
*/
public class ShuoMeiStrategy implements IStrategy {
@Override
public void findAWife() {
System.out.println("通過說媒的方式,找到了媳婦兒");
}
}
/**
* 相親策略
*
* @author qichunjie 2018/5/28
*/
public class XiangQinStrategy implements IStrategy {
@Override
public void findAWife() {
System.out.println("通過相親的方式,找到一個(gè)媳婦兒");
}
}
有人說你可以自由戀愛?。ㄎ液螄L不想),有人說相親才是靠譜的,有人又說了當(dāng)然是說媒啊,成功率高啊。但是作為當(dāng)事人,我要怎么選呢?當(dāng)然是采納一個(gè),然后去執(zhí)行啦
/**
* @author qichunjie 2018/5/28
*/
public class Strategy implements IStrategy {
private IStrategy iStrategy;
public Strategy(IStrategy iStrategy) {
this.iStrategy = iStrategy;
}
@Override
public void findAWife() {
if (iStrategy != null) {
iStrategy.findAWife();
}
}
}
我們采納一個(gè)策略(傳入一個(gè)IStrategy), 然后執(zhí)行(使用傳入的策略),剩下的就看結(jié)果了。當(dāng)然,在開發(fā)中,這些策略必須是正確執(zhí)行的。 最后,我們要采用XX提供的策略去找對象啦,結(jié)果如何,請看下回分解!
/**
* @author qichunjie 2018/5/28
*/
public class Main {
/**
* 策略模式的測試入口
*/
public static void main(String[] args) {
Strategy strategy = new Strategy(new XiangQinStrategy());
strategy.findAWife();
}
}
這么分解開來,其實(shí)很簡單。有人就說了,你這個(gè)這么麻煩,為什么使用它?Ok,我們來分析一下它的好處以及解決的問題。
- 使用接口來定義一個(gè)統(tǒng)一規(guī)范,實(shí)現(xiàn)指定的目的。(代碼再也不會(huì)那么混亂了)
2.不同的策略類實(shí)現(xiàn)單一的策略,單一職責(zé)原則。好處是只能單一,可復(fù)用性好(越單一的代碼,復(fù)用率越高)
3.如果添加新策略,我們直接添加一個(gè)擴(kuò)展類繼承IStrategy, 然后傳入即可。從來不需要?jiǎng)悠渌呗缘娜魏螙|西,擴(kuò)展性好
- 刪除一個(gè)策略,修改一個(gè)策略,不會(huì)影響其他策略。如上GlideUtils你很可能會(huì)動(dòng)到其他代碼卻不知道。符合開閉原則。
這里提到了單一職責(zé)原則以及開閉原則,簡單的說一下。 當(dāng)你的代碼糅合了多種混合而成,那么它幾乎是不可能復(fù)用的。如果把它拆分為單獨(dú)的功能塊,那么它的復(fù)用率將大大提高; 而且拆分越細(xì), 復(fù)用率越高。舉個(gè)例子,設(shè)置View寬高為屏幕比例。我們要做的是
- 先獲取DisplayMetrics
- 獲取屏幕寬高
- 設(shè)置View的尺寸
如果寫在一個(gè)方法中,我們可能只能設(shè)置View的尺寸,并且局限于View的布局,也就是LinearLayout.LayoutParams或者RelativeLayout.LayoutParams。 如果拆分為獲取屏幕的尺寸和設(shè)置View尺寸兩個(gè)方法。 那么屏幕尺寸的獲取,也是可以復(fù)用的。如果按上面1,2,3拆分為3個(gè)方法, 那么獲取屏幕密度我們也可以使用1方法。所以,拆分的粒度越小,可復(fù)用性越好。
單一職責(zé)的好處二是,拆分后的功能是通過方法組合的方式來實(shí)現(xiàn),如果足夠規(guī)范,我們一眼即可以看出這個(gè)方法實(shí)現(xiàn)的是什么功能。拆分之前, 我們必須閱讀完整個(gè)代碼后,才會(huì)知道它是做什么的。
再說開閉原則, 對擴(kuò)展開發(fā),對修改關(guān)閉。為什么呢? 因?yàn)榇a的修改是具有一定危險(xiǎn)性的,如果不同的策略在一個(gè)類中,在修改一項(xiàng)策略時(shí),很可能會(huì)波及其他策略,而我們自己還不知道。因?yàn)閷π薷谋3肿钚〉挠騺泶_保我們代碼的維護(hù)性, 在對修改關(guān)閉時(shí), 同時(shí)還要滿足我們的修改需求,因此改為通過擴(kuò)展的形式來實(shí)現(xiàn)代碼的修改。保持修改代碼的域最小化。這樣的代碼維護(hù)性,安全性會(huì)是最高的。
如上示例的策略,我們要修改相親策略,那么只修改XiangQinStragy即可,毫不影響其他策略。我們要新增策略,擴(kuò)展一個(gè)策略類,也不需要修改Strategy類。這使得安全性提高了很多。
本文中,我們可以得到一些關(guān)于架構(gòu)設(shè)計(jì)的點(diǎn):
為什么使用接口以及如何使用接口?
靜態(tài)工具類和單例工具類的區(qū)別是什么?你會(huì)用了嗎?
單一職責(zé)提高復(fù)用率, 減少代碼修改域
開閉原則使你盡可能小的減少你的失誤,從而影響其他代碼
如果你還不理解上面的4個(gè)問題,建議帶著問題回去閱讀一遍本篇文章。希望本篇文章可以使你提供開發(fā)效率, 減少你的加班時(shí)間。
根據(jù)策略模式,我們來說明一下如何一行代碼更新你的框架。
以圖片加載框架為例, 框架的更新很快,而且功能也越來越強(qiáng)大,性能越來越好。 因此框架的切換是必要的,那么如何做到切換框架時(shí),我們付出的代價(jià)最小呢? 策略模式給了你答案。
- 無論切換什么框架,我們要使用的功能是確定的。首先,我們來定義一個(gè)規(guī)范接口。比如我們需要加載圖片,從緩存加載圖片, 加載動(dòng)圖
/**
* @author qichunjie 2018/6/4
*/
public interface ImageLoader {
void display(Context context, String url, ImageView targetView);
void display(Context context, String url, ImageView targetView, boolean cache);
void loadGif(Context context, String url, ImageView targetView);
}
- 實(shí)現(xiàn)一個(gè)框架策略, 以Glide為例(只剖析思想,具體實(shí)現(xiàn)請百度Glide官網(wǎng))
/**
* @author qichunjie 2018/6/4
*/
public class GlideImageLoader implements ImageLoader {
@Override
public void display(Context context, String url, ImageView targetView) {
// 使用Glide實(shí)現(xiàn)display圖片加載
}
@Override
public void display(Context context, String url, ImageView targetView, boolean cache) {
// 使用Glide實(shí)現(xiàn)從緩存display圖片加載
}
@Override
public void loadGif(Context context, String url, ImageView targetView) {
// 使用Glide實(shí)現(xiàn)加載動(dòng)圖
}
}
- 我們需要一個(gè)策略加載類(開閉原則,通過擴(kuò)展來替代該類代碼的修改)
/**
* @author qichunjie 2018/6/4
*/
public class ImageLoadStratergy implements ImageLoader {
public ImageLoader imageLoader;
public ImageLoadStratergy(ImageLoader imageLoader) {
this.imageLoader = imageLoader;
}
@Override
public void display(Context context, String url, ImageView targetView) {
if(imageLoader != null) {
imageLoader.display(context, url, targetView);
}
}
@Override
public void display(Context context, String url, ImageView targetView, boolean cache) {
if(imageLoader != null) {
imageLoader.display(context, url, targetView, cache);
}
}
@Override
public void loadGif(Context context, String url, ImageView targetView) {
if(imageLoader != null) {
imageLoader.loadGif(context, url, targetView);
}
}
}
4.為了便于全局使用,我們替換為單例模式
/**
* @author qichunjie 2018/6/4
*/
public class ImageLoadStratergy implements ImageLoader {
private static ImageLoadStratergy stratergy;
private ImageLoader imageLoader;
private ImageLoadStratergy() {
}
public void setImageLoader(ImageLoader imageLoader) {
this.imageLoader = imageLoader;
}
public static ImageLoadStratergy getLoader() {
if (stratergy == null) {
synchronized (ImageLoadStratergy.class) {
if (stratergy == null) {
stratergy = new ImageLoadStratergy();
}
}
}
return stratergy;
}
@Override
public void display(Context context, String url, ImageView targetView) {
if (imageLoader != null) {
imageLoader.display(context, url, targetView);
} else {
throw new NullPointerException("imageLoader is null, setImageLoader first");
}
}
@Override
public void display(Context context, String url, ImageView targetView, boolean cache) {
if (imageLoader != null) {
imageLoader.display(context, url, targetView, cache);
} else {
throw new NullPointerException("imageLoader is null, setImageLoader first");
}
}
@Override
public void loadGif(Context context, String url, ImageView targetView) {
if (imageLoader != null) {
imageLoader.loadGif(context, url, targetView);
} else {
throw new NullPointerException("imageLoader is null, setImageLoader first");
}
}
}
- 我們來使用吧
ImageLoadStratergy.getLoader().loadGif(this, gifUrl, image_ad);
- 如果有新框架呢? 我們只需要擴(kuò)展一個(gè)類,實(shí)現(xiàn)ImageLoader
/**
* @author qichunjie 2018/6/4
*/
public class FrescoImageLoader implements ImageLoader {
@Override
public void display(Context context, String url, ImageView targetView) {
// 使用Fresco實(shí)現(xiàn)圖片加載
}
@Override
public void display(Context context, String url, ImageView targetView, boolean cache) {
// 使用Fresco實(shí)現(xiàn)圖片緩存加載
}
@Override
public void loadGif(Context context, String url, ImageView targetView) {
// 使用Fresco實(shí)現(xiàn)動(dòng)圖加載
}
}
- 然后初始化一下,最好是在Application中
ImageLoadStratergy.getLoader().setImageLoader(new FrescoImageLoader());
重新加載一下FrescoImageLoader,就可以正常使用了。是不是很簡單呢?
如果本篇文章中你有所收獲,請不要吝惜你的贊,謝謝?。?!