Skip to content.

Sections
Personal tools
You are here: Home » コミュニティ » masarl memorial » homepage3.nifty.com » masarl » article » Excel VBA - Decorator パターンモデル

Excel VBA - Decorator パターンモデル

Document Actions

Excel VBA - Decorator パターンモデル

1999/08/27 更新 石井 勝

概要

ここでは,Decorator パターンモデルという Excel プログラミングに関するアーキテクチャを解説します.これは,Decorator パターンをアーキテクチャレベルにまで拡張したモデルで,継承が使えない VB プログラミングで威力を発揮すると思います.まだ実験段階なので,このモデルが実用化できるかは今後の課題です.

Excel プログラミングとは?

Excel のプログラミングを一言で表すと,VBA プログラミングで Excel を拡張する,ということです.オブジェクト指向の立場で拡張といえば,継承ですね.したがって次のように任意の ExcelObject クラスを継承してプログラミングできればいいですね:

継承による Excel オブジェクトの拡張

例えば Worksheet クラスから MyWorksheet クラスを継承すればいいわけです.そうすると望みのカスタマイズされたワークシートが使えるようになります.これが本来のオブジェクト指向におけるフレームワークを使ったプログラミングスタイルです.

ところが,Excel プログラミングの場合こうするわけにはいきません.最初から Worksheet オブジェクトが Excel のオブジェクトモデルの一部として提供されており,このオブジェクトを継承を使って拡張できないのです.容易に変更できるのは,Worksheet オブジェクトのプロパティだけです.どうしたらいいのでしょうか?

Worksheet オブジェクトのクラスが最初から変更できない以上,このオブジェクトを拡張するには動的継承のような機能が必要です.動的継承は,すでにインスタンスとして存在するオブジェクトを拡張するということです.もちろんこんな機能は VBA にないので別の方法を考える必要があります.そこで,GoF デザインパターンカタログを調べると,Decorator パターンにこんな記述がされているのがわかります.

「オブジェクトに責任を動的に追加する.Decorator パターンは,サブクラス化よりも柔軟な機能拡張方法を提供する」

これですね.Excel のオブジェクトを拡張するには,この Decorator パターンを使うのがよさそうです.ExcelObject を下図のように Decorator クラスで拡張すればよいのです.

DecoratorによるExcelオブジェクトの拡張

Decorator クラスは,target として参照する ExcelObject を外から拡張します.Worksheet オブジェクトを拡張するには,MyWorksheetDecorator クラスを作ればよいわけです.このように,継承を使って拡張できない Excel プログラミングでは,Decorator パターンによる拡張が一番いいのではないかと思われます.

ところで,Decorator パターンをそのまま適用するとクラス図は次のようになることがわかります.

DecoratorによるExcelオブジェクトの拡張(理想版)

もちろん VBA でこんなことはできません.この辺をちゃんと実装できないのが VBA プログラミングの問題点でしょう.なお,上の図の MyDecorator が先ほどの Decorator に相当していることに注意してください.

Excel オブジェクトモデル

Excelのオブジェクトモデルをクラス図を使って表すと,次のようになります.

Excel のオブジェクトモデル

Application クラスが Excel 本体を表し,Application は Excel ブックファイルである Workbook クラスを複数所有しています.Workbook クラスは,シートからなり,シートは Worksheet か Chart のいずれかです.これは,Excel のユーザなら直感的に理解できると思います.なお,図では Sheet クラスを抽象クラスとして書いていますが,本来の Excel オブジェクトモデルにはこのようなクラスは存在しません.あえて理解しやすくするために Sheet クラスを導入しました.ご注意を.

さて,この記事では簡単のために Excel のChart クラスを無視して次のモデルであると考えましょう.実際にグラフシート単独で使うことはあまりないと思います(ちがうのかな?).

Excel オブジェクトモデル簡易版

前のセクションで書いたとおり,Excel を拡張するのには Decorator パターンを使います.そうすると,Application, Workbook, Worksheet に対応して AppDecorator, BookDecorator, SheetDecorator を用意すればいいですね.

Decoratorクラスの構造

オブジェクトの所有関係も同じにしていることに注意してください.また,SheetDecorator が0以上の多重度に設定されているのは,Decorator によって拡張されない生のままの Excel ワークシートもありうるからです.このように,Excel のオブジェクトモデルの構造とパラレルに Decorator 側のオブジェクト構造を定義するのが, Decorator パターンモデルなのです.上図を合わせて書けば,次のようになります.

