4.4 AppDecoratorの修正(後編)

前項でAppDecoratorの共通部と固有部の分離は終わりました。ここでは、より汎用に使えるように、複数のブックと複数のBookDecoratorが使えるようにAppDecoratorを修正していきます。

マクロを分離する目的は、複数のブックに同じ機能を提供することです。したがって、同時に複数のブックを開いたときに、必要な機能を全てのブックに提供できなければなりません。しかし、前項のAppDecoratorでは、BookDecorator型の変数bookDecoのみでBookDecoratorを保持しています。1つのBookDecoratorには1つのブックが対応しますから、複数のブックでは同じ機能を使えません。

また、中規模のシステムになると、ある機能を実現するために別々のマクロによって動く複数のブックを組み合わせて使うことがあります。もし、現在作成しているフレームワークで複数のBookDecoratorを保持できるようになれば、複数のシートにまたがっているマクロを一元的に管理することが出来るでしょう。

では、以上2つの機能を現在まで作成してきたフレームワークに組み込むように修正してみましょう。そのためには、

を扱えることが必要になります。複数のBookDecoratorを扱うには、BookDecoratorの代わりにBookDecoratorのコレクションを使うだけです。では、多様なBookDecoratorを扱うにはどうすればよいでしょうか。

比較のために、前項で作成したBookDecoratorの初期化ルーチンをもう一度見てみましょう。


[AppDecorator]
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

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

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

AppDecoratorは、各ブックに固有の処理を担当するBookDelegate(の疑似派生クラス)の作成処理をBookCheckerに委譲しています。ですから、別のBookDelegateをもつBookDecoratorを作成するには、別のBookDelegateを生成するためのBookCheckerの疑似派生クラスを作成すればよいことになります。従って、複数種なBookDecoratorを扱うには、複数のBookCheckerを使えるようにする必要があります。

以上の前提をふまえて実装していきましょう。まずはメンバ変数の宣言です。


Public WithEvents app As Application
Private bookDecorators As New Collection 'of BookDecorator
Private bookCheckers As New Collection 'of BookChecker

BookDecoratorとBookCheckerがそれぞれコレクションとして管理されています。


Public Sub Initialize()
    Set app = Application
    Dim wb As Workbook
    SetBookCheckers
    If bookCheckers.Count > 0 Then
        For Each wb In Workbooks
            CreateBookDecorator wb
        Next
    Else
        Unload Me
    End If
End Sub

Private Sub SetBookCheckers()

End Sub

初期化ルーチンです。ここで注目していただきたいのは、Initializeメソッドの中ほどから呼び出されているSetBookCheckersです。この追加されたメソッドは、BookCheckerのコレクションへの登録をおこなうためのものです。SetBookCheckersメソッドは、必要なBookDecoratorを疑似継承したクラスをbookCheckersコレクションに登録します。実装はマクロごとに異なりますから、デフォルトの処理はなく、状況に応じて加筆します。あるBookDecoratorであるConcreteBookChecker1, 2を登録するには以下のようにします:


Private Sub SetBookCheckers()
    bookCheckers.Add Item:=New ConcreteBookChecker1
    bookCheckers.Add Item:=New ConcreteBookChecker2
End Sub

AppDecoratorのSetBookCheckersメソッドは各AppDecoratorに固有の処理です。これまでの議論から言えば、この処理は新たなクラスAppDelegateを作成して委譲すべきと感じるかもしれません。そこで、SetBookCheckersメソッドを分離しなかった理由について補足します。

BookDecoratorなどでは、BookDelegateに固有の処理を分離・委譲していました。このようにしたのは、複数種のBookDecoratorを1つのマクロで使用する際に、書き換えなければいけない箇所を減らすためでした。一方、異なるExcelのアプリケーションを使うことはないですから、AppDecoratorは複数になりません。したがって、AppDecoratorの場合は、それ自身の中に変更すべき箇所を書いた方が、加筆修正の量が少なくてすみます。そこでAppDecoratorだけはDelegateを持たないように設計してあります。

また、BookCheckerは、BookDecoratorの生成の時だけに関与するクラスであり、Excelアプリよりむしろブックに密接しているクラスです。そこでこのクラスにもAppDelegateという名称を与えていません(BookFactoryの良かったかも・・・)。

では、先を見てみましょう。CreateBookDecoratorメソッドの修正例です:


Private Sub CreateBookDecorator(wb As Workbook)
    Dim bc As Object 'BookChecker
    For Each bc In bookCheckers
        If bc.CheckBook(wb) = True Then
            Dim bd As New BookDecorator
            bd.Initialize wb, bc.CreateBookDelegate, Me
            bookDecorators.Add Item:=bd
        End If
    Next
End Sub

各BookCheckerについて走査し、条件が合えばBookCheckerが指定するBookDelegateと結合されたBookDecoratorがbookDecoratorsコレクションに格納されます。

次に、閉じるときの処理です:


Private Sub app_WorkbookBeforeClose(ByVal wb As Workbook, Cancel As Boolean)
    Dim bd As BookDecorator
    Dim i As Integer
    i = 1
    For Each bd In bookDecorators
        If bd.wbook Is wb Then
            bookDecorators.Remove i
        End If
        i = i + 1
    Next
End Sub

格納されているBookDecoratorを走査し、閉じようとしているブックのBookDecoratorを全て解放しています。

以上のような変更によって、複数のブックと複数のBookDecoratorが使えるようになり、フレームワークの再利用性があがりました。