23種設(shè)計模式,畢生心血,咳咳吐血去了

(一)什么是設(shè)計模式

1. 基本定義:設(shè)計模式(Design pattern)是一套被反復(fù)使用的代碼設(shè)計經(jīng)驗(yàn)的總結(jié)缸废。使用設(shè)計模式的目的是為了可重用代碼讓代碼更容易被他人理解。設(shè)計模式是是軟件工程的基石脈絡(luò)驶社,如大廈的結(jié)構(gòu)一樣呆奕。

2. Design pattern的四大要素:模式名(Name),問題(Question)衬吆,解決方案(Solution)梁钾,效果(Efftive)。

3. OO(面向?qū)ο螅┑牧笤瓌t:單一職責(zé)原則逊抡,開閉原則姆泻,里氏替換原則,依賴倒置原則冒嫡,接口隔離原則拇勃,迪米特原則。

單一職責(zé)原則:一個類中應(yīng)該是一組相關(guān)性很高的函數(shù)孝凌,數(shù)據(jù)的封裝方咆。兩個完全不一樣的功能就不應(yīng)該放在一個類中。

開閉原則:對修改封閉蟀架,對擴(kuò)展放開瓣赂。里氏替換原則:抽象和繼承榆骚;所有引用基類的地方必須能透明的使用其子類的對象。

里氏替換原則:抽象和繼承煌集;所有引用基類的地方必須能透明的使用其子類的對象妓肢。

依賴倒置原則:抽象不應(yīng)該依賴細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴抽象苫纤。

接口隔離原則:將大接口改成多個小接口碉钠。

迪米特原則:也稱為最少知識原則,一個對象應(yīng)該對另一個對象有最少的了解卷拘。

(二)設(shè)計模式的分類

(1)創(chuàng)建型模式5種:

單例模式喊废,抽象工廠模式,工廠模式栗弟,原型模式污筷,建造者模式。(口訣:單原建造者横腿,東西二廠)

(2)結(jié)構(gòu)型模式7種:

適配器模式颓屑,橋接模式,裝飾模式耿焊,組合模式揪惦,外觀模式,享元模式,代理模式。(口訣:一器一橋一代理状勤;裝飾組合外觀)

(3)行為型模式11種:

觀察者模式谒府,中介者模式敦间,訪問者模式,解釋器模式,迭代器模式,備忘錄模式措左,責(zé)任鏈模式,狀態(tài)模式避除,策略模式怎披,命令模式,模板模式瓶摆。(口訣:三者兩器凉逛、一錄一鏈一模板,狀態(tài)策略命令)

單利模式

單利模式有一下特點(diǎn):

1. 單例類只能有一個實(shí)例群井。

2. 單例類必須自己創(chuàng)建自己的唯一實(shí)例状飞。

3. 單例類必須給所有其他對象提供這一實(shí)例。

首先說一下經(jīng)常用的

1.餓漢式

public class Singleton {

? ? private Singleton() {

? ? ? ? //構(gòu)造方法為private,防止外部代碼直接通過new來構(gòu)造多個對象

? ? }

? ? //在類初始化時,已經(jīng)自行實(shí)例化,所以是線程安全的诬辈。

? ? private static final Singleton single? = new Singleton();

? ? //通過getInstance()方法獲取實(shí)例對象

? ? public static Singleton getInstance() {

? ? ? ? return single;

? ? }

}

優(yōu)點(diǎn):寫法簡單酵使,線程安全

缺點(diǎn):沒有懶加載效果,如果沒有使用過的話會造成內(nèi)存浪費(fèi)

2.懶漢式(線程不安全)

public class Singleton {

? ? private static Singleton singleton = null;

? ? public static Singleton getInstance() {

? ? ? ? if (singleton == null){

? ? ? ? ? ? //在第一次調(diào)用getInstance()時才實(shí)例化自晰,實(shí)現(xiàn)懶加載,所以叫懶漢式

? ? ? ? ? ? singleton = new Singleton();

? ? ? ? }

? ? ? ? return singleton;

? ? }

? ? private Singleton() {

? ? }

}

優(yōu)點(diǎn):實(shí)現(xiàn)了懶加載效果

缺點(diǎn):線程不安全

3.懶漢式(線程安全)

public class Singleton {

? ? private static Singleton singleton = null;

? ? //加上synchronized同步

? ? public static synchronized Singleton getInstance() {

? ? ? ? if (singleton == null){

? ? ? ? ? ? //在第一次調(diào)用getInstance()時才實(shí)例化凝化,實(shí)現(xiàn)懶加載,所以叫懶漢式

? ? ? ? ? ? singleton = new Singleton();

? ? ? ? }

? ? ? ? return singleton;

? ? }

? ? private Singleton() {

? ? }

}

優(yōu)點(diǎn):實(shí)現(xiàn)了懶加載稍坯,線程也安全了

缺點(diǎn):使用了synchronized會造成不必要的開銷酬荞,而且大部分時候我們是用不到同步的

4.雙重檢查鎖定(DCL)

