Interpreter (インタープリタ) パターン

Interpreter とは、通訳とか通訳する人とか、解釈プログラムなんて意味です。
何かの形式で書かれた文書があったとして、その規則の定義とその規則にのっとってるかを解釈する機能を
を一緒に提供するデザインパターンです。
何かの形式の具体例を挙げると、HTML や XML や SQL 等があります。


サンプルでは、XML を扱う事にしました。
XML は通常、 DTD を使って文法を定義・解釈しますが、DTD の役割をプログラムで実装してみる事にしました。
文法チェックは、大きく分けて 2 種類あり、1 つは階層のチェック(この要素の下にはあの要素が無いといけない...とか)と、 もう 1 つは要素の値のチェックを行います。

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

■クラス図

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

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

■サンプルの説明

サンプルの見た目1
XML読込 ボタンをクリックすると、独自の規則にのっとた XML ファイルを読み込んで、
文法チェックを行います。また、画面に表示されている DataGrid コントロールに XML の要素名と値を表示します。
サンプルのプロジェクトダウンロード

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

■コード

アプリケーションのエントリポイント 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 Button1 As System.Windows.Forms.Button
  Friend WithEvents DataGrid1 As System.Windows.Forms.DataGrid
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
    Me.Button1 = New System.Windows.Forms.Button
    Me.DataGrid1 = New System.Windows.Forms.DataGrid
    CType(Me.DataGrid1, System.ComponentModel.ISupportInitialize).BeginInit()
    Me.SuspendLayout()
    '
    'Button1
    '
    Me.Button1.Location = New System.Drawing.Point(96, 8)
    Me.Button1.Name = "Button1"
    Me.Button1.TabIndex = 0
    Me.Button1.Text = "Button1"
    '
    'DataGrid1
    '
    Me.DataGrid1.DataMember = ""
    Me.DataGrid1.HeaderForeColor = System.Drawing.SystemColors.ControlText
    Me.DataGrid1.Location = New System.Drawing.Point(10, 40)
    Me.DataGrid1.Name = "DataGrid1"
    Me.DataGrid1.TabIndex = 1
    '
    'StartUpForm
    '
    Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12)
    Me.ClientSize = New System.Drawing.Size(292, 266)
    Me.Controls.Add(Me.DataGrid1)
    Me.Controls.Add(Me.Button1)
    Me.Name = "StartUpForm"
    Me.Text = "Form1"
    CType(Me.DataGrid1, System.ComponentModel.ISupportInitialize).EndInit()
    Me.ResumeLayout(False)

  End Sub

#End Region

  '画面がロードされた時のイベント
  Private Sub StartUpForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Me.Text = "Interpreter"
    Me.Button1.Text = "XML読込"
    Me.DataGrid1.Location = New Point(10, 40)
    Me.DataGrid1.Size = New Size(270, 210)
    Me.DataGrid1.DataSource = Nothing
    Me.DataGrid1.ReadOnly = True
  End Sub

  'XML読込ボタン押下時のイベント
  Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim xmlPath As String = System.IO.Path.Combine(Application.StartupPath, "hogehoge.xml")
    Dim dt As DataTable = MyXmlReader.Read(xmlPath)
    Me.DataGrid1.DataSource = dt

    Dim con As Context = New Context(dt)
    Dim rootNode As IXmlNode = New RootNode(con)
    rootNode.Parse()

    MessageBox.Show("構文解析がおわりました", _
            Me.Text, _
            MessageBoxButtons.OK, _
            MessageBoxIcon.Information)
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

XML の中身を全て保持しているクラスです。
各要素から、「次の要素は?」「子要素は?」と尋ねられたら、
「ハイ、つぎこれ!」と渡す役割を担っています。

