blog

デザインパターン学習ノート:Proxyパターン

概要\nはじめに\n\nソフトウェア開発では、時にはプロキシと同様の機能を提供する必要がある、いくつかの理由のために、クライアントが直接オブジェクトにアクセスしたくないか、またはすることはできませんが...

Jan 27, 2021 · 21 min. read
シェア

概要

はじめに

ソフトウェア開発では、時にはプロキシと同様の機能を提供する必要がある、いくつかの理由のために、クライアントはしたくないか、または直接オブジェクトにアクセスすることはできません、この時点で間接的なアクセスを達成するために第三者を介してプロキシを呼び出すことができます、このスキームは、デザインパターンに対応するプロキシパターンと呼ばれています。

Proxy パターンは広く使われている構造設計パターンで、多くのバリエーションがあります。Proxyパターンでは新しいプロキシオブジェクトが導入され、クライアントオブジェクトとターゲットオブジェクトの間の仲介役として動作し、クライアントが見ることができないものを取り除いたり、クライアントが必要とする追加サービスを追加したりすることができます。

定義

Proxyパターン:オブジェクトのプロキシを提供し、プロキシオブジェクトが元のオブジェクトへの参照を制御するパターン。

Proxyパターンはオブジェクト構造化パターンです。

構造

役割

  • サブジェクト:実サブジェクトとプロキシサブジェクトに共通のインターフェースを宣言。
  • プロキシ: 実トピックへの内部参照を持ち、実トピックオブジェクトを操作できます。プロキシ・トピック・ロールは、いつでも実際のトピックを置き換えることができるように、実際のトピック・ロールと同じインタフェースを提供します。プロキシロールは実トピックの使用を制御することもでき、必要に応じて実トピックオブジェクトを作成または削除したり、実トピックの使用を制限したりすることができます。通常、プロキシ・トピックの役割では、クライアントが特定のアクションを呼び出すか、後で実行する必要があります。
  • RealSubject:プロキシ・ロールによって表現される実際のオブジェクトを定義し、実際のビジネス・オペレーションは実際のサブジェクト・ロールに実装され、クライアントはプロキシ・ロールを通して間接的に実際のサブジェクト・ロールのオペレーションを呼び出すことができます。

分類

プロキシパターンは、その目的や実装方法によっていくつかのカテゴリーに分けることができますが、一般的なものは以下の通りです:

  • リモートプロキシ(Remote Proxy): 異なるアドレス空間にあるオブジェクトのローカルプロキシを提供します。リモートプロキシは "アンバサダー "とも呼ばれます。
  • 仮想プロキシ:大量のリソースを消費するオブジェクトを作成する必要がある場合、まずそれを表す比較的少量のリソースを消費するオブジェクトを作成します。
  • 保護されたプロキシ:オブジェクトへのアクセスを制御し、異なるユーザーに異なるレベルのアクセスを提供することができます。
  • キャッシュプロキシ:ターゲット操作の結果を複数のクライアントで共有できるように、一時的なストレージを提供します。
  • インテリジェント参照プロキシ: オブジェクトが参照されたときに、そのオブジェクトが呼び出された回数を記録するなどの追加操作を提供します。

装飾パターンとの違い

ProxyパターンとDecorationパターンは実装が似ていますが、主な違いは以下の通りです:

  • プロキシパターンとデコレーションパターンはどちらも動的に責任を追加することができますが、プロキシパターンは、特権制御、キャッシュ処理、スマート参照、リモートアクセスなど、元の責任と同じ問題ドメインに属さない、まったく新しい責任を追加します。デコレーションパターンの場合、具体的なコンポーネントクラスにいくつかの関連する責任を追加します。
  • 異なる目的:プロキシパターンの目的はオブジェクトへのアクセスを制御することであり、装飾パターンはオブジェクトに動的に機能を追加することです。

典型的な実装

ステップ

  • 抽象トピックの役割の定義: 抽象クラス/インタフェースとして定義、抽象ビジネスメソッドの宣言
  • 実際のトピック ロールの定義:抽象トピック ロールを継承/実装して、実際のビジネス オペレーションを実装します。
  • プロキシ・トピック・ロールの定義:抽象トピック・ロールを継承/実装し、クライアントの要求を実際のトピック・ロールに転送して呼び出すとともに、必要に応じて呼び出しの前後に関連処理を実行します。

