引言
在我們?nèi)粘i_發(fā)中,我們或多或少會有這樣的抱怨:
- 我擦连茧, 為什么要換框架? 換這么多地方值纱, 肯定會有大量的問題吧
- 唉坯汤,領導怎么想的惰聂,為什么又要改需求,萬一其他地方出一堆問題怎么辦搓幌?
- 哎呀我去溉愁,我改的不是這兒啊,為啥這個地方還報錯了呢撤蟆?
如果你有上面的抱怨,說明你該學習設計模式了少年家肯。
上面很多問題讨衣,其實總結原因如下:
- 代碼冗余,混亂固蚤,理不清頭緒
- 耦合重愿险, 出處相關
- 擴展性差辆亏,擴展起來很麻煩
- 可重用性低,到處存在相似的代碼
設計模式就是為了解決這些問題缤弦。如果你也遇到了上述問題其一或者若干彻磁,你還覺得設計模式和日常開發(fā)無關嗎衷蜓?
今天我們講的設計模式,是策略模式斋陪。掌握了策略模式置吓,想要切換框架衍锚? So Easy !!!
那么通過引入場景,我們一步步的來看度宦,策略模式到底好在哪?為什么要使用設計模式斗埂?
開始啦呛凶!
近幾年聽說流行一種很好用的框架漾稀, 簡單易用建瘫,高效高能,聽說是叫Glide啰脚。 這是一種圖片加載框架橄浓,好處多多; 我相信這種聽說在多年前就有相似的場景匀们,那就是UniverseImageLoader准给。
如果我們還停留在初級階段泄朴,工作經(jīng)驗不足祖灰,那么我們是這么寫的
// 在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)存管理好=е稀!硬鞍!
有一天固该,老大來說伐坏,測試說了桦沉,圖片地址不能用的時候我們不能顯示空白啊金闽,這樣太丑了,加個默認圖片吧呐矾。我們想:這還不容易苔埋, Ctrl + H 搜一下Glide, 每個地方改一下唄! 然而改著改著蜒犯,我們忽然發(fā)現(xiàn)组橄,總共有1000處使用了Glide, 我們真是欲哭無淚。這時有一定開發(fā)經(jīng)驗的小王來了罚随,用鄙視的眼光看著我們玉工。“你這個傻鳥”淘菩,你寫個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);
}
}
我們偷偷開心黄绩, 嘿嘿爽丹,這下再改的話,就不用加班了吧!后來我們需要添加一些個性化設置袒餐,我們在方法中需要添加一些變量處理,但是我們不想把所有的變量都設為靜態(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)方法實現(xiàn)的工具類靴姿, 和單例模式的工具類,區(qū)別在哪维雇?
1.靜態(tài)方法工具類是包含以下特征的靜態(tài)方法的集合吱型,它的靜態(tài)方法是獨立的,無任何外部依賴的触徐。
1.單例模式工具類是系統(tǒng)的撞鹉, 依賴外部資源或初始化的。
我們需要初始化Glide配置发皿,也需要依賴一定的外部資源惶室,因此改為單例模式。 但是問題又來了夹界,好多小伙伴都在改這個類鸠踪,加入了好多不同的甚至重復的方法营密, 我們甚是苦惱, 最終我們決定通過接口來約束功能的實現(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);
}
我們覺得很完美, 就像欣賞藝術品一樣看著這段代碼踪央,內(nèi)心暗喜健无。這時領導來了叠穆,說小明啊硼被, 這個最近出了新的圖片加載框架嚷硫,那是相當牛逼啊,要不你改下吧。 這時你有點小得意了负懦,還好設計的好系吭,改一個類就好了村斟。打開編譯器,三下五除二改好了逾滥,你覺得So Easy, 老子真乃神人也。但是一運行舔哪,不對啊,怎么好多地方都有問題。原來的代碼都刪除了陕悬,現(xiàn)在的也不行,如果是緊急狀態(tài)狂秦,怎么辦?
接下來就是正題了堪簿,策略模式痊乾。
策略模式是什么?
策略模式是指有一定行動內(nèi)容的相對穩(wěn)定的策略名稱哪审。
優(yōu)點:
1湿滓、 策略模式提供了管理相關的算法族的辦法朝氓。策略類的等級結構定義了一個算法或行為族赵哲。恰當使用繼承可以把公共的代碼轉(zhuǎn)移到父類里面筷屡,從而避免重復的代碼喻鳄。
2、 策略模式提供了可以替換繼承關系的辦法颜曾。繼承可以處理多種算法或行為。如果不是用策略模式臀叙,那么使用算法或行為的環(huán)境類就可能會有一些子類,每一個子類提供一個不同的算法或行為。但是厌处,這樣一來算法或行為的使用者就和算法或行為本身混在一起。決定使用哪一種算法或采取哪一種行為的邏輯就和算法或行為的邏輯混合在一起洒敏,從而不可能再獨立演化它碎。繼承使得動態(tài)改變算法或行為變得不可能傻挂。
3绪抛、 使用策略模式可以避免使用多重條件轉(zhuǎn)移語句症副。多重轉(zhuǎn)移語句不易維護瓦糕,它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起咕娄,統(tǒng)統(tǒng)列在一個多重轉(zhuǎn)移語句里面圣贸,比使用繼承的辦法還要原始和落后滑负。
缺點:
1、客戶端必須知道所有的策略類痴鳄,并自行決定使用哪一個策略類橡类。這就意味著客戶端必須理解這些算法的區(qū)別勇凭,以便適時選擇恰當?shù)乃惴悺Q言之,策略模式只適用于客戶端知道所有的算法或行為的情況。
2箩艺、 策略模式造成很多的策略類琅催,每個具體策略類都會產(chǎn)生一個新類。有時候可以通過把依賴于環(huán)境的狀態(tài)保存到客戶端里面嫁佳,而將策略類設計成可共享的瓤漏,這樣策略類實例可以被不同客戶端使用彻消。換言之,可以使用享元模式來減少對象的數(shù)量崔步。
以上是百度百科的定義稳吮。 那么什么是策略模式? 相信大家都看過歷史井濒,有一個場景大家也都熟悉灶似。謀臣XX向主公獻計,往往都會提供多個計謀瑞你, 讓主公根據(jù)自己的決定選擇一個酪惭,并言明厲害,策略A乃上上之策者甲, B乃上策春感, C乃下策。這就是策略虏缸。什么是策略模式呢鲫懒? 我們從三要素來分析:
策略的目的是什么? 聯(lián)合孫權刽辙,抵抗曹操窥岩。在程序中體現(xiàn)為接口。策略再多宰缤,但是實現(xiàn)的目的是一樣的
繼承目的的不同的策略颂翼,接口規(guī)范了策略的目的, 策略豐富了目的的達成方式慨灭。如投降曹操朦乏, 火燒赤壁, 蠻力抵抗
呈現(xiàn)策略并使用氧骤, 主公想要什么計策呻疹, 那么謀士要獻上什么策略,主公來使用
我們已找對象為例语淘, 進行說明诲宇。
- 我們的目的是什么? 找到一個合適的對象惶翻。定義一個接口姑蓝,用來限定策略的執(zhí)行目標。
/**
* 不同策略所需要達到的最終目標
* @author qichunjie 2018/5/28
*/
public interface IStrategy {
/**
* 找媳婦
*/
public void findAWife();
}
- 目標已確定吕粗,那么我們有什么策略呢纺荧? 1) 自由戀愛 2) 相親 3) 說媒 。 那么我們有了三個IStrategy的實現(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("通過相親的方式,找到一個媳婦兒");
}
}
有人說你可以自由戀愛耙楸谩(我何嘗不想)占贫,有人說相親才是靠譜的,有人又說了當然是說媒啊先口,成功率高啊型奥。但是作為當事人,我要怎么選呢碉京?當然是采納一個厢汹,然后去執(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();
}
}
}
我們采納一個策略(傳入一個IStrategy), 然后執(zhí)行(使用傳入的策略)谐宙,剩下的就看結果了烫葬。當然,在開發(fā)中凡蜻,這些策略必須是正確執(zhí)行的搭综。 最后,我們要采用XX提供的策略去找對象啦咽瓷,結果如何设凹,請看下回分解!
/**
* @author qichunjie 2018/5/28
*/
public class Main {
/**
* 策略模式的測試入口
*/
public static void main(String[] args) {
Strategy strategy = new Strategy(new XiangQinStrategy());
strategy.findAWife();
}
}
這么分解開來茅姜,其實很簡單闪朱。有人就說了,你這個這么麻煩钻洒,為什么使用它奋姿?Ok,我們來分析一下它的好處以及解決的問題。
- 使用接口來定義一個統(tǒng)一規(guī)范素标,實現(xiàn)指定的目的称诗。(代碼再也不會那么混亂了)
2.不同的策略類實現(xiàn)單一的策略,單一職責原則头遭。好處是只能單一寓免,可復用性好(越單一的代碼癣诱,復用率越高)
3.如果添加新策略,我們直接添加一個擴展類繼承IStrategy袜香, 然后傳入即可撕予。從來不需要動其他策略的任何東西,擴展性好
- 刪除一個策略蜈首,修改一個策略实抡,不會影響其他策略。如上GlideUtils你很可能會動到其他代碼卻不知道欢策。符合開閉原則吆寨。
這里提到了單一職責原則以及開閉原則,簡單的說一下踩寇。 當你的代碼糅合了多種混合而成啄清,那么它幾乎是不可能復用的。如果把它拆分為單獨的功能塊姑荷,那么它的復用率將大大提高盒延; 而且拆分越細, 復用率越高鼠冕。舉個例子添寺,設置View寬高為屏幕比例。我們要做的是
- 先獲取DisplayMetrics
- 獲取屏幕寬高
- 設置View的尺寸
如果寫在一個方法中懈费,我們可能只能設置View的尺寸计露,并且局限于View的布局,也就是LinearLayout.LayoutParams或者RelativeLayout.LayoutParams憎乙。 如果拆分為獲取屏幕的尺寸和設置View尺寸兩個方法票罐。 那么屏幕尺寸的獲取,也是可以復用的泞边。如果按上面1该押,2,3拆分為3個方法阵谚, 那么獲取屏幕密度我們也可以使用1方法蚕礼。所以,拆分的粒度越小梢什,可復用性越好奠蹬。
單一職責的好處二是,拆分后的功能是通過方法組合的方式來實現(xiàn)嗡午,如果足夠規(guī)范囤躁,我們一眼即可以看出這個方法實現(xiàn)的是什么功能。拆分之前, 我們必須閱讀完整個代碼后狸演,才會知道它是做什么的言蛇。
再說開閉原則, 對擴展開發(fā)宵距,對修改關閉猜极。為什么呢? 因為代碼的修改是具有一定危險性的消玄,如果不同的策略在一個類中,在修改一項策略時丢胚,很可能會波及其他策略翩瓜,而我們自己還不知道。因為對修改保持最小的域來確保我們代碼的維護性携龟, 在對修改關閉時兔跌, 同時還要滿足我們的修改需求,因此改為通過擴展的形式來實現(xiàn)代碼的修改峡蟋。保持修改代碼的域最小化坟桅。這樣的代碼維護性,安全性會是最高的蕊蝗。
如上示例的策略仅乓,我們要修改相親策略,那么只修改XiangQinStragy即可蓬戚,毫不影響其他策略夸楣。我們要新增策略,擴展一個策略類子漩,也不需要修改Strategy類豫喧。這使得安全性提高了很多。
本文中幢泼,我們可以得到一些關于架構設計的點:
為什么使用接口以及如何使用接口紧显?
靜態(tài)工具類和單例工具類的區(qū)別是什么?你會用了嗎缕棵?
單一職責提高復用率孵班, 減少代碼修改域
開閉原則使你盡可能小的減少你的失誤,從而影響其他代碼
如果你還不理解上面的4個問題挥吵,建議帶著問題回去閱讀一遍本篇文章重父。希望本篇文章可以使你提供開發(fā)效率, 減少你的加班時間忽匈。
根據(jù)策略模式房午,我們來說明一下如何一行代碼更新你的框架。
以圖片加載框架為例丹允, 框架的更新很快郭厌,而且功能也越來越強大袋倔,性能越來越好。 因此框架的切換是必要的折柠,那么如何做到切換框架時宾娜,我們付出的代價最小呢? 策略模式給了你答案扇售。
- 無論切換什么框架前塔,我們要使用的功能是確定的。首先承冰,我們來定義一個規(guī)范接口华弓。比如我們需要加載圖片,從緩存加載圖片困乒, 加載動圖
/**
* @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);
}
- 實現(xiàn)一個框架策略寂屏, 以Glide為例(只剖析思想,具體實現(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實現(xiàn)display圖片加載
}
@Override
public void display(Context context, String url, ImageView targetView, boolean cache) {
// 使用Glide實現(xiàn)從緩存display圖片加載
}
@Override
public void loadGif(Context context, String url, ImageView targetView) {
// 使用Glide實現(xiàn)加載動圖
}
}
- 我們需要一個策略加載類(開閉原則娜搂,通過擴展來替代該類代碼的修改)
/**
* @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);
- 如果有新框架呢? 我們只需要擴展一個類百宇,實現(xiàn)ImageLoader
/**
* @author qichunjie 2018/6/4
*/
public class FrescoImageLoader implements ImageLoader {
@Override
public void display(Context context, String url, ImageView targetView) {
// 使用Fresco實現(xiàn)圖片加載
}
@Override
public void display(Context context, String url, ImageView targetView, boolean cache) {
// 使用Fresco實現(xiàn)圖片緩存加載
}
@Override
public void loadGif(Context context, String url, ImageView targetView) {
// 使用Fresco實現(xiàn)動圖加載
}
}
- 然后初始化一下考廉,最好是在Application中
ImageLoadStratergy.getLoader().setImageLoader(new FrescoImageLoader());
重新加載一下FrescoImageLoader,就可以正常使用了携御。是不是很簡單呢若未?
如果本篇文章中你有所收獲熬拒,請不要吝惜你的贊,謝謝!S北洹姜胖!