Context.vb
'Xmlの全要素を管理する
Public Class Context

  Private m_dataTable As DataTable
  Private m_currentIndex As Integer

  'コンストラクタ
  Public Sub New(ByVal dt As DataTable)
    Me.m_dataTable = dt
    Me.m_currentIndex = 0
  End Sub

  '次の要素を取得
  Public Function NextToken() As String()
    If Me.m_currentIndex + 1 > Me.m_dataTable.Rows.Count - 1 Then
      Return Nothing '終端の場合はnullを返却
    End If
    Me.m_currentIndex += 1
    Return CurrentToken()
  End Function

  '現在の要素を取得
  Public Function CurrentToken() As String()
    Dim value(1) As String
    value(0) = DirectCast(Me.m_dataTable.Rows(Me.m_currentIndex)(0), String)
    value(1) = DirectCast(Me.m_dataTable.Rows(Me.m_currentIndex)(1), String)
    Return value
  End Function

  '前の要素を取得
  Public Function PreviousToken() As String()
    If Me.m_currentIndex - 1 < 0 Then
      Return Nothing '最前面の場合はnullを返却
    End If
    Me.m_currentIndex -= 1
    Return CurrentToken()
  End Function

  '現在の要素を飛ばして次の要素を取得
  Public Function SkipToken() As String()
    Return NextToken()
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

XML ファイルを読み込む為のヘルパー的なクラスです。

MyXmlReader.vb
Imports System.Xml

Public Class MyXmlReader

  'Xmlを読み込んで、DataTableに保存
  Public Shared Function Read(ByVal xmlPath As String) As DataTable

    Dim reader As XmlTextReader = New XmlTextReader(xmlPath)
    Dim dt As DataTable = New DataTable
    dt.Columns.Add("ElementName", GetType(String))
    dt.Columns.Add("ElementValue", GetType(String))

    Try
      While reader.Read()
        Dim elementName As String = ""
        Dim elementValue As String = ""
        Dim row As DataRow = Nothing

        If reader.NodeType = XmlNodeType.Element Then
          elementName = reader.Name
          reader.Read()
          If reader.NodeType = XmlNodeType.Text Then
            elementValue = reader.Value
          End If
          row = dt.NewRow()
          row("ElementName") = elementName
          row("ElementValue") = elementValue
          dt.Rows.Add(row)
        End If
      End While

      Return dt
    Finally
      If Not reader Is Nothing Then
        reader.Close()
      End If
    End Try
  End Function

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

XML の中の各要素の統一インターフェースです。

IXmlNode.vb
'XMLのノードインターフェース
Public Interface IXmlNode

  '値チェックのメソッド定義
  Sub Parse()

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

XML の Root要素 を示すクラスです。
IXmlNode を実装しています。

RootNode.vb
'Rootノード
Public Class RootNode
  Implements IXmlNode

  Private m_context As Context

  'コンストラクタ
  Public Sub New(ByVal con As Context)
    Me.m_context = con
  End Sub

  '値チェックのメソッド実装
  Public Sub Parse() Implements IXmlNode.Parse
    Me.m_context.SkipToken()
    Dim nextNode As IXmlNode = New MemberNode(Me.m_context)
    nextNode.Parse()
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

サンプルでは、Root要素 の下には、会員(Member)という要素がある決まりです。
会員(Member)という要素を表現するクラスです。
IXmlNode を実装しています。

MemberNode.vb
'会員
Public Class MemberNode
  Implements IXmlNode

  Private m_context As Context

  'コンストラクタ
  Public Sub New(ByVal con As Context)
    Me.m_context = con
  End Sub

  '値チェックのメソッド実装
  Public Sub Parse() Implements IXmlNode.Parse

    '子要素のチェック
    Dim childArray As ArrayList = New ArrayList
    While (Not Me.m_context.NextToken() Is Nothing)
      If Me.m_context.CurrentToken(0) = "会員" Then
        Me.m_context.PreviousToken() '次の要素なので前へ戻っておく(↓でNextTokenにてチェックする為)
        Exit While
      End If
      Dim childNode As IXmlNode = New MemberChildNode(Me.m_context)
      childNode.Parse()
      childArray.Add(childNode)
    End While
    If childArray.Count <> 3 Then
      Throw New CommonExceptionAndHandler.MyException("会員の子要素が足りません")
    End If

    '次の会員へ進む
    Dim nextToken() As String = Me.m_context.NextToken()
    If Not nextToken Is Nothing Then
      Dim nextNode As IXmlNode = New MemberNode(Me.m_context)
      nextNode.Parse()
    End If
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

