文章目錄
- Android開發學習筆記之設計模式——裝飾模式&橋接模式
-
- 裝飾模式
-
- 概述
- 應用場景
- 優缺點
- 實作
- 橋接模式
-
- 概述
- 應用場景
- 優缺點
- 實作
Android開發學習筆記之設計模式——裝飾模式&橋接模式
裝飾模式
概述
在開發過程中,我們可能會對某個類進行功能擴充,通常情況下,擴充方式存在以下兩種:
- 繼承:使用繼承機制是給現有類添加功能的一種有效途徑,通過繼承一個現有類可以使得子類在擁有自身方法的同時還擁有父類的方法。但是這種方法是靜态的,使用者不能控制增加行為的方式和時機;
- 組合:即将一個類的對象嵌入另一個對象中,通過組合的方式由另一個對象來決定是否調用嵌入對象的行為以便擴充自己的行為;
使用繼承的方式來進行功能擴充實作簡單,但是這種方式是靜态的,耦合度高,而且随着功能的增加,排列組合的方式會迅速增加進而導緻繼承的子類數量暴增。根據
合成複用
原則,我們應該盡可能少的使用繼承,而是使用組合的方式來實作功能上的擴充,使用組合關系來建立一個包裝對象(即裝飾對象)來包裹真實對象,并在保持真實對象的類結構不變的前提下,為其提供額外的功能,而這就是裝飾模式的目标。
裝飾模式即指在不改變現有對象結構的情況下,動态地給該對象增加一些職責(即增加其額外功能)的模式,它屬于對象結構型模式,又叫包裝器模式。
應用場景
- 當不能采用繼承的方式對系統進行擴充或者采用繼承不利于系統擴充和維護時,可以使用裝飾模式。不能采用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴充,為支援每一種組合将産生大量的子類,使得子類數目呈爆炸性增長;第二類是因為類定義不能繼承(如final類);
- 如果希望在不影響其它類的情況下,動态添加或撤銷功能時,我們可以使用裝飾模式。
優缺點
優點:
- 裝飾器是繼承的有力補充,比繼承靈活,在不改變原有對象的情況下,動态的給一個對象擴充功能,即插即用;
- 裝飾模式通過一種動态的方式來擴充一個對象的功能,通過配置檔案可以在運作時選擇不同的裝飾器,進而實作不同的行為;
- 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合。可以使用多個具體裝飾類來裝飾同一對象,得到功能更為強大的對象;
- 具體構件類與具體裝飾類可以獨立變化,使用者可以根據需要增加新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有代碼無須改變,符合
。開閉原則
缺點:
- 使用裝飾模式會使系統的類的數量增加,變得更為複雜,同時也會使排錯更加困難。
實作
裝飾模式的實作其實和代理模式的實作思路類似,不同的使代理模式強調的是提供相同的接口,而裝飾模式是加強接口。裝飾模式主要可以分為以下四部分:
- 抽象部件類:聲明裝飾器和被裝飾對象的公用接口,規範其職責;
- 具體部件類:被裝飾對象,實作了抽象部件類,定義了基本職責,通過裝飾類可以擴充其職責;
- 抽象裝飾類:繼承了抽象部件類,持有一個指向被裝飾對象即具體部件類的引用成員變量,可以通過其子類擴充被裝飾對象。
- 具體裝飾類:實作了抽象裝飾類,通過持有的被裝飾對象擴充其職責。
具體結構如下圖所示:
實際上,在Java中存在很多裝飾模式的實作,Java中的I/O标準庫就采用了裝飾模式,其中,InputStream 的子類 FilterInputStream,OutputStream 的子類 FilterOutputStream,Reader 的子類 BufferedReader 以及 FilterReader,還有 Writer 的子類 BufferedWriter、FilterWriter 以及 PrintWriter 等,它們都是抽象裝飾類。可以通過裝飾類來增強類的功能。
我們可以模拟實作一個加密的資料讀取裝飾類,首先,我們需要建構一個簡單的資料讀取類作為被裝飾對象,具體如下:
/**
* 抽象部件類,聲明了讀寫方法
*/
interface DataSource {
fun writeData(data : String)
fun readData(): String
}
/**
* 具體部件類,被裝飾對象,實作了基本讀寫方法
*/
class FileDataSource(private val file: String) : DataSource{
override fun writeData(data: String) {
//TODO 實作寫資料
println("寫入資料${data}到檔案${file}中")
}
override fun readData(): String {
//TODO 實作讀資料
println("從檔案${file}中讀取資料")
return ""
}
}
我們可以看到,在上述代碼中,我們實作了一個基本檔案讀寫類,此時,我們需要對該類進行功能擴充,實作加密的功能,此時,我們可以利用裝飾模式,先建立一個抽象的裝飾器,其繼承了
DataSource
并且持有一個
DataSource
對象,如下:
/**
* 抽象的裝飾器,持有一個DataSource對象,這就是被裝飾對象
*/
abstract class DataSourceDecorator(protected val dataSource: DataSource) : DataSource
然後,基礎抽象裝飾類,實作其職責方法,并且進行功能加強,具體如下:
/**
* 具體的裝飾器,通過持有的DataSource對象實作基本職責,并對其進行功能加強
*/
class EncryptionDecorator(dataSource: DataSource) : DataSourceDecorator(dataSource){
override fun writeData(data: String) {
dataSource.writeData(encode(data))//寫入加密後的資料
}
override fun readData(): String {
return decode(dataSource.readData())
}
private fun encode(data: String) : String{
//TODO 傳回加密後的資料
println("加密資料${data}")
return data
}
private fun decode(data: String): String{
//TODO 傳回解密後的資料
println("解密資料${data}")
return data
}
}
如此一來,我們就實作了一個簡單的裝飾器模式,在使用
FileDataSource
類時,如果需要,我們可以通過
EncryptionDecorator
對其進行裝飾,進而實作加密功能,如下:
fun main() {
val test = FileDataSource("file.txt")
test.writeData("test message")
test.readData()
//使用加密
val decorator = EncryptionDecorator(test)
decorator.writeData("decorator test message")
decorator.readData()
}
輸出結果如下:
寫入資料test message到檔案file.txt中
從檔案file.txt中讀取資料
加密資料decorator test message
寫入資料decorator test message到檔案file.txt中
從檔案file.txt中讀取資料
解密資料
從上述的代碼中,我們可以看到,使用裝飾者模式相比繼承的方式,最大的差別就是動态添加功能,我們可以随時在需要的時候使用裝飾器進行功能擴充,同時,由于裝飾器
EncryptionDecorator
本身就繼承自
DataSource
,如果我們還需要在加密後進行壓縮等操縱,我們可以直接建立一個對應的壓縮裝飾器,然後再包一層,就可以同時實作加密和壓縮功能了,使用起來非常靈活。
在上述的介紹中,我們學習了裝飾模式的基本結構和實作方式,但是在實際的開發中,我們可以根據實際情況進行修改,比如,其四個部分并不是必須的,在一些情況下,我們完全可以對裝飾模式進行簡化,根據情況将抽象部件類和抽象裝飾類簡化掉,進而減少類的數量。
橋接模式
概述
我們知道對于類的功能擴充存在着繼承群組合兩種方式,而橋接模式就是利用組合的方式将抽象和實作分離,進而降低耦合度,提高可擴充性。
首先,我們需要了解抽象部分和實作部分,這和我們程式設計語言中的抽象和接口實作無關,實際上這個抽象部分和實作部分的描述更像是在開發系統中功能上的不同部分的區分。抽象部分 (也被稱為接口) 是一些實體的高階控制層。 該層自身不完成任何具體的工作, 它需要将工作委派給實作部分層 (也被稱為平台)。在實際的程式中, 抽象部分是圖形使用者界面 (GUI), 而實作部分則是底層作業系統代碼 (API), GUI 層調用 API 層來對使用者的各種操作做出響應。
- 抽象部分:控制層,對外提供了功能接口,用于實作相關功能,但實際功能通過調用實作部分來完成。
- 實作部分:底層實作層,完成了功能的實際實作,提供了接口供抽象部分使用。
橋接模式即通過組合的方式将抽象部分與它的實作部分分離,使它們都可以獨立地變化。它是用組合關系代替繼承關系來實作,進而降低了抽象和實作這兩個可變次元的耦合度。
應用場景
- 如果一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜态的繼承聯系,通過橋接模式可以使它們在抽象層建立一個關聯關系。
- 抽象化角色和實作化角色可以以繼承的方式獨立擴充而互不影響,在程式運作時可以動态将一個抽象化子類的對象和一個實作化子類的對象進行組合,即系統需要對抽象化角色和實作化角色進行動态耦合。
- 一個類存在兩個獨立變化的次元,且這兩個次元都需要進行擴充。
- 雖然在系統中使用繼承是沒有問題的,但是由于抽象化角色和具體化角色需要獨立變化,設計要求需要獨立管理這兩者。
- 對于那些不希望使用繼承或因為多層次繼承導緻系統類的個數急劇增加的系統,橋接模式尤為适用。
優缺點
優點:
- 實作了抽象部分和實作部分的分離,提高了擴充性,在兩個變化次元中任意擴充一個次元,都不需要修改原有系統;
- 實作細節對客戶透明,可以對使用者隐藏實作細節;
- 抽象部分專注于處理高層邏輯, 實作部分處理平台細節,符合
;單一職責原則
- 不使用繼承,而是使用組合的方式實作,符合
;合成複用原則
- 你可以新增抽象部分和實作部分, 且它們之間不會互相影響,符合
。開閉原則
缺點:
- 由于聚合關系建立在抽象層,要求開發者針對抽象化進行設計與程式設計,能正确地識别出系統中兩個獨立變化的次元,這增加了系統的了解與設計難度。
實作
對于橋接模式的實作也可以分為四個部分,如下:
- 抽象部分:持有實作部分引用,提供高層控制邏輯接口;
- 具體的抽象角色:依賴于實作部分實作了控制邏輯接口業務邏輯;
- 實作部分:定義實作化角色的接口,供擴充抽象化角色調用;
- 具體實作角色:給出實作部分功能接口的具體實作。
具體如下圖所示:
對于橋接模式的實作,其首先在于區分好抽象角色和具體角色以及對其接口的設計。例如,存在一個形狀
Shape
類,有包括矩形
Rect
和圓形
Circle
,同時又存在不同的顔色
Color
,如紅色
Red
和綠色
Green
等,每個形狀都可能存在着不同的顔色,此時,如果使用繼承的形式,就可能存在很多的組合方式,是以我們可以使用橋接模式來實作。首先,我們可以分析,在該例中存在形狀和顔色兩個次元,其中最終呈現出來的應該是一個帶有顔色的形狀,是以其抽象部分為形狀,實作部分為顔色,首先,我們可以建立抽象部分和實作部分的抽象類
Shape
和
Color
類如下:
/**
* 抽象部分,持有一個實作部分的引用
*/
abstract class Shape(protected val color: Color) {
/**
* 抽象部分接口
*/
abstract fun draw()
}
/**
* 實作部分
*/
interface Color{
/**
* 實作部分的接口
*/
fun fill()
}
其實作如下:
/**
* 抽象部分實作
*/
class Rect(color: Color) : Shape(color){
override fun draw() {
println("畫一個矩形")
color.fill()
}
}
/**
* 實作角色
*/
class Red : Color{
override fun fill() {
println("填充紅色")
}
}
然後,我們隻需要根據形狀和顔色在兩個次元分别進行擴充,兩個次元互相獨立,互不影響,我們可以建立出不同顔色的各種形狀。