public class Singleton {

? ? //volatile 能夠防止代碼的重排序,保證得到的對象是初始化過

? ? private volatile static Singleton singleton = null;

? ? private Singleton() {

? ? }

? ? public static Singleton getInstance() {

? ? ? ? //第一次檢查瞧哟,避免不必要的同步

? ? ? ? if (singleton == null){

? ? ? ? ? ? //同步

? ? ? ? ? ? synchronized (Singleton.class){

? ? ? ? ? ? ? ? //第二次檢查混巧,為null時才創(chuàng)建

? ? ? ? ? ? ? ? if (singleton == null){

? ? ? ? ? ? ? ? ? ? singleton = new Singleton();

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return singleton;

? ? }

}

優(yōu)點(diǎn):懶加載,線程安全勤揩,效率高

缺點(diǎn):volatile影響一點(diǎn)性能咧党,高并發(fā)下有一定的缺陷,某些情況下DCL會失效陨亡,雖然概率小

5.靜態(tài)內(nèi)部類

public class Singleton {

? ? private Singleton() {

? ? }

? ? public static Singleton getInstance() {

? ? ? ? //第一次調(diào)用getInstance方法時才加載SingletonHolder并初始化sInstance

? ? ? ? return SingletonHolder.sInsstance;

? ? }

? ? //靜態(tài)內(nèi)部類

? ? private static class SingletonHolder{

? ? ? ? private static final Singleton sInsstance = new Singleton();

? ? }

}

優(yōu)點(diǎn):懶加載傍衡,線程安全,推薦使用

缺點(diǎn):個人還沒發(fā)現(xiàn)

6.枚舉單例

public enum Singleton {

? ? //定義一個枚舉的元素负蠕,它就是Singleton的一個實(shí)例

? ? INSTANCE;

? ? public void doSomething(){

? ? }

}

優(yōu)點(diǎn):線程安全蛙埂,寫法簡單,能防止反序列化重新創(chuàng)建對象

缺點(diǎn):可讀性不高遮糖,枚舉會比靜態(tài)常量多那么一丁點(diǎn)的內(nèi)存

7.使用容器實(shí)現(xiàn)單例模式

//單例管理類

public class SingletonManager {

? ? private static Map<String,Object> objectMap = new HashMap<String, Object>();

? ? public static void registerService(String key,Object instance){

? ? ? ? if (!objectMap.containsKey(key)){

? ? ? ? ? ? objectMap.put(key,instance);//添加單例

? ? ? ? }

? ? }

? ? public static Object getService(String key){

? ? ? ? return objectMap.get(key);//獲取單例

? ? }

}

優(yōu)點(diǎn):方便管理

缺點(diǎn):寫法稍微復(fù)雜

注意事項(xiàng)

1.使用反射會破壞單例模式绣的,所以應(yīng)該慎用反射

Constructor con = Singleton.class.getDeclaredConstructor();

con.setAccessible(true);

Singleton singleton1 = (Singleton) con.newInstance();

Singleton singleton2 = (Singleton) con.newInstance();

//結(jié)果為false,singeton1和singeton2將是兩個不同的實(shí)例

System.out.println(singleton1 == singleton2);

可以通過當(dāng)?shù)诙握{(diào)用構(gòu)造函數(shù)是拋出異常來防止反射破壞單例,以懶漢式為例:

public class Singleton {

? ? private static boolean flag = true;

? ? private static Singleton single = null;

? ? private Singleton() {

? ? ? ? if (flag){

? ? ? ? ? ? flag = ! flag;

? ? ? ? }else {

? ? ? ? ? ? throw new RuntimeException("單例模式被破壞");

? ? ? ? }

? ? }

? ? public static Singleton getInstance(){

? ? ? ? if (single == null){

? ? ? ? ? ? single = new Singleton();

? ? ? ? }

? ? ? ? return single;

? ? }

}

2.反序列化時也能破壞單例模式欲账,可以重寫readResolve方法避免屡江,以餓漢式為例:

public class Singleton implements Serializable {

? ? private Singleton() {

? ? }

? ? private static final Singleton single = new Singleton();

? ? public static Singleton getInstance() {

? ? ? ? return single;

? ? }

? ? //重寫readResolve()

? ? private Object readResolve() throws ObjectStreamException {

? ? ? ? return single;

? ? }

}

應(yīng)用場景

1.頻繁訪問數(shù)據(jù)庫或文件對象。

2.工具類對象

3.創(chuàng)建對象時耗時過多或耗費(fèi)資源過多赛不,但經(jīng)常用到的對象

優(yōu)點(diǎn)

1.內(nèi)存中只存在一個對象惩嘉,節(jié)省了系統(tǒng)資源

2.避免對資源的多重占用,例如一個文件操作踢故,由于只有一個實(shí)例存在內(nèi)存中文黎,避免對統(tǒng)一資源文件的同時操作

缺點(diǎn)

1.獲取對象時不能用new

2.單例對象如果持有Context,那么很容易引發(fā)內(nèi)存泄露畴椰。

3.單例模式一般沒有接口臊诊,擴(kuò)展很困難,若要擴(kuò)展斜脂,只能修改代碼來實(shí)現(xiàn)

建造者模式

定義

將一個復(fù)雜對象的構(gòu)建與它的表示分離抓艳,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示

介紹

1.建造者模式屬于創(chuàng)建型模式

2.建造者模式主要用來創(chuàng)建復(fù)雜的對象,用戶可以不用關(guān)心其建造過程和細(xì)節(jié)

3.例如:當(dāng)要組裝一臺電腦時帚戳,我們選擇好CPU玷或、內(nèi)存儡首、裝機(jī)師傅就把電腦給組裝起來,我們不需要關(guān)心是怎么拼裝起來的

UML類圖

角色說明

Product(產(chǎn)品類) :要創(chuàng)建的復(fù)雜對象偏友。在本類圖中蔬胯,產(chǎn)品類是一個具體的類,而非抽象類位他。實(shí)際編程中氛濒,產(chǎn)品類可以是由一個抽象類與它的不同實(shí)現(xiàn)組成,也可以是由多個抽象類與他們的實(shí)現(xiàn)組成鹅髓,也是可以由多個抽象類與他們的實(shí)現(xiàn)組成舞竿。

Builder(抽象建造者):創(chuàng)建產(chǎn)品的抽象接口,一般至少有一個創(chuàng)建產(chǎn)品的抽象方法和一個返回產(chǎn)品的抽象方法窿冯。引入抽象類骗奖,是為了更容易擴(kuò)展。

ConcreteBuilder(實(shí)際的建造者):繼承Builder類醒串,實(shí)現(xiàn)抽象類的所以抽象方法执桌。實(shí)現(xiàn)具體的建造過程和細(xì)節(jié)。

Director(指揮者類):分配不同的建造者來創(chuàng)建產(chǎn)品芜赌,統(tǒng)一組裝流程仰挣。

實(shí)現(xiàn)

1.定義具體的產(chǎn)品(Product):電腦

public class Computer {

? ? private String mCPU;

? ? private String mMemory;

? ? private String mHD;

? ? public void setmCPU(String mCPU) {

? ? ? ? mCPU = mCPU;

? ? }

? ? public void setmMemory(String mMemory) {

? ? ? ? mMemory = mMemory;

? ? }

? ? public void setmHD(String mHD) {

? ? ? ? mHD = mHD;

? ? }

}

2.定義抽象建造者(Builder):組裝電腦的過程

public abstract class Builder {

? ? //組裝CPU

? ? public abstract void buildCUP(String cpu);

? ? //組裝內(nèi)存

? ? public abstract void buildMemory(String memory);

? ? //組裝硬盤

? ? public abstract void buildeHD(String hd);

? ? //返回組裝好的電腦

? ? public abstract Computer create();

}

3.創(chuàng)建具體的建造者(ConcreteBuilder):裝機(jī)人員

public class ConcreteBuilder extends Builder {

? ? //創(chuàng)建產(chǎn)品實(shí)例

? ? private Computer mComputer = new Computer();

? ? //組裝CPU

? ? @Override

? ? public void buildCUP(String cpu) {

? ? ? ? mComputer.setmCPU(cpu);

? ? }

? ? //組裝內(nèi)存

? ? @Override

? ? public void buildMemory(String memory) {

? ? ? ? mComputer.setmMemory(memory);

? ? }

? ? //組裝硬盤

? ? @Override

? ? public void buildeHD(String hd) {

? ? ? ? mComputer.setmHD(hd);

? ? }

? ? //返回組裝好的電腦

? ? @Override

? ? public Computer create() {

? ? ? ? return mComputer;

? ? }

}

4.定義指揮者類(Director):老板委派任務(wù)給裝機(jī)人員

public class Director {

? ? private Builder mBuilder = null;

? ? public Director(Builder mBuilder) {

? ? ? ? this.mBuilder = mBuilder;

? ? }

? ? //指揮裝機(jī)人員組裝電腦

? ? public void Construct(String cpu, String memory, String hd){

? ? ? ? mBuilder.buildCUP(cpu);

? ? ? ? mBuilder.buildMemory(memory);

? ? ? ? mBuilder.buildeHD(hd);

? ? }

}

5.測試方法

public void CreatComputer() {

? ? Builder builder = new ConcreteBuilder();

? ? Director director = new Director(builder);

? ? director.Construct("i7-6700","三星DDR4","希捷1T");

}

應(yīng)用場景

1.創(chuàng)建一些復(fù)雜的對象時,對象內(nèi)部的構(gòu)鍵過程存在復(fù)雜變化较鼓。

2.相同的構(gòu)建過程椎木,不同的執(zhí)行順序,產(chǎn)生不同結(jié)果時博烂。

3.不同配置的構(gòu)建對象香椎,產(chǎn)生不同結(jié)果時。

優(yōu)點(diǎn)

1.封裝性良好禽篱,隱藏內(nèi)部構(gòu)建細(xì)節(jié)畜伐。

2.易于解耦,將產(chǎn)品本身于產(chǎn)品創(chuàng)建過程進(jìn)行解耦躺率,可以使用相同的創(chuàng)建過程來得到不同的產(chǎn)品玛界,也就說細(xì)節(jié)依賴抽象。

3.易于擴(kuò)展悼吱,具體的建造者類之間相互獨(dú)立慎框,增加新的具體建造者無需修改原有類庫的代碼。

4.易于精確控制對象的創(chuàng)建后添,由于具體的建造者是獨(dú)立的笨枯,因此可以對建造過程逐步細(xì)化,而不對其他的模塊產(chǎn)生任何影響。

缺點(diǎn)

1.產(chǎn)生多余的Build對象以及Dirextor類馅精。

2.建造者模式所創(chuàng)建的產(chǎn)品一般具有較多的共同點(diǎn)严嗜,其組成部分相似;如果產(chǎn)品之間的差異性很大洲敢,則不適合使用建造者模式漫玄,因此其使用范圍受到一定的限制。

3.如果產(chǎn)品的內(nèi)部復(fù)雜压彭,可能會導(dǎo)致需要定義很多具體建造者來實(shí)現(xiàn)這種變化睦优,導(dǎo)致系統(tǒng)變得很龐大。

Android中的源碼分析

Android中的AlertDialog.Builder就是使用了Builder模式來構(gòu)建AlertDialog的哮塞。

AlertDialog.Builder的簡單用法

//創(chuàng)建一個builder對象

AlertDialog.Builder builder = new AlertDialog.Builder(this);

builder.setIcon(R.drawable.aa);

builder.setTitle("標(biāo)題");

builder.setMessage("信息");

builder.setPositiveButton("確定", new DialogInterface.OnClickListener() {

? ? @Override

? ? public void onClick(DialogInterface dialog, int which) {

? ? }

});

//創(chuàng)建AlertDialog對象

AlertDialog alertDialog = builder.create();

//展示AlertDialog

alertDialog.show();

通過Builder對象來構(gòu)建lcon刨秆、Title凳谦、Message等忆畅,講AlertDialog的構(gòu)建過程和細(xì)節(jié)隱藏了起來

AlertDialog相關(guān)源碼分析

public class AlertDialog extends AppCompatDialog implements DialogInterface {

final AlertController mAlert;

protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {

? ? super(context, resolveDialogTheme(context, themeResId));

//創(chuàng)建AlertController對象

? ? this.mAlert = new AlertController(this.getContext(), this, this.getWindow());

? }

//設(shè)置Title

public void setTitle(CharSequence title) {

? ? super.setTitle(title);

//保存在AlertController對象中

? ? this.mAlert.setTitle(title);

}

//設(shè)置Message

public void setMessage(CharSequence message) {

//保存在AlertController對象中

? ? this.mAlert.setMessage(message);

}

//設(shè)置Icon

public void setIcon(int resId) {

//保存在AlertController對象中

? ? this.mAlert.setIcon(resId);

}

protected void onCreate(Bundle savedInstanceState) {

? ? super.onCreate(savedInstanceState);

//安裝AlertDialog的內(nèi)容

? ? this.mAlert.installContent();

}

//AlertDialog其他代碼略

public static class Builder {

//構(gòu)建AlertDialog對象所需要的參數(shù)都存放在P中

private final AlertParams P;

public Builder(@NonNull Context context) {

? ? this(context, AlertDialog.resolveDialogTheme(context, 0));

}

public Builder(@NonNull Context context, @StyleRes int themeResId) {

//初始化AlertParams對象

? ? this.P = new AlertParams(new ContextThemeWrapper(context, AlertDialog.resolveDialogTheme(context, themeResId)));

? ? this.mTheme = themeResId;

}

public Context getContext() {

? ? return this.P.mContext;

}

public AlertDialog.Builder setTitle(@Nullable CharSequence title) {

//保存title到P中

? ? this.P.mTitle = title;

? ? return this;

}

public AlertDialog.Builder setMessage(@Nullable CharSequence message) {

//保存message

? ? this.P.mMessage = message;

? ? return this;

}

public AlertDialog.Builder setIcon(@DrawableRes int iconId) {

//保存IconId

? ? this.P.mIconId = iconId;

? ? return this;

}

//Builder其他代碼略

public AlertDialog create() {//構(gòu)建AlertDialog

AlertDialog dialog = new AlertDialog(this.P.mContext, this.mTheme);

//將P中的參數(shù)設(shè)置到AlertController中

this.P.apply(dialog.mAlert);

//其他設(shè)置代碼略

return dialog;

}

}

}

//Dialog源碼

public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, View.OnCreateContextMenuListener, Window.OnWindowDismissedCallback {

? ? ? ? //其他代碼略

? ? ? ? public void show() {

? ? ? ? ? ? //前面代碼略

? ? ? ? ? ? if (!mCreated) {

//分發(fā)onCreate

? ? ? ? ? ? ? ? dispatchOnCreate(null);

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? final Configuration config=mContext.getResources().getConfiguration();

? ? ? ? ? ? ? ? mWindow.getDecorView().dispatchConfigurationChanged(config);

? ? ? ? ? ? }

? ? //調(diào)用onStart()

? ? ? ? ? ? onStart();

? ? ? ? ? ? mDecor = mWindow.getDecorView();

? ? ? ? ? ? //設(shè)置參布局參數(shù)略

? ? ? ? ? ? mWindowManager.addView(mDecor, l);//添加到WindowManager

? ? ? ? ? ? mShowing = true;

? ? ? ? ? ? sendShowMessage();

? ? ? ? }

? ? ? ? //分發(fā)onCreate

? ? ? ? void dispatchOnCreate(Bundle savedInstanceState) {

? ? ? ? ? ? if (!mCreated) {

//調(diào)用AlertDialog的onCreate方法,創(chuàng)建AlertDialog視圖

? onCreate(savedInstanceState);

? ? ? ? ? ? ? ? mCreated = true;

? ? ? ? ? ? }

? ? ? ? }

? ? }

//AlertController源碼

public class AlertController {

//其他代碼略

public void installContent() {//安裝內(nèi)容

//選擇合適的布局

int contentView = selectContentView();

//布局添加到Window中

mWindow.setContentView(contentView);

//把dialog.mAlert對象中需要構(gòu)建的元素逐個添加設(shè)置到Window上,即構(gòu)建我們設(shè)置的布局發(fā)生在這一步中

setupView();

? ? ? }

}

簡單流程說明

1.通過AlertDialog.Builder設(shè)置各種屬性后(如:setTitle())尸执,這些屬性信息會保存在P變量中家凯,P變量的類型為AlertController.AlertParams。

2.調(diào)用builder.create()即可返回一個AlertDialog對象如失。

2.1builder.create()方法中首先會創(chuàng)建一個AlertDialog對象绊诲,AlertDialog對象構(gòu)造時會初始化WindowManager和Window。

2.2builder.create()創(chuàng)建完AlertDialog對象后褪贵,會調(diào)用P.apply(dialog.mAlert)掂之;即把P變量中所存儲的用來構(gòu)建AlertDialog對象的元素設(shè)置到了dialog.mAlert中,dialog.mAlert的類型為AlertController脆丁。

3.調(diào)用AlertDialog的show()方法世舰,展示界面。

3.1show()方法中會調(diào)用dispatchOnCreate(null)槽卫,dispatchOnCreate(null)調(diào)起onCreate(),onCreate()會調(diào)起mAlert.installContent()跟压;即安裝AlertDialog的內(nèi)容。

3.2installContent()中會調(diào)用mWindow.setContentView(mAlertDialogLayout);即把mAlertDialogLayout這個布局加到Window中去歼培。

3.3 調(diào)完mWindow.setContentView(mAlertDialogLayout)后會調(diào)用setupView()震蒋,setupView()中會把dialog.mAlert對象中需要構(gòu)建的元素逐個添加設(shè)置到mWindow上。

3.4 最后通過把view添加到mWindowManager上展示出來躲庄。

總結(jié)

1.builder模式隱藏了這種復(fù)雜的構(gòu)建過程查剖,只需幾行簡單的代碼就把AlertDialog給展示出來了。

2.AlertDialog的builder中并沒有抽象建造者(Builder)噪窘、Director(指揮者類)等角色笋庄。AlertDialog.Builder同時扮演了Builder、ConcreteBuilder、Director等角色无切,這是Android中的一種簡化荡短,也值得我們?nèi)W(xué)習(xí)使用。

簡單工廠模式

定義

定義一個用于創(chuàng)建對象的接口哆键,讓子類決定實(shí)例化那個類

介紹

簡單工廠模式屬于創(chuàng)建模式

簡單工廠模式又叫做靜態(tài)工廠模式

UML類圖

角色說明

Product(抽象產(chǎn)品類):要創(chuàng)建的復(fù)雜對象掘托,定義對象的公共接口。

ConcreteProduct(具體產(chǎn)品類):實(shí)現(xiàn)Product接口籍嘹。

Factory(工廠類):返回ConcreteProduct實(shí)例闪盔。

實(shí)現(xiàn)

1.創(chuàng)建抽象產(chǎn)品類,定義公共接口:

//抽象產(chǎn)品類

public abstract class Product {

? ? public abstract void show();

}

2.創(chuàng)建具體產(chǎn)品類辱士,繼承Product類:

//具體產(chǎn)品類A

public class ProductA extends Product {

? ? @Override

? ? public void show() {

? ? ? ? System.out.println("product A");

? ? }

? ? //具體產(chǎn)品類B

? ? public class ProductB extends Product{

? ? ? ? @Override

? ? ? ? public void show() {

? ? ? ? ? ? System.out.println("product B");

? ? ? ? }

? ? }

}

3.創(chuàng)建具體工廠類泪掀,創(chuàng)建具體的產(chǎn)品:

public class Factory {


? ? public static Product create(String productName) {

? ? ? ? Product product = null;

? ? ? ? //通過switch語句控制生產(chǎn)那種商品

? ? ? ? switch (productName) {

? ? ? ? ? ? case "A":

? ? ? ? ? ? ? ? product = new ProductA();

? ? ? ? ? ? ? ? break;

? ? ? ? ? ? case "B":

? ? ? ? ? ? ? ? product = new ProductB();

? ? ? ? ? ? ? ? break;

? ? ? ? }

? ? ? ? return product;

? ? }

}

4.測試方法:

private void test() {

? ? //生產(chǎn)ProductA

? ? Factory.create("A").show();

? ? //生產(chǎn)ProductB

? ? Factory.create("B").show();

? ? try{

? ? ? ? //生產(chǎn)ProductC

? ? ? ? Factory.create("C").show();

? ? }catch (NullPointerException e){

? ? ? ? //沒有ProductC,會報錯

? ? ? ? System.out.println("沒有ProductC");

? ? }

}

應(yīng)用場景

生成復(fù)雜對象時,確定只有一個工廠類颂碘,可以使用簡單工廠模式异赫。否則有多個工廠類的話,使用工廠模式方法头岔。

優(yōu)點(diǎn)

代碼解耦塔拳,創(chuàng)建實(shí)例的工作與使用實(shí)例的工作分開,使用者不必關(guān)心類對象如何創(chuàng)建峡竣。

缺點(diǎn)

1.違背開放封閉原則靠抑,若需添加新產(chǎn)品則必須修改工廠類邏輯,會造成工廠邏輯過于復(fù)雜

2.簡單工廠模式使用靜態(tài)工廠犯法适掰,因此靜態(tài)方法不能被繼承和重寫

3.工廠類包含所有實(shí)例(產(chǎn)品)的創(chuàng)建邏輯颂碧,若工廠類出錯,則會造成整個系統(tǒng)都會受到影響

工廠方法模式與簡單工廠模式比較

1.工廠方法模式有抽象工廠類类浪,簡單工廠模式?jīng)]有抽象工廠類且其工廠類的工廠方法是靜態(tài)的

