Iterator (イテレータ) パターン

簡単に言うと、『集合体』の持っている『要素』を順番に数え上げるデザインパターンです。
『集合体』と『要素』とは。概念的なお話をしましょう。
例えば、ビールケースの中に一杯ビールが入っている様子を想像してみて下さい。
この場合、ビールケースが『集合体』で、ビール一本一本が『要素』になります。
もうちょっと、システム屋さんに身近な例で言うと、テーブルの中にレコードが一杯ありますよね。
その場合、テーブルが『集合体』で、レコードが『要素』になります。


上の例で挙げた、ビールケースとテーブルの『集合体』としての違いは、
ビールケースは 1 ケースの上限が 24 本で、テーブルは上限は決定してないに等しいですよね。(正確にはテーブルの場合は DBMS 依存)
上限があるものについては、配列で管理した方が効率的だし、
上限がないものに関しては System.Collections.ArrayList 等で管理した方が楽ちんです。


だけど、扱うモノによって『集合体』を使っている部分を、全部実装し直すのは大変だし。
そんな時に、有用なデザインパターンが Iterator パターンです。

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

■クラス図

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

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

■サンプルの説明

サンプルの見た目
氏名とあだなを入力して、追加ボタンをクリックすると、チームのメンバーを追加します。
一覧表示をクリックすると、追加されたメンバーの一覧をリストボックスに表示します。
サンプルのプロジェクトダウンロード

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

■コード

アプリケーションのエントリポイント StartUpForm です。
StartUpFormは、『要素』の追加と数え上げの窓口を担っていますが、
『集合体』(サンプルでは Team )の細かい仕様(上限があるとかないとか)は意識していません。

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 TextBox2 As System.Windows.Forms.TextBox
  Friend WithEvents Button1 As System.Windows.Forms.Button
  Friend WithEvents Button2 As System.Windows.Forms.Button
  Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
  Friend WithEvents Label1 As System.Windows.Forms.Label
  Friend WithEvents Label2 As System.Windows.Forms.Label
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
    Me.TextBox1 = New System.Windows.Forms.TextBox
    Me.TextBox2 = New System.Windows.Forms.TextBox
    Me.Button1 = New System.Windows.Forms.Button
    Me.Button2 = New System.Windows.Forms.Button
    Me.ListBox1 = New System.Windows.Forms.ListBox
    Me.Label1 = New System.Windows.Forms.Label
    Me.Label2 = New System.Windows.Forms.Label
    Me.SuspendLayout()
    '
    'TextBox1
    '
    Me.TextBox1.Location = New System.Drawing.Point(72, 32)
    Me.TextBox1.Name = "TextBox1"
    Me.TextBox1.Size = New System.Drawing.Size(136, 19)
    Me.TextBox1.TabIndex = 0
    Me.TextBox1.Text = "TextBox1"
    '
    'TextBox2
    '
    Me.TextBox2.Location = New System.Drawing.Point(72, 56)
    Me.TextBox2.Name = "TextBox2"
    Me.TextBox2.Size = New System.Drawing.Size(136, 19)
    Me.TextBox2.TabIndex = 1
    Me.TextBox2.Text = "TextBox2"
    '
    'Button1
    '
    Me.Button1.Location = New System.Drawing.Point(208, 56)
    Me.Button1.Name = "Button1"
    Me.Button1.TabIndex = 2
    Me.Button1.Text = "Button1"
    '
    'Button2
    '
    Me.Button2.Location = New System.Drawing.Point(96, 88)
    Me.Button2.Name = "Button2"
    Me.Button2.TabIndex = 3
    Me.Button2.Text = "Button2"
    '
    'ListBox1
    '
    Me.ListBox1.ItemHeight = 12
    Me.ListBox1.Location = New System.Drawing.Point(32, 120)
    Me.ListBox1.Name = "ListBox1"
    Me.ListBox1.Size = New System.Drawing.Size(232, 112)
    Me.ListBox1.TabIndex = 4
    '
    'Label1
    '
    Me.Label1.Location = New System.Drawing.Point(0, 32)
    Me.Label1.Name = "Label1"
    Me.Label1.TabIndex = 5
    Me.Label1.Text = "Label1"
    '
    'Label2
    '
    Me.Label2.Location = New System.Drawing.Point(0, 56)
    Me.Label2.Name = "Label2"
    Me.Label2.TabIndex = 6
    Me.Label2.Text = "Label2"
    '
    '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.Label2)
    Me.Controls.Add(Me.TextBox1)
    Me.Controls.Add(Me.Label1)
    Me.Controls.Add(Me.Button2)
    Me.Controls.Add(Me.ListBox1)
    Me.Controls.Add(Me.Button1)
    Me.Name = "StartUpForm"
    Me.Text = "Form1"
    Me.ResumeLayout(False)

  End Sub

