4.3 AppDecorator再考(前編)

前項ではブックをフックするBookDecoratorから固有の部分をBookDelegateとして分離させる方法を示しました。この項ではその上位に位置するAppDecoratorの実装を、共通部分を固有部分という観点から考えていきましょう。

AppDecoratorはExcelの最上位のイベントをフックするためのクラスです。AppDecoratorはBookDecoratorを保持し、その取得と解放を行います。まずはAppDecoratorの機能を検討するために、前章で扱ったfoo.xls関係のAppDecoratorを示します:


Private WithEvents app As Application
Private bookDeco As BookDecorator

Public Sub Initialize()
    Set app = Application
    Dim wb As Workbook
    For Each wb In Workbooks
        CreateBookDecorator
    Next
End Sub

Private Sub CreateBookDecorator(wb As Workbook)
    If wb.Name = "foo.xls" Then
        Set bookDeco = New BookDecorator
        bookDeco.Initialize wb
    End If
End Sub

Private Sub app_WorkbookOpen(ByVal wb As Workbook)
    CreateBookDecorator
End Sub

Private Sub app_WorkbookBeforeClose(ByVal wb As Workbook, Cancel As Boolean)
    If wb.Name = ("foo.xls") Then
        Set bookDeco = Nothing
    End If
End Sub

では、この中で、どの動作がブック固有で、どれが共通しているかを検討してみましょう。とはいっても、ブック固有の処理は"foo.xls"が出てくる部分だけです。そこで、foo.xlsが出ている2ヶ所について考えてみます。

この2ヶ所は何をしているのでしょうか。CreateBookDecoratorでは、BookDecoratorを確保する前に、BookDecoratorを確保すべきかどうかを判定しています。そしてWorkbookBeforeCloseイベントの中では、ブックを閉じる際に、閉じようとしているブックにはBookDecoratorが設定されているのかを判定し、必要に応じてBookDecoratorを解放しています。そこで、これら処理を一般化して文字で書くと以下のようになるでしょう:


Private Sub CreateBookDecorator(wb As Workbook)
    If 指定したブックがDecoratorの取得条件に合っている Then
        Set bookDeco = New BookDecorator
        bookDeco.Initialize wb
    End If
End Sub

Private Sub app_WorkbookBeforeClose(ByVal wb As Workbook, Cancel As Boolean)
    If 閉じようとしているブックがBookDecoratorを持っている Then
        Set bookDeco = Nothing
    End If
End Sub

このうち、後者、すなわちWorkbookBeforeCloseメソッド中の条件文は、汎用的な方法で書き直せます。「閉じようとしているブックがBookDecoratorを持っている」ということは、「閉じようとしているのがBookDecoratorがフックしているブックかどうか」ということです。つまり、閉じようとしているブックと、BookDecoratorが保持しているブックが同一かどうかがわかればオーケーです。従って、以下のように書けます:


Private Sub app_WorkbookBeforeClose(ByVal wb As Workbook, Cancel As Boolean)
    If bookDeco.wbook Is wb Then
        Set bookDeco = Nothing
    End If
End Sub

つぎに、Initializeメソッドに今一度焦点を当てて、もう一度固有部分の分離について考えていきます。前項で扱ったように、今使っているBookDecoratorは固有の処理をBookDelegateに分離したものを想定しています。前章で扱ったとおり、AppDecoratorはBookDecoratorを作成する責任があります。したがって、BookDecoratorを作成するメソッドには、適切なBookDelegateを関連づける処理が必要になります。

そこで、BookDecoratorをある移譲先クラスConcreteBookDelegateと関連づけたいとしましょう。このとき、作成するCreateBookDecoratorメソッドは以下のようになります。


Private Sub CreateBookDecorator()
    If 指定したブックがDecoratorの取得条件に合っている Then
        Set bookDeco = New BookDecorator
        Set bookDele = New ConcreteBookDelegate
        bookDeco.Initialize wb, bookDele, Me
    End If
End Sub

なお、前章の例ではBookDecoratorはAppDecoratorを保存していませんでしたが、一般化するために、BookDecoratorのparent変数によってAppDecoratorを保持するようにしてあります(前項参照)。そこでBookDecorator.InitializeメソッドにはAppDecoratorが引数として追加されています。最後のMeのことですね。

それでは、AppDecoratorの機能のうち、固有の処理を分離してみましょう。今回「取得条件」の他に、新たに「適切なBookDelegateを取得する」という固有の機能が追加されています。この2つの機能は互いに関連がありますので、これらを1つのクラスに分離します。このクラスはAppDelegateでも良いのですが、後で述べる理由によってBookCheckerという名前にしておきます。そこで、BookCheckerの枠を示します:


[BookChecker]
Public Function CheckBook(wb As Workbook) As Boolean
    'ブックの条件式をここに書く
End Function

Public Function CreateBookDelegate() As Object
    '適当なBookDelegateを返す
End Function

CreateBookDelegateメソッドの例として、ConcreteBookDelegateを返すメソッドを示せば、以下のようになります:


Public Function CreateBookDelegate() As Object
    Set CreateBookDelegate = New ConcreteBookDelegate
End Function

CheckBookメソッドは、BookCheckerを擬似的に継承したクラスで書き加えます。そして、BookCheckerの中身を書き加えたあるクラスConcreteBookCheckerを使う時を例にすれば、AppDecoratorは以下のように修正されます:


[AppDecorator]
Private bookCheck As Object 'BookChecker

(略)

Private Sub Initialize()
(略)
    Set bookCheck = New ConcreteBookChecker
End Sub

Private Sub CreateBookDecorator(wb As Workbook)
    If bookCheck.CheckBook(wb) = True Then
        Set bookDeco = New BookDecorator
        Set bookDele = bookCheck.CreateBookDelegate
        bookDeco.Initialize wb, bookDele, Me
    End If
End Sub

これで、固有の処理を分離したAppDecoratorができあがりました。最後にソースを今一度示します。


[AppDecorator]
Private WithEvents app As Application
Private bookDeco As BookDecorator
Private bookCheck As Object 'BookChecker

Public Sub Initialize()
    Set app = Application
    Dim wb As Workbook
    Set bookCheck = New ConcreteBookChecker
    For Each wb In Workbooks
        CreateBookDecorator
    Next
End Sub

Private Sub CreateBookDecorator(wb As Workbook)
    If bookCheck.CheckBook(wb) = True Then
        Set bookDeco = New BookDecorator
        Set bookDele = New ConcreteBookDelegate
        bookDeco.Initialize wb, bookDele, Me
    End If
End Sub

Private Sub app_WorkbookOpen(ByVal wb As Workbook)
    CreateBookDecorator
End Sub

Private Sub app_WorkbookBeforeClose(ByVal wb As Workbook, Cancel As Boolean)
    If bookDeco.wbook Is wb Then
        Set bookDeco = Nothing
    End If
End Sub

[BookChecker]
Public Function CheckBook(wb As Workbook) As Boolean
    'ブックの条件式をここに書く
End Function

Public Function CreateBookDelegate() As Object
    Set CreateBookDelegate = New ConcreteBookDelegate
End Function