Bridge (ブリッジ) パターン

ごちゃごちゃしていて機能拡張するのに困難な状況になっている階層と実装を、大まかな役割毎の階層にわけ、整理して、
それぞれの機能を拡張しやすくするデザインパターンです。


サンプルでは、データベースにアクセスしてファイル作成を行う実装の階層と、ファイルを出力をする機能の階層にわけました。
ファイルを出力する機能の階層のクラスのコンストラクタに、実装の階層のクラスのインスタンスを渡す事により、
Bridge(橋渡し)を実現しています。
Zip 圧縮処理は、vjslib.dll を参照設定して実装しています。

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

■クラス図

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

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

■サンプルの説明

サンプルの見た目1
データを取得する元の DataBase を選択し、Zip 圧縮する場合はチェックボックスにチェックを入れます。
出力ボタンをクリックすると、アプリケーションの開始ディレクトリに、指定された DataBase から取ってきた
jobs テーブルの全レコードのcsvファイルをアプリケーション開始ディレクトリにファイル出力します。
チェックボックスにチェックが入っている場合は、ZIp 圧縮されています。
サンプルのプロジェクトダウンロード

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

■コード

アプリケーションのエントリポイント 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 Label1 As System.Windows.Forms.Label
  Friend WithEvents ComboBox1 As System.Windows.Forms.ComboBox
  Friend WithEvents Button1 As System.Windows.Forms.Button
  Friend WithEvents CheckBox1 As System.Windows.Forms.CheckBox
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
    Me.Label1 = New System.Windows.Forms.Label
    Me.ComboBox1 = New System.Windows.Forms.ComboBox
    Me.Button1 = New System.Windows.Forms.Button
    Me.CheckBox1 = New System.Windows.Forms.CheckBox
    Me.SuspendLayout()
    '
    'Label1
    '
    Me.Label1.Location = New System.Drawing.Point(8, 8)
    Me.Label1.Name = "Label1"
    Me.Label1.TabIndex = 0
    Me.Label1.Text = "Label1"
    '
    'ComboBox1
    '
    Me.ComboBox1.Location = New System.Drawing.Point(112, 8)
    Me.ComboBox1.Name = "ComboBox1"
    Me.ComboBox1.Size = New System.Drawing.Size(121, 20)
    Me.ComboBox1.TabIndex = 2
    Me.ComboBox1.Text = "ComboBox1"
    '
    'Button1
    '
    Me.Button1.Location = New System.Drawing.Point(96, 64)
    Me.Button1.Name = "Button1"
    Me.Button1.TabIndex = 4
    Me.Button1.Text = "Button1"
    '
    'CheckBox1
    '
    Me.CheckBox1.Location = New System.Drawing.Point(8, 32)
    Me.CheckBox1.Name = "CheckBox1"
    Me.CheckBox1.TabIndex = 5
    Me.CheckBox1.Text = "CheckBox1"
    '
    'StartUpForm
    '
    Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12)
    Me.ClientSize = New System.Drawing.Size(292, 266)
    Me.Controls.Add(Me.CheckBox1)
    Me.Controls.Add(Me.Button1)
    Me.Controls.Add(Me.ComboBox1)
    Me.Controls.Add(Me.Label1)
    Me.Name = "StartUpForm"
    Me.Text = "Form1"
    Me.ResumeLayout(False)

  End Sub

