Java設計模式——代理模式
前言:
上一篇說到了策略模式。單類圖上來說,它和本篇要說的代理模式還真是有些像似。都需要一個公用的介面,還有一些實作類別。代理類(封裝類)封裝了一個介面對象,提供用戶端調用。這些都很類似。不過,有一個細節需要我們注意一下,那就是這裡的代理類也需要去繼承這裡的公用介面。而在策略模式中,封裝類則不需要這麼做。
概述:
代理模式就是定義一個原對象的代理對象,來協助原對象和系統之外的業務作溝通。也就是說,如果我們不能直接或是不願直接去使用原對象,那麼我們就可以使用建立一個原對象的代理來進行操作。
模式說明:舉例--話題訪問1.背景
現在我們假設我們要構建一個網站。這個網站允許三類使用者訪問,分別是:普通註冊成員、站長管理員、遊客。
那麼這個時候,各個不同的訪問者對某一網頁的內容都有不同的許可權。比如,普通註冊成員可以訪問、發表和評論(回複)一個話題;站長管理員可以訪問、發表、評論以及刪除一個話題;而遊客則只能訪問這個網頁。
2.類圖
圖-1 代理模式類圖
根據上面例子中的需求,我們可以畫出圖-1中的類圖。不過雖然類圖中的三個實作類別都有實現介面中所有方法,不過其實這些實現方法卻不適合所有的訪問者對象。比如,Member雖然有remove方法,不過普通會員並不能真的刪除一個話題。所以這個控制就十分有必要了。
3.代碼及說明:1.公用介面
在公用介面中需要定義一些訪問者的基本行為。分別是訪問話題、發表話題、評論話題和刪除話題。
public interface Visitor { public void visit() throws RoleExcption; public void publish() throws RoleExcption; public void comment() throws RoleExcption; public void remove() throws RoleExcption;}
2.具體實作類別
本例中具體實作類別倒沒什麼太多的內容,只是重寫了實現介面中的方法。
public class Manager implements Visitor { @Override public void visit() { System.out.println("我是網站的管理員,我訪問了這個主題"); } @Override public void publish() { System.out.println("我是網站的管理員,我發表了一個主題"); } @Override public void comment() { System.out.println("我是網站的管理員,我評論了一個主題"); } @Override public void remove() { System.out.println("我是網站的管理員,我刪除了一個主題"); }}
3.代理類
代理類的作用還是比較重要,和必要的。他們負責來控制一個訪問者對話題的行為,防止越權的行為。比如一個遊客訪問者是不能發表和評論話題的。那麼可以通過代理中方法作控制方式來實現。如下:
public class ProxyVisitor implements Visitor { private Visitor visitor = null; public ProxyVisitor() { // 預設情況是遊客 visitor = new Tourist(); } public ProxyVisitor(Visitor _visitor) { visitor = _visitor; } @Override public void visit() throws RoleExcption { visitor.visit(); } @Override public void publish() throws RoleExcption { if (visitor instanceof Tourist) { throw new RoleExcption("遊客不能發表主題,請先註冊帳號"); } visitor.publish(); } @Override public void comment() throws RoleExcption { if (visitor instanceof Tourist) { throw new RoleExcption("遊客不能發表評論,請先註冊帳號"); } visitor.comment(); } @Override public void remove() throws RoleExcption { if (!(visitor instanceof Manager)) { throw new RoleExcption("只有管理員才可以刪除主題"); } visitor.remove(); }}
4.用戶端
這裡我們通過不同的訪問者進行不同的操作,觀察結果。
public class VisitorClient { public static void main(String[] args) { Manager manager = new Manager(); Member member = new Member(); Tourist tourist = new Tourist(); ProxyVisitor visitor = null; System.out.println("------------------- 1 --------------------"); try { visitor = new ProxyVisitor(manager); visitor.visit(); visitor.publish(); visitor.comment(); visitor.remove(); } catch (RoleExcption e) { System.err.println(e); } ThreadUtils.sleep(50); System.out.println("------------------- 2 --------------------"); try { visitor = new ProxyVisitor(member); visitor.visit(); visitor.publish(); visitor.comment(); visitor.remove(); } catch (RoleExcption e) { System.err.println(e); } ThreadUtils.sleep(50); System.out.println("------------------- 3 --------------------"); try { visitor = new ProxyVisitor(tourist); visitor.visit(); visitor.publish(); visitor.comment(); visitor.remove(); } catch (RoleExcption e) { System.err.println(e); } }}
5.測試結果 圖-2 網站訪問代理測試結果
舉例--學生考試1.背景
我們都經曆過學生時代,也都有考過試,當然也都有作過弊(什嗎?你沒有作過弊?那當我沒說...)。
學生考試是一個公開的行為,而學生作弊卻不能公開。所以,我們的考試方法是公有的,而作弊卻是私人的。可是,如果我們的用戶端程式需要我們在考試的時候來作一下弊。要怎麼辦呢?你可能會說,這好辦,反射啊。的確是這樣的,是要用到反射。不過這個反射我們封裝在了代理類裡面了,再從代理類裡把這個方法打成公開的方法。在下面的類圖中也會體現。
2.類圖
這個類圖跟上面話題訪問者的類圖很像。不過還是有一個地方不一樣,發現了沒有?那就是實作類別中有一個比介面類中多出的方法cheat(作弊),且是一個私人的方法,不過在代理類裡,這個方法被打成了公開的了。
圖-3 代理模式類圖
3.代碼及說明1.公用介面
說明同上
public interface Student { public void examinate(); public void announce(); }
2.具體實作類別
說明同上
public class SeniorStudent implements Student { private String name = ""; private int score = 0; public SeniorStudent(String _name) { name = _name; } @Override public void examinate() { score = NumberUtils.randomInteger(40, 150); } @SuppressWarnings("unused") private void cheat() { System.out.println(name + "正在作弊...噓..."); score += (NumberUtils.randomInteger(150 - score)); } @Override public void announce() { System.out.println(name + "考了" + score + "分"); }}
3.代理類
注意看這裡的cheat方法,是一個公開的方法。在這個方法裡,使用了Java的反射來實現對具體實作類別的私人方法進行訪問。
public class StudentProxy implements Student { private Student student = null; public StudentProxy(Student _student) { student = _student; } @Override public void examinate() { student.examinate(); } @Override public void announce() { student.announce(); } public void cheat() throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class clazz = Class.forName(student.getClass().getName()); Method method = clazz.getDeclaredMethod("cheat"); method.setAccessible(true); method.invoke(student); }}
4.用戶端
說明同上
public class StudentProxyClient { public static void main(String[] args) { StudentProxy proxy = new StudentProxy(new Pupil("小明")); proxy.examinate(); proxy.announce(); try { proxy.cheat(); } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } proxy.announce(); }}
5.測試結果
圖-4 學生考試代理測試結果
GitHub源碼下載:
https://github.com/William-Hai/DesignPattern-Proxy