なぜプロキシが必要なのでしょうか?
このパターンは、オブジェクトへのアクセス制御に関連する問題の解決に役立ちます。「なぜアクセス制御が必要なのでしょうか?」と疑問に思うかもしれません。何が何であるかを理解するのに役立ついくつかの状況を見てみましょう。例1
大量の古いコードを含む大規模なプロジェクトがあり、データベースからレポートをエクスポートするクラスがあると想像してください。クラスは同期的に動作します。つまり、データベースがリクエストを処理している間、システム全体はアイドル状態になります。レポートの生成には平均して 30 分かかります。したがって、エクスポート プロセスは午前 12 時 30 分に開始され、管理者は午前中にレポートを受け取ります。監査の結果、通常の営業時間内に報告書をすぐに受け取れる方がよいことが判明しました。開始時刻を延期することはできません。また、データベースからの応答を待つ間システムをブロックすることもできません。解決策は、システムの動作方法を変更し、別のスレッドでレポートを生成およびエクスポートすることです。このソリューションにより、システムは通常どおり動作し、管理者は最新のレポートを受け取ることができます。しかし、問題があります。システムの他の部分がその機能を使用しているため、現在のコードを書き直すことができません。この場合、プロキシ パターンを使用して、レポートのエクスポート要求を受信し、開始時刻を記録し、別のスレッドを起動する中間プロキシ クラスを導入できます。レポートが生成されると、スレッドは終了し、全員が満足します。例 2
開発チームはイベント Web サイトを作成しています。新しいイベントに関するデータを取得するために、チームはサードパーティ サービスにクエリを実行します。特別なプライベート ライブラリにより、サービスとの対話が容易になります。開発中に問題が発見されました。サードパーティ システムは 1 日に 1 回データを更新しますが、ユーザーがページを更新するたびにリクエストが送信されます。これにより、大量のリクエストが作成され、サービスが応答を停止します。解決策は、サービスの応答をキャッシュし、ページがリロードされるときにキャッシュされた結果を訪問者に返し、必要に応じてキャッシュを更新することです。この場合、プロキシ設計パターンは、既存の機能を変更しない優れたソリューションです。デザインパターンの背後にある原則
このパターンを実装するには、プロキシ クラスを作成する必要があります。これはサービス クラスのインターフェイスを実装し、クライアント コードの動作を模倣します。このようにして、クライアントは実際のオブジェクトではなくプロキシと対話します。原則として、すべてのリクエストはサービス クラスに渡されますが、その前後に追加のアクションが伴います。簡単に言えば、プロキシはクライアント コードとターゲット オブジェクトの間の層です。古くて非常に遅いハードディスクからクエリ結果をキャッシュする例を考えてみましょう。ロジックを変更できない古代のアプリの電車の時刻表について話しているとします。毎日決まった時刻に更新されたタイムテーブルを収録したディスクが挿入されます。したがって、次のようになります。TrainTimetable
インターフェース。ElectricTrainTimetable
、このインターフェイスを実装します。- クライアント コードは、このクラスを通じてファイル システムと対話します。
TimetableDisplay
クライアントクラス。そのprintTimetable()
メソッドはクラスのメソッドを使用しますElectricTrainTimetable
。