抽象テーマの役割

ここでは単にインターフェースとして実装されています:

interface Subject
{
 void request();
}

リアルテーマの役割

Abstract Themes インターフェースを実装し、実際のビジネス操作を実行します:

class RealSubject implements Subject
{
 public void request()
 {
 System.out.println("リアルテーマロールメソッド");
 }
}

プロキシ・トピックの役割

一般に、実際のビジネスメソッドを呼び出すときや、関連する操作を行うときは、同じように抽象トピックインターフェースを実装します:

class Proxy implements Subject
{
 private RealSubject subject = new RealSubject();
 public void pre()
 {
 System.out.println("プリプロキシ操作");
 }
 public void request()
 {
 pre();
 subject.request();
 post();
 }
 public void post()
 {
 System.out.println("ポストプロキシ操作");
 }
}

クライアント

クライアントが抽象トピックロールに対してプログラムするには、プロキシが不要な場合は実際のトピックロールをインスタンス化し、プロキシが必要な場合はプロキシトピックロールをインスタンス化すれば十分です:

public static void main(String[] args) 
{
 Subject subject = new RealSubject();
 subject.request();
 System.out.println("
プロキシを使う:
");
 subject = new Proxy();
 subject.request();
}

すでに検索機能を持つシステムは、エージェントパターンを使ってシステムを設計し、検索に認証とログを追加する必要があります。

デザインは以下の通り:

  • 抽象テーマの役割:サーチャー
  • リアルテーマの役割:リアルサーチャー
  • プロキシのテーマの役割:ProxySearcher

コードは以下の通り:

public class Test
{
 public static void main(String[] args) 
 {
 Searcher subject = new ProxySearcher();
 subject.search();
 }
}
interface Searcher
{
 void search();
}
class RealSearcher implements Searcher
{
 public void search()
 {
 System.out.println("検索");
 }
}
class ProxySearcher implements Searcher
{
 private RealSearcher subject = new RealSearcher();
 public void validate()
 {
 System.out.println("認証");
 }
 public void search()
 {
 validate();
 subject.search();
 log();
 }
 public void log()
 {
 System.out.println("ロギング、クエリー数+1");
 }
}

検索を実行し、最初にユーザーを認証し、次に検索を実行し、検索が完了したらログを記録することは、スマート参照エージェントと同様に保護エージェントの例です。

動的プロキシと静的プロキシ

静的プロキシ

通常、各プロキシクラスはコンパイル後にバイトコードファイルを生成し、プロキシが実装するインターフェイスとプロキシメソッドは固定されており、このプロキシはスタティックプロキシと呼ばれます。

スタティックプロキシでは、クライアントはプロキシを通して RealSubject のリクエストメソッドを呼び出します。

静的プロキシの利点は、実装が簡単であることです。しかし、プロキシ・クラスと実際のサブジェクト・クラスがあらかじめ存在する必要があります:

  • 異なる実テーマクラスのプロキシ
  • 実オブジェクトクラスの異なるメソッドのプロキシ

新しい代理クラスを追加する必要があり、システム内のクラス数が大幅に増える可能性があります。

これは静的プロキシの最大の欠点です。 システム内のクラス数を減らすために、動的プロキシを使用することができます。

動的プロキシ

動的プロキシは、システムが動的に同じプロキシクラスの実際のニーズに応じてプロキシクラスを作成することができます別の実際のサブジェクトクラスの数のプロキシになることができ、異なるメソッドのプロキシになることができます、Javaでの動的プロキシの実装では、Proxyクラスだけでなく、InvocationHandlerインターフェイスが必要です。

Proxy

Proxy クラスは、動的なプロキシ・クラスとインスタンス・オブジェクトを作成するためのメソッドを提供します:

  • public static Class<?> getProxy(ClassLoader loader,Class<?> ... interfaces)このメソッドは Class 型のプロキシ・クラスを返します。パラメータにはクラス・ローダを指定し、プロキシのインターフェイス配列を指定します。
  • public staitc Object newProxyInstance(ClassLoader loader,Class<?> [] interfaces,InvocationHandler h)最初のパラメータはクラスローダ、2番目のパラメータはプロキシ・クラスによって実装されるインタフェースのリストを表します。

InvocationHandler

InvocationHandlerインターフェイスはProxyProgramクラスの実装インターフェイスで、Proxyインスタンスの呼び出しハンドラのパブリックな親として機能します:

  • public Object invoke(Object proxy,Method method,Object [] args)このメソッドは、プロキシ・クラスのインスタンスへのメソッド呼び出しを処理し、対応する結果を返すために使用されます。最初のパラメータはプロキシ・クラスのインスタンス、2 番目のパラメータはプロキシされるメソッド、3 番目のパラメータはメソッドのパラメータ配列を表します。

クライアントがダイナミックプロキシオブジェクトのメソッドを呼び出すと、呼び出し要求は自動的にInvocationHandlerのinvokeメソッドに転送され、invokeによって要求の統一処理が実現されます。

データアクセスDaoレイヤーにメソッド呼び出しロギングを追加し、動的プロキシパターンを使用して設計された各メソッドが呼び出された時間と結果を記録します。

デザインは以下の通り:

  • 抽象サブジェクトの役割: AbstractUserDao
  • リアルテーマ役割: UserDao1+UserDao2
  • リクエストハンドラの役割: DAOLogHandler
  • プロキシのトピックロール: 手動で定義する必要はありません。

まず、抽象的なテーマのキャラクターをデザインします:

interface AbstarctUserDao
{
 void findUserById(String id);
}

次に、インターフェイスを実装する2つの具象クラスを作成します:

class UserDao1 implements AbstarctUserDao
{
 public void findUserById(String id)
 {
 System.out.println("1データベースからidを検索する " + 
 ("1".equals(id) ? "成功" : "失敗した"));
 }
}
class UserDao2 implements AbstarctUserDao
{
 public void findUserById(String id)
 {
 System.out.println("2データベースからidを検索する " + 
 ("2".equals(id) ? "成功" : "失敗した"));
 }
}

次にリクエスト処理の役割を定義します:

class DAOLogHandler implements InvocationHandler
{
 private Object object;
 public DAOLogHandler(Object object)
 {
 this.object = object;
 }
 @Override
 public Object invoke(Object proxy,Method method,Object [] args) throws Throwable
 {
 beforeInvoke();
 Object result = method.invoke(object, args);
 postInvoke();
 return result;
 }
 private void beforeInvoke()
 {
 System.out.println("時間を記録する");
 }
 private void postInvoke()
 {
 System.out.println("結果をログに記録する");
 }
}

その核となるのは、InvocationHandlerのinvokeメソッドの実装で、抽象トピック・ロールのメソッドが呼び出されると、処理のためにメソッドに自動的に転送されます。

Object result = method.invoke(object.args)つまり、抽象トピック・ロールに3つのメソッドA()、B()、C()があると仮定すると、A()が呼び出されたとき、A()の呼び出しは内部を置き換えます:

@Override
public Object invoke(Object proxy,Method method,Object [] args) throws Throwable
{
 beforeInvoke();
 Object result = A(args);
 postInvoke();
 return result;
}

B()を呼び出すと、以下の関数を呼び出すのと同じことになります:

@Override
public Object invoke(Object proxy,Method method,Object [] args) throws Throwable
{
 beforeInvoke();
 Object result = B(args);
 postInvoke();
 return result;
}

以下はテストクライアントのコードです:

public static void main(String[] args) 
{
 AbstarctUserDao userDao1 = new UserDao1();
 AbstarctUserDao proxy = null;
 InvocationHandler handler = new DAOLogHandler(userDao1);
 proxy = AbstarctUserDao.class.cast(
 Proxy.newProxyInstance(AbstarctUserDao.class.getClassLoader(), new Class[]{AbstarctUserDao.class}, handler)
 );
 proxy.findUserById("2");
 AbstarctUserDao userDao2 = new UserDao2();
 handler = new DAOLogHandler(userDao2);
 proxy = AbstarctUserDao.class.cast(
 Proxy.newProxyInstance(AbstarctUserDao.class.getClassLoader(),new Class[]{AbstarctUserDao.class},handler)
 );
 proxy.findUserById("2");
}

出力は以下の通り:

テストコードでは、エージェント・トピック・ロールは以下のステートメントによって生成されます:

proxy = AbstarctUserDao.class.cast(
 Proxy.newProxyInstance(AbstarctUserDao.class.getClassLoader(), new Class[]{AbstarctUserDao.class}, handler)
);

ここで、cast() メソッドは、変換前の安全チェックを伴う強制的な型変換のラッピングと同等です。

リモートプロキシ

概要

リモート・プロキシは、クライアント・プログラムがリモート・ホスト上のオブジェクトにアクセスできるようにする一般的なプロキシ・モデルです。 リモート・プロキシはクライアントからネットワークの詳細を隠しますので、クライアントはネットワークの存在について考える必要がありません。クライアントは、プロキシされているリモート・ビジネス・オブジェクトがリモートではなくローカルであると完全に仮定することができます。 リモート・プロキシ・オブジェクトは、ネットワーク通信の大部分を引き受け、リモート・ビジネスへのメソッド呼び出しを担当します。

リモートビジネスオブジェクトは、リモートビジネスオブジェクトとネットワーク通信へのアクセスを担当しているローカルホスト内のプロキシオブジェクトがあり、それはクライアントに透過的です。クライアントは、プロキシオブジェクト内のローカルホストと直接対話する方法によって定義されたサービスインターフェイスに従ってのみ、特定のビジネスの実装を気にする必要はありませんすることができます。

RMI(Remote Method Invocation,リモートメソッド呼び出し)Javaでは、このメカニズムを使ってリモート・プロキシ(あるJVMのオブジェクトが別のJVMのオブジェクトを呼び出せるようにする)を実装できます。

RMI

この単純な例には、以下の4つのクラスがあります:

  • インターフェース: Hello
  • インターフェース実装クラス: HelloImpl
  • サーバ: HelloServer
  • クライアント: HelloClient

コードは以下の通り:

interface Hello extends Remote
{
 String sayHello(String name) throws RemoteException;
}

単純な sayHello メソッドです。中のメソッドは RemoteException をスローするように宣言する必要があることに注意してください。

次にインターフェイスの実装クラスです:

public class HelloImpl extends UnicastRemoteObject implements Hello{
 public HelloImpl() throws RemoteException
 {
 super();
 }
 public String sayHello(String name) throws RemoteException
 {
 System.out.println("Hello");
 return "Hello"+name;
 }
}

sayHelloメソッドを実装します。

次はサーバー側です:

public class HelloServer {
 public static void main(String[] args) {
 try {
 Hello hello = new HelloImpl();
 LocateRegistry.createRegistry(8888);
 System.setProperty("java.rmi.server.hostname", ".1");
 Naming.bind("rmi://localhost:8888/hello", hello);
 System.out.println("オブジェクトのリモートバインディングが成功した");
 } catch (Exception e) {
 e.printStackTrace();
 }
 }
}

サーバーは、最初にローカルポートを登録し、システムプロパティrmiサービスのホスト名をローカルアドレス、つまり127.0.0.1に設定し、それが対応するipを変更することができますサーバー上に展開されている場合。次のステップは、Namingの静的メソッドbindを介してRMIサーバにURLをバインドし、それをhello.と命名することです。

そして最後にクライアント:

public class HelloClient {
 public static void main(String[] args) {
 try
 {
 Hello hello = Hello.class.cast(
 Naming.lookup("rmi://.1:8888/hello")
 );
 System.out.println(hello.sayHello("111"));
 }
 catch(Exception e)
 {
 e.printStackTrace();
 }
 }
}

クライアントは、Namingのルックアップを使用して、パラメータURLで対応するリモートサービスオブジェクトhelloを検索し、それが見つかったときにそれを返し、それを強制的にHelloに変換し、リモートオブジェクトのメソッドsayHelloを呼び出すことができます。

まずサーバー側を実行します:

それからクライアントを起動してください:

サーバー側からの結果を見ることができます。

サーバー側をもう一度確認してください:

これはsayHelloの呼び出しの結果であることがわかります。

仮想プロキシ

概要

システムリソースを大量に消費したり、ロードに時間がかかったりするオブジェクトに対して、これらのオブジェクトのための仮想エージェントを提供することができます。実オブジェクトが作成されると、仮想エージェントは実オブジェクトの代理として動作し、実オブジェクトが作成されると、仮想エージェントはユーザのリクエストを実オブジェクトに転送します。

適用可能な状況

バーチャルエージェントの使用には、以下の2つの状況が考えられます:

  • オブジェクト自体の複雑さやネットワークなどの理由でオブジェクトのロードに時間がかかるため、今回は比較的ロード時間の短いプロキシオブジェクトを使って実際のオブジェクトを表すことができます
  • 仮想プロキシは、システムリソースを大量に消費するオブジェクトをロードするときにも便利です。仮想プロキシは、多くのメモリを消費するオブジェクトや、扱いが非常に複雑なオブジェクトを、必要になるまで遅延させることを可能にし、比較的小さなプロキシオブジェクトが実際のオブジェクトを表すために使われます。

利点と欠点

  • 利点: アプリケーションの起動時にすべてのオブジェクトを作成してロードする必要がなくなるため、アプリケーションの起動が高速化されます。
  • 欠点:アプリケーション固有のオブジェクトが作成される保証はなく、オブジェクトにアクセスする前にNULL化する必要があります。

短い例

そこに物事について話をする上司を見つけるために人々のグループがあり、物事について話をする必要が最初に上司のアシスタントを介してアポイントメントを作成する必要があります、この事だけの予定はアシスタントが完了する必要があります、上司が表示される必要がある場合にのみ、タスク内の予定リストの実際の実装は、仮想エージェントのパターン設計の使用。

デザインは以下の通り:

  • 抽象テーマの役割:証明可能
  • リアルテーマ 役割:ボス
  • プロキシのテーマ 役割:アシスタント

コードは以下の通り:

//抽象テーマの役割
interface Approvable
{
 void approve();
}

次のステップでは、本物のテーマキャラクター「ボス」を定義します:

class Boss implements Approvable
{
 private List<String> orders = new LinkedList<>();
 
 static
 {
 System.out.println("
ボスは
");
 }
 public Boss(List<String> orders)
 {
 this.orders = orders;
 }
 public void addOrder(String order)
 {
 orders.add(order);
 }
 @Override
 public void approve()
 {
 while(orders.size() > 0)
 {
 System.out.println("オーナーは<"+orders.remove(0)+">");
 }
 }
}

保留中のイベントを保存するには「リスト」を使用し、すべてのイベントの処理を示すには「承認」を使用します。

エージェントテーマの役割は以下の通りです:

class Assistant implements Approvable
{
 private List<String> orders = new LinkedList<>();
 private volatile Boss boss;
 public void addOrder(String order)
 {
 if(boss != null)
 {
 System.out.println(" <"+order+">予約リストに追加する");
 boss.addOrder(order);
 }
 else
 {
 System.out.println(" <"+order+">予約リストに追加する");
 orders.add(order);
 }
 }
 @Override
 public void approve()
 {
 if(boss == null)
 {
 synchronized(this)
 {
 if(boss == null)
 {
 boss = new Boss(orders);
 }
 }
 }
 boss.approve(); 
 }
}

addイベント関数では、まずボスがNULLかどうかを判断し、NULLの場合、ボスオブジェクトが作成されていないことを意味し、その後、アシスタントが予定リストに追加させ、NULLではない場合、ボスオブジェクトがすでに存在することを意味し、直接予定リストに追加するボスに引き渡されます。

テストクラス:

public static void main(String[] args) 
{
 Assistant assistant = new Assistant();
 assistant.addOrder("ボスを見つける");
 assistant.addOrder("上司に借金を頼む」);
 assistant.addOrder("上司とチャットする");
 assistant.approve();
 assistant.addOrder("ボスを夕食に誘う");
 assistant.addOrder("ボスと一杯やる");
 assistant.approve();
}

出力は以下の通り:

キャッシュプロキシ

キャッシングプロキシは、複数のクライアントが結果を共有できるように、特定のターゲット操作の結果の一時的なストレージを提供します。ここでは、統合を使用するサードパーティライブラリのダウンロードのYouTubeのキャッシングをシミュレートするために、キャッシングプロキシのモデルが使用されています。

デザインは以下の通り:

  • サードパーティライブラリのエミュレートThirdPartyYouTubeLib+ThirdPartyYouTubeClass
  • キャッシュプロキシのシミュレーション: YouTubeCacheProxy
  • 模擬ダウンローダー: YouTubeDownloader
  • ThirdPartyYouTubeLibは抽象的なサブジェクトの役割です

ThirdPartyYouTubeClassThirdPartyYouTubeLibまず、通常ソースコードに実装されていないサードパーティのライブラリがあり、そこにインターフェイスがあり、YouTubeCacheProxyはそれを実装している、ということです:

  • ThirdPartyYouTubeClassは実際のサブジェクトの役割です
  • YouTubeCacheProxyはプロキシトピックの役割です
  • YouTubeCacheProxy はプロキシテーマの役割です。

抽象テーマの役割

まず、抽象トピックの役割を定義します:

interface ThirdPartyYouTubeLib
{
 HashMap<String,Video> popularVideos();
 Video getVideo(String videoId);
}

人気のある動画を取得する方法と、IDに基づいて特定の動画を取得する方法があります。

リアルテーマの役割

class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib
{
 private static final String URL = "https://..com";
 @Override
 public HashMap<String,Video> popularVideos()
 {
 connectToServer(URL);
 return getRandomVideos();
 }
 @Override
 public Video getVideo(String id)
 {
 connectToServer(URL+id);
 return getSomeVideo(id);
 }
 private int random(int min,int max)
 {
 return min+(int)(Math.random()*((max-min)+1));
 }
 private void experienceNetworkLatency()
 {
 int randomLatency = random(5, 10);
 for(int i=0;i<randomLatency;++i)
 {
 try
 {
 Thread.sleep(100);
 } 
 catch(InterruptedException e)
 {
 e.printStackTrace();
 }
 }
 }
 private void connectToServer(String url)
 {
 System.out.println("リンク先 " + url + " ...");
 experienceNetworkLatency();
 System.out.println("接続に成功する!
");
 }
 private HashMap<String,Video> getRandomVideos()
 {
 System.out.println("人気のダウンロード");
 experienceNetworkLatency();
 HashMap<String,Video> map = new HashMap<>();
 map.put("1111111",new Video("1111","1111.mp4"));
 map.put("2222222",new Video("2222","2222.avi"));
 map.put("3333333",new Video("3333","3333.mov"));
 map.put("4444444",new Video("4444","4444.mkv"));
 System.out.println("完全ダウンロード!
");
 return map;
 }
 private Video getSomeVideo(String id)
 {
 System.out.println("idが""のオブジェクトをダウンロードする+id+" ;
 experienceNetworkLatency();
 System.out.println("完全ダウンロード!
");
 return new Video(id,"title");
 }
}

人気のビデオや特定のビデオをフェッチする場合、サーバーへの接続のシミュレーションが実行され、最初の出力でxxxへの接続を促し、続いてネットワーク遅延のシミュレーションを行い、最後にダウンロードが完了し、対応するビデオが返されることを促します。

プロキシ・トピックの役割

class YouTubeCacheProxy implements ThirdPartyYouTubeLib
{
 private ThirdPartyYouTubeLib youtubeService = new ThirdPartyYouTubeClass();
 private HashMap<String,Video> cachePopular = new HashMap<>();
 private HashMap<String,Video> cacheAll = new HashMap<>();
 @Override
 public HashMap<String,Video> popularVideos()
 {
 if(cachePopular.isEmpty())
 {
 cachePopular = youtubeService.popularVideos();
 }
 else
 {
 System.out.println("キャッシュからヒットを取り出す"); 
 }
 return cachePopular;
 }
 @Override
 public Video getVideo(String id)
 {
 Video video = cacheAll.get(id);
 if(video == null)
 {
 video = youtubeService.getVideo(id);
 cacheAll.put(id,video);
 }
 else
 {
 System.out.println("キャッシュから、id "+id+" ;
 }
 return video;
 }
 public void reset()
 {
 cachePopular.clear();
 cacheAll.clear();
 }
}

キャッシュが存在する場合は、キャッシュから直接取得します。存在しない場合は、最初に get video メソッドを呼び出してキャッシュに保存し、次に取得するときにキャッシュから取得します。

その他

class Video
{
 private String id;
 private String title;
 private String data;
 public Video(String id,String title)
 {
 this.id = id;
 this.title = title;
 }
 //getter+setter...
}
class YouTubeDownloader
{
 private ThirdPartyYouTubeLib api;
 public YouTubeDownloader(ThirdPartyYouTubeLib api)
 {
 this.api = api;
 }
 public boolean useCacheProxy()
 {
 return api instanceof YouTubeCacheProxy;
 }
 public void renderVideoPage(String id)
 {
 Video video = api.getVideo(id);
 System.out.println("
-------------------------------------------");
 System.out.println("ID:"+video.getId());
 System.out.println(" :"+video.getTitle());
 System.out.println(" :"+video.getData());
 System.out.println("
-------------------------------------------");
 }
 public void renderPopularVideos()
 {
 HashMap<String,Video> list = api.popularVideos();
 System.out.println("
-------------------------------------------");
 System.out.println("ホット");
 list.forEach((k,v)->System.out.println("ID:"+v.getId()+"	 :"+v.getTitle()));
 System.out.println("
-------------------------------------------");
 }
}

テスト

public class Test
{
 public static void main(String[] args) 
 {
 YouTubeDownloader naiveDownloader = new YouTubeDownloader(new ThirdPartyYouTubeClass());
 YouTubeDownloader smartDownloader = new YouTubeDownloader(new YouTubeCacheProxy());
 long navie = test(naiveDownloader);
 long smart = test(smartDownloader);
 System.out.println("キャッシング・プロキシによる時間の節約:"+(navie-smart)+"ms");
 }
 private static long test(YouTubeDownloader downloader)
 {
 long startTime = System.currentTimeMillis();
 downloader.renderPopularVideos();
 downloader.renderVideoPage("1111");
 downloader.renderPopularVideos();
 downloader.renderVideoPage("2222");
 downloader.renderVideoPage("3333");
 downloader.renderVideoPage("4444");
 long estimatedTime = System.currentTimeMillis() - startTime;
 System.out.println(downloader.useCacheProxy() ? "キャッシュされたランタイムを使用する:" : "キャッシュされたランタイムを使用しない:");
 System.out.println(estimatedTime+"ms
");
 return estimatedTime;
 }
}

2つのダウンローダーがシミュレートされ、1つはネイティブ・ダウンロード、もう1つはキャッシング・プロキシが使用されました:

httpsに接続する://..com ...
接続に成功する!
人気のダウンロード
完全ダウンロード!
-------------------------------------------
 
ID:4444  :4444.mkv
ID:2222  :2222.avi
ID:3333  :3333.mov
ID:1111  :1111.mp4
-------------------------------------------
https」に接続する://..com1111" ...
接続に成功する!
オブジェクトをダウンロードする
完全ダウンロード!
-------------------------------------------
ID:1111
 :title
 :null
-------------------------------------------
httpsに接続する://..com ...
接続に成功する!
人気のダウンロード
完全ダウンロード!
-------------------------------------------
 
ID:4444  :4444.mkv
ID:2222  :2222.avi
ID:3333  :3333.mov
ID:1111  :1111.mp4
-------------------------------------------
https」に接続する://..com2222" ... 
接続に成功する!
オブジェクトをダウンロードする
完全ダウンロード!
-------------------------------------------
ID:2222
 :title
 :null
-------------------------------------------
https」に接続する://..com3333" ... 
接続に成功する!
id 3333をダウンロードする
完全ダウンロード!
-------------------------------------------
ID:3333
 :title
 :null
-------------------------------------------
https」に接続する://..com4444" ... 
接続に成功する!
id 4444をダウンロードする
完全ダウンロード!
-------------------------------------------
ID:4444
 :title
 :null
-------------------------------------------
キャッシュされたランタイムを使用しない:
9312ms
httpsに接続する://..com ...
接続に成功する!
人気のダウンロード
完全ダウンロード!
-------------------------------------------
 
ID:4444  :4444.mkv
ID:2222  :2222.avi
ID:3333  :3333.mov
ID:1111  :1111.mp4
-------------------------------------------
https」に接続する://..com1111" ...
接続に成功する!
オブジェクトをダウンロードする
完全ダウンロード!
-------------------------------------------
ID:1111
 :title
 :null
-------------------------------------------
人気のあるオブジェクトをキャッシュから取得する
-------------------------------------------
 
ID:4444  :4444.mkv
ID:2222  :2222.avi
ID:3333  :3333.mov
ID:1111  :1111.mp4
-------------------------------------------
https」に接続する://..com2222" ...
接続に成功する!
オブジェクトをダウンロードする
完全ダウンロード!
-------------------------------------------
ID:2222
 :title
 :null
-------------------------------------------
https」に接続する://..com3333" ...
接続に成功する!
id 3333をダウンロードする
完全ダウンロード!
-------------------------------------------
ID:3333
 :title
 :null
-------------------------------------------
https」に接続する://..com4444" ...
接続に成功する!
id 4444をダウンロードする
完全ダウンロード!
-------------------------------------------
ID:4444
 :title
 :null
-------------------------------------------
キャッシュされたランタイムを使用する:
7611ms
キャッシング・プロキシによる時間の節約:1701ms

キャッシュ・プロキシが時間の節約になっていることがわかります。最初のフェッチ動画を除いて、それ以降のフェッチはキャッシュから直接取得されます。

主な利点

  • 結合の低減:プロキシパターンは、呼び出し側と着呼側を調整することができ、システムの結合をある程度低減することができます。
  • 柔軟性と拡張性:クライアントは、抽象的なテーマの役割のためにプログラムすることができ、ソースコードを変更することなく、エージェントクラスを追加したり置き換えたりすることができます。
  • 全体的な効率の向上:リモートプロキシは、2つの異なるアドレス空間にあるオブジェクトにアクセスするための実装メカニズムを提供し、より多くのリソースを消費するオブジェクトや操作をより性能の良いコンピュータに移動できるようにすることで、システム全体の運用効率を向上させます。
  • オーバーヘッドの節約:バーチャルエージェントは、リソース集約度の高いオブジェクトをリソース集約度の低いオブジェクトで表現することで、システムのオーバーヘッドを節約することができます。
  • アクセス許可の制御:保護エージェントは、オブジェクトへのアクセスを制御し、ユーザごとに異なるアクセスレベルを提供することができます。

主な欠点

  • 処理速度低下:クライアントと実サブジェクトの間に、プロテクション・プロキシなどのプロキシ・オブジェクトが追加されるため、処理が遅くなる可能性があります。
  • 実装の複雑さ:プロキシパターンの実装には追加の操作が必要で、リモートプロキシなど、実際には非常に複雑なプロキシパターンもあります。

シナリオ

  • クライアントはリモートプロキシを使ってリモートホストのオブジェクトにアクセスする必要があります。
  • リソース集約度の高いオブジェクトを、リソース集約度の低いオブジェクトで表現する必要がある場合は、仮想プロキシを使用します。
  • アクセス制御の必要性、保護剤の使用
  • 頻繁にアクセスされる操作の結果を一時的に保存する必要がある場合は、キャッシュプロキシを使ってください。
  • オブジェクトにアクセスするための追加操作を提供する必要がある場合は、スマート参照プロキシを使用します。

Read next

Flask 開発フォーラム - データベース

最後に、最も基本的なホームページを作ったので、ユーザーのログインと登録処理を実装してみましょう。 まず、始める前にデータベースが必要です。ここではMySQLデータベースを選びました。 設定 ...

Jan 27, 2021 · 10 min read