《Head First 設計模式》:代理模式

正文

一、定義

代理模式爲另一個對象提供一個替身或佔位符以控制對這個對象的訪問。

要點:

  • 代理模式爲一個對象創建了代理對象,讓代理對象控制對該對象的訪問。被代理的對象可以是遠程的對象、創建開銷大的對象或者需要安全控制的對象。
  • 代理類型:遠程代理、虛擬代理、保護代理等。
    • 遠程代理:控制訪問遠程對象。
    • 虛擬代理:控制訪問創建開銷大的資源。
    • 保護代理:基於權限控制對資源的訪問。

二、實現步驟

1、創建主題接口

/**
 * 主題接口
 */
public interface Subject {

    /**
     * 請求主題執行某種動作
     */
    void request();
}

2、創建真實主題(被代理類),並繼承主題接口

真實主題是真正做事的主題對象。

/**
 * 真正做事的真實主題(被代理類)
 */
public class RealSubject implements Subject {

    @Override
    public void request() {
        System.out.println("RealSubject do something...");
    }
}

3、創建主題代理(代理類),並繼承主題接口

主題代理持有真正做事的真實主題,並控制對真實主題的訪問。

/**
 * 主題代理(代理類)
 */
public class SubjectProxy implements Subject {

    /**
     * 持有真實主題
     */
    RealSubject realSubject;
    
    @Override
    public void request() {
        System.out.println("SubjectProxy receives and controls request, and entrust request to RealSubject...");
        // 代理並控制對真實主題的訪問,比如權限控制、訪問資源控制等
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        // ...
        
        // 將請求委託給真實主題
        realSubject.request();
    }
}

4、使用主題代理訪問主題

public class Test {

    public static void main(String[] args) {
        // 代理
        SubjectProxy proxy = new SubjectProxy();
        // 通過代理請求主題
        proxy.request();
    }
}

三、舉個栗子

1、背景

我們打算建立一個應用程序,用來展示你最喜歡的 CD 封面。CD 封面的圖我們可以從一些網站的在線服務中獲取。

唯一的問題是,限於連接帶寬和網絡負載,下載可能需要一些時間,所以在等待圖像加載時,應該顯示一些東西。我們也不希望在等待圖像時,整個應用程序被掛起。一旦圖像被加載完成,剛纔顯示的東西應該消失,然後圖像顯示出來。

2、實現

使用虛擬代理管理圖片的加載、顯示。

(1)創建圖片接口

/**
 * 圖片接口
 */
public interface Image {

    /**
     * 繪圖
     */
    void paint();
}

(2)創建真正做事的圖片類

/**
 * 真正進行圖片操作的真實圖片類
 */
public class RealImage implements Image {

    String url;
    String description;
    
    public RealImage(String url,  String description) {
        this.url = url;
        this.description = description;
        load(url);
    }
    
    @Override
    public void paint() {
        System.out.println("Image is painted");
    }

    /**
     * 模擬加載圖片
     */
    private void load(String url) {
        try {
            System.out.println("Loading image from: " + url);
            for (int i = 0; i < 10; i++) {
                System.out.print("===");
                Thread.sleep(200);
            }
            System.out.println("===>100%");
            System.out.println("Loading image is finished!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

(3)創建圖片代理

/**
 * 圖片代理
 */
public class ImageProxy implements Image {
    
    RealImage realImage;
    String imageUrl;
    /**
     * 是否已加載圖片
     */
    boolean hasLoaded = false;
    
    public ImageProxy(String imageUrl) {
        this.imageUrl = imageUrl;
    }

    @Override
    public void paint() {
        if (realImage != null) {
            // 有圖片,則直接繪圖
            realImage.paint();
        } else {
            // 沒有圖片,則先顯示加載中的信息,再去加載並繪製圖片
            System.out.println("Loading CD cover, please wait...");
            loadAndPaint();
        }
    }
    
    /**
     * 加載並繪製圖片
     */
    private void loadAndPaint() {
        if (hasLoaded) {
            return;
        }
        hasLoaded = true;
        // 加載圖片是一個比較耗時的操作,爲了避免程序阻塞,採用異步處理
        new Thread(new Runnable() {
            @Override
            public void run() {
                realImage = new RealImage(imageUrl, "CD Cover");
                // 加載完後,再進行繪圖
                realImage.paint();
            }
        }).start();
    }
}

(4)使用圖片代理進行繪圖

public class Test {

    public static void main(String[] args) throws InterruptedException {
        // 圖片代理
        ImageProxy imageProxy = new ImageProxy("https://www.jingqueyimu.com/images/xxx.jpg");
        // 使用圖片代理進行繪圖
        System.out.println("首次繪圖:");
        imageProxy.paint();
        Thread.sleep(3000);
        System.out.println("再次繪圖:");
        imageProxy.paint();
    }
}

四、Java 動態代理

1、創建主題接口

/**
 * 主題接口
 */
public interface Subject {

    /**
     * 請求主題執行某種動作
     */
    void request();
}

2、創建真實主題(被代理類),並繼承主題接口

/**
 * 真正做事的真實主題
 */
public class RealSubject implements Subject {

    @Override
    public void request() {
        System.out.println("RealSubject do something...");
    }
}

3、使用 Java 動態代理,代理真實主題

public class Test {

    public static void main(String[] args) {
        // 真實主題(被代理類)
        Subject realSubject = new RealSubject();
        
        // 調用處理器
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 代理實例的方法被調用時,最終會執行該方法
                System.out.println("InvocationHandler start to invoke...");
                // do something...
                // 調用真實主題(被代理類)的方法
                method.invoke(realSubject, args);
                // do something...
                return null;
            }
        };
        
        // 使用 Proxy 創建代理
        Subject subjectProxy = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(), 
                realSubject.getClass().getInterfaces(), 
                handler);
        
        // 通過代理髮起請求
        subjectProxy.request();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章