プロキシパターンは、対象オブジェクトへのアクセスを間接的に制御する構造的デザインパターンです。主に機能拡張やセキュアなアクセス制御を実現するため、システムの柔軟性を高める目的で採用されます。
このパターンの核心は、クライアントが直接対象オブジェクトを操作せず、プロキシオブジェクトを経由してリクエストを転送する点にあります。具体的な利点として、既存機能への非侵襲的な拡張や、対象オブジェクトの実装詳細を隠蔽する物理的分離が挙げられます。
静的プロキシの実装例
コンパイル時に明示的に定義されるプロキシクラスを用いた実装です。以下は商品販売システムにおける仲介者パターンの実装例です。
package com.example.proxy;
interface ProductSelling {
void sellProduct(double amount);
}
class BrokerAgent implements ProductSelling {
private final ProductSelling manufacturer;
public BrokerAgent(ProductSelling manufacturer) {
this.manufacturer = manufacturer;
}
@Override
public void sellProduct(double amount) {
manufacturer.sellProduct(calculateCommission(amount));
}
private double calculateCommission(double amount) {
double commission = 10.0;
System.out.printf("仲介手数料: %.2f円%n", commission);
return amount - commission;
}
}
class Manufacturer implements ProductSelling {
@Override
public void sellProduct(double amount) {
System.out.printf("実収入: %.2f円%n", amount);
}
}
public class StaticProxyExample {
public static void main(String[] args) {
ProductSelling agent = new BrokerAgent(new Manufacturer());
agent.sellProduct(100.0);
}
}
この実装の課題は、対象インターフェースの変更が発生した場合にプロキシクラスの修正が必要となる点です。特に複数の対象オブジェクトを扱うシステムでは、メンテナンスコストが顕著に増加します。
動的プロキシの実装メカニズム
実行時にリフレクションを活用してプロキシインスタンスを生成するアプローチです。JDKのProxyクラスとInvocationHandlerインタフェースを組み合わせることで、対象オブジェクトの実行前後に横断的関心事を挿入できます。
package com.example.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Performance {
void perform(String piece);
void move(String location);
}
class Artist implements Performance {
@Override
public void perform(String piece) {
System.out.printf("演奏開始: %s%n", piece);
}
@Override
public void move(String location) {
System.out.printf("移動先: %s%n", location);
}
}
class PerformanceHandler implements InvocationHandler {
private final Object target;
public PerformanceHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[実行前] パフォーマンス準備中");
Object result = method.invoke(target, args);
System.out.println("[実行後] 観客拍手");
return result;
}
}
public class DynamicProxyExample {
public static void main(String[] args) {
Artist artist = new Artist();
PerformanceHandler handler = new PerformanceHandler(artist);
Performance proxy = (Performance) Proxy.newProxyInstance(
artist.getClass().getClassLoader(),
artist.getClass().getInterfaces(),
handler
);
proxy.perform("交響曲第5番");
proxy.move("ステージ中央");
}
}
動的プロキシの特徴として、対象オブジェクトのインターフェースに依存しない汎用的なハンドラ実装が可能です。リフレクションによるメソッド呼び出しのオーバーヘッドはあるものの、実行時の柔軟性と保守性の向上が大きなメリットです。
リフレクションを活用したメソッド実行
動的プロキシの基盤技術であるリフレクションの基本動作を示すサンプルです。
package com.example.proxy;
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
Method greetMethod = GreetingService.class.getMethod("greet", String.class);
greetMethod.invoke(new StandardGreeting(), "山田");
greetMethod.invoke(new FormalGreeting(), "佐藤");
}
}
interface GreetingService {
void greet(String name);
}
class StandardGreeting implements GreetingService {
@Override
public void greet(String name) {
System.out.printf("こんにちは、%sさん%n", name);
}
}
class FormalGreeting implements GreetingService {
@Override
public void greet(String name) {
System.out.printf("ごきげんよう、%s様%n", name);
}
}