Excel VBAをクラスを使って改善しよう: タイプセーフEnum
タイプセーフEnumとは
Excel VBAにはEnumという機能があります。
数値に変数名を付けることができ、値を自動で指定することも、任意の値を指定することもできます。
この機能を利用して、配列の列名に使ったり、区分値に使ったりすることがあります。
例えば、以下のようなアンケート紹介会社の会員一覧を表示する例を考えてみます。
Option Explicit
Public Enum UserTableColumnPosition
POSITON_ID = 1
POSITON_NAME
POSITON_AGE
POSITON_MARRIED
End Enum
Public Enum MarriedType
TYPE_NO = 0
TYPE_YES = 1
TYPE_NO_ANSWER = 9
End Enum
Public Sub Test()
' 例えばシートからデータを取得して二次元配列に格納されていたとする
Dim sampleInputData() As Variant
With ThisWorkbook.Worksheets("Sheet1").Range("A1").CurrentRegion
sampleInputData = .Rows(2 & ":" & .Rows.Count).value
End With
' 表示確認
Dim rowIndex As Long
For rowIndex = LBound(sampleInputData, 1) To UBound(sampleInputData, 1)
Call Output( _
sampleInputData(rowIndex, UserTableColumnPosition.POSITON_NAME), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITON_AGE), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITON_MARRIED))
Next
End Sub
Private Sub Output(pUserName As Variant, pAge As Variant, pMarriedType As Variant)
Debug.Print pUserName + "/" + CStr(pAge) + "歳" + "/" + "結婚歴:" + MarriedTypeName(CLng(pMarriedType))
End Sub
Private Function MarriedTypeName(value As MarriedType) As String
Select Case value
Case MarriedType.TYPE_NO
MarriedTypeName = "未婚"
Case MarriedType.TYPE_YES
MarriedTypeName = "既婚"
Case MarriedType.TYPE_NO_ANSWER
MarriedTypeName = "非公開"
Case Else
Debug.Print "未定義の区分値が引数に指定された!"
Debug.Assert False
End Select
End Function
Test()プロシージャを実行すると、イミディエイトウィンドウには、
山田/25歳/結婚歴:未婚
田中/40歳/結婚歴:既婚
佐藤/35歳/結婚歴:非公開
と表示されます。
Enumの問題点とは
ただし、Enumには次のような問題があります。
- 引数で型を指定しても実体はLong型であるため、不正な値を指定できる
- Enum値に応じた名前や個別の処理を行いたい場合、至る所にEnum値を使ったSelect Case文が必要になるため、修正が大変
例で言うと、MarriedTypeName()ファンクションの引数はMarriedTypeですが、Long型の数値であれば実は何でも渡すことができます。
結果、Case Elseに該当し、アサーションが発生してしまいます。
Public Sub TestNoEnum()
Call MarriedTypeName(2) 'これはDebug.Asssetで一時中断する
End Sub
このケースのように例外を扱えるようにしていればまだしも、Case Elseがなく、予期しない動作となった場合、デバッガでコードを追いかけることになります。
追加の要件が発生
さて、ここで追加の要件があり、指定した結婚歴に応じて一覧から対象者を抽出することとなりました。
例えば、
- 独身者向けアンケートでは、独身であることが条件です。
- 既婚者向けアンケートでは、結婚していることが条件です。
このようなケースを考えてみます。
一般的な方法では、各アンケート分、サブルーチンを作って対応することになるかと思います。
Public Sub TestMarried()
Dim sampleInputData() As Variant
With ThisWorkbook.Worksheets("Sheet1").Range("A1").CurrentRegion
sampleInputData = .Rows(2 & ":" & .Rows.Count).value
End With
' 表示確認
Dim rowIndex As Long
For rowIndex = LBound(sampleInputData, 1) To UBound(sampleInputData, 1)
Call OutputMarried( _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_NAME), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_AGE), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_MARRIED))
Next
End Sub
Public Sub TestNoMarried()
Dim sampleInputData() As Variant
With ThisWorkbook.Worksheets("Sheet1").Range("A1").CurrentRegion
sampleInputData = .Rows(2 & ":" & .Rows.Count).value
End With
' 表示確認
Dim rowIndex As Long
For rowIndex = LBound(sampleInputData, 1) To UBound(sampleInputData, 1)
Call OutputNoMarried( _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_NAME), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_AGE), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_MARRIED))
Next
End Sub
Private Sub OutputMarried(pUserName As Variant, pAge As Variant, pMarriedType As Variant)
Select Case CLng(pMarriedType)
Case MarriedType.TYPE_NO
'何もしない
Case MarriedType.TYPE_YES
Call Output(pUserName, pAge, pMarriedType)
Case MarriedType.TYPE_NO_ANSWER
'何もしない
Case Else
Debug.Print "未定義の区分値が引数に指定された!"
Debug.Assert False
End Select
End Sub
Private Sub OutputNoMarried(pUserName As Variant, pAge As Variant, pMarriedType As Variant)
Select Case CLng(pMarriedType)
Case MarriedType.TYPE_NO
Call Output(pUserName, pAge, pMarriedType)
Case MarriedType.TYPE_YES
'何もしない
Case MarriedType.TYPE_NO_ANSWER
'何もしない
Case Else
Debug.Print "未定義の区分値が引数に指定された!"
Debug.Assert False
End Select
End Sub
さらなる追加の要件が・・・
現在は名前と結婚歴だけを表示していますが、紹介するアンケートには離婚歴などで紹介可否がある場合があるので、結婚歴に状態を追加したとします。
この時、
- 離婚歴がない場合は現状と変わらない表示
- 離婚して結婚していない場合は、離婚と表示
- 離婚して結婚した場合は、再婚と表示
という要件が発生した場合、どうしたらよいでしょうか。
例でいうと以下のようになります。
サンプル(未婚、離婚歴なし) => 名前/年齢/結婚歴:未婚
サンプル(未婚、離婚歴あり) => 名前/年齢/結婚歴:離婚
サンプル(既婚、離婚歴なし) => 名前/年齢/結婚歴:既婚
サンプル(未婚、離婚歴あり) => 名前/年齢/結婚歴:再婚
サンプル(非公開、離婚歴有無どちらでも) => 名前/年齢/結婚歴:非公開
ワークシート上は以下のような入力になります。
この時、結婚歴の値2は離婚、値3は再婚とします。
この要件に対応した場合、上記の表示以外にもアンケート対象者の抽出条件にも影響があります。
- 独身者向けアンケートの対象に離婚歴があるユーザも追加する
- 既婚者向けアンケートの対象に再婚しているユーザも追加する
この時のコードは次のようになります。Select Caseの修正があちこちに発生している点に注意して下さい。
Public Enum MarriedType '数値が読みにくいので並び変えました
TYPE_NO = 0
TYPE_YES = 1
TYPE_X_NO = 2 '離婚を追加
TYPE_X_YES = 3 '再婚を追加
TYPE_NO_ANSWER = 9
End Enum
Private Function MarriedTypeName(value As MarriedType) As String
Select Case value
Case MarriedType.TYPE_NO
MarriedTypeName = "未婚"
Case MarriedType.TYPE_YES
MarriedTypeName = "既婚"
Case MarriedType.TYPE_X_NO
MarriedTypeName = "離婚" '追加
Case MarriedType.TYPE_X_YES
MarriedTypeName = "再婚" '追加
Case MarriedType.TYPE_NO_ANSWER
MarriedTypeName = "非公開"
Case Else
Debug.Print "未定義の区分値が引数に指定された!"
Debug.Assert False
End Select
End Function
これで問題なく表示ができます。
山田/25歳/結婚歴:未婚
田中/40歳/結婚歴:既婚
佐藤/35歳/結婚歴:非公開
石井/24歳/結婚歴:離婚
古田/50歳/結婚歴:再婚
アンケートの条件については、既存のプロシージャに次のような拡張を行いました。
Public Sub TestMarriedEx()
Dim sampleInputData() As Variant
With ThisWorkbook.Worksheets("Sheet2").Range("A1").CurrentRegion
sampleInputData = .Rows(2 & ":" & .Rows.Count).value
End With
' 表示確認
Dim rowIndex As Long
For rowIndex = LBound(sampleInputData, 1) To UBound(sampleInputData, 1)
Call OutputMarriedEx( _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_NAME), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_AGE), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_MARRIED))
Next
End Sub
Public Sub TestNoMarriedEx()
Dim sampleInputData() As Variant
With ThisWorkbook.Worksheets("Sheet2").Range("A1").CurrentRegion
sampleInputData = .Rows(2 & ":" & .Rows.Count).value
End With
' 表示確認
Dim rowIndex As Long
For rowIndex = LBound(sampleInputData, 1) To UBound(sampleInputData, 1)
Call OutputNoMarriedEx( _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_NAME), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_AGE), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_MARRIED))
Next
End Sub
Private Sub OutputMarriedEx(pUserName As Variant, pAge As Variant, pMarriedType As Variant)
Select Case CLng(pMarriedType)
Case MarriedType.TYPE_NO
'何もしない
Case MarriedType.TYPE_YES
Call Output(pUserName, pAge, pMarriedType)
Case MarriedType.TYPE_X_NO
'何もしない
Case MarriedType.TYPE_X_YES
Call Output(pUserName, pAge, pMarriedType)
Case MarriedType.TYPE_NO_ANSWER
'何もしない
Case Else
Debug.Print "未定義の区分値が引数に指定された!"
Debug.Assert False
End Select
End Sub
Private Sub OutputNoMarriedEx(pUserName As Variant, pAge As Variant, pMarriedType As Variant)
Select Case CLng(pMarriedType)
Case MarriedType.TYPE_NO
Call Output(pUserName, pAge, pMarriedType)
Case MarriedType.TYPE_YES
'何もしない
Case MarriedType.TYPE_X_NO
Call Output(pUserName, pAge, pMarriedType)
Case MarriedType.TYPE_X_YES
'何もしない
Case MarriedType.TYPE_NO_ANSWER
'何もしない
Case Else
Debug.Print "未定義の区分値が引数に指定された!"
Debug.Assert False
End Select
End Sub
ここまで何とかなったように見えますが、実は修正不足があります。
Enum値に2(離婚)を追加したことで、
Public Sub TestNoEnum()
Call MarriedTypeName(2) 'これはDebug.Asssetで一時中断する
End Sub
このコードはアサーションで一時中断せず、正常終了するようになってしまいました。
Enum値の判断ロジックは全てSubプロシージャ内で実行されいたため、なかなか気が付きにくい不具合です。
修正する場合は、引数に10など、Enum値にない値を修正する必要があります。
以上のように、クラスを導入しなくても要件を満たすことは可能です。
ただし、機能性以外の観点(保守性、拡張性、可読性など)は著しく損なわれています。
クラスを導入する
それではクラスを使ってみて、違いを見ていきます。
まず、一番はじめの要件を満たすコードです。
Enum値をまとめて扱うクラスです。
Option Explicit
Private OBJENUM_TYPE_NOSET_ As MarriedEnumValue
Private OBJENUM_TYPE_NO_ As MarriedEnumValue
Private OBJENUM_TYPE_YES_ As MarriedEnumValue
Private OBJENUM_TYPE_NO_ANSWER_ As MarriedEnumValue
Private valueOfDic_ As Dictionary
Private Sub Class_Initialize()
Set OBJENUM_TYPE_NOSET_ = New MarriedNoSetValue
Set OBJENUM_TYPE_NO_ = New NoMarriedValue
Set OBJENUM_TYPE_YES_ = New MarriedValue
Set OBJENUM_TYPE_NO_ANSWER_ = New NoAnswerValue
Set valueOfDic_ = New Dictionary
Call Init
End Sub
Private Sub Init()
Call valueOfDic_.Add(OBJENUM_TYPE_NOSET.Value, OBJENUM_TYPE_NOSET)
Call valueOfDic_.Add(OBJENUM_TYPE_NO.Value, OBJENUM_TYPE_NO)
Call valueOfDic_.Add(OBJENUM_TYPE_YES.Value, OBJENUM_TYPE_YES)
Call valueOfDic_.Add(OBJENUM_TYPE_NO_ANSWER.Value, OBJENUM_TYPE_NO_ANSWER)
End Sub
Public Function ValueOf(pValue As MarriedType) As MarriedEnumValue
If Not valueOfDic_.Exists(pValue) Then
Set ValueOf = OBJENUM_TYPE_NOSET_
Exit Function
End If
Set ValueOf = valueOfDic_(pValue)
End Function
Public Property Get OBJENUM_TYPE_NOSET() As MarriedEnumValue
Set OBJENUM_TYPE_NOSET = OBJENUM_TYPE_NOSET_
End Property
Public Property Get OBJENUM_TYPE_NO() As MarriedEnumValue
Set OBJENUM_TYPE_NO = OBJENUM_TYPE_NO_
End Property
Public Property Get OBJENUM_TYPE_YES() As MarriedEnumValue
Set OBJENUM_TYPE_YES = OBJENUM_TYPE_YES_
End Property
Public Property Get OBJENUM_TYPE_NO_ANSWER() As MarriedEnumValue
Set OBJENUM_TYPE_NO_ANSWER = OBJENUM_TYPE_NO_ANSWER_
End Property
Enum値を表すクラスです。
Option Explicit
Public Property Get Value() As Long
End Property
Public Property Get Name() As String
End Property
Public Function IsMarried() As Boolean
End Function
Public Function IsNoMarried() As Boolean
End Function
Public Function Equals(pTarget As MarriedEnumValue) As Boolean
End Function
設定なしのEnum値クラス
Option Explicit
Implements MarriedEnumValue
Private value_ As MarriedType
Private name_ As String
Private Sub Class_Initialize()
value_ = MarriedType.TYPE_NO_SET
name_ = "設定なし"
End Sub
Private Property Get MarriedEnumValue_Name() As String
MarriedEnumValue_Name = name_
End Property
Private Property Get MarriedEnumValue_Value() As Long
MarriedEnumValue_Value = value_
End Property
Private Function MarriedEnumValue_Equals(pTarget As MarriedEnumValue) As Boolean
MarriedEnumValue_Equals = (value_ = pTarget.Value)
End Function
Private Function MarriedEnumValue_IsMarried() As Boolean
MarriedEnumValue_IsMarried = False
End Function
Private Function MarriedEnumValue_IsNoMarried() As Boolean
MarriedEnumValue_IsNoMarried = False
End Function
既婚を表すEnum値クラス
Option Explicit
Implements MarriedEnumValue
Private value_ As MarriedType
Private name_ As String
Private Sub Class_Initialize()
value_ = MarriedType.TYPE_YES
name_ = "既婚"
End Sub
Private Property Get MarriedEnumValue_Name() As String
MarriedEnumValue_Name = name_
End Property
Private Property Get MarriedEnumValue_Value() As Long
MarriedEnumValue_Value = value_
End Property
Private Function MarriedEnumValue_Equals(pTarget As MarriedEnumValue) As Boolean
MarriedEnumValue_Equals = (value_ = pTarget.Value)
End Function
Private Function MarriedEnumValue_IsMarried() As Boolean
MarriedEnumValue_IsMarried = True
End Function
Private Function MarriedEnumValue_IsNoMarried() As Boolean
MarriedEnumValue_IsNoMarried = False
End Function
未婚を表すEnum値クラス
Option Explicit
Implements MarriedEnumValue
Private value_ As MarriedType
Private name_ As String
Private Sub Class_Initialize()
value_ = MarriedType.TYPE_NO
name_ = "未婚"
End Sub
Private Property Get MarriedEnumValue_Name() As String
MarriedEnumValue_Name = name_
End Property
Private Property Get MarriedEnumValue_Value() As Long
MarriedEnumValue_Value = value_
End Property
Private Function MarriedEnumValue_Equals(pTarget As MarriedEnumValue) As Boolean
MarriedEnumValue_Equals = (value_ = pTarget.Value)
End Function
Private Function MarriedEnumValue_IsMarried() As Boolean
MarriedEnumValue_IsMarried = False
End Function
Private Function MarriedEnumValue_IsNoMarried() As Boolean
MarriedEnumValue_IsNoMarried = True
End Function
実行を確認する標準モジュール。
Option Explicit
Public Enum MarriedType
TYPE_NO_SET = -1 '設定していない状態を表すEnum値を追加
TYPE_NO = 0
TYPE_YES = 1
' 初めの要件を満たすためコメントアウト
' TYPE_X_NO = 2 '離婚を追加
' TYPE_X_YES = 3 '再婚を追加
TYPE_NO_ANSWER = 9
End Enum
Public Sub TestWithClass()
Dim sampleInputData() As Variant
With ThisWorkbook.Worksheets("Sheet1").Range("A1").CurrentRegion
sampleInputData = .Rows(2 & ":" & .Rows.Count).Value
End With
' Enumクラスを作成
Dim vMarriedEnum As MarriedEnum
Set vMarriedEnum = New MarriedEnum
' 表示確認
Dim rowIndex As Long
For rowIndex = LBound(sampleInputData, 1) To UBound(sampleInputData, 1)
Dim vEnumValue As Long
vEnumValue = CLng(sampleInputData(rowIndex, UserTableColumnPosition.POSITION_MARRIED))
' Enumクラスから値クラスを取得
Dim vMarriedEnumValue As MarriedEnumValue
Set vMarriedEnumValue = vMarriedEnum.ValueOf(vEnumValue)
Call Output( _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_NAME), _
sampleInputData(rowIndex, UserTableColumnPosition.POSITION_AGE), _
vMarriedEnumValue.Name) '値のクラスの名前を引数として渡す
Next
End Sub
Private Sub Output(pUserName As Variant, pAge As Variant, pMarriedTypeName As String)
Debug.Print pUserName + "/" + CStr(pAge) + "歳" + "/" + "結婚歴:" + pMarriedTypeName
End Sub
Public Sub TestNoEnumWithClass()
' Enumクラスを作成
Dim vMarriedEnum As MarriedEnum
Set vMarriedEnum = New MarriedEnum
' Enumクラスから値クラスを取得
Dim vMarriedEnumValue As MarriedEnumValue
Set vMarriedEnumValue = vMarriedEnum.ValueOf(2)
If vMarriedEnumValue.Equals(vMarriedEnum.OBJENUM_TYPE_NOSET) Then
Debug.Print "Enumにない値が指定されたのでNoSetが返る。期待通り"
Else
Debug.Print "想定外のEnum値が追加されているのでNoSetではない"
Debug.Print vMarriedEnumValue.Value, vMarriedEnumValue.Name
Debug.Assert False
End If
End Sub
なお、クラス化とはあまり関係ありませんが、未設定状態を表すEnum値を追加しています。
TYPE_NO_SET = -1 '設定していない状態を表すEnum値を追加
タイプセーフEnumの実装方法
MarriedEnumクラスは各Enum値クラスをメンバーとして持っています。
ただ、持ち方に特徴があって、MarriedEnumValue型として持っています。
メンバーはコンストラクタ(Class_Initialize())で初期化していますが、各Enum値を表す実クラスで初期化しています。
これはポリモーフィズムを使って各実装クラスをMarriedEnumValue型として扱っています。
MarriedEnumValue型としていても、インスタンス(オブジェクト)はNewした型に依存します。
クラス図で示すと以下のようになります。
図のIマークはインターフェースを表しています。Excel VBAではインターフェースとは呼称しません(前述のコードのとおり、classで定義しています)が、一般的なオブジェクト指向言語における使われ方からインターフェースとしてします。
インターフェースは期待される振る舞いを定義する(メソッドの引数と戻り値)だけで、実装方法はimplementsした実装クラスに依存します。
MarriedEnumクラスは各Enum値を読み取り専用プロパティとして公開しています。
各Enum値のメンバはPrivateにして隠蔽します。
また、内部にDictionaryで値とオブジェクトのマッピングを作成しています。
これはSelect Caseの代替です。
ただし、クラスを使わない実装では複数のSelect Caseがありましたが、クラスではここだけです。
次にEnum値クラスを見ていきます。
MarriedEnumValueインターフェースにはValue()、Name()の読み取り専用プロパティを公開しています。実装クラスは適切な値を返すよう期待されています。
実装クラスは、これらのプロパティに定数を返しています。
Private value_ As MarriedType
Private name_ As String
Private Sub Class_Initialize()
value_ = MarriedType.TYPE_YES '元のEnum定義の値を指定
name_ = "既婚" '名前を設定
End Sub
Private Property Get MarriedEnumValue_Name() As String
MarriedEnumValue_Name = name_
End Property
Private Property Get MarriedEnumValue_Value() As Long
MarriedEnumValue_Value = value_
End Property
各Enum値に1つの実装クラスを定義していきます。
また、各Enum値には既婚か未婚かを返すメソッドが定義されています。
Public Function IsMarried() As Boolean
End Function
Public Function IsNoMarried() As Boolean
End Function
これは次の利用方法で詳細を見ていきます。
タイプセーフEnumの利用方法
利用方法は簡単です。
通常のクラス利用と同様、Newしてオブジェクトを生成します。
次いで、入力値からEnum値オブジェクトをValueOf()メソッドで取得します。
ValueOf()メソッドはMarriedEnumValueを返します。
' Enumクラスを作成
Dim vMarriedEnum As MarriedEnum
Set vMarriedEnum = New MarriedEnum
' Enumクラスから値クラスを取得
Dim vMarriedEnumValue As MarriedEnumValue
Set vMarriedEnumValue = vMarriedEnum.ValueOf(vEnumValue)
あらかじめ必要なEnum値が分かっている場合は、読み取り専用プロパティから選択できます。
vMarriedEnum.OBJENUM_TYPE_NOSET
取得したEnum値は実装クラスに依存した未婚、既婚を返します。
クラスを利用しない場合は、Enum値で分岐して処理を行うという発想です。
クラスを利用する場合は、それぞれのEnum値に適切な振る舞いを期待するという発想に切り替えます。
MarriedEnumValue型でオブジェクトを利用すると、ポリモーフィズムの機能によって適切な未婚、既婚を返すことができます。
追加要件への対応
ここまでクラスを使った実装方法を確認してきましたが、これだけではあまりメリットは感じられません。
まず、通常のEnum定義に値を追加します。
Public Enum MarriedType
TYPE_NO_SET = -1 '設定していない状態を表すEnum値を追加
TYPE_NO = 0
TYPE_YES = 1
TYPE_X_NO = 2 '離婚を追加
TYPE_X_YES = 3 '再婚を追加
TYPE_NO_ANSWER = 9
End Enum
次に、Enum値クラスを作成します。MarriedXValue型としました。定数を設定するコンストラクタのみ抜粋します。
Private Sub Class_Initialize()
value_ = MarriedType.TYPE_X_YES
name_ = "再婚"
End Sub
同様に離婚も追加します。
Private Sub Class_Initialize()
value_ = MarriedType.TYPE_X_NO
name_ = "離婚"
End Sub
MarriedEnumクラスは以下の修正を行います。
Private OBJENUM_TYPE_NOSET_ As MarriedEnumValue
Private OBJENUM_TYPE_NO_ As MarriedEnumValue
Private OBJENUM_TYPE_YES_ As MarriedEnumValue
Private OBJENUM_TYPE_X_YES_ As MarriedEnumValue '追加
Private OBJENUM_TYPE_X_NO_ As MarriedEnumValue '追加
Private OBJENUM_TYPE_NO_ANSWER_ As MarriedEnumValue
Private valueOfDic_ As Dictionary
Private Sub Class_Initialize()
Set OBJENUM_TYPE_NOSET_ = New MarriedNoSetValue
Set OBJENUM_TYPE_NO_ = New NoMarriedValue
Set OBJENUM_TYPE_YES_ = New MarriedValue
Set OBJENUM_TYPE_X_YES_ = New MarriedXValue '追加
Set OBJENUM_TYPE_X_NO_ = New NoMarriedXValue '追加
Set OBJENUM_TYPE_NO_ANSWER_ = New NoAnswerValue
Set valueOfDic_ = New Dictionary
Call Init
End Sub
Private Sub Init()
Call valueOfDic_.Add(OBJENUM_TYPE_NOSET.Value, OBJENUM_TYPE_NOSET)
Call valueOfDic_.Add(OBJENUM_TYPE_NO.Value, OBJENUM_TYPE_NO)
Call valueOfDic_.Add(OBJENUM_TYPE_YES.Value, OBJENUM_TYPE_YES)
Call valueOfDic_.Add(OBJENUM_TYPE_X_YES.Value, OBJENUM_TYPE_X_YES) '追加
Call valueOfDic_.Add(OBJENUM_TYPE_X_NO.Value, OBJENUM_TYPE_X_NO) '追加
Call valueOfDic_.Add(OBJENUM_TYPE_NO_ANSWER.Value, OBJENUM_TYPE_NO_ANSWER)
End Sub
'追加
Public Property Get OBJENUM_TYPE_X_YES() As MarriedEnumValue
Set OBJENUM_TYPE_X_YES = OBJENUM_TYPE_X_YES_
End Property
'追加
Public Property Get OBJENUM_TYPE_X_NO() As MarriedEnumValue
Set OBJENUM_TYPE_X_NO = OBJENUM_TYPE_X_NO_
End Property
追加と記述された部分を変更しています。
他のコードは修正しなくても動作します。
また、テストで追加したTestNoEnumWithClass()ファンクションはこの修正で期待通りでないことを明示してくれますので、すぐに修正できます。
これ以外の修正は不要です!
判断するロジックに変更が入らないため、修正箇所もMarriedEnum内に閉じています。他はクラスを追加するだけですので影響もありません。
この拡張性、感じてもらえたでしょうか。
まとめ
長い説明となりましたが、クラスの効能を理解し、Excel マクロでクラスを使う人が増えることを願っています。
また、ちょっと難しいなと感じた人向けにそもそもクラスとは何か、オブジェクト指向開発とは、といった入門書を執筆中です。
公開しましたらそちらもよろしくお願いします。
Discussion