代理模式(Proxy Pattern)是一種結構型設計模式,結構型模式描述如何將類或對象按某種佈局組成更大的結構。它允許你提供一個代理對象來控制對另一個對象的訪問。代理對象擁有與實際對象相同的介面,因此它可以被用來代替實際對象。 ...
前言
設計模式是一種高級編程技巧,也是一種通用的解決方案。它能在不同的應用場景中使用,它可以提高代碼的可讀性、可復用性和可維護性。設計模式的學習能提高我們的編程能力以及代碼質量,同時也能提高我們的開發效率,減少代碼的維護成本。
掌握設計模式對於開發軟體而言是非常重要的,熟練地應用設計模式能讓我們更加自信地去編寫程式,另一方面,這也對我們的面試至關重要,這可是妥妥的加分項呢。所以為了我們的代碼質量和未來發展而言,我們都要對設計模式有一定的瞭解和應用。
設計模式有23種,在一篇文章中全部講完肯定是不可能的,這篇文章會先介紹 代理模式(Proxy Pattern)
,本文的代碼示例用的是 Swift 語言編寫。
代理模式(Proxy Pattern)
代理模式(Proxy Pattern)是一種結構型設計模式,結構型模式描述如何將類或對象按某種佈局組成更大的結構。它允許你提供一個代理對象來控制對另一個對象的訪問。代理對象擁有與實際對象相同的介面,因此它可以被用來代替實際對象。
代理對象可以在調用實際對象之前或之後執行一些額外的操作,例如記錄日誌、緩存數據、控制訪問許可權等。這種模式通常被用於遠程代理、虛擬代理(Virtual Proxy)、保護代理和延遲載入等應用場景。
代理模式:為其他對象提供一種代理以控制對這個對象的訪問。
最 初 的 定 義 出 現 於 《設 計 模 式 》 ( A d d i s o n - W e s l e y , 1 9 9 4 ) 。
代碼示例
代理模式(Proxy Pattern)通用類圖
Subject
主題介面,定義了真實主題和代理主題的公共介面,客戶端可以通過主題介面來訪問真實主題或者代理主題。RealSubject
真實主題,實現了主題介面,定義了真正的業務邏輯。Proxy
代理主題,也實現了主題介面,同時還持有了一個真實主題的引用,客戶端通過代理主題來訪問真實主題,代理主題可以對訪問進行控制。
以下是通用代碼:
protocol Subject {
func request()
}
class RealSubject: Subject {
func request() {
print("RealSubject handling request")
}
}
class Proxy: Subject {
private let realSubject = RealSubject()
func request() {
print("Proxy handling request")
realSubject.request()
}
}
客戶端調用代碼:
let subject = Proxy()
subject.request()
偽代碼(老默,我想吃魚了)
突然想到了一個很有趣的例子,比如說我現在想吃魚,那吃魚得先去菜市場把魚買回來吧,一般來說都是自己去菜市場里買魚。但是我現在只想吃魚而不想親自去買魚,那我就和代理人說一聲,說我想吃魚了,代理人就會代替我去菜市場把魚帶回來給我,亦或者是代理人找的別人去買也是可以的。偽代碼如下所示:
protocol 主題介面 {
func 去買魚()
}
class 老默: 主題介面 {
func 去買魚() {
print("把魚買回來了")
}
}
class 代理人: 主題介面 {
private let 我是老默 = 老默()
func 去買魚() {
我是老默.去買魚()
}
}
客戶端調用的偽代碼如下所示:
let 我是一個代理 = 代理類()
我是一個代理.去買魚()
每次想吃魚我們就找到代理人,由它來安排,至於誰去我也不在乎。
虛擬代理(Virtual Proxy)
我們拿虛擬代理(Virtual Proxy)來作為例子講解說明,它是代理模式(Proxy Pattern)的一種,它在代理模式的應用中比較常見。
虛擬代理(Virtual Proxy)控制訪問創建開銷大的資源,其通過在代理對象和實際對象之間添加一層代理層,來實現對實際對象的延遲載入或緩存。
ImageLoader
定義一個協議,規定了代理對象和實際對象需要實現的方法。RealImageLoader
是遵循ImageLoader
的實際對象,是實際做事的圖片載入器。ImageLoaderProxy
是遵循ImageLoader
的代理對象,它持有RealImageLoader
的引用,並且還實現了一個簡單的圖片緩存機制,以避免重覆下載相同的圖片。
我們首先編寫 ImageLoader
協議,定義相同的介面方法:
protocol ImageLoader {
func loadImage(url: URL, completion: @escaping (UIImage?) -> Void)
}
接下來,編寫圖片載入器 RealImageLoader
類,並遵循 ImageLoader
協議:
class RealImageLoader: ImageLoader {
func loadImage(url: URL, completion: @escaping (UIImage?) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
completion(nil)
return
}
let image = UIImage(data: data)
completion(image)
}.resume()
}
}
我們使用 URLSession
來執行一個非同步的網路請求,將圖片數據下載下來,並將其轉換為 UIImage
對象並傳遞給回調閉包。
接下來,編寫 ImageLoaderProxy
代理類:
class ImageLoaderProxy: ImageLoader {
private let realImageLoader = RealImageLoader()
private var cachedImages: [URL: UIImage] = [:]
func loadImage(url: URL, completion: @escaping (UIImage?) -> Void) {
if let cachedImage = cachedImages[url] {
completion(cachedImage)
} else {
completion(UIImage(named: "image_loading_bg"))
realImageLoader.loadImage(url: url) { [weak self] image in
guard let self = self else { return }
if let image = image {
self.cachedImages[url] = image
}
completion(image)
}
}
}
}
當客戶端調用 loadImage(url:completion:)
方法時,代理對象首先會判斷是否已經緩存了對應的圖片,如果已經緩存,則直接返回緩存的圖片,否則先傳遞一個預設的載入圖片用於顯示過渡,再使用真正的圖片載入器 RealImageLoader
載入圖片,併在載入完成後緩存圖片。
最後編寫客戶端的調用代碼:
let imageLoader: ImageLoader = ImageLoaderProxy()
if let url = URL(string: "https://img2.baidu.com/it/u=2082637540,462915030&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=888") {
imageLoader.loadImage(url: url) { [weak self] image in
DispatchQueue.main.async {
self?.imageView.image = image
}
}
}
這裡需要註意的是,imageLoader在網路請求的過程中不能被釋放,否則閉包里的 self 會為空,你可以放在 controller 的屬性當中。
DispatchQueue.main.async
方法使線程回到主線程執行圖片的載入。
效果呈現
建議在開發者工具當中設置你的網路,將其設置為低速率的,這樣方便我們查看效果。
總結
代理模式(Proxy Pattern)允許你提供一個代理對象來控制對另一個對象的訪問,隱藏具體的實現細節,一定程式上降低了系統的耦合度。可以起到保護目標對象的傷。可以對目標對象的功能增加,如本文介紹虛擬代理使用的圖片載入例子,在其中加入了圖片緩存就是這個形式。
當然它也是有缺點的,使用代理模式(Proxy Pattern)可能會使類的數量增加,也可能會增加代碼的複雜度,因為它涉及到多個對象之間的協作。代理模式可能會導致有性能損失,因為客戶端需要通過代理對象來訪問真實對象,從而增加了額外的開銷。
結語
本文章的代碼已經整理到 GitHub 上,請點擊鏈接獲取。
記得以前看過的一個新聞,有一句結尾的話印象挺深刻的,分享出來給大家:“時間過得真系快,又系時候講拜拜”。話題轉回來,我會定期發佈一些技術文章,如果這篇文章對你有幫助,請你關註我。
關於作者
博文作者:GarveyCalvin
公眾號:凡人程式猿
本文版權歸作者所有,歡迎轉載,但必須保留此段聲明,並給出原文鏈接,謝謝合作!