2.工廠方法模式新增產(chǎn)品時只需要新建一個工廠類即可载城,符合開放封閉原則;而簡單工廠模式需要直接修改工廠類戚宦,違反了開放封閉原則

簡單工廠模式的優(yōu)化

由于簡單工廠模式新增產(chǎn)品是需要直接修改工廠類个曙,違反了開放封閉原則。因此可以使用反射來創(chuàng)建實(shí)例對象受楼,確保能夠遵循開放封閉原則

反射實(shí)現(xiàn)工廠類

public class Factory {

? ? public static <T extends Product> T create(Class<T> clz){

? ? ? ? Product product = null;

? ? ? ? try{

? ? ? ? ? ? //反射出實(shí)例

? ? ? ? ? product = (Product) Class.forName(clz.getName()).newInstance();

? ? ? ? }catch (Exception e){

e.printStackTrace();

? ? ? ? }

? ? ? ? return (T) product;

? ? }

}

測試方法

public void test(){

? ? //生產(chǎn)ProductA

? ? Factory.create(ProductA.class).show();

? ? //生產(chǎn)ProductB

? ? Factory.create(ProductB.class).show();

}

總結(jié)

使用反射來實(shí)現(xiàn)工廠類垦搬,新增產(chǎn)品時無需修改工廠類,但是使用反射來創(chuàng)建實(shí)例對象的話會比正常使用new來創(chuàng)建的要慢艳汽。