printTimetable()
、ElectricTrainTimetable
クラスはディスクにアクセスし、データをロードし、それをクライアントに提示します。システムは正常に機能しますが、非常に遅いです。その結果、キャッシュ メカニズムを追加することでシステムのパフォーマンスを向上させることが決定されました。これは、プロキシ パターンを使用して行うことができます。 
TimetableDisplay
と対話していることにさえ気づきません。ElectricTrainTimetableProxy
新しい実装では、1 日に 1 回タイムテーブルがロードされます。繰り返しリクエストの場合は、以前にメモリからロードされたオブジェクトを返します。
プロキシに最適なタスクは何ですか?
このパターンが確実に役立つ状況をいくつか示します。- キャッシング
- 遅延または遅延初期化 必要に応じてオブジェクトをロードできるのに、なぜすぐにオブジェクトをロードするのでしょうか?
- リクエストのロギング
- データとアクセスの中間検証
- ワーカースレッドの開始
- オブジェクトへのアクセスを記録する
長所と短所
- + サービスオブジェクトへのアクセスを自由に制御できます
- + サービスオブジェクトのライフサイクル管理に関連する追加機能
- + サービスオブジェクトなしで動作します
- + パフォーマンスとコードのセキュリティが向上します。
- - 追加リクエストによりパフォーマンスが悪化する可能性があります
- - クラス階層がより複雑になります
実際のプロキシ パターン
ハードディスクから電車の時刻表を読み取るシステムを実装してみましょう。
public interface TrainTimetable {
String[] getTimetable();
String getTrainDepartureTime();
}
メインインターフェイスを実装するクラスは次のとおりです。
public class ElectricTrainTimetable implements TrainTimetable {
@Override
public String[] getTimetable() {
ArrayList<String> list = new ArrayList<>();
try {
Scanner scanner = new Scanner(new FileReader(new File("/tmp/electric_trains.csv")));
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
list.add(line);
}
} catch (IOException e) {
System.err.println("Error: " + e);
}
return list.toArray(new String[list.size()]);
}
@Override
public String getTrainDepartureTime(String trainId) {
String[] timetable = getTimetable();
for (int i = 0; i < timetable.length; i++) {
if (timetable[i].startsWith(trainId+";")) return timetable[i];
}
return "";
}
}
電車の時刻表を取得するたびに、プログラムはディスクからファイルを読み取ります。しかし、それは私たちの悩みの始まりにすぎません。たとえ 1 つの列車の時刻表を取得するたびに、ファイル全体が読み取られます。このようなコードが、してはいけないことの例にのみ存在するのは良いことです :) クライアント クラス:
public class TimetableDisplay {
private TrainTimetable trainTimetable = new ElectricTrainTimetable();
public void printTimetable() {
String[] timetable = trainTimetable.getTimetable();
String[] tmpArr;
System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
for (int i = 0; i < timetable.length; i++) {
tmpArr = timetable[i].split(";");
System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
}
}
}
ファイル例:
9B-6854;London;Prague;13:43;21:15;07:32
BA-1404;Paris;Graz;14:25;21:25;07:00
9B-8710;Prague;Vienna;04:48;08:49;04:01;
9B-8122;Prague;Graz;04:48;08:49;04:01
テストしてみましょう:
public static void main(String[] args) {
TimetableDisplay timetableDisplay = new timetableDisplay();
timetableDisplay.printTimetable();
}
出力:
Train From To Departure time Arrival time Travel time
9B-6854 London Prague 13:43 21:15 07:32
BA-1404 Paris Graz 14:25 21:25 07:00
9B-8710 Prague Vienna 04:48 08:49 04:01
9B-8122 Prague Graz 04:48 08:49 04:01
次に、パターンを導入するために必要な手順を見てみましょう。
-
元のオブジェクトの代わりにプロキシの使用を許可するインターフェイスを定義します。この例では、これは です
TrainTimetable
。 -
プロキシクラスを作成します。サービス オブジェクトへの参照が必要です (クラス内で作成するか、コンストラクターに渡します)。
プロキシ クラスは次のとおりです。
public class ElectricTrainTimetableProxy implements TrainTimetable { // Reference to the original object private TrainTimetable trainTimetable = new ElectricTrainTimetable(); private String[] timetableCache = null @Override public String[] getTimetable() { return trainTimetable.getTimetable(); } @Override public String getTrainDepartureTime(String trainId) { return trainTimetable.getTrainDepartureTime(trainId); } public void clearCache() { trainTimetable = null; } }
この段階では、元のオブジェクトへの参照を持つクラスを作成し、すべての呼び出しをそのオブジェクトに転送しているだけです。
-
プロキシクラスのロジックを実装してみましょう。基本的に、呼び出しは常に元のオブジェクトにリダイレクトされます。
public class ElectricTrainTimetableProxy implements TrainTimetable { // Reference to the original object private TrainTimetable trainTimetable = new ElectricTrainTimetable(); private String[] timetableCache = null @Override public String[] getTimetable() { if (timetableCache == null) { timetableCache = trainTimetable.getTimetable(); } return timetableCache; } @Override public String getTrainDepartureTime(String trainId) { if (timetableCache == null) { timetableCache = trainTimetable.getTimetable(); } for (int i = 0; i < timetableCache.length; i++) { if (timetableCache[i].startsWith(trainId+";")) return timetableCache[i]; } return ""; } public void clearCache() { trainTimetable = null; } }
getTimetable()
timetable 配列がメモリにキャッシュされているかどうかをチェックします。そうでない場合は、ディスクからデータをロードするリクエストを送信し、結果を保存します。timetable がすでに要求されている場合、メモリからオブジェクトがすぐに返されます。シンプルな機能のおかげで、getTraindeliveryTime() メソッドを元のオブジェクトにリダイレクトする必要はありません。新しいメソッドでその機能を複製しただけです。
こんなことはしないでください。コードを複製するか、同様のことを行う必要がある場合は、何か問題が発生したことになるため、別の角度から問題をもう一度見る必要があります。この単純な例では、他に選択肢はありませんでした。しかし、実際のプロジェクトでは、コードはより正確に記述される可能性が高くなります。
-
クライアント コードで、元のオブジェクトの代わりにプロキシ オブジェクトを作成します。
public class TimetableDisplay { // Changed reference private TrainTimetable trainTimetable = new ElectricTrainTimetableProxy(); public void printTimetable() { String[] timetable = trainTimetable.getTimetable(); String[] tmpArr; System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time"); for (int i = 0; i < timetable.length; i++) { tmpArr = timetable[i].split(";"); System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]); } } }
チェック
Train From To Departure time Arrival time Travel time 9B-6854 London Prague 13:43 21:15 07:32 BA-1404 Paris Graz 14:25 21:25 07:00 9B-8710 Prague Vienna 04:48 08:49 04:01 9B-8122 Prague Graz 04:48 08:49 04:01
素晴らしい、正しく動作します。
特定の条件に応じて、オリジナル オブジェクトとプロキシ オブジェクトの両方を作成するファクトリのオプションを検討することもできます。
GO TO FULL VERSION