Proxy (プロキシ) パターン

あるモノに対してのアクセスを制御するためにパイプ役を設けるデザインパターンです。
会社から、インターネットを閲覧する際、プロキシサーバーを通してアクセスする場面が多いと思います。
このプロキシサーバーの主な役目として、e-Words によると
"企業などの内部ネットワークとインターネットの境にあって、
直接インターネットに接続できない内部ネットワークのコンピュータに代わって、
「代理」としてインターネットとの接続を行なうコンピュータのこと。"
とあります。


サンプルでは、上記例のプロキシの様に、画面から直接 Web にアクセスさせず、プロキシクラスを通して
Web にアクセスし、指定した URL の HTML を取得してから、画面に表示します。
また、http:// 以外で開始する URL は、当サンプルでは対象外とするため、プロキシクラスから例外が投げられます。

このカテゴリーの先頭へ このページの先頭へ

■クラス図

ここで用いるサンプルのクラス図です。
準備中...

このカテゴリーの先頭へ このページの先頭へ

■サンプルの説明

サンプルの見た目1
TextBox に任意の URL を入力し、Html取得ボタンをクリックすると
下の MultiLine の TextBox に HTML が表示されます。
サンプルでは一律 UTF-8 でエンコーディングしています。
サンプルのプロジェクトダウンロード

このカテゴリーの先頭へ このページの先頭へ

■コード

アプリケーションのエントリポイント StartUpForm です。

StartUpForm.vb
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 と 本当のオブジェクトの共通のインターフェースです。

ISubject.vb
Public Interface ISubject

  '指定されたUrlのHtmlを取得するメソッド定義
  Function GetHtml(ByVal urlString As String) As String

End Interface
このカテゴリーの先頭へ このページの先頭へ

本当のオブジェクトです。本当に HTTP リクエストを投げて、
インターネット上に表示されている HTML を取得します。
ISubject を実装しています。

RealSubject.vb
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 を実装しています。

Proxy.vb
'プロクシ
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 だけで吸収できてしまいます。

このカテゴリーの先頭へ このページの先頭へ