設計模式-代理模式
代理模式的概念
代理模式(PRoxy pattern)是一種結構型的設計模式,代理模式在程序開發中的運用非常廣泛。簡單地描述代理模式就是:為其他對象(被代理對象)提供一種代理以控制對原有對象的操作。實際的行為是由被代理對象完成的。 代理模式可以分為兩部分,靜態代理 和 動態代理,它們的區別將在下面詳細介紹。
角色介紹:
Suject: 抽象主題類 該類的主要職責是申明真是主題與代理的共同接口方法,該類既可以是個抽象類也可以是個接口(具有抽象方法)。 RealSubject: 真實主題類 該類也稱為委托類或者被代理類,改類定義了代理所表示的真是對象(也就是實現了抽象方法),由其執行具體的業務邏輯。 ProxySubject:代理類 這個類的對象持有一個對真實主題的引用,在這個類所實現的接口方法中調用真實主題類中相應的方法執行,這樣就實現了代理的目的。 Client:客戶類 也就是使用代理類的類型,客戶類通過代理類間接地調用了真實主題類中定義的方法。
代理模式的實現
簡單的例子
針對,上方的角色介紹,舉一個簡單的例子:在現實的世界中,打公司一般有,原告 和原告的代理律師這樣兩個角色,他們要做的事情是 辯護。于是,我們可以實現以下幾個類。 抽象主題類:interface IDefender,這個接口中有個 抽象方法 abstract public void defend(); 表示辯護這個行為。
public interface IDefender { abstract public void defend();//辯護行為}123123真實主題類: class Accuser,實現IDefender這個接口,具有具體的邏輯行為
public class Accuser implements IDefender { @Override public void defend() { System.out.println("被告嚴重侵犯了公民的人身自由權...嗶哩嗶哩"); }}12345671234567代理類:AccuserProxy 類,原告請了個代理律師幫助它打官司,也就是AccuserProxy,由代理律師控制原告的表現
public class AccuserProxy implements IDefender { //持有被代理類的引用 private IDefender iDefend; public AccuserProxy(IDefender iDefend) { this.iDefend = iDefend; } @Override public void defend() { beforeDefend(); // 被代理類的行為 iDefend.defend(); afterDefend(); } //修飾的方法 private void beforeDefend(){ System.out.print("我是原告律師,以下是我方辯詞"); } //修飾的方法 private void afterDefend(){ System.out.print("辯護完畢"); }}123456789101112131415161718192021222324123456789101112131415161718192021222324客戶端:Client類,使用代理類的角色
public class Client { public static void main(String[] args) { Accuser accuser = new Accuser(); accuser.defend();//此時輸出 被告嚴重侵犯了公民的人身自由權...嗶哩嗶哩 AccuserProxy accuserProxy = new AccuserProxy(accuser); accuserProxy.defend(); // 輸出:我是原告律師,以下是我方辯詞,被告嚴重侵犯了公民的人身自由權...嗶哩嗶哩,辯護完畢 ///////////////////////////////////////////////////// // 因為,我們的代理類和被代理類 實現的是同一接口,如果將引用類型 寫成IDfender, // 那么在調用 defend(),方法的使用 客戶完全感覺不到被代理類的存在,當然因為我們這里的 // 被代理類是通過構造函數傳進去的,軟件開發中,有時候直接在被代理類中實例化代理類,這樣使用起來就更完美了。 IDefender accuser2 = new Accuser(); IDefender accuser2Proxy = new AccuserProxy(accuser2); accuser2.defend(); }}1234567891011121314151617181912345678910111213141516171819 代理模式的運用符合開閉原則的定義,軟件中的對象(類、模塊、函數)應該對于拓展是開發的,對于修改是封閉的。我們不需要修改被代理類 (Accuser),使用代理模式就可以實現對原有功能的加強。以上代碼只是一個簡答的運用場景,現實開發中代理模式的運用非常廣泛,它可以解決多方面的問題。
Androd 開發中的運用例子
Android API的版本迭代很快,在每次的版本更新中 通常會加強一些原有的功能,對原有的類會增加新的接口,而每個版本能夠調用的API 可能會都不同,比如 狀態欄 Notification 。
Notfification可以分為4類,一類是正常視圖,也就是我們通常在狀態欄看到的 高度為 64dp 的長條狀通知視圖;一類是在 API16 中引入的以 Style 方式展示的 MediaStyle、InboxStyle、BigTextStyle 和 BigPictureStyle 四種Notification 風格樣式;一類也是在 API16 中引入的可以將通知視圖顯示為256dp 高度大視圖的 bingContentView;最后一類是在 L 中引入的 headsUpContentVIew
現在,我們的 App需要根據不同的版本實例化不同 Notification 顯示不同的視圖,在高版本顯示的視圖當然就更豐富,低版本更單調。如果你直接在 Activity 中判斷版本,加上一坨的 switch 或者 if else 語句當然也是可以的。但是,我們現在來看一下 運用設計模式的思想,如何將代碼抽離,對于客戶端來說(Activity or Fragment),不應該關心這些細節。 以下的代碼來自《Android源碼設計模式解析與實戰》一書。
抽象主題類: Notify類, Notify抽象類 聲明了 NotificationManager 和NotificatoinCompat.Builder 2個成員變量來處理和通知的一些邏輯。在構造函數中初始化所有子類都會調用的邏輯方法。
public abstract class Notify { protected Context context; protected NotificationManager nm; protected NotificationCompat.Builder builder; public Notify(Context context){ this.context = context; nm = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE); builder = new NotificationCompat.Builder(context); builder.setSmallIcon(R.mipmap.ic_launcher) .setContentIntent(PendingIntent.getActivities( context, 0, new Intent[]{new Intent(context, NotifyActivity.class)}, PendingIntent.FLAG_UPDATE_CURRENT)); } /** * 發送一條通知 */ public abstract void send(); /** * 取消一條通知 */ public abstract void cancel();}1234567891011121314151617181920212223242512345678910111213141516171819202122232425真實主題類:NotifyNormal ,繼承抽象主題類,簡單重寫抽象方法
public class NotifyNormal extends Notify{ public NotifyNormal(Context context) { super(context); } @Override public void send() { Notification n = builder.build(); n.contentView = new RemoteViews(context.getPackageName(), R.layout.remote_notify_proxy_normal); nm.notify(0,n); } @Override public void cancel() { nm.cancel(0); }}123456789101112131415161718123456789101112131415161718真實主題類 : 與NormalNotify的不同在于 還實現了bigContentView 的初始化,這是在 API 16以上才能調用的
public class NotifyBig extends Notify{ public NotifyBig(Context context) { super(context); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) @Override public void send() { Notification n = builder.build(); n.contentView = new RemoteViews(context.getPackageName(), R.layout.remote_notify_proxy_normal); n.bigContentView = new RemoteViews(context.getPackageName(),R.layout.remote_notify_proxy_big); nm.notify(0,n); } @Override public void cancel() { nm.cancel(0); }}123456789101112131415161718192021123456789101112131415161718192021 源碼中還有個 NotifyHeadUp 類,它的唯一區別就是 在bigContentVie 的基礎上還能實現 Notification 的 headsUpContentView ,這個 View 是在 API 20以上 才能使用的,當我們的 App 以全屏的方式展開的時候如果收到了通知,這個 View 就會浮動展示于屏幕頂部。
代理類: NotifyProxy ,持有被代理類對象,在這個示例中,我們根據不同的運行時環境 API 實例化不同的 被代理類對象。
public class NotifyProxy extends Notify{ //代理類持有被代理類的引用 private Notify notify; public NotifyProxy(Context context) { super(context); //根據版本實例化不同的被代理對象 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ notify = new NotifyHeadUp(context); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ notify = new NotifyBig(context); } else { notify = new NotifyNormal(context); } } @Override public void send() { notify.send(); } @Override public void cancel() { notify.cancel(); }}12345678910111213141516171819202122232425261234567891011121314151617181920212223242526 客戶端類:也就是我們的Activity,在 Activity 中,我們直接將 Context 傳入,代理類會幫我們實例化合適的被代理對象。
new NotifyProxy(NotifyActivity.this).send();11 在這個實例中,我們通過代理模式 ,使用一個代理類來針對不同的運行時系統版本,實例化不同的 Notificaition 的子類,而在客戶端中 簡單地調用代理類的send方法就可以。
靜態代理模式總結
代理模式通過代理類對外部提供統一的接口,在代理類中實現對被代理類的附加操作,從而可以在不影響外部調用的情況下實現系統的拓展,我覺得代理模式可能在一個程序項目的開發初期運用不到,而在項目成型而又有了新的變化、升級等,可以考慮用代理模式來實現,這樣可以不需要修改原有的代碼。
動態代理模式
其實上文所講述的內容只是代理模式的一部分,代理模式還有更為強大的動態代理模式。以下是這 2 個的區別:
靜態代理模式:在我們的代碼運行前,代理類的class編譯文件就已經存在了 動態代理模式:在 code 階段并不存在被代理類,而且并不知道要代理哪個對象,利用 java 的反射機制在運行期動態地生成代理者的對象,代理誰將會在代碼執行階段決定。
動態代理模式的實現
Java 已經為我們提供了一個便捷的動態代理接口 InvocationHandler ,我們重寫其調用方法 invoke。以前面 我們的代理律師的例子為例,看看具體的操作是怎么樣的
public class DynamicProxy implements InvocationHandler{ private Object object;// 被代理類的類引用 public DynamicProxy(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { beforeDefend(); //調用被代理類對象的方法 Object result = method.invoke(object, args); afterDefend(); return result; } private void beforeDefend(){ System.out.print("我是原告律師,以下是我方辯詞"); } private void afterDefend(){ System.out.print("辯護完畢"); }}1234567891011121314151617181920212223242512345678910111213141516171819202122232425 解釋一下這個 invoke 方法,我們通過 invoke 方法來調用具體的被代理方法,也就是真實的方法,如果對反射機制了解的話,method.invoke(object,args) 這句代碼應該很好理解,object 是被代理類,method 是被代理類的方法,args 是方法的參數。
我們僅僅是實現了 InvocationHandler 的接口,那么接下來該做什么呢?怎么實現動態生成代理者對象呢?Java 的 java.lang.reflect 包下 還有一個 Proxy 類,它有個靜態方法 newProxyInstance(),我們使用這個方法生成。以前面 我們的代理律師的例子為例,
public class Client { public static void main(String[] args) { //被代理類 IDefender accuser = new Accuser(); accuser.defend(); DynamicProxy proxy = new DynamicProxy(accuser); ClassLoader loader = accuser.getClass().getClassLoader(); IDefender proxyIDefender = (IDefender) Proxy.newProxyInstance(loader, new Class[]{IDefender.class}, proxy); proxyIDefender.defend(); }}12345678910111213141234567891011121314 這個客戶端的輸出結果,和之前是一樣的,可以發現,我們將之前代理類的工作,轉換到 InvocationHandler 的 invoke() 方法去執行,不再需要關心到底需要代理誰。
動態代理在 Retrofit 框架中的運用
Retrofit 是 Android 上流行的 Http Client請求庫先看以下一段代碼
interface GitHubService { @GET("/repos/{owner}/{repo}/contributors") List<Contributor> repoContributors( @Path("owner") String owner, @Path("repo") String repo);}123456123456Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .build();//代理模式的運用GitHubService service = retrofit.create(GitHubService.class);1234512345Call<List<Repo>> repos = service.repoContributors("owner","repo");11 由于我們要研究的方向是動態代理模式,所以我們直接深入主題,看一下這段代碼
GitHubService service = retrofit.create(GitHubService.class);11 GitHubService 是個接口,它作為Retrofit.create()方法的參數傳入,這個方法的調用的返回對象 是個 GitHubService 的實例,那么它是怎么實現的呢?以下是 create()方法的代碼,看沒看到 Proxy.newProxyInstance() ,和 InvocationHandler()。同樣,在這里 Retrofit 通過 注解 和 動態代理,用戶只需要使用 注解 作用在抽象方法和抽象方法的參數上申明,這些 注解、方法參數、 方法返回值類型就提供了一次網絡請求所需的信息,而具體的操作是由Retrofit通過解析這些信息,在運行期生成代理對象去調用。
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } return loadMethodHandler(method).invoke(args); } }); }1234567891011121314151617181920212212345678910111213141516171819202122動態代理模式總結
動態代理模式在代碼的運行階段才生成 代理類對象,動態代理模式運用在需要對訪問做特殊處理,比如對某個方法的調用加入權限驗證;或者是對原來的方法進行統一的拓展,比如加入日志記錄等,代理模式還被運用在實現 AOP ,大家可以去了解一下。