觀察者模式

定義

定義對象間的一種一個對多的依賴關(guān)系猴贰,當(dāng)一個對象的狀態(tài)發(fā)送改變時,所以依賴于它的對象都得到通知并被自動更新

介紹

1.觀察者屬于行為型模式

2.觀察者模式又被稱作發(fā)布/訂閱模式

3.觀察者模式主要用來解耦河狐,將被觀察者和觀察者解耦米绕,讓他們之間沒有沒有依賴或者依賴關(guān)系很小

UML類圖

角色說明

Subject(抽象主題):又叫抽象被觀察者瑟捣,把所有觀察者對象的引用保存到一個集合里,每個主題都可以有任何數(shù)量的被觀察者栅干。抽象主題提供一個接口迈套,可以增加和刪除觀察者對象。

ConcreteSubject(具體主題):又叫具體被觀察者桑李,將有關(guān)狀態(tài)存入具體觀察者對象角撞,在具體主題內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知。

Observer(抽象觀察者):為所有的具體觀察者定義一個接口梆造,在得到主題通知時更新自己烂斋。

ConcreteObserver(具體觀察者):實(shí)現(xiàn)抽象觀察者定義的更新接口歇僧,當(dāng)?shù)玫街黝}更改通知時更新自身的狀態(tài)