#End Region

  'チーム
  Private m_myTeam As Team

  '画面がロードされた時のイベント
  Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Me.Text = "Iterator"
    Me.TextBox1.Text = ""
    Me.TextBox2.Text = ""
    Me.Button1.Text = "追加"
    Me.Button2.Text = "一覧表示"
    Me.Label1.Text = "氏名"
    Me.Label2.Text = "あだな"
    Me.ListBox1.Items.Clear()

    '空のチームを作成する
    Me.m_myTeam = New Team
  End Sub

  '追加ボタンをクリックした時のイベント
  Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
    Dim addUser As User
    addUser = New User(Me.TextBox1.Text, Me.TextBox2.Text)
    Me.m_myTeam.AppendUser(addUser)

    'メッセージの表示
    MessageBox.Show(addUser.Name + "(" + addUser.NickName + ")" + "を追加しました", _
            Me.Text, _
            MessageBoxButtons.OK, _
            MessageBoxIcon.Information)
    Me.TextBox1.Text = ""
    Me.TextBox2.Text = ""
    Me.TextBox1.Focus()
  End Sub

  '一覧表示ボタンをクリックした時のイベント
  Private Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click
    Dim teamIterator As IIterator = Me.m_myTeam.CreateIterator()

    While (teamIterator.HasNext())
      Dim objUser As User = DirectCast(teamIterator.SerchNext(), User)
      Me.ListBox1.Items.Add(objUser.Name + "(" + objUser.NickName + ")")
    End While
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

『集合体』を表すインターフェースの ICollection です。
このインターフェースを implements する クラスは CreateIterator メソッドで
自身の持っている『要素』の数え上げを行ってくれる人( IIterator )を実装します。

ICollection.vb
'集合体を表すインタフェース
Public Interface ICollection

  'イテレータを作成するメソッド定義
  Function CreateIterator() As IIterator

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

『集合体』の持っている要素を数え上げてくれるインターフェースの IIterator です。

IIterator.vb
'反復子インターフェース
Public Interface IIterator

  '次の要素があるか調べる
  Function HasNext() As Boolean

  '次の要素を取得する
  Function SerchNext() As Object

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

今度は具体的な『集合体』です。人の集合→チームとしました。
メンバー数の上限は特に定めないので、 System.Collections.ArrayList で管理する事にしました。

Team.vb
'チーム
Public Class Team
  Implements ICollection

  'チームに所属する人達
  Private m_users As ArrayList
  '今現在の人の位置
  Private m_last As Integer

  'コンストラクタ
  Public Sub New()
    Me.m_users = New ArrayList
  End Sub

  '位置を指定して要素を取得する
  Public Function GetUserAt(ByVal index As Integer) As User
    Return DirectCast(Me.m_users(index), User)
  End Function

  '人を追加する
  Public Sub AppendUser(ByVal addUser As User)
    Me.m_users.Add(addUser)

    '現在の人数に1人追加する
    Me.m_last += 1
  End Sub

  '存在する人の数を取得する
  Public Function GetLength() As Integer
    Return Me.m_last
  End Function

  'イテレータを作成するメソッド実装
  Public Function CreateIterator() As IIterator Implements ICollection.CreateIterator
    Return New TeamIterator(Me)
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

チーム(集合体)に属する人達(要素)の数え上げを行うクラスです。
IIterator を実装しています。

TeamIterator.vb
'チームのイテレータ
Public Class TeamIterator
  Implements IIterator

  'チーム
  Private m_team As Team
  '位置
  Private m_index As Integer

  'コンストラクタ
  Public Sub New(ByVal tm As Team)
    Me.m_team = tm
    Me.m_index = 0
  End Sub

  '次の要素があるか調べる
  Public Function HasNext() As Boolean Implements IIterator.HasNext
    If Me.m_index < Me.m_team.GetLength() Then
      Return True
    Else
      Return False
    End If
  End Function

  '次の要素を取得する
  Public Function SerchNext() As Object Implements IIterator.SerchNext
    Dim objUser As User = Me.m_team.GetUserAt(Me.m_index)
    Me.m_index += 1
    Return objUser
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

チーム(集合体)に属する人達(要素)のクラスです。

User.vb
'人
Public Class User
  '名前
  Private m_name As String = ""
  'あだ名
  Private m_nickname As String = ""

  'コンストラクタ
  Public Sub New(ByVal name As String, _
           ByVal nickname As String)
    Me.m_name = name
    Me.m_nickname = nickname
  End Sub

  '名前アクセサ
  Public ReadOnly Property Name() As String
    Get
      Return Me.m_name
    End Get
  End Property

  'あだ名アクセサ
  Public ReadOnly Property NickName() As String
    Get
      Return Me.m_nickname
    End Get
  End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

■ひとこと

上記サンプルで、もしチームは「サッカーチームにしよう」と思い、 ArrayList で管理するのをやめるとしたら?
Team クラスと、TeamIterator クラスを修正するだけで、とりあえずは済みそうですね。


Iterator パターンはデザインパターンの入り口として、書籍やインターネット上に、大変易しいサンプルが沢山あります。
参考にしてみてくださいね。

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