對于開發(fā)人員來說叁熔,設(shè)計(jì)模式有時候就是一道坎,但是設(shè)計(jì)模式又非常有用沸移,過了這道坎黔州,它可以讓你水平提高一個檔次耍鬓。而在android開發(fā)中,必要的了解一些設(shè)計(jì)模式又是必須的流妻,因?yàn)樵O(shè)計(jì)模式在Android源碼中牲蜀,可以說是無處不在。對于想系統(tǒng)的學(xué)習(xí)設(shè)計(jì)模式的同學(xué)绅这,這里推薦一本書涣达,《大話設(shè)計(jì)模式》。
Android常用設(shè)計(jì)模式系列:
面向?qū)ο蟮幕A(chǔ)特征
面向?qū)ο蟮脑O(shè)計(jì)原則
單例模式
模板模式
適配器模式
工廠模式
代理模式
原型模式
策略模式
Build模式
觀察者模式
裝飾者模式
中介模式
門面模式
代理模式
裝飾者模式是非常常見的設(shè)計(jì)模式之一证薇,寫個筆記度苔,記錄一下我的學(xué)習(xí)過程和心得。
首先了解一些裝飾模者式的定義浑度。
動態(tài)地給一個對象添加一些額外的職責(zé)寇窑。就增加功能來說,裝飾模式相比生成子類更為靈活箩张。
- 裝飾者模式屬于結(jié)構(gòu)型模式甩骏。
- 裝飾者模式在生活中應(yīng)用實(shí)際上也非常廣泛,一如一間房先慷,放上廚具饮笛,它就是廚房;放上床,就是臥室论熙。
- 通常我們擴(kuò)展類的功能是通過繼承的方式來實(shí)現(xiàn)福青,但是裝飾者模式是通過組合的方式來實(shí)現(xiàn),這是繼承的替代方案之一脓诡。
涉及角色及說明:
Component(抽象組件):接口或者抽象類无午,被裝飾的最原始的對象。具體組件與抽象裝飾角色的父類祝谚。
ConcreteComponent(具體組件):實(shí)現(xiàn)抽象組件的接口指厌。
Decorator(抽象裝飾角色):一般是抽象類,抽象組件的子類踊跟,同時持有一個被裝飾者的引用踩验,用來調(diào)用被裝飾者的方法;同時可以給被裝飾者增加新的職責(zé)。
ConcreteDecorator(具體裝飾類):抽象裝飾角色的具體實(shí)現(xiàn)商玫。
就以裝修房間為例子箕憾,講一下實(shí)現(xiàn)。
1 創(chuàng)建抽象組件
這里是一個抽象房子類拳昌,定義一個裝修的方法:
public abstract class Room {
public abstract void fitment();//裝修方法
}
2 創(chuàng)建具體組件
現(xiàn)在有一間新房子袭异,已經(jīng)裝上了電:
public class NewRoom extends Room {//繼承Room
@Override
public void fitment() {
System.out.println("這是一間新房:裝上電");
}
}
3 創(chuàng)建抽象裝飾角色
要為房子裝修,定義抽象的房間裝飾類:
public abstract class RoomDecorator extends Room {//繼承Room炬藤,擁有父類相同的方法
private Room mRoom;//持有被裝飾者的引用御铃,這里是需要裝修的房間
public RoomDecorator(Room room) {
this.mRoom = room;
}
@Override
public void fitment() {
mRoom.fitment();//調(diào)用被裝飾者的方法
}
}
4 創(chuàng)建具體裝飾類
我們要將房間裝修成臥室和廚房碴里,其具體實(shí)現(xiàn)是不同的:
public class Bedroom extends RoomDecorator {//臥室類,繼承自RoomDecorator
public Bedroom(Room room) {
super(room);
}
@Override
public void fitment() {
super.fitment();
addBedding();
}
private void addBedding() {
System.out.println("裝修成臥室:添加臥具");
}
}
public class Kitchen extends RoomDecorator {//廚房類上真,繼承自RoomDecorator
public Kitchen(Room room) {
super(room);
}
@Override
public void fitment() {
super.fitment();
addKitchenware();
}
private void addKitchenware() {
System.out.println("裝修成廚房:添加廚具");
}
}
5 客戶端測試:
public void test() {
Room newRoom = new NewRoom();//有一間新房間
RoomDecorator bedroom = new Bedroom(newRoom);
bedroom.fitment();//裝修成臥室
RoomDecorator kitchen = new Kitchen(newRoom);
kitchen.fitment();//裝修成廚房
}
輸出結(jié)果:
這是一間新房:裝上電
裝修成臥室:添加臥具
這是一間新房:裝上電
裝修成廚房:添加廚具
廣泛應(yīng)用
我們都知道Activity
咬腋、Service
、Application
等都是一個Context
睡互,這里面實(shí)際上就是通過裝飾者模式來實(shí)現(xiàn)的根竿。下面以startActivity()
這個方法來簡單分析一下。
1 Context類
Context
實(shí)際上是個抽象類就珠,里面定義了大量的抽象方法寇壳,其中就包含了startActivity()
方法:
public abstract class Context {//抽象類
public abstract void startActivity(@RequiresPermission Intent intent);//抽象方法
//其他代碼略
}
2 ContextImpl類
Context
類的具體實(shí)現(xiàn)實(shí)際上是在ContextImpl
類,里面具體實(shí)現(xiàn)了startActivity()
方法:
class ContextImpl extends Context {
@Override
public void startActivity(Intent intent) {
warnIfCallingFromSystemProcess();
startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, Bundle options) {//具體實(shí)現(xiàn)原理這里就不分析了
warnIfCallingFromSystemProcess();
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
// be launched in.
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
//其他代碼略
}
3 ContextWrapper類
通常我們在Activity
妻怎、Service
里面調(diào)用startActivity()
方法壳炎,實(shí)際上是調(diào)用他們的父類ContextWrapper
里面的startActivity()
方法,我們先來看下Activity
逼侦、Service
的繼承關(guān)系:
可以看到Activity
匿辩、Service
都是繼承自ContextWrapper
,再來看看ContextWrapper
的代碼:
public class ContextWrapper extends Context {//Context包裝類
Context mBase;//持有Context引用
public ContextWrapper(Context base) {//這里的base實(shí)際上就是ContextImpl
mBase = base;
}
@Override
public void startActivity(Intent intent) {
mBase.startActivity(intent);//調(diào)用ContextImpl的startActivity()方法
}
//其他代碼略
}
4 總結(jié)
Context
類在這里就充當(dāng)了抽象組件的角色偿洁,ContextImpl
類則是具體的組件撒汉,而ContextWrapper
就是具體的裝飾角色沟优,通過擴(kuò)展ContextWrapper
增加不同的功能涕滋,就形成了Activity
、Service
等子類挠阁。最后账劲,放一張總的UML類圖幫助理解:
總結(jié)
裝飾模式 VS 繼承
- 裝飾模式:對于一個給定的對象呻引,同時可能有不同的裝飾對象,客戶端可以通過它的需要選擇合適的裝飾對象發(fā)送消息。
- 繼承:對于所有可能的聯(lián)合较剃,客戶期望很容易增加任何的 困難
裝飾模式 | 繼承 |
---|---|
用來擴(kuò)展特定對象的功能 | 用來擴(kuò)展一類對象的功能 |
不需要子類 | 需要子類 |
動態(tài)地 | 靜態(tài)地 |
運(yùn)行時分配職責(zé) | 編譯時分派職責(zé) |
防止由于子類而導(dǎo)致的復(fù)雜和混亂 | 導(dǎo)致很多子類產(chǎn)生,在一些場合蝉稳,報(bào)漏類的層次 |
更多的靈活性 | 缺乏靈活性 |
總結(jié)一下裝飾者模式的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
采用組合的方式破婆,可以動態(tài)的擴(kuò)展功能,同時也可以在運(yùn)行時選擇不同的裝飾器寻歧,來實(shí)現(xiàn)不同的功能掌栅。
有效避免了使用繼承的方式擴(kuò)展對象功能而帶來的靈活性差,子類無限制擴(kuò)張的問題码泛。
被裝飾者與裝飾者解偶猾封,被裝飾者可以不知道裝飾者的存在,同時新增功能時原有代碼也無需改變噪珊,符合開放封閉原則晌缘。
缺點(diǎn)
裝飾層過多的話齐莲,維護(hù)起來比較困難。
如果要修改抽象組件這個基類的話磷箕,后面的一些子類可能也需跟著修改选酗,較容易出錯。
適用場景
需要擴(kuò)展一個類的功能搀捷,或給一個類增加附加功能時
需要動態(tài)的給一個對象增加功能星掰,這些功能可以再動態(tài)的撤銷
當(dāng)不能采用繼承的方式對系統(tǒng)進(jìn)行擴(kuò)充或者采用繼承不利于系統(tǒng)擴(kuò)展和維護(hù)時。