實(shí)現(xiàn)

繼續(xù)以送快遞為例柄错,快遞員有時只是把快遞拉到樓下敢伸,然后就通知收件人下樓取快遞钓丰。

創(chuàng)建抽象觀察者

定義一個接到通知的更新方法躯砰,即收件人收到通知后的反應(yīng):

//抽象觀察者

public interface Observer {

? ? //更新方法

? ? public void update(String message);


}

創(chuàng)建具體觀察者

實(shí)現(xiàn)抽象觀察者中的方法,這里創(chuàng)建兩個類携丁,一個男孩類和一個女孩類琢歇,定義他們收到通知后的反應(yīng):

public class Boy implements Observer {

? ? //名字

? ? private String name;

? ? public Boy(String name) {

? ? ? ? this.name = name;

? ? }

? ? //男孩的具體反應(yīng)

? ? @Override

? ? public void update(String message) {

? ? ? System.out.println(name + ",收到了信息:" + message+"屁顛顛的去取快遞.");

? ? }

}

public class Girl implements Observer{

? ? //名字

? ? private String name;

? ? public Girl(String name) {

? ? ? ? this.name = name;

? ? }

? ? //女孩的具體反應(yīng)

? ? @Override

? ? public void update(String message) {

? ? ? System.out.println(name + ",收到了信息:" + message+"讓男朋友去取快遞~");

? ? }

}

創(chuàng)建抽象主題

即抽象被觀察者,定義添加梦鉴,刪除李茫,通知等方法:

//抽象被觀察者

public interface Observable {


? ? //添加觀察者

? ? void add(Observer observer);

? ? //刪除觀察者

? ? void remove(Observer observer);

? ? //通知觀察者

? ? void notify(String message);

}

創(chuàng)建具體主題

即具體被觀察者,也就是快遞員尚揣,派送快遞時根據(jù)快遞信息來通知收件人讓其來取件:

//快遞員

public class Postman implements Observable {

? ? //保存收件人(觀察者)的信息

? ? private List<Observer> personList = new ArrayList<>();

? ? //添加收件人

? ? @Override

? ? public void add(Observer observer) {

? ? ? ? personList.add(observer);

? ? }

? ? //移除收件人

? ? @Override

? ? public void remove(Observer observer) {

? ? ? ? personList.remove(observer);

? ? }

? ? //逐一通知收件人(觀察者)

? ? @Override

? ? public void notify(String message) {

? ? ? ? for (Observer observer : personList){

? ? ? ? ? ? observer.update(message);

? ? ? ? }

? ? }

}

客戶端測試

public void test() {

? ? Postman postman = new Postman();


? ? Observer boy1 = new Boy("路飛");

? ? Observer boy2 = new Boy("路飛");

? ? Observer girl = new Girl("娜美");


? ? postman.add(boy1);

? ? postman.add(boy2);

? ? postman.add(girl);


? ? postman.notify("快遞到了涌矢,請下樓領(lǐng)取");

}

說明

實(shí)際上,JDK內(nèi)部也內(nèi)置了Observable(抽象被觀察者)快骗,Observer(抽象觀察者)這兩個類娜庇,我們也可以直接拿來有塔次,其代碼如下:

//抽象觀察者

public interface Observer {

? ? //更新方法

? ? void update(Observable observable, Object arg);

}

//抽象被觀察者

public class Observable {

? ? //定義改變狀態(tài),默認(rèn)為false

? ? private boolean changed = false;

? ? //定義一個觀察者list

? ? private final ArrayList<Observer> observers;

? ? //構(gòu)造函數(shù)名秀,初始化一個觀察者list來保存觀察者

? ? public Observable() {

? ? ? ? observers = new ArrayList<>();

? ? }

? ? //添加觀察者励负,帶同步字段的,所以是線程安全的

? ? public synchronized void addObserver(Observer o) {

? ? ? ? if (o == null)

? ? ? ? ? ? throw new NullPointerException();

? ? ? ? if (!observers.contains(o)) {

? ? ? ? ? ? observers.add(o);

? ? ? ? }

? ? }

? ? //刪除觀察者

? ? public synchronized void deleteObserver(Observer o) {

? ? ? ? observers.remove(o);

? ? }

? ? //通知所以觀察者匕得,無參數(shù)

? ? public void notifyObservers() {

? ? ? ? notifyObservers(null);

? ? }

? ? //通知所有觀察者继榆,帶參數(shù)

? ? public void notifyObservers(Object arg) {

? ? ? ? Observer[] arrLocal;

? ? ? ? //加synchronized字段,保證多線程下操作沒有問題

? ? ? ? synchronized (this) {

? ? ? ? ? ? //這里做了是否發(fā)生改變的判斷汁掠,是為了防止出現(xiàn)無意義的更新

? ? ? ? ? ? if (!hasChanged())

? ? ? ? ? ? ? ? return;

? //ArrayList轉(zhuǎn)換成一個臨時的數(shù)組略吨,這樣就防止了通知,添加考阱,移除同時發(fā)生可能導(dǎo)致的異常

? ? ? ? ? ? arrLocal = observers.toArray(new Observer[observers.size()]);

? ? ? ? ? ? //清除改變狀態(tài)翠忠,設(shè)置為false

? ? ? ? ? ? clearChanged();

? ? ? ? }

? ? ? ? //遍歷逐一通知

? ? ? ? for (int i = arrLocal.length-1; i>=0; i--)

? ? ? ? ? ? arrLocal[i].update(this,arg);

? ? }

? ? //清楚所有觀察者

? ? public synchronized void deleteObservers() {

? ? ? ? observers.clear();

? ? }

? ? //設(shè)置被觀察者為改變狀態(tài),設(shè)置為true

? ? protected synchronized void setChanged() {

? ? ? ? changed = true;

? ? }

? ? //清除改變狀態(tài)乞榨,設(shè)置為false

? ? protected synchronized void clearChanged() {

? ? ? ? changed = false;

? ? }

? ? //返回當(dāng)前的改變狀態(tài)

? ? public synchronized boolean hasChanged() {

? ? ? ? return changed;

? ? }

? ? //觀察者數(shù)量

? ? public synchronized int countObservers() {

? ? ? ? return observers.size();

? ? }

}

應(yīng)用場景

1.當(dāng)一個對象的改變需要通知其他對象改變時秽之,而且它不知道具體有多少個對象有待改變時。

2.當(dāng)一個對象必須通知其它對象吃既,而它又不能假定其它對象是誰考榨。

3.跨系統(tǒng)的消息交換場景,如消息隊(duì)列鹦倚、事件總線的處理機(jī)制河质。