Decoratorパターンモデルの構造

Excel 全体を拡張するのに,そのオブジェクト構造がそっくりパラレルなDecoratorクラス郡を用意してやる,というのがミソです.パッケージ図は次のようになりますね.

Decoratorパターンモデルのパッケージ図

このように,Decorator 側とExcel 側で分離してプログラムを行うということがお分かりいただけたかと思います.

ところで,SheetDecorator, BookDecorator, AppDecorator の間で所有関係を持たせていることに疑問をもっている方もいらっしゃるかもしれません.その理由にひとつは,Decorator オブジェクトの寿命を管理するためです.Decorator オブジェクトをローカルで作って ExcelObject に引っ付けても,Decorator オブジェクトはすぐに破棄されてしまいます.そのため,AppDecorator をグローバル変数として用意し, この AppDecorator が BookDecorator の寿命を管理します.さらに BookDecorator は,SheetDecorator オブジェクトの寿命を管理します.例えば, BookDecorator がひっついているワークブックが閉じられた場合,それに付随する SheetDecorator もみんな破棄する,という処理などを行います.

さらにユーザのコマンドは,

  • アプリケーションレベルのコマンド
  • ブックレベルのコマンド
  • シートレベルのコマンド

に分類できます.これらのコマンド処理の実装の役割を分担するためにも Decorator をそれぞれのレベルに分けて構成しています.

Decorator パターンモデルの実装

WithEvents キーワード

実際の VBA のプログラミングでは,WithEvents キーワードとクラスモジュールを使って Decorator クラスが実装できます.WithEvents を使えば,ターゲットとなるオブジェクトのイベントをフックすることが可能です.ターゲットオブジェクトのイベントをフックすることによって結果的にそのオブジェクトを拡張できます.VBA では,Decorator に相当するクラスモジュールに次のコードを書くだけでOKです.

Public WithEvents Target As ExcelObject

ExcelObject には,実際には Application, Workbook, Worksheet などが入ります.Decorator のTarget プロパティに Excel のオブジェクトを指定することでそのオブジェクトのイベントをフックできるようになるのです.例えば,

Dim aBookDecorator As New BookDecorator
Dim aWorkbook As Workbook

    ...

Set aBookDecorator.Target = aWorkbook

としておき, BookDecorator の方で aWorkbook オブジェクトの Activate イベントをフックするコードは BookDecorator のメソッドとして

Private Sub Target_Activate()
    'Activate イベント処理コードをここに書く
End Sub

としておきます.このように,WithEvents を使えば VBA でDecoratorパターンが簡単に実装できるわけです.

ここまで読んだ方は,Decorator パターンというより Strategy パターンと呼んだほうがいいのではないか? と思われるかもしれません.そう呼ばない理由は,オブジェクトを参照する方向にあります.Strategy パターンの場合,機能を拡張される側のオブジェクトが Strategy オブジェクトを参照していますが,Decorator パターンでは,機能を拡張される側のオブジェクトは,Decoratorのことは何も知りません.だからここでは Decorator パターンと呼ぶのがふさわしいと考えます.イベントをフックする以外のコードもすべて Decorator 側に書きます.Excelのオブジェクトにメソッドを追加することはできないわけですから.

メタクラス標準モジュール

これは VB 全般の話になりますが,VB のクラスモジュールでは,クラス変数やクラスメソッドを定義することができません.困ったもんですね.これを解決するには,標準モジュールを使って各クラスごとにクラス変数とクラスメソッドを定義するのがよいと思われます.標準モジュールにした場合,自動的にメタクラスの Singleton の要請が満たされるからです.図に書くと次のようになります.

標準モジュールによるクラス変数,クラスメソッドの定義

上図のように,XXX というクラスモジュールに対して XXXClass という標準モジュール(メタクラス標準モジュール)を用意するコーディングルールに従うのがよいでしょう.クラス変数,クラスメソッドの使い方は次のようになります.

'クラス変数の使用例
VBObjectClass.ClassVariable = aValue

'クラスメソッドの使用例
VBObjectClass.ClassMethod

本来は VBObjectClass という限定子をつける必要がない場合もありますが,上のように必ずつけるようにします.また,注釈のファイル名は Visual Basic の場合のファイル名です.Excel の場合でもモジュールをエクスポートすればそういうファイル名が自動的につけられます.