このサンプルで扱う最下層の会員の子要素を表すクラスです。
会員の子要素には、会員番号・氏名・都道府県の 3 つが存在します。
IXmlNode を実装しています。

MemberChildNode.vb
Public Class MemberChildNode
  Implements IXmlNode

  Private m_context As Context
  Private m_Dt As DataTable

  'コンストラクタ
  Public Sub New(ByVal con As Context)
    Me.m_context = con

    Dim mdbPath As String = System.IO.Path.Combine(Application.StartupPath, "Interpreter.mdb")
    Dim connection_String As String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + mdbPath + ";User Id=admin;Password=;"
    Dim myDa As CommonDataAccess.AbstructDataAccess
    myDa = New CommonDataAccess.OleDataAccess
    Try
      myDa.Open(connection_String)
      Me.m_Dt = myDa.DataSelect("SELECT 都道府県名 FROM 都道府県マスタ")
    Finally
      If Not myDa Is Nothing Then myDa.Close()
    End Try
  End Sub

  '値チェックのメソッド実装
  Public Sub Parse() Implements IXmlNode.Parse

    If Me.m_context.CurrentToken(0) <> "会員番号" AndAlso Me.m_context.CurrentToken(0) <> "氏名" AndAlso _
       Me.m_context.CurrentToken(0) <> "都道府県" Then
      Throw New CommonExceptionAndHandler.MyException("会員の子要素が不正です")
    End If

    If Me.m_context.CurrentToken(0) = "会員番号" Then
      '会員番号は半角数字5桁以内
      If System.Text.RegularExpressions.Regex.Match(Me.m_context.CurrentToken(1), "^[0-9]{5}$").Success Then
        Throw New CommonExceptionAndHandler.MyException("会員番号 " + Me.m_context.CurrentToken(1) + "は正しくないです")
      End If
    ElseIf Me.m_context.CurrentToken(0) = "氏名" Then
      '氏名は半角英小文字10桁以内
      If System.Text.RegularExpressions.Regex.Match(Me.m_context.CurrentToken(1), "^[a-z]{10}$").Success Then
        Throw New CommonExceptionAndHandler.MyException("氏名 " + Me.m_context.CurrentToken(1) + "は正しくないです")
      End If
    ElseIf Me.m_context.CurrentToken(0) = "都道府県" Then
      '都道府県は Access のテーブルに存在する事
      If Me.m_Dt.Select("都道府県名='" + Me.m_context.CurrentToken(1) + "'").Length <> 1 Then
        Throw New CommonExceptionAndHandler.MyException("都道府県 " + Me.m_context.CurrentToken(1) + "は正しくないです")
      End If
    End If

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

■ひとこと

このデザインパターンの用途は、他のデザインパターンと比較すると思いっきり範囲が狭まっていると思います。
実際に使う機会ってあるのかしら...
# 実はこのデザインパターンをどう適用すべきか、非常に悩みました...。
ただ、このサンプルに限ったお話であれば、規則の中で、要素の追加・削除があったとしても柔軟に対応できるのではないでしょうか。


全部で 23 個のデザインパターンをやってみました。いかがでしたでしょうか?
結構大変でしたが(^^;)、大変勉強になりました。
私が oop を始めたばっかしの時、先輩に「言語の仕様とか、初心者向けの本の内容とか大体理解できるようになったんですが、
次は何を勉強したらいいと思いますか?」と尋ねたところ、
「デザインパターンを勉強してみたら?」と言われました。正直、その当時はさっぱり理解できませんでしたが、
実務で「たしかこんなのあったよな...」と実際触っていくうちに、デザインパターンの素晴らしさを体験する事ができました。
当ホームページが、デザインパターンの素晴らしさを理解するに当たって、皆様の少しでもお役に立てれば、こんなに嬉しい事はありません。

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