優(yōu)點(diǎn)

1.解除觀察者與主題之間的耦合。讓耦合雙方都依賴于抽象申鱼,而不是依賴具體愤诱。從而使得自己的變換都不會影響另一邊的變換。

2.易于擴(kuò)展捐友,對同一主題新增觀察者時無需修改原有代碼。

缺點(diǎn)

1.依賴關(guān)系并未完全解除溃槐,抽象主題仍然依賴抽象觀察者匣砖。

2.使用觀察者模式時需要考慮一下開發(fā)效率和運(yùn)行效率的問題,程序中包括一個被觀察者昏滴、多個觀察者猴鲫,開發(fā)、調(diào)試等內(nèi)容會比較復(fù)雜谣殊,而且在Java中消息的通知一般是順序執(zhí)行拂共,那么一個觀察者卡頓,會影響整體的執(zhí)行效率姻几,在這種情況下势告,一般會采用異步實(shí)現(xiàn)抚恒。

3.可能會引起多余的數(shù)據(jù)通知。

Android中的源碼分析

1.控件中的Listener監(jiān)聽方式

Android中我們遇到最常用的觀察者模式就是各種控件的監(jiān)聽,如下:

Button button = findViewById(R.id.button);

button.setOnClickListener(new View.OnClickListener() {

? ? @Override

? ? public void onClick(View v) {

? ? ? ? Log.d(TAG, "onClick: ");

? ? }

});

上面的代碼中逸嘀,button就是具體的主題厘熟,也就是被觀察者屯蹦;new出來的View.OnClickListener對象就是具體的觀察者;OnClickListener實(shí)際上就是個接口绳姨,也就是抽象觀察者登澜;通過setOnClickListener把觀察者注冊到被觀察者中。

一旦button捕獲的點(diǎn)擊事件飘庄,即狀態(tài)發(fā)生變化的時候脑蠕,就會通過回調(diào)注冊的OnClickListener觀察者的onClick方法會來通知觀察者,Button狀態(tài)發(fā)生變化跪削。

1.相關(guān)源碼分析:

//抽象觀察者

public interface OnClickListener {

//只有onClick這個方法

? ? void onClick(View v);

}

public void setOnClickListener(@Nullable OnClickListener l) {

? ? if (!isClickable()) {

? //設(shè)置為可點(diǎn)擊

? ? ? ? setClickable(true);

? ? }

//把傳入的 OnClickListener 對象賦值給了 getListenerInfo().mOnClickListener,即mListenerInfo的mOnClickListener持有OnClickListener對象的引用

? ? getListenerInfo().mOnClickListener = l;

}

//返回ListenerInfo對象,這里是一個單例模式

ListenerInfo getListenerInfo() {

? ? if (mListenerInfo != null) {

? ? ? ? return mListenerInfo;

? ? }

? ? mListenerInfo = new ListenerInfo();

? ? return mListenerInfo;

}

//執(zhí)行點(diǎn)擊事件

public boolean performClick() {

? ? final boolean result;

? ? final ListenerInfo li = mListenerInfo;

? ? if (li != null && li.mOnClickListener != null) {

? ? ? ? playSoundEffect(SoundEffectConstants.CLICK);

? //執(zhí)行onClick方法,li.mOnClickListener即OnClickListener對象

? ? ? ? li.mOnClickListener.onClick(this);

? ? ? ? result = true;

? ? } else {

? ? ? ? result = false;

? ? }

? ? sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

? ? notifyEnterOrExitForAutoFillIfNeeded(true);

? ? return result;

}

2.Adapter的notifyDataSetChanged()方法

當(dāng)我們使用ListView時谴仙,需要更新數(shù)據(jù)時我們就會調(diào)用Adapter的notifyDataSetChanged()方法,那么我們來看看notifyDataSetChanged()的實(shí)現(xiàn)原理碾盐,這個方法是定義在BaseAdapter中具體代碼如下:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {

//數(shù)據(jù)集被觀察者

? ? private final DataSetObservable mDataSetObservable = new DataSetObservable();

? ? public boolean hasStableIds() {

? ? ? ? return false;

? ? }

? ? //注冊觀察者

? ? public void registerDataSetObserver(DataSetObserver observer) {

? ? ? ? mDataSetObservable.registerObserver(observer);

? ? }

//注銷觀察者

? ? public void unregisterDataSetObserver(DataSetObserver observer) {

? ? ? ? mDataSetObservable.unregisterObserver(observer);

? ? }

? ? //數(shù)據(jù)集改變時晃跺,通知所有觀察者

? ? public void notifyDataSetChanged() {

? ? ? ? mDataSetObservable.notifyChanged();

? ? }

}

//其他代碼略

由上面的代碼可以看出BaseAdapter實(shí)際上就是使用了觀察者模式,BaseAdapter就是具體的被觀察者毫玖。接下來mDataSetObservable.notifyChanged()的實(shí)現(xiàn):

public class DataSetObservable extends Observable<DataSetObserver> {

? ? public void notifyChanged() {

? ? ? ? synchronized(mObservers) {

? //遍歷所有觀察者掀虎,并調(diào)用他們的onChanged()方法

? ? ? ? ? ? for (int i = mObservers.size() - 1; i >= 0; i--) {

? ? ? ? ? ? ? ? mObservers.get(i).onChanged();

? ? ? ? ? ? }

? ? ? ? }

? ? }

//其他代碼略

}

現(xiàn)在我們看到了有觀察者額影子,那么這些觀察者時從哪里來的呢付枫?實(shí)際上這些觀察者是在ListView通過setAdapter()設(shè)置Adapter時產(chǎn)生的:

public class ListView extends AbsListView {

//其他代碼略

public void setAdapter(ListAdapter adapter) {

//如果已存在Adapter烹玉,先注銷該Adapter的觀察者

? ? if (mAdapter != null && mDataSetObserver != null) {

? ? ? ? mAdapter.unregisterDataSetObserver(mDataSetObserver);

? ? }

//其他代碼略

super.setAdapter(adapter);

if (mAdapter != null) {

? ? mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();

? ? mOldItemCount = mItemCount;

//獲取Adapter中的數(shù)據(jù)的數(shù)量

? mItemCount = mAdapter.getCount();

? checkFocus();

//創(chuàng)建一個數(shù)據(jù)集觀察者

? ? mDataSetObserver = new AdapterDataSetObserver();

//注冊觀察者

? ? mAdapter.registerDataSetObserver(mDataSetObserver);

//其他代碼略

}

}

從上面的代碼可以看到,觀察者有了阐滩,那么這個觀察者主要是干什么的呢二打?

class AdapterDataSetObserver extends DataSetObserver {

? ? private Parcelable mInstanceState = null;

? ? //觀察者的核心實(shí)現(xiàn)

? ? @Override

? ? public void onChanged() {

? ? ? ? mDataChanged = true;

? ? ? ? mOldItemCount = mItemCount;

//獲取Adapter中的數(shù)據(jù)的數(shù)量

? ? ? ? mItemCount = getAdapter().getCount();

? ? ? ? if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null

? ? ? ? ? ? ? ? && mOldItemCount == 0 && mItemCount > 0) {

? ? ? ? ? ? AdapterView.this.onRestoreInstanceState(mInstanceState);

? ? ? ? ? ? mInstanceState = null;

? ? ? ? } else {

? ? ? ? ? ? rememberSyncState();

? ? ? ? }

? ? ? ? checkFocus();

? ? ? ? //重新布局

? ? ? ? requestLayout();

? ? }

? ? //其他代碼略

}