したがって Decorator 側の物理的な構成は,次のようになります.

物理的なDecoratorパターンモデル構成図

SheetDecorator, BookDecorator, AppDecorator はクラスモジュール,SheetDecoratorClass, BookDecoratorClass, AppDecoratorClass は標準モジュールとして配置します.

ActiveDecorator 変数

Excel プログラミングで次に問題となるのが,ユーザからのマクロ呼び出しです.例えばユーザがメニューを選択してマクロを呼び出す場合,そのマクロは単なるプロシージャでなければなりません.したがってある特定の Decorator オブジェクトのメソッドを呼ぶ,ということはできません.

これを解決するには,ActiveDecorator 変数を作って現在どの Decorator がアクティブになっているかを管理する必要があります.Excel でもすでに ActiveWorkbook と ActiveSheet という変数が用意されていますが,これと同じように,グローバルにアクセスできる変数,ActiveBookDecorator と ActiveSheetDecorator を用意します.

実際には,AppDecoratorClass に

Public ActiveInstance As AppDecorator

というクラス変数を用意しておき,AppDecorator のインスタンス変数として ActiveBookDecorator ,BookDecorator のインスタンス変数として ActiveSheetDecorator を用意しておけば十分です.例えば,SheetDecorator のメソッド DoCommand を呼び出すマクロは 次のように記述できます.

Private Function GetActiveSheetDecorator() As SheetDecorator
    Set GetActiveSheetDecorator = _
        AppDecoratorClass.ActiveInstance.ActiveBookDecorator.ActiveSheetDecorator
End Function

Sub DoCommand()
    GetActiveSheetDecorator().DoCommand
End Sub

上のコードを SheetDecoratorClass のプロシージャとして定義しておき,メニューに登録する際は

Dim mnuDoCommand As CommandBarControl

    ...

With mnuDoCommand
    .Caption = "コマンド実行"
    .OnAction = "SheetDecoratorClass.DoCommand"
End With

のように,メタクラス標準モジュールの同名クラスメソッドを OnAction プロパティに登録しておきます(メソッド名を同じにするのはあくまでコーディングルールです).

ActiveDecorator の設定自体は,各 BookDecorator と SheetDecorator が Activate イベントと Deactivate イベント等をフックすることによって実装します.以下のプログラムでは,SheetDecorator が Parent である BookDecorator の ActiveSheetDecorator プロパティを自分自身に設定,または解除しています.

Public Sub OnFocus()
    Set Parent.ActiveSheetDecorator = Me
    'その他,メニューの変更などActiveになった場合の処理を書く
End Sub

Public Sub OnBlur()
    Set Parent.ActiveSheetDecorator = Nothing
    'その他,メニューの破棄などActiveでなくなった場合の処理を書く
End Sub

Private Sub Target_Activate()
    OnFocus
End Sub

Private Sub Target_Deactivate()
    OnBlur
End Sub

別メソッドとして OnFocus と OnBlur を用意しているのは,別のところでも明示的に呼び出す必要があるためです.

サンプルプログラム

Decorator パターンモデルを使ったサンプルプログラムを用意しました(DecoratorModel.xls).この Excel ファイルは Web ブラウザから直接開くと動作しません.一旦ファイルを保存してから使用してください.

このプログラムは,Excel のメニューバーに DecoratorModel というメニューを追加します.メニューコマンドは,以下のようになっています.

メニュー名 メソッド名 コマンドの内容
ブックの新規作成 AppDecorator.NewBook BookDecorator によって拡張されたブックを新規作成する
ブックの数... AppDecorator.ShowBookDecoratorCount 現在オープンされている BookDecorator の数を表示する
シートの追加 BookDecorator.AddSheet SheetDecorator によって拡張されたワークシートを追加する
シートの数... BookDecorator.ShowSheetDecoratorCount そのブックの SheetDecorator の数を表示する
シートコマンド実行 SheetDecorator.DoCommand シートの背景色をランダムに変更する

上のメニューは,常に表示されているわけではありません.ブックを切り替えたり,シートを切り替えたりするタイミングで必要なメニューだけが表示されます.その部分も注意してみてください.

念のため,ソースだけを見たいという方のために各モジュールをファイルとしてエクスポートしておいたものも用意しました(利便性を考えファイルの拡張子をtxtに変えてます).面倒な方はこちらをどうぞ.