デザインパターン　Proxyパターン

# Proxyパターン
特定のオブジェクトに対するアクセスや操作を、Proxy（代理）オブジェクトを介して行う方法。

Decorator パターンに似ている。

https://qiita.com/AsahinaKei/items/6fb3e76099514fb1757a

Decorator パターンが、既存のオブジェクトに対して透過的に新たな振る舞い、責務を追加することを目的としているのに対して、Proxy パターンは**アクセス制御を主な目的としている**。

また、Decorartor パターンではオブジェクトが何重にもラップされることがあるのに対して、Proxy パターンでは通常オブジェクトが何重にも重ねてラップされることはない。

ただし、Proxy パターンは、アクセス制御以外にも様々な目的で利用できる。

## 利用場面

#### アクセス制御・保護プロキシ
アクセス制御や権限管理に利用される。
Proxy を介入させることで、特定のユーザーに対してのみアクセスを許可し、他のユーザーからのアクセスに制限をかけることができる。

#### リモートアクセス制御・リモートプロキシ
Proxy が（リモート）サーバー上に存在するオブジェクトにアクセスするための手段として利用される。
クライアント側からは、クライアントと同じローカル環境に存在するオブジェクトを操作しているように見えるが、実際にはネットワーク越しのリモート環境のオブジェクトに対する操作が実行される。

#### 遅延初期化・仮想プロキシ
リソースの遅延初期化に利用される。
特定のオブジェクトの生成や初期化に時間がかかる場合、それらの処理をプロキシに行わせることでプログラムの効率化を図ることができる。

#### キャッシング
仮想プロキシの特殊な形式。
計算コストの高い操作結果を一時的に保存し、同じ操作が要求された時に結果をキャッシュから提供することで、ネットワークによる処置の遅延を軽減させることができる。
また、複数のクライアントが結果を共有することもできる。
また、複数のスレッドからのアクセスを安全に行うためのアクセス制御にも利用できる。

#### モック
Proxy がテスト時に本番用のオブジェクトの代わりに使用できるモックオブジェクトとなる。

## Proxyパターンの構成要素

#### Subject
主体、主題。
Proxy と RealSubject 共通のインターフェース（または抽象クラス）。

#### Proxy
RealSubject の代理。
RealSubject をラップすることで、クライアントからの要求を中継、または制御する。
**RealSubject への参照を保持する**（コンポジション）。

#### Real Subject
実際の処理を実装したオブジェクト。
Proxy によってラップされるため、クライアントから直接利用されなくなり、Proxy を介して利用されるようになる。

![Proxy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069520/8d63d076-d93a-508a-6ca2-cc09e76ce948.png)

## 保護プロキシの例

`RealSubject` が持つ `request1()`、`request2()` を使用不可にしたい場合、`Proxy` を利用することでアクセスを制御することができる。

```java:Subject.java
public interface Subject {
    public void request1();
    public void request2();
    public void request3();
}
```

```java:RealSubject.java
public class RealSubject implements Subject {

    @Override
    public void request1() {
        System.out.println("request1 を実行します。");
    }

    @Override
    public void request2() {
        System.out.println("request2 を実行します。");
    }

    @Override
    public void request3() {
        System.out.println("request3 を実行します。");
    }
}
```

```java:Proxy.java
public class Proxy implements Subject {
    // Real Subject を保持
    private Subject realSubject = new RealSubject();

    @Override
    public void request1() {
        // Proxy によって、使用不可のメソッドにできる（例外を発生させる方法もある）
        System.out.println("権限がありません。");
    }

    @Override
    public void request2() {
        // Real Subject に処理を転送する
        realSubject.request2();
    }

    @Override
    public void request3() {
        // Proxy によって、使用不可のメソッドにできる（例外を発生させる方法もある）
        System.out.println("権限がありません。");
    }
}
```

```java:Main.java
public class Main {
    public static void main(String[] args) {

        // Real Subject を使用した時
        Subject realSubject = new RealSubject();
        realSubject.request1();
        realSubject.request2();
        realSubject.request3();
        // >> request1 を実行します。
        // >> request2 を実行します。
        // >> request3 を実行します。


        // Proxy を利用した時
        Subject proxy = new Proxy();
        proxy.request1();
        proxy.request2();
        proxy.request3();
        // >> 権限がありません。
        // >> request2 を実行します。
        // >> 権限がありません。
    }
}
```

