■ オブジェクト指向プログラミングに挑戦
さてExcelVBA入門講座、仕上げはオブジェクト指向プログラミングに挑戦してみましょう。
開発で構造化プログラミングを突き詰めていくとやがてオブジェクト指向プログラミングに行き着きます。
ここではオブジェクト指向がなにかということについては言及しません。
Web上で膨大な数のオブジェクト指向について解説したサイトがありますので概念的なものはそちらに譲ります。
またごく一部のオブジェクト指向信奉者(?)の方の宗教的なオブジェクト指向論もここでは取り上げません。
オブジェクト指向はプログラミングをより効率化するための、たんなる手法でありそれ以上でもそれ以下でもありません。
そしてなにより構造化プログラミングが理解できていれば、決して難しいものでもありません。
VBAは基本的に手続き型言語で、オブジェクト指向言語の側面を持っているにすぎません。ですので完全なオブジェクト指向プログラミングというものは難しい(クラスの継承などはサポートされていません)、それでもオブジェクト指向プログラミングを目指したほうがいいのです。
それはVBAのみならず他の開発言語(たとえばJava等)がどのような原理で動作し、VBAプログラミングとどこが違いどこが同じなのか…を理解するのに役立つからです。
さらに大規模なプロジェクト開発ではオブジェクト指向を用いて開発を進めることでより安全かつより効率的に開発を進めることが可能になります。
・クラスモジュールをつかってみよう!
オブジェクト指向の第一歩はクラスの設計です。
プロジェクトエクスプローラで右クリック、挿入>クラスモジュールをクリックすることでクラスモジュールを追加できます。
追加したらプロパティウィンドウでオブジェクト名を"SyainCls"に変更してください。
これはとても重要なプロセスです。なぜならオブジェクトのインスタンスを実体化するときにはこのクラス名で宣言してやる必要があるからです。
さてここまで準備が整ったら、早速コーディングしてみましょう。
' 標準モジュールに記述ここから −−−−−−−−−−−−−−−−−−−−−−−−−−−
Public Sub test()
SyainForm.Show
End Sub
' 標準モジュールに記述ここまで −−−−−−−−−−−−−−−−−−−−−−−−−−−
' フォームモジュールに記述ここから −−−−−−−−−−−−−−−−−−−−−−−−−
Dim WrkSyainSet As SyainCls
Private Sub UserForm_Initialize()
Set WrkSyainSet = New SyainCls
If WrkSyainSet.SetError Then
MsgBox "SyainMSTに不正なデータがあるため処理を中断しました", vbCritical
BtnOK.Enabled = False
End If
Call ClearLabel
BtnOK.Caption = "OK"
End Sub
Private Sub BtnOK_Click()
If WrkSyainSet.ChkSyainID(TxtID) Then
Call SetLabel
Else
MsgBox "社員IDが有効ではありません", vbCritical
Call ClearLabel
End If
End Sub
Private Function SetLabel()
LblName = WrkSyainSet.Name
LblKinzoku = WrkSyainSet.Kinzoku
LblSyozoku = WrkSyainSet.Syozoku
LblYakusyoku = WrkSyainSet.Yakusyoku
End Function
Private Function ClearLabel()
LblName = ""
LblKinzoku = ""
LblSyozoku = ""
LblYakusyoku = ""
End Function
Private Sub UserForm_Terminate()
Set WrkSyainSet = Nothing
End Sub
' フォームモジュールに記述ここまで −−−−−−−−−−−−−−−−−−−−−−−−−
' クラスモジュールに記述ここから −−−−−−−−−−−−−−−−−−−−−−−−−−
Private Type SyainData
Id As Long
Name As String
Kinzoku As Long
Syozoku As String
Yakusyoku As String
End Type
Private mWrkSyainData() As SyainData
Private mGetSyainData As SyainData
Private mSetError As Boolean
Private Sub Class_Initialize()
Dim i As Long
Dim WrkRange As Variant
On Error GoTo ErrGyo
WrkRange = Sheets("SyainMST").UsedRange
ReDim mWrkSyainData(UBound(WrkRange))
For i = 2 To UBound(WrkRange)
mWrkSyainData(i).Id = WrkRange(i, 1)
mWrkSyainData(i).Name = WrkRange(i, 2)
mWrkSyainData(i).Kinzoku = WrkRange(i, 3)
mWrkSyainData(i).Syozoku = WrkRange(i, 4)
mWrkSyainData(i).Yakusyoku = WrkRange(i, 5)
Next i
mSetError = False
Exit Sub
ErrGyo:
mSetError = True
End Sub
Public Function ChkSyainID(ByVal pTxtID As String) As Boolean
Dim i As Long
If IsNumeric(pTxtID) Then
If Len(pTxtID) = 4 Then
For i = 1 To UBound(mWrkSyainData)
If mWrkSyainData(i).Id = pTxtID Then
mGetSyainData = mWrkSyainData(i)
ChkSyainID = True
Exit Function
End If
Next i
End If
End If
ChkSyainID = False
End Function
Public Property Get SetError() As Boolean
SetError = mSetError
End Property
Public Property Get Name() As String
Name = mGetSyainData.Name
End Property
Public Property Get Kinzoku() As Long
Kinzoku = mGetSyainData.Kinzoku
End Property
Public Property Get Syozoku() As String
Syozoku = mGetSyainData.Syozoku
End Property
Public Property Get Yakusyoku() As String
Yakusyoku = mGetSyainData.Yakusyoku
End Property
' クラスモジュールに記述ここまで −−−−−−−−−−−−−−−−−−−−−−−−−−
げ、ずいぶんコードが増えてるじゃん!そう思ったあなた、あなたは正しい。
確かに、前回に比べてコードが増えてます。そして、プログラムの構造もかなり違っています。
でもこちらのほうが、まちがいなく「いいコード」です。
以下、順を追って説明します。
「標準モジュール」
構造体の記述が消えています。
メインのSub TestはあいかわらずSyainFormを呼び出しているだけです。
「フォームモジュール」
モジュール変数でDim WrkSyainSet As SyainClsを宣言しています。じつはこのWrkSyainSetがオブジェクトです。
フォームを表示させた時(UserForm_Initialize)に Set WrkSyainSet = New SyainCls で実体化しています。
これ以降はフォームを閉じるとき(UserForm_Terminate)に Set WrkSyainSet = Nothing でインスタンスを破棄するまで、このオブジェクトを使い続けます。
WrkSyainSet.SetErrorはオブジェクトWrkSyainSetのもつプロパティを参照しています。
このプロパティがTRUEのとき、社員マスタの取り込みに失敗しています。
BtnOK_Click()イベントでWrkSyainSet.ChkSyainID(TxtID)というメソッドを呼び出しています。メソッド=命令でしたよね。
この返り値がTRUEのときのみ、フォームのラベルのTxtIDに入力された社員IDの社員データが取得できています。
SetLabel関数では指定された社員IDのプロパティを参照し、名前や勤続年数等のデータをセットしています。
ずいぶん便利ですね、このWrkSyainSetというオブジェクト。それもそのはずです。私がそういう風にクラスモジュールでクラスを設計したからです。
「クラスモジュール」
標準モジュールにあったSyainData構造体の定義がこんなところに来ています。しかもPrivate指定で…。
他の変数もすべてPrivate指定です。
Class_Initialize はコンストラクタとして機能する部分です。クラスのインスタンスを実体化したときに一度だけ実行されます。
終了時に実行させたいときはClass_Terminateを作成して記述します。
今回はインスタンスの初期化時にmWrkSyainDataの中にSyainMSTのデータを格納させています。おまけに取り込みエラーチェックもここで行っています。
エラーがあるとmSetError変数にTRUEが入ります。
ChkSyainID(ByVal pTxtID As String) As Booleanは引数で渡されたデータが社員IDとして正しいものかをチェックしています。
さらにmWrkSyainDataのなかに該当する社員IDがあるかをチェック、あればmGetSyainDataにそのデータを代入し、自身はTRUEを返します。(つまりこの社員IDの人が見つかったよ!ということです)
Public Property Get〜 の各行はこのクラスのプロパティを取得するためのものです。
中身はなんてことはない、先ほどのmSetErrorやmGetSyainDataの中身を返り値に代入しているだけです。
なんでこんなことするんでしょう?
これはオブジェクト指向の特徴のひとつ「データ隠蔽」を実現するためです。(カプセル化ともいいます)
mSetErrorの中身は外部から直接変更することはできません。変更させたい場合はPublic Property Letで引数を渡して代入するしかありません。
しかしこのクラスにはProperty Letが用意してないため、外部から変更することは不可能です。
そもそもmSetErrorの値は、データを正しく取り込めたかどうかを真・偽で保持するので、勝手に変えられては困るのです。ですので値の参照はできますが、値の変更は勝手にさせないようにクラスを設計しているのです。
さてこのオブジェクト指向によるプログラミング、前回の構造化プログラミングに比べてよくなってるんでしょうか?
まず、社員マスタに係るデータとメソッドがすべてクラスモージュール内に集結しました。これはプログラムの独立性をさらに高め、再利用可をさらに行いやすくしています。
構造化プログラミングの到達できなかった部分は、プログラム内のデータ(プロパティ)とそれを扱う関数(メソッド)を統一できなかったところにあります。
Publicな構造体や関数は、他のプロシージャから容易に呼び出せます。それは便利な反面、危険性も重ねそなえています。
いつでも変更できる構造体のデータや、関係のない場所から呼び出せる関数は、プログラマが常に管理する必要がありコーディングした本人の手を離れたとたん、本来の使い方や目的から離れた呼び出しをいつでも行われる可能性のある心細い存在でした。
オブジェクト指向ではデータ(プロパティ)と関数(メソッド)をオブジェクトの型(クラス)として設計します。
そして目には見えないオブジェクトを生成して扱い、利用します。
オブジェクトのインスタンスは破棄されるまで有効で、必要ならば複数のオブジェクトを簡単に作り、並列して処理することもできます。
これは変数や関数の世界では到達することが不可能な世界です。
「オブジェクト指向は現実世界を抽象化する」などとよく言われますが、そんなに難しく考えず「変数」や「関数」の先にあるものとして「オブジェクト」をとらえ、利用していただければと思います。
|
|
|