最終就是在AdapterDataSetObserver這個類里面的onChanged()方法中實(shí)現(xiàn)了布局的更新。

簡單總結(jié)

當(dāng)ListView的數(shù)據(jù)發(fā)生變化時掂榔,我調(diào)用Adapter的notifyDataSetChanged()方法继效,這個方法又會調(diào)用所以觀察者(AdapterDataSetObserver)的onChanged()方法症杏,onChanged()方法又會調(diào)用requestLayout()方法重新進(jìn)行布局。

BroadcastReceiver

BroadcastReceiver作為Android的四大組件之一莲趣,實(shí)際上也是一個典型的觀察者模式鸳慈,通過sendBroadcast發(fā)送廣播時,只有注冊了相應(yīng)的IntentFilter的BroadcastReceiver對象才會收到這個廣播信息喧伞,其onReceive方法才會被調(diào)起走芋,BroadcastReceiver的代碼比較復(fù)雜

其他

另外,一些著名的第三方事件總線庫潘鲫,比如RxJava翁逞、RxAndroid、EventBus等等溉仑,也是使用了觀察者模式挖函,有興趣的可以去看一下他們的源碼。

工廠模式

定義

定義一個用于創(chuàng)建對象的接口浊竟,讓子類決定實(shí)例化哪個類

介紹

1.工廠方法模式屬于創(chuàng)鍵型模式怨喘。

2.工廠方法模式主要用來創(chuàng)建復(fù)雜的對象,簡單對象能夠使用new來創(chuàng)建就不用工廠方法模式來創(chuàng)建

UML類圖

角色說明

Product(抽象產(chǎn)品類):要創(chuàng)建的復(fù)雜對象振定,定義對象的公共接口必怜。

ConcreteProduct(具體產(chǎn)品類):實(shí)現(xiàn)Product接口。

Factory(抽象工廠類):該方法返回一個Product類型的對象后频。

ConcreteFactory(具體工廠類):返回ConcreteProduct實(shí)例梳庆。

實(shí)現(xiàn)

1.創(chuàng)建抽象產(chǎn)品類,定義公共接口:

//抽象產(chǎn)品類

public abstract class Product {

? ? public abstract void show();

}

2.創(chuàng)建具體產(chǎn)品類卑惜,繼承Product類:

//具體產(chǎn)品類A

public class ProductA extends Product {

? ? @Override

? ? public void show() {

? ? ? ? System.out.println("product A");

? ? }

}

//具體產(chǎn)品類B

public class ProductB extends Product {

? ? @Override

? ? public void show() {

? ? ? ? System.out.println("product B");

? ? }

}

3.創(chuàng)建抽象工廠類膏执,定義公共接口:

//抽象工廠類

public abstract class Factory {

? ? public abstract Product create();

}

4.創(chuàng)建具體工廠類,繼承抽象工廠類露久,實(shí)現(xiàn)創(chuàng)建具體的產(chǎn)品:

//具體工廠類A

public class FactoryA extends Factory {

? ? @Override

? ? public Product create() {

? ? ? ? //創(chuàng)建ProductA

? ? ? ? return new ProductA();

? ? }

}

//具體工廠類B

public class FactoryB extends Factory {

? ? @Override

? ? public Product create() {

? ? ? ? //創(chuàng)建ProductB

? ? ? ? return new ProductB();

? ? }

}

測試方法:

public void test() {

? ? //產(chǎn)品A

? ? FactoryA factoryA = new FactoryA();

? ? Product productA = factoryA.create();

? ? productA.show();

? ? //產(chǎn)品B

? ? FactoryB factoryB = new FactoryB();

? ? Product productB = factoryB.create();

? ? productB.show();

}

應(yīng)用場景

生成復(fù)雜對象時更米,無需知道具體類名,只需知道想應(yīng)的工廠方法即可毫痕。

優(yōu)點(diǎn)

1.符合開放封閉原則壳快。新增產(chǎn)品時,只需增加相應(yīng)的具體產(chǎn)品類和相應(yīng)的工廠子類即可

2.符合單一職責(zé)原則镇草。每個具體工廠類只負(fù)責(zé)創(chuàng)建對應(yīng)的產(chǎn)品。

缺點(diǎn)

1.一個具體工廠只能創(chuàng)建一種具體產(chǎn)品瘤旨。

2.增加新產(chǎn)品時梯啤,還需要增加相應(yīng)的工廠類,系統(tǒng)類的個數(shù)將成對增加存哲,增加了系統(tǒng)的復(fù)雜度和性能開銷

3.引入的抽象類也會導(dǎo)致類結(jié)構(gòu)的復(fù)雜化

Android中的源碼分析

Android中的ThreadFactory就是使用了工廠方法模式來生成線程的因宇,線程就是ThreadFactory的產(chǎn)品

ThreadFactory相關(guān)源碼分析

//抽象產(chǎn)品:Runnable

public interface Runnable {

? ? public abstract void run();

}

//具體產(chǎn)品:Thread

public class Thread implements Runnable {

//構(gòu)造方法

public Thread(Runnable target, String name) {

? init(null, target, name, 0);

}

@Override

//實(shí)現(xiàn)抽象產(chǎn)品的抽象方法

public void run() {

? ? if (target != null) {

? ? ? ? ? ? target.run();

? ? }

}

//其他代碼略

}

//抽象工廠:ThreadFactory

public interface ThreadFactory {

? ? Thread newThread(Runnable r);

}

//具體工廠:AsyncTask中的實(shí)現(xiàn)

private static final ThreadFactory threadFactory = new ThreadFactory() {

? ? private final AtomicInteger mCount = new AtomicInteger(1);

? ? //實(shí)現(xiàn)抽象工廠的抽象方法

? ? @Override

? ? public Thread newThread(Runnable r) {

? ? ? ? //返回Thread這個產(chǎn)品

? ? ? ? return new Thread(r,"AsyncTask #"+mCount.getAndIncrement());

? ? }

};

總結(jié)

1.這里只要是介紹Android系統(tǒng)中工廠模式的應(yīng)用七婴,線程和AsyncTask的原理就不說了。

2.通過ThreadFactory察滑,我們可以創(chuàng)建出不同的Thread來打厘。

3.同樣,我們可以創(chuàng)建另外類似的工廠贺辰,生產(chǎn)某種專門的線程户盯,非常容易擴(kuò)展。

責(zé)任鏈模式

定義

一個請求沿著一條“鏈”傳遞饲化,直到該“鏈”上的某個處理者處理它為止莽鸭。

介紹

1.責(zé)任鏈模式屬于行為型模式。

2.多個對象中吃靠,每個對象都持有下一個對象的引用硫眨,這就構(gòu)成了鏈這種結(jié)構(gòu)。

3.一個請求通過鏈的頭部巢块,一直往下傳遞到鏈上的每一個結(jié)點(diǎn)礁阁,直到有某個結(jié)點(diǎn)對這個請求做出處理為止,這就是責(zé)任鏈模式族奢。

