Excel VBAのインターフェース継承を試す
Excel VBAには一応クラスの機能がありオブジェクト志向でプログラムを作成することもできます.さて,今日はImplements
キーワードを利用したインターフェース継承を試してみます.インターフェース継承をざっくりと説明すると,クラスモジュールを「扱う側」の処理を共通化することに利用できる考え方となるように思います.多相性の実現が可能となると言えるかも知れません.
実装例として,ある関数で記述される数値の配列をクラス化し,それをグラフとして描画する処理を共通化してみます.そこで,以下のようなものを実装してみます.
- グラフを描画するための関数 (
DrawGraph()
) - 上記関数に与えるインタフェースを定義したクラス(抽象クラス
iGraphData
) - 抽象クラスを継承し,データを保持するクラス(正弦波クラス
cSinusoidal
,直線クラスcLinear
)
まず,グラフを描画するのに必要なデータを考えると以下のようなものがありそうです.
- データの数とデータ
- 横軸と縦軸の名前
- 関数名
これらを取得するためのプロパティを定義した抽象クラスを作れば良さそうです.例えば以下のようにします.
' iGraphData.bas Option Explicit ' データの数を取得する Public Property Get nD() As Long End Property ' i番目のXを取得する Public Property Get XV(tIdx As Long) As Double End Property ' i番目のYを取得する Public Property Get V(tIdx As Long) As Double End Property ' 横軸の名称を取得する Public Property Get strXVName() As String End Property ' 縦軸の名称を取得する Public Property Get strVName() As String End Property ' 関数の名称を取得する Public Property Get strFName() As String End Property
次に上記のようなクラスを与えて,グラフとして描画する関数DrawGraph
を以下のように記述します.
' DrawGraph.bas Option Explicit ' グラフを描画する関数 Public Sub DrawGraph(tSheet As String, tGD As iGraphData) Dim i As Long ' シートを作成する With ThisWorkbook .Sheets.Add After:=.Sheets(.Sheets.Count) .Sheets(.Sheets.Count).Name = tSheet End With With ThisWorkbook.Worksheets(tSheet) ' 前に出す .Activate ' データを書き出す For i = 0 To tGD.nD - 1 .Cells(i + 1, 1) = tGD.XV(i) .Cells(i + 1, 2) = tGD.V(i) Next ' グラフを描画する .Shapes.AddChart.Select With ActiveChart '.HasTitle = True .ChartType = xlLine .Axes(xlCategory, xlPrimary).HasTitle = True .Axes(xlCategory, xlPrimary).AxisTitle.Characters.Text = tGD.strXVName .Axes(xlValue, xlPrimary).HasTitle = True .Axes(xlValue, xlPrimary).AxisTitle.Characters.Text = tGD.strVName .SetSourceData Source:=Range("A1") With .SeriesCollection(1) .Values = Range(Cells(1, 2), Cells(1 + tGD.nD - 1, 2)) .XValues = Range(Cells(1, 1), Cells(1 + tGD.nD - 1, 1)) .MarkerStyle = xlMarkerStyleCircle .Name = tGD.strFName .MarkerStyle = xlMarkerStyleNone ' マーカーを消す End With .Parent.Top = Range("C2").Top .Parent.Left = Range("C2").Left End With End With End Sub
やっていることは横軸の値と縦軸の値を1列目と2列目に書き出し,線グラフとして描画するものです.
さて,どちらかと言えばここからが本題です.クラスiGraphData
は関数DrawGraph
とのデータのやり取りを記述するもので,何の処理も記述していませんから,数値データを保持するための具体的なクラスとしてcSinusoical
を実装します.
' cSinusoical.bas Option Explicit Private mNData As Long Private mValue() As Double Private mXValue() As Double Private mScale As Double Private mTheta0 As Double Private mIsCalculated As Boolean ' インターフェース継承 Implements iGraphData ' データの数を取得する Public Property Get iGraphData_nD() As Long If mIsCalculated Then iGraphData_nD = mNData End If End Property ' i番目のXを取得する Public Property Get iGraphData_XV(tIdx As Long) As Double If mIsCalculated Then iGraphData_XV = mXValue(tIdx) End If End Property ' i番目のXを取得する Public Property Get iGraphData_V(tIdx As Long) As Double If mIsCalculated Then iGraphData_V = mValue(tIdx) End If End Property ' 横軸の名称を取得する Public Property Get iGraphData_strXVName() As String iGraphData_strXVName = "Theta" End Property ' 縦軸の名称を取得する Public Property Get iGraphData_strVName() As String iGraphData_strVName = "Y" End Property ' 関数の名称を取得する Public Property Get iGraphData_strFName() As String iGraphData_strFName = "Sinusoidal Curve" End Property ' コンストラクタ Private Sub Class_Initialize() mIsCalculated = False End Sub ' デストラクタ Private Sub Class_Terminate() End Sub ' 関数値を計算する Public Sub Calc(tXVmin As Double, tXVMax As Double, tDiv As Long, _ tScale As Double, tTheta0 As Double) Dim i As Long ' 領域確保 ReDim mValue(tDiv - 1) As Double ReDim mXValue(tDiv - 1) As Double mScale = tScale mTheta0 = tTheta0 mNData = tDiv ' 計算を実行する For i = 0 To mNData - 1 mXValue(i) = tXVmin + i * (tXVMax - tXVmin) / tDiv mValue(i) = mScale * Sin(mXValue(i) + mTheta0) Next mIsCalculated = True End Sub
このクラスは以下の関数値を計算し配列として保持するものです.
同様に以下の直線の関数値を計算して配列として保持するクラスcLinear
を実装します.
' cLinear.bas Option Explicit Private mNData As Long Private mValue() As Double Private mXValue() As Double Private mAValue As Double Private mBValue As Double Private mIsCalculated As Boolean ' インターフェース継承 Implements iGraphData ' データの数を取得する Public Property Get iGraphData_nD() As Long If mIsCalculated Then iGraphData_nD = mNData End If End Property ' i番目のXを取得する Public Property Get iGraphData_XV(tIdx As Long) As Double If mIsCalculated Then iGraphData_XV = mXValue(tIdx) End If End Property ' i番目のXを取得する Public Property Get iGraphData_V(tIdx As Long) As Double If mIsCalculated Then iGraphData_V = mValue(tIdx) End If End Property ' 横軸の名称を取得する Public Property Get iGraphData_strXVName() As String iGraphData_strXVName = "X" End Property ' 縦軸の名称を取得する Public Property Get iGraphData_strVName() As String iGraphData_strVName = "Y" End Property ' 関数の名称を取得する Public Property Get iGraphData_strFName() As String iGraphData_strFName = "Linear Function" End Property ' コンストラクタ Private Sub Class_Initialize() mIsCalculated = False End Sub ' デストラクタ Private Sub Class_Terminate() End Sub ' 関数値を計算する Public Sub Calc(tXVmin As Double, tXVMax As Double, tDiv As Long, _ tAvalue As Double, tBvalue As Double) Dim i As Long ' 領域確保 ReDim mValue(tDiv - 1) As Double ReDim mXValue(tDiv - 1) As Double mAValue = tAvalue mBValue = tBvalue mNData = tDiv ' 計算を実行する For i = 0 To mNData - 1 mXValue(i) = tXVmin + i * (tXVMax - tXVmin) / tDiv mValue(i) = mAValue * mXValue(i) + mBValue Next mIsCalculated = True End Sub
最後に呼び出し側である関数ExecDrawGraph
を以下のように実装します.
' Main.bas Option Explicit Public Sub ExecDrawGraph() Dim cSin As cSinusoidal Dim cLin As cLinear Set cSin = New cSinusoidal Call cSin.Calc(0, 2 * 3.14, 100, 1.5, -3.14 / 2) Call DrawGraph("sin_curve", cSin) Set cLin = New cLinear Call cLin.Calc(0, 10, 100, 3, 2) Call DrawGraph("linear_func", cLin) End Sub
これらを実行するとシートsin_curve
に正弦波の関数が,linear_func
に直線の関数が描画されます.
内部処理がそれぞれ微妙に異なる二つのクラスcSinusoidal
とcLinear
についてグラフを描画する関数を共通化できたことが分かります.この程度の例では何が利点かは分かりづらいのですが,クラスモジュールと継承を使うことは以下の利点があります.
- データとデータを扱う手段をクラスとしてまとめ込める
- インターフェース継承によってクラスを「扱う側」の処理を共通にできる
データに対する処理が複雑化した場合,似たような処理に共通の実装を用いるにはクラスモジュールを使うべきでしょう.重要なのはインターフェースを継承したクラスの内部処理がどのように実装されていても良いという点です.今回の例ではcSinusoidal
とcLinear
は双方ともデータを配列で保持していましたが,ファイルやシートに記述されたデータを読み出すクラスであっても,データ列が二次元であればiGraphData
を継承することで関数DrawGraph()
によってグラフを描画できます.
ちなみに,cSinusoidal
とcLinear
の中身がほとんど同じであるわけですが,実装継承ができればこの辺りもかなり綺麗になるように思います.とは言え,VBAの仕様上それはできないようなので仕方がないのかも知れません.