## 遅延初期化の例
遅延初期化は、`Proxy` の生成時に `RealSubject` を生成するのではなく、`request1()`、`request2()`、`request3()`のいずれかのリクエストが要求されたタイミングで生成を行う。

`RealSubject`の生成が非常に負荷のかかる処理であるために、アプリケーション起動時などに初期化処理を実行させたくない場合や、`RealSubject`が非常にサイズの大きなオブジェクトであるために、メモリを節約したい場合などに有効。

また、循環参照・循環依存（あるオブジェクトAが別のオブジェクトBに依存し、一方で、オブジェクトBもオブジェクトAに依存しているような状況）が発生している状況で、オブジェクトの初期化の順番の管理が複雑になってしまった場合にも有効。

```java:Proxy.java
public class Proxy implements Subject {

    private Subject realSubject;

    private Subject getRealSubject() {
        if (realSubject == null) {
            // Real Subject は初回リクエストを受けつけたタイミングで初めて生成される
            realSubject = new RealSubject();
        }

        return realSubject;
    }

    @Override
    public void request1() {
        getRealSubject().request1();
    }

    @Override
    public void request2() {
        getRealSubject().request2();
    }

    @Override
    public void request3() {
        getRealSubject().request3();
    }
}
```

## キャッシングの例
`RealSubject` が、通信を利用した重たい処理によって何らかのデータを取得しているような場合に、同じデータに対して複数回取得リクエストが行われたときに、２度目以降のリクエストでは通信を使用せず、`Proxy` が保持するキャッシュを利用させることができる。

```java:Subject.java
public interface Subject {
    public String fetchData(String key);
}
```

```java:RealSubject.java
public class RealSubject implements Subject {
    @Override
    public String fetchData(String key) {
        // 実際のデータを取得する処理（通信を利用した重たい処理）
        System.out.println("通信中...");
        System.out.println(key + "を使用して、データを取得します。。。");
        return key + "をもとに取得したデータ";
    }
}
```

```java:Proxy.java
import java.util.HashMap;
import java.util.Map;

public class Proxy implements Subject {

    private Subject realSubject;

    // データをキャッシュしながら保持するためのマップ
    private Map<String, String> cache = new HashMap<>();

    // コンストラクタが Real Subject を受け取る
    public Proxy(Subject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public String fetchData(String key) {
        // キャッシュにデータが存在する場合、キャッシュからデータを取得
        if (cache.containsKey(key)) {
            System.out.println("キャッシュから" + key + "に対応するデータを取得します。");
            return cache.get(key);
        } else {
            // キャッシュにデータが存在しない場合、RealSubject からデータを取得
            String data = realSubject.fetchData(key);
            // 取得したデータをキャッシュに保存
            cache.put(key, data);
            return data;
        }
    }
}
```

```java:Main.java
public class Main {
    public static void main(String[] args) {
        Subject realSubject = new RealSubject();
        Subject proxy = new Proxy(realSubject);

        System.out.println(proxy.fetchData("key1"));
        System.out.println(proxy.fetchData("key2"));
        System.out.println(proxy.fetchData("key1"));
        System.out.println(proxy.fetchData("key3"));
        System.out.println(proxy.fetchData("key2"));

        // >> 通信中...
        // >> key1を使用して、データを取得します。。。
        // >> key1をもとに取得したデータ

        // >> 通信中...
        // >> key2を使用して、データを取得します。。。
        // >> key2をもとに取得したデータ

        // >> キャッシュからkey1に対応するデータを取得します。
        // >> key1をもとに取得したデータ

        // >> 通信中...
        // >> key3を使用して、データを取得します。。。
        // >> key3をもとに取得したデータ

        // >> キャッシュからkey2に対応するデータを取得します。
        // >> key2をもとに取得したデータ
    }
}
```


# 動的Proxy
Proxy クラスが実行時に作成されるProxy パターン。

通常の静的な Proxy よりも、さらに柔軟性、拡張性が求められる場面で利用される。

Java では `java.lang.reflect` パッケージで動的プロキシがサポートされている。Proxy クラスは`java.lang.reflect`クラスによって**プログラム実行時に作成される**ため、開発者は Proxy クラスを実装する必要がない。

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/reflect/package-summary.html

## リフレクション

https://qiita.com/AsahinaKei/items/e5319b53e110e200e8dd

リフレクションはプログラムの**実行時に**クラス（型情報）やメソッドの情報にアクセスするための仕組みであり、動的Proxy を実現する上で重要な役割を果たしている。