4.責(zé)任鏈模式一般分為處理者與請求者姥闭。具體的處理者分別處理請求者的行為。

UML類圖

角色說明

Handler(抽象處理者):抽象類或者接口歹鱼,定義處理請求的方法以及持有下一個Handler的引用泣栈。

ConcreteHandler1,ConcreteHandler2(具體處理者):實(shí)現(xiàn)抽象處理類弥姻,對請求進(jìn)行處理南片,如果不處理則轉(zhuǎn)發(fā)給下一個處理者。

Client(客戶端):即要使用責(zé)任鏈模式的地方庭敦。

實(shí)現(xiàn)

以送快遞為例疼进,單個快遞員只負(fù)責(zé)某個片區(qū)的快遞,若某個快遞目的地不屬于當(dāng)前的片區(qū)秧廉,則交給下一個快遞員來處理伞广,直到有人處理為止。

創(chuàng)建抽象處理者類

定義處理請求的方法以及持有下一個Handler的引用:

//快遞員抽象類

public abstract class Postman {

? ? //下一個快遞員

? ? protected Postman nextPostman;

? ? //派送快遞

? ? public abstract void handleCourier(String address);

}

創(chuàng)建具體處理者類

實(shí)現(xiàn)抽象處理者類中的方法:

//北京快遞員

public class BeijingPostman extends Postman{

? ? @Override

? ? public void handleCourier(String address) {

? ? ? ? //北京地區(qū)的則派送

? ? ? ? if (address.equals("Beijing")){

? ? ? ? ? ? System.out.println("派送到北京");

? ? ? ? ? ? return;

? ? ? ? }else{

? ? ? ? ? ? //否則交給下一個快遞員去處理

? ? ? ? ? ? nextPostman.handleCourier(address);

? ? ? ? }

? ? }

}

//上禾鄣纾快遞員

public class ShanghaiPostman extends Postman{

? ? @Override

? ? public void handleCourier(String address) {

? ? ? ? if(address.equals("Shanghai")){

? ? ? ? ? ? System.out.println("派送到上海");

? ? ? ? ? ? return;

? ? ? ? }else{

? ? ? ? ? ? nextPostman.handleCourier(address);

? ? ? ? }

? ? }

}

//廣州快遞員

public class GuangzhouPostman extends Postman{

? ? @Override

? ? public void handleCourier(String address) {

? ? ? ? if(address.equals("Guangzhou")){

? ? ? ? ? ? System.out.println("派送到廣州");

? ? ? ? ? ? return;

? ? ? ? }else{

? ? ? ? ? ? nextPostman.handleCourier(address);

? ? ? ? }

? ? }

}

客戶端測試

public void test() {

? ? //創(chuàng)建不同的快遞員對象

? ? BeijingPostman beijingPostman = new BeijingPostman();

? ? ShanghaiPostman shanghaiPostman = new ShanghaiPostman();

? ? GuangzhouPostman guangzhouPostman = new GuangzhouPostman();

? ? //創(chuàng)建下一個結(jié)點(diǎn)

? ? beijingPostman.nextPostman = shanghaiPostman;

? ? shanghaiPostman.nextPostman = guangzhouPostman;

? ? //處理不同地區(qū)的快遞嚼锄,都是從首結(jié)點(diǎn)北京快遞員開始

? ? System.out.println("有一個上海快遞需要派送:");

? ? beijingPostman.handleCourier("Shanghai");

? ? System.out.println("有一個廣州快遞需要派送:");

? ? beijingPostman.handleCourier("Guangzhou");

? ? System.out.println("有一個美國快遞需要派送:");

? ? beijingPostman.handleCourier("America");

}

說明

1.上面的請求只是一個簡單的地址字符串蔽豺,如果是一些復(fù)雜的請求区丑,可以封裝成獨(dú)立的對象。如:普通快遞和生鮮快遞,生鮮快遞還需快遞員做冷鏈處理等等沧侥。

2.請求實(shí)際上可以從責(zé)任鏈中的任意結(jié)點(diǎn)開始可霎,即可以從上海快遞員開始處理也行宴杀。

3.責(zé)任鏈中的結(jié)點(diǎn)順序?qū)嶋H也可以調(diào)整癣朗,即北京->廣州->上海的順序也行。

4.責(zé)任鏈也可以過某些結(jié)點(diǎn)去處理請求旺罢,如北京->廣州,越過了上海旷余。

5.對于請求,只有兩種結(jié)果:一個某個結(jié)點(diǎn)對其進(jìn)行了處理主经,如上面例子上海荣暮、廣州快遞,這種叫純的責(zé)任鏈罩驻;另一個則是所以結(jié)點(diǎn)都不進(jìn)行處理穗酥,如美國的快遞,這種叫不純的責(zé)任鏈模式惠遏。我們所見到的基本都是不純的責(zé)任鏈砾跃。

應(yīng)用場景

1.多個對象處理同一請求時,但是具體由哪個對象去處理需要運(yùn)行時做判斷节吮。

2.具體處理者不明確的情況下抽高,向這組對象提交了一個請求。

優(yōu)點(diǎn)

1.代碼的解耦透绩,請求者與處理者的隔離分開翘骂。

2.易于擴(kuò)展,新增處理者往鏈上加結(jié)點(diǎn)即可帚豪。

缺點(diǎn)

1.責(zé)任鏈過長的話曲横,或者鏈上的結(jié)點(diǎn)判斷處理時間太長的話會影響性能底洗,特別是遞歸循環(huán)的時候腋寨。

2.請求有可能遍歷完鏈都得不到處理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粉寞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子烛亦,更是在濱河造成了極大的恐慌诈泼,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煤禽,死亡現(xiàn)場離奇詭異铐达,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)檬果,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門娶桦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事衷畦。” “怎么了知牌?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵祈争,是天一觀的道長。 經(jīng)常有香客問我角寸,道長菩混,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任扁藕,我火速辦了婚禮沮峡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘亿柑。我一直安慰自己邢疙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布望薄。 她就那樣靜靜地躺著疟游,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痕支。 梳的紋絲不亂的頭發(fā)上颁虐,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機(jī)與錄音卧须,去河邊找鬼另绩。 笑死,一個胖子當(dāng)著我的面吹牛花嘶,可吹牛的內(nèi)容都是我干的笋籽。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼察绷,長吁一口氣:“原來是場噩夢啊……” “哼干签!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拆撼,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤容劳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后闸度,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體竭贩,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年莺禁,在試婚紗的時候發(fā)現(xiàn)自己被綠了留量。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖楼熄,靈堂內(nèi)的尸體忽然破棺而出忆绰,到底是詐尸還是另有隱情,我是刑警寧澤可岂,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布错敢,位于F島的核電站,受9級特大地震影響缕粹,放射性物質(zhì)發(fā)生泄漏稚茅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一平斩、第九天 我趴在偏房一處隱蔽的房頂上張望亚享。 院中可真熱鬧,春花似錦绘面、人聲如沸欺税。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽魄衅。三九已至,卻和暖如春塘辅,著一層夾襖步出監(jiān)牢的瞬間晃虫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工扣墩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哲银,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓呻惕,卻偏偏與公主長得像荆责,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子亚脆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

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