Proxy (プロキシ) パターン
あるモノに対してのアクセスを制御するためにパイプ役を設けるデザインパターンです。
会社から、インターネットを閲覧する際、プロキシサーバーを通してアクセスする場面が多いと思います。
このプロキシサーバーの主な役目として、e-Words によると
"企業などの内部ネットワークとインターネットの境にあって、
直接インターネットに接続できない内部ネットワークのコンピュータに代わって、
「代理」としてインターネットとの接続を行なうコンピュータのこと。"
とあります。
サンプルでは、上記例のプロキシの様に、画面から直接 Web にアクセスさせず、プロキシクラスを通して
Web にアクセスし、指定した URL の HTML を取得してから、画面に表示します。
また、http:// 以外で開始する URL は、当サンプルでは対象外とするため、プロキシクラスから例外が投げられます。
■クラス図
ここで用いるサンプルのクラス図です。
準備中...
■サンプルの説明

TextBox に任意の URL を入力し、Html取得ボタンをクリックすると
下の MultiLine の TextBox に HTML が表示されます。
サンプルでは一律 UTF-8 でエンコーディングしています。
サンプルのプロジェクトダウンロード
■コード
アプリケーションのエントリポイント StartUpForm です。
Public Class StartUpForm Inherits System.Windows.Forms.Form #Region " Windows フォーム デザイナで生成されたコード " Public Sub New() MyBase.New() ' この呼び出しは Windows フォーム デザイナで必要です。 InitializeComponent() ' InitializeComponent() 呼び出しの後に初期化を追加します。 AddHandler Application.ThreadException, AddressOf CommonExceptionAndHandler.ExceptionHandler.MyHandler End Sub ' Form は、コンポーネント一覧に後処理を実行するために dispose をオーバーライドします。 Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub ' Windows フォーム デザイナで必要です。 Private components As System.ComponentModel.IContainer ' メモ : 以下のプロシージャは、Windows フォーム デザイナで必要です。 'Windows フォーム デザイナを使って変更してください。 ' コード エディタを使って変更しないでください。 Friend WithEvents TextBox1 As System.Windows.Forms.TextBox Friend WithEvents Button1 As System.Windows.Forms.Button Friend WithEvents TextBox2 As System.Windows.Forms.TextBox <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() Me.TextBox1 = New System.Windows.Forms.TextBox Me.Button1 = New System.Windows.Forms.Button Me.TextBox2 = New System.Windows.Forms.TextBox Me.SuspendLayout() ' 'TextBox1 ' Me.TextBox1.Location = New System.Drawing.Point(8, 8) Me.TextBox1.Name = "TextBox1" Me.TextBox1.TabIndex = 0 Me.TextBox1.Text = "TextBox1" ' 'Button1 ' Me.Button1.Location = New System.Drawing.Point(208, 8) Me.Button1.Name = "Button1" Me.Button1.TabIndex = 1 Me.Button1.Text = "Button1" ' 'TextBox2 ' Me.TextBox2.Location = New System.Drawing.Point(8, 40) Me.TextBox2.Multiline = True Me.TextBox2.Name = "TextBox2" Me.TextBox2.TabIndex = 2 Me.TextBox2.Text = "TextBox2" ' 'StartUpForm ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12) Me.ClientSize = New System.Drawing.Size(292, 266) Me.Controls.Add(Me.TextBox2) Me.Controls.Add(Me.Button1) Me.Controls.Add(Me.TextBox1) Me.Name = "StartUpForm" Me.Text = "Form1" Me.ResumeLayout(False) End Sub #End Region 'プロクシ Private m_myProxy As ISubject '画面がロードされた時のイベント Private Sub StartUpForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load Me.Text = "Proxy" Me.TextBox1.Text = "" Me.TextBox1.Size = New Size(190, 20) Me.Button1.Text = "Html取得" Me.TextBox2.Text = "" Me.TextBox2.Multiline = True Me.TextBox2.Size = New Size(270, 210) Me.TextBox2.ScrollBars = ScrollBars.Vertical 'プロクシの作成 Me.m_myProxy = New Proxy End Sub 'Html取得ボタン押下時のイベント Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click Me.TextBox2.Text = Me.m_myProxy.GetHtml(Me.TextBox1.Text) End Sub End Class
Proxy パターンでは、本当のオブジェクトと代理人(Proxy)は同一インターフェースであるべし、とされています。
Proxy と 本当のオブジェクトの共通のインターフェースです。
Public Interface ISubject '指定されたUrlのHtmlを取得するメソッド定義 Function GetHtml(ByVal urlString As String) As String End Interface
本当のオブジェクトです。本当に HTTP リクエストを投げて、
インターネット上に表示されている HTML を取得します。
ISubject を実装しています。
Imports System.Net '本当にWebサーバーに対してリクエストを投げるクラス Public Class RealSubject Implements ISubject '指定されたUrlのHtmlを取得するメソッド実装 Public Function GetHtml(ByVal urlString As String) As String Implements ISubject.GetHtml Dim wc As WebClient = New WebClient Dim st As System.IO.Stream Dim sr As System.IO.StreamReader Try st = wc.OpenRead(urlString) 'UTF8 エンコーディングする sr = New System.IO.StreamReader(st, System.Text.Encoding.UTF8) Return sr.ReadToEnd() Finally st.Close() sr.Close() End Try End Function End Class
プロキシです。キャッシュ(自身で持っている HashTable)に 画面からリクエストがあった URL
が存在した場合は、それを返し、無い場合は RealSubject に処理を委譲します。
ISubject を実装しています。
'プロクシ Public Class Proxy Implements ISubject 'Htmlを保持するテーブル Private m_htmlTable As Hashtable '本当にWebサーバーに対してリクエストを投げるクラス Private m_realSubject As RealSubject Public Sub New() Me.m_htmlTable = New Hashtable Me.m_realSubject = New RealSubject End Sub '指定されたUrlのHtmlを取得するメソッド実装 Public Function GetHtml(ByVal urlString As String) As String Implements ISubject.GetHtml If Not urlString.ToLower.StartsWith("http://") Then Throw New CommonExceptionAndHandler.MyException("Urlが不正です") End If If Me.m_htmlTable.ContainsKey(urlString) Then Return DirectCast(Me.m_htmlTable(urlString), String) Else '自身が持っていないUrlの場合は、RealSubjectに委譲する Dim strHtml As String = Me.m_realSubject.GetHtml(urlString) Me.m_htmlTable.Add(urlString, strHtml) Return strHtml End If End Function End Class
■ひとこと
このデザインパターンのお話と似たものに、Windows のファイルシステムにおける
ショートカットが存在します。ショートカットをデスクトップ上に配置しておいて、実行したい時に
ショートカットをダブルクリックするとそのショートカットの実態が実行されます。
このように、実は代理オブジェクトなんだけど、本物に触ったときと変わらない動きをするのが プロキシの役目です。
サンプルの例でいうと、RealSubject と StartUpForm の結合度を弱めて、Proxy を用意してあげる事により、
『特定の URL だけは、常に最新のものを取りたい』といった変更があった場合にも、
Proxy だけで吸収できてしまいます。