Java はコンパイル型言語であるため、通常、型情報の解析は**コンパイル時に**行われる。リフレクションを使用した場合、型情報の解析が実行時に行われるため、通常よりもコストがかかる。

さらに、リフレクションを使用すると、非公開（プライベート）メソッドへのアクセスも可能なため、セキュリティ上のリスクがあることも理解しながら使用する必要がある。

## 動的 Proxy パターンの構成要素

#### Subject
Proxy と RealSubject 共通のインターフェース（または抽象クラス）。

#### Real Subject
実際の処理を実装したオブジェクト。

#### Proxy Class
Dynamic Proxy を生成するためのクラス。
Java では`java.lang.reflect.Proxy`クラスが Proxy Class の機能を持つ。
Dynamic Proxy オブジェクトを、静的メソッド `Proxy.newProxyInstance()` によってプログラム**実行時に**生成する。
> public static Object newProxyInstance(
> ClassLoader loader,　　　→　クラスローダー
> Class<?>[] interfaces,　　　→　インターフェース（ `Subject` ）
> InvocationHandler h　　　→　呼び出しハンドラ（ `InvocationHandler` ）
> ) throws IllegalArgumentException


#### Dynamic Proxy
開発者は実装しないクラス。
**実行時に**動的に生成される Real Subject の代理オブジェクト。
Dynamic Proxy は、メソッド実行のリクエストを受け取ると、Invocation Handler にそのリクエストを転送する。

#### Invocation Handler
呼び出しハンドラ。メソッドの呼び出しに対応する役割を持つ。
**Real Subject への参照を保持している**（コンポジション）。
Proxy と同様に、`java.lang.reflect`パッケージに含まれる。
Invocation Handler は、Dynamic Proxy から転送されたリクエストを処理する。
処理、と言うのは、メソッド名、引数などの情報をもとに、実行すべき処理を判断する。
メソッド名で判断する場合は「get」から始まるメソッドが呼ばれたとか、特定の文字列を含むメソッド名が呼ばれた、など。
引数で判断する場合は、引数の数や、引数の型など。

:::note info
**Invocation Handler** が実質的に Proxy の役割を果たす
:::

![Dynamic_Proxy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069520/02ddaf4b-a09e-1552-6356-0ac5f42ed224.png)

## 一連の流れ
![Dynamic_Proxy_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069520/9e28334f-3169-9380-e480-92e7eeff4926.png)

![Dynamic_Proxy_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069520/3de7b828-b660-c9c1-c8d3-007098cefc78.png)

![Dynamic_Proxy_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069520/a4893e37-941b-ba15-febc-235cdc4d3067.png)

![Dynamic_Proxy_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069520/05e7fe7f-f0f3-b959-1c07-f75cfbbc691c.png)

## 動的Proxy の例

```java:Subject.java
public interface Subject {
    public void request1();
    public void request2(String arg);
}
```

```java:RealSubject.java
public class RealSubject implements Subject {

    @Override
    public void request1() {
        System.out.println("request1 を実行します。");
    }

    @Override
    public void request2(String arg) {
        System.out.println("request2 を実行します。　引数：" + arg);
    }
}
```

```java:MyInvocationHandler.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    private Subject realSubject;

    public MyInvocationHandler(Subject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
        
            // メソッド名で判断する
            if (method.getName().contains("1")) {
                System.out.println("ここで代理処理を実行する");
                return method.invoke(realSubject, args);
                
            // 引数の型で判断する
            } else if (args[0] instanceof String) {
                System.out.println("ここで代理処理を実行する");
                return method.invoke(realSubject, args);
                
            }
        } catch (Exception e) {
            // Real Subject が例外を発生させた時の処理
            
        }

        // Real Subject の処理は実行されず、クライアントには null が返却される
        return null;
    }
}
```

```java:Main.java
import java.lang.reflect.Proxy;

public class Main {

    public static void main(String[] args) {

        // Real Subject
        Subject realSubject = new RealSubject();
        
        // 動的Proxy を生成する
        Subject proxy = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                new MyInvocationHandler(realSubject)
        );

        proxy.request1();
        // > ここで代理処理を実行する
        // > request1 を実行します。

        proxy.request2("val2");
        // > ここで代理処理を実行する
        // > request2 を実行します。　引数：val2
    }
}
```

# 参考
・<a href="https://amzn.asia/d/8MCJXIB" target="_blank" rel="noopener noreferrer">Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本</a>