[VBA] インタフェース継承を使用する際にクラス変数として配列を使用する方法

概要

この記事について

VBAではインタフェースを用いることによって、 オブジェクト指向のプログラミングをすることができる。

ただ、後述するように
クラス変数に特徴的な制約があるため、
継承したクラスにクラス変数として配列をもたせる場合に
通常の変数と同じようにするとエラーとなる。

この記事で、その現象と、
回避方法について記したい。

説明のために作成したExcelファイルとソースコード、テスト用データはこちらでダウンロードできます。

作成環境

Windows 10 Home(64bit)
MSOffice 2016

概要

About

チームクラスインタフェースclsAbsTeamを実装したチームクラスclsAnalyzeTeamclsNewTeamが存在する。

それぞれのチームクラスが、
チームメンバー名前の格納用配列と、
指定されたインデックスのチームメンバー名前を取得するメソッドを持つ。

クラス図(変数・メソッド記載無し)

クラス図(変数・メソッド記載無し)

エラーの発生

About

次のようにインタフェースと各継承先クラスを実装する。

 1'*** clsAbsTeam ***
 2
 3Option Explicit
 4
 5'**************************
 6'*チームクラスインタフェース
 7'**************************
 8
 9'定数欄
10
11'変数欄
12
13
14'******************************************************************************************
15'*getter/setter欄
16'******************************************************************************************
17
18
19Public Function getMemberName(ByVal idx As Long) As String
20
21End Function
22
 1'*** clsAnalyzeTeam ***
 2
 3Option Explicit
 4
 5Implements clsAbsTeam
 6
 7'**************************
 8'*チームクラス 解析チーム
 9'**************************
10
11'定数欄
12
13'変数欄
14
15
16'******************************************************************************************
17'*getter/setter欄
18'******************************************************************************************
19
20
21'******************************************************************************************
22'*関数名    :clsAbsTeam_getMemberName
23'*機能      :チームメンバーの名前取得
24'*引数      :対象者のインデックス番号
25'*戻り値    :チームメンバーの名前
26'******************************************************************************************
27Private Function clsAbsTeam_getMemberName(ByVal idx As Long) As String
28    
29    '定数
30    
31    '変数
32    
33    '***ここに名前取得の処理を入れる***
34    
35    
36ExitHandler:
37
38    Exit Function
39        
40End Function
41
42
 1'*** clsNewTeam ***
 2
 3Option Explicit
 4
 5Implements clsAbsTeam
 6
 7'**************************
 8'*チームクラス 新設のチーム
 9'**************************
10
11'定数欄
12
13'変数欄
14
15
16'******************************************************************************************
17'*getter/setter欄
18'******************************************************************************************
19
20
21'******************************************************************************************
22'*関数名    :clsAbsTeam_getMemberName
23'*機能      :チームメンバーの名前取得
24'*引数      :対象者のインデックス番号
25'*戻り値    :チームメンバーの名前
26'******************************************************************************************
27Private Function clsAbsTeam_getMemberName(ByVal idx As Long) As String
28    
29    '定数
30    
31    '変数
32    
33    '***ここに名前取得の処理を入れる***
34    
35    
36ExitHandler:
37
38    Exit Function
39        
40End Function
41
42

ここで、チームメンバーの名前を収納するための配列をもたせるために、
インタフェースにPublicなスコープの配列変数を定義しようとする。

 1'*** clsAbsTeam ***
 2
 3Option Explicit
 4
 5'**************************
 6'*チームクラスインタフェース
 7'**************************
 8
 9'定数欄
10
11'変数欄
12Public arrayMenberName(1 To 6) As String
13
14
15'******************************************************************************************
16'*getter/setter欄
17'******************************************************************************************
18
19
20Public Function getMemberName(ByVal idx As Long) As String
21
22End Function
23

すると、
定数、固定長文字列、配列、ユーザー定義型および Declare ステートメントは、
オブジェクト モジュールのパブリック メンバーとしては使用できません。

とのエラーが発生する。

エラー

原因

VBAの仕様上、
配列やユーザ定義型などの一部の型の変数は、
Publicのスコープで宣言することができるのは標準モジュールに限られる。
それは、インタフェースで使用するクラスであっても同様のようである。

そのため、クラスのPrivateな配列に外部からアクセスできる仕組みを作らなければならない。

回避方法

インタフェースにはgetter/setterのみ定義し、
継承先のクラスにPrivateなスコープの配列変数を所持させて、
継承したgetter/setterを通してアクセスできるようにする。

 1'*** clsAbsTeam ***
 2
 3Option Explicit
 4
 5'**************************
 6'*チームクラスインタフェース
 7'**************************
 8
 9'定数欄
10
11'変数欄
12
13
14'******************************************************************************************
15'*getter/setter欄
16'******************************************************************************************
17Public Property Let arrayMenberName(ByVal idx As Long, ByVal name As String)
18
19End Property
20
21Public Property Get arrayMenberName(ByVal idx As Long) As String
22
23End Property
24
25
26
27
28Public Function getMemberName(ByVal idx As Long) As String
29
30End Function
31
 1'*** clsAnalyzeTeam ***
 2
 3Option Explicit
 4
 5Implements clsAbsTeam
 6
 7'**************************
 8'*チームクラス 解析チーム
 9'**************************