#End Region

  'DataBase
  Private Const DB_SQLSERVER As String = "SQLServer"
  Private Const DB_ACCESS As String = "ACCESS"

  '画面がロードされた時のイベント
  Private Sub StartUpForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Me.Text = "Bridge"

    Me.Label1.Text = "DataBase"

    Me.ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList
    Me.ComboBox1.Items.Clear()
    Me.ComboBox1.Items.Add(Me.DB_SQLSERVER)
    Me.ComboBox1.Items.Add(Me.DB_ACCESS)
    Me.ComboBox1.SelectedIndex = 0

    Me.CheckBox1.Text = "圧縮する"
    Me.Button1.Text = "出力"
  End Sub

  '出力ボタンをクリックした時のイベント
  Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click

    Dim myFileOutputter As FileOutputter
    Dim outputter As AbstractOutputter

    'DataBase
    If Me.ComboBox1.Text = Me.DB_SQLSERVER Then
      outputter = New SqlOutputter
    Else
      outputter = New OleDbOutputter
    End If

    '圧縮の有無
    If Me.CheckBox1.Checked Then
      myFileOutputter = New FileOutputterEx(outputter)
    Else
      myFileOutputter = New FileOutputter(outputter)
    End If

    '出力
    Dim outputpath As String = myFileOutputter.Output("SELECT * FROM jobs")

    MessageBox.Show("出力が終わりました" + ControlChars.NewLine + outputpath, _
            Me.Text, _
            MessageBoxButtons.OK, _
            MessageBoxIcon.Information)
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

実装のクラス階層の抽象の親クラスです。

AbstractOutputter.vb
'実装のクラス階層
Public MustInherit Class AbstractOutputter

  'ファイル出力メソッド定義
  Public MustOverride Function Output(ByVal sqlScript As String) As String

  'DataTableを元にファイル出力を行う
  Protected Function CsvOutput(ByVal dt As DataTable) As String
    Dim saveFilePath As String = System.IO.Path.Combine(Application.StartupPath, _
                  DateTime.Now.ToString("yyyyMMddHHmmss") + ".csv")
    Dim sw As System.IO.StreamWriter
    Try
      sw = New System.IO.StreamWriter(saveFilePath, False, System.Text.Encoding.Default)
      For Each row As DataRow In dt.Rows
        For i As Integer = 0 To dt.Columns.Count - 1
          If i > 0 Then sw.Write(","c)
          sw.Write("""" + row(i).toString() + """")
        Next
        sw.WriteLine()
      Next

      Return saveFilePath
    Finally
      If Not sw Is Nothing Then sw.Close()
    End Try
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

実装のクラス階層の Access データベースより値を取得してファイル作成を行うクラスです。
AbstractOutputter を継承しています。

OleDbOutputter.vb
'実装のクラス階層
Public Class OleDbOutputter
  Inherits AbstractOutputter

  'ファイル出力メソッド実装
  Public Overrides Function Output(ByVal sqlScript As String) As String
    Dim dt As DataTable
    Dim mdbPath As String = System.IO.Path.Combine(Application.StartupPath, "Bridge.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)
      dt = myDa.DataSelect(sqlScript)
    Finally
      If myDa Is Nothing Then myDa.Close()
    End Try
    Return MyBase.CsvOutput(dt)
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

実装のクラス階層の SQLServer データベースより値を取得してファイル作成を行うクラスです。
AbstractOutputter を継承しています。

SqlOutputter.vb
'実装のクラス階層
Public Class SqlOutputter
  Inherits AbstractOutputter

  'ファイル出力メソッド実装
  Public Overrides Function Output(ByVal sqlScript As String) As String
    Dim dt As DataTable
    Const CONNECTION_STRING As String = "Data Source=(local);" + _
                      "Initial Catalog=pubs;" + _
                      "Integrated Security=SSPI;"
    Dim myDa As CommonDataAccess.AbstructDataAccess
    myDa = New CommonDataAccess.SqlServerDataAccess
    Try
      myDa.Open(CONNECTION_STRING)
      dt = myDa.DataSelect(sqlScript)
    Finally
      If myDa Is Nothing Then myDa.Close()
    End Try

    '出力
    Return MyBase.CsvOutput(dt)
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

機能のクラス階層のクラスです。
Output メソッドは、実装のクラス階層のオブジェクトを用いてファイル作成を行います。

FileOutputter.vb
'機能のクラス階層
Public Class FileOutputter

  '実装の階層のオブジェクト
  Protected m_outputter As AbstractOutputter

  'コンストラクタ(引数のoutputterが橋渡しの役となる)
  Public Sub New(ByVal outputter As AbstractOutputter)
    Me.m_outputter = outputter
  End Sub

  'ファイル出力
  Public Overridable Function Output(ByVal sqlScript As String) As String
    Return Me.m_outputter.Output(sqlScript)
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

機能のクラス階層のクラスです。FileOutputter クラスを拡張し、
Output メソッドで、実装のクラス階層のオブジェクトを用いてファイル作成を行った後、
Zip 圧縮を行います。

FileOutputterEx.vb
'機能のクラス階層
'FileOutputの拡張クラス
Public Class FileOutputterEx
  Inherits FileOutputter

  'コンストラクタ
  Public Sub New(ByVal outputter As AbstractOutputter)
    MyBase.New(outputter)
  End Sub

  '圧縮して出力する
  Public Overrides Function Output(ByVal sqlScript As String) As String
    Dim normalOutputPath As String = MyBase.m_outputter.Output(sqlScript)

    '圧縮
    Return Me.ToZipFile(normalOutputPath)
  End Function

  'Zip圧縮
  Private Function ToZipFile(ByVal fileName As String) As String
    Dim zipFileName As String = fileName + ".zip"

    Dim myZipOutStream As java.util.zip.ZipOutputStream
    Dim myFileOutputStream As java.io.FileOutputStream
    myFileOutputStream = New java.io.FileOutputStream(zipFileName)
    myZipOutStream = New java.util.zip.ZipOutputStream(myFileOutputStream)

    Dim myFile As java.io.File = New java.io.File(fileName)
    putFileToZip(myZipOutStream, myFile)

    '出力ストリームを閉じる
    myZipOutStream.flush()
    myZipOutStream.close()

    Return zipFileName
  End Function

  'ZipOutputStreamへの書き出し
  Private Sub putFileToZip(ByVal out As java.util.zip.ZipOutputStream, _
               ByVal myFile As java.io.File)

    Dim input As java.io.BufferedInputStream
    Dim buf(256) As System.SByte
    Dim crc As java.util.zip.CRC32 = New java.util.zip.CRC32

    'CRC32を求める
    Dim size As Integer
    input = New java.io.BufferedInputStream(New java.io.FileInputStream(myFile))
    While (True)
      size = input.read(buf, 0, buf.Length)
      If size = -1 Then Exit While
      crc.update(buf, 0, size)
    End While

    'エントリの作成
    Dim entry As java.util.zip.ZipEntry = New java.util.zip.ZipEntry(myFile.getName())
    '圧縮メソッド設定
    entry.setMethod(java.util.zip.ZipEntry.DEFLATED)
    '圧縮前サイズ設定
    entry.setSize(myFile.length())
    entry.setTime(DateTime.Now.Ticks)
    'CRCを設定
    entry.setCrc(crc.getValue())
    out.putNextEntry(entry)

    '圧縮元ファイルの内容を書き込む。
    input = New java.io.BufferedInputStream(New java.io.FileInputStream(myFile))
    Dim sz As Integer
    While (True)
      sz = input.read(buf, 0, buf.Length)
      If sz = -1 Then Exit While
      crc.update(buf, 0, sz)
      out.write(buf, 0, sz)
    End While

    'エントリを閉じる
    out.closeEntry()
    out.flush()
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

■ひとこと

サンプルの場合、後から Zip 圧縮の処理が追加されたのだとしたら、
階層に分けておく事により、機能の追加が容易に出来てしまうわけです。
これが、もし、ファイル出力を行うクラスが、それぞれ SqlServer 用と、OleDb 用に 1 クラスずつしか
存在しなかった場合の事を想像してみて下さい。 また、その上さらに Odbc 用のクラスを追加した場合の事も想像してみて下さい。


実装の階層(SqlOutputter クラス等)と機能を提供する階層(FileOutputter クラス等)を分離しておく事により、
各々の階層の拡張を容易にすることができます。
例えば、機能として『暗号化ファイルを作成する』という機能が追加された場合、
FileOutputter クラスを継承して新たなクラスを 1 つ作り、OutPut メソッドでファイルの暗号化の実装を行えばよいのです。

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