10
11'定数欄
12
13'変数欄
14Private myArrayMenberName(1 To 6) As String
15
16
17
18'******************************************************************************************
19'*getter/setter欄
20'******************************************************************************************
21Private Property Let clsAbsTeam_arrayMenberName(ByVal idx As Long, ByVal name As String)
22    myArrayMenberName(idx) = name
23End Property
24
25Private Property Get clsAbsTeam_arrayMenberName(ByVal idx As Long) As String
26    clsAbsTeam_arrayMenberName = myArrayMenberName(idx)
27End Property
28
29
30
31'******************************************************************************************
32'*関数名    :clsAbsTeam_getMemberName
33'*機能      :チームメンバーの名前取得
34'*引数      :対象者のインデックス番号
35'*戻り値    :チームメンバーの名前
36'******************************************************************************************
37Private Function clsAbsTeam_getMemberName(ByVal idx As Long) As String
38    
39    '定数
40    
41    '変数
42    
43    clsAbsTeam_getMemberName = idx & "番目のチームメンバーは" & myArrayMenberName(idx) & "です。"
44    
45    
46ExitHandler:
47
48    Exit Function
49        
50End Function
51
 1'*** clsNewTeam ***
 2
 3Option Explicit
 4
 5Implements clsAbsTeam
 6
 7'**************************
 8'*チームクラス 新設のチーム
 9'**************************
10
11'定数欄
12
13'変数欄
14
15
16'******************************************************************************************
17'*getter/setter欄
18'******************************************************************************************
19Private Property Let clsAbsTeam_arrayMenberName(ByVal idx As Long, ByVal name As String)
20    '何もしない
21End Property
22
23Private Property Get clsAbsTeam_arrayMenberName(ByVal idx As Long) As String
24    clsAbsTeam_arrayMenberName = "名前無し"
25End Property
26
27'******************************************************************************************
28'*関数名    :clsAbsTeam_getMemberName
29'*機能      :チームメンバーの名前取得
30'*引数      :対象者のインデックス番号
31'*戻り値    :チームメンバーの名前
32'******************************************************************************************
33Private Function clsAbsTeam_getMemberName(ByVal idx As Long) As String
34    
35    '定数
36    
37    '変数
38    
39    clsAbsTeam_getMemberName = "※新設のチームにはメンバーが存在しません。"
40    
41    
42ExitHandler:
43
44    Exit Function
45        
46End Function
47

上記のようにしてやれば、
エラーを回避し、なおかつ予期した動作と同じ動作をする配列を継承先クラスに持たせることができる。

なお、新設チームクラスの場合はメンバーが居ないため、
配列変数自体をクラスに持たせる必要がなく、持たせていない。

サンプル

クラス図

クラス図

コード(M_Calling)

  1'*** M_Calling ***
  2
  3Option Explicit
  4
  5'**************************
  6'*呼び出し元モジュール
  7'**************************
  8
  9
 10'******************************************************************************************
 11'*関数名    :output3rdMemberName
 12'*機能      :動作テスト
 13'*引数      :
 14'*戻り値    :True > 正常終了、False > 異常終了
 15'******************************************************************************************
 16Public Sub testFunc()
 17    
 18    '定数
 19    Const FUNC_NAME As String = "testFunc"
 20    
 21    '変数
 22    Dim team As clsAbsTeam
 23    Dim coll As New Collection
 24    
 25    On Error GoTo ErrorHandler
 26
 27    '解析チームの名前を設定する
 28    Set team = New clsAnalyzeTeam
 29    team.arrayMenberName(1) = "佐藤"
 30    team.arrayMenberName(3) = "Mike"
 31    team.arrayMenberName(5) = "梦蝶"
 32    
 33    '処理するチームを追加
 34    coll.Add team
 35    coll.Add New clsNewTeam
 36    
 37    '名前を出力
 38    If Not outputSelectedMemberName(coll, 3) Then GoTo ExitHandler
 39
 40ExitHandler:
 41
 42    Exit Sub
 43    
 44ErrorHandler:
 45
 46    MsgBox "エラーが発生したため、マクロを終了します。" & _
 47           vbLf & _
 48           "関数名:" & FUNC_NAME & _
 49           vbLf & _
 50           "エラー番号:" & Err.Number & vbNewLine & _
 51           Err.Description, vbCritical, "Interface-Array-Member"
 52        
 53    GoTo ExitHandler
 54        
 55End Sub
 56
 57
 58
 59'******************************************************************************************
 60'*関数名    :outputSelectedMemberName
 61'*機能      :引数のコレクションの各チームの「idx」番目のメンバー名前を出力する
 62'*引数      :
 63'*戻り値    :True > 正常終了、False > 異常終了
 64'******************************************************************************************
 65Private Function outputSelectedMemberName(ByVal collTeam As Collection, ByVal idx As Long) As Boolean
 66    
 67    '定数
 68    Const FUNC_NAME As String = "outputSelectedMemberName"
 69    
 70    '変数
 71    Dim cntTeam As clsAbsTeam
 72    
 73    On Error GoTo ErrorHandler
 74
 75    outputSelectedMemberName = False
 76    
 77    For Each cntTeam In collTeam
 78        Debug.Print cntTeam.getMemberName(idx)
 79    Next cntTeam
 80
 81TruePoint:
 82
 83    outputSelectedMemberName = True
 84
 85ExitHandler:
 86
 87    Exit Function
 88    
 89ErrorHandler:
 90
 91    MsgBox "エラーが発生したため、マクロを終了します。" & _
 92           vbLf & _
 93           "関数名:" & FUNC_NAME & _
 94           vbLf & _
 95           "エラー番号:" & Err.Number & vbNewLine & _
 96           Err.Description, vbCritical, "Interface-Array-Member"
 97        
 98    GoTo ExitHandler
 99        
100End Function
101
102

実行

テスト関数testFuncを実行すると、
イミディエイトウィンドウに次のように出力される。

13番目のチームメンバーはMikeです。
2※新設のチームにはメンバーが存在しません。

これにより、
インタフェースを実装したクラスでも
クラス変数として配列をもたせることが可能となることがわかった。

関連記事

comments powered by Disqus

Translations: