VBA

【EXCELVBA】クラス入門【初心者向け】

EXCEL VBA CLASS

皆さんは、VBAクラスモジュール というものを、使ったことがありますか?
クラスモジュールを使いこなせるようになると、オブジェクト指向をVBAに取り入れることができます。

コードの質がとても上がりますし、コードの安全性・再利用性も格段に上がりますので、使い方を覚えておくのがおすすめです!

ただ、クラスやオブジェクト指向は概念が難しく使いこなせないと思っている方も多いのではないでしょうか?

    この記事で解説すること

    ・クラスの基礎
    ・メンバ変数を使った変数の使い方
    ・アクセサを使った汎用的な変数の使い方(カプセル化)

    といった基礎的な方法を、解説します。

    クラスモジュールとは

    クラスモジュールとは、標準モジュールと同じくVBAのコードを書く場所の一つです。

    クラスモジュールを使うメリットは次の通りです。

    クラスのメリット

    ・変数の値をどこからでも書き換えられるリスクを減らすことができる
    ・どのモジュールの関数を実行しているか見ただけでわかるようになる
    ・引数の数が多い関数を作りたい場合でも長い関数を使わなくてすむ

    上記は一例ですが、クラスモジュールを使いこなすと、メンテナンスに優れたコードを書くことができます。

    クラスの作成

    それでは、クラスを作成していきましょう。
    オブジェクト指向の説明で、よく使用される人を管理する「人オブジェクト」を作成していきます。

    画像の様にクラスモジュールを追加してください。

    VBE_CLASS

    挿入したクラスモジュールのオブジェクト名を【clsPerson】へ変更してください。

    次にクラスモジュールへ下記のコードを記述ORコピペしてください。

    Option Explicit
    
    '性別を表す列挙体
    Public Enum PrSex
        prSexMale = 0
        prSexFemale = 1
    End Enum
    
    Public Name As String '名前
    Public Age As Long    '年齢
    Public Sex As PrSex   '性別
    Public Address As String'住所
    

    上記クラスが、【人オブジェクトの設計書】つまりクラスです。

    「名前」「年齢」「性別」「住所」の4つの属性を、それぞれ変数「Name」「Age」「Sex」「Address」で、それぞれ保持します。

    あくまでクラスは設計書なので、いくつでもインスタンス(実体)を作成し、使用する事が出来ます。

    クラスは定義しただけでは、何も出来ません。
    定義した設計書から、インスタンス(実体)を作成し使用して初めて効果を発揮します。

    では実際に使用してみましょう!
    標準モジュールを挿入して下記コードを入力してみてください。

    Option Explicit
    
    Sub CreateInstance()
        Dim myPerson(1 To 3) As clsPerson
        Dim i As Long
        Dim mySex As String
        For i = LBound(myPerson) To UBound(myPerson)
            Set myPerson(i) = New clsPerson
        Next
        With myPerson(1)
            .Name = "山田太郎"
            .Age = 30
            .Sex = prSexMale
            .Address = "静岡県富士市"
        End With
        With myPerson(2)
            .Name = "田中太一"
            .Age = 32
            .Sex = prSexMale
            .Address = "大阪府大阪市"
        End With
        With myPerson(3)
            .Name = "舩岡由美"
            .Age = 26
            .Sex = prSexFemale
            .Address = "岡山県津山市"
        End With
    
        For i = LBound(myPerson) To UBound(myPerson)
            With myPerson(i)
                If .Sex = prSexMale Then
                    mySex = "男性"
                Else
                    mySex = "女性"
                End If
                Debug.Print .Name, .Age & "歳", mySex, .Address
            End With
        Next
    End Sub
    

    実行してみるとイミディエイトウィンドウに下記の様に個々のインスタンスに設定した属性が表示されると思います。

    EXCEL VBA CLASS

    この様に、それぞれのインスタンスが独自の属性を持つ事が可能です。
    また、標準モジュールで行っている様な、For Eachループでオブジェクトを一斉にループして処理することも可能です。

    しかし、まだメソッドもプロパティもなく、あるのはパブリック変数(メンバ変数)だけです。
    これではまだ、オブジェクトらしくありませんね。

    事実、上記のことを実現するだけでよければ、ユーザー定義型を使用しても実現できます。

    カプセル化

    実は上記で紹介したコードの様に、パブリック変数を使う事は、オブジェクト指向型プログラミングでは、あまり推奨されていません。

    なぜなら、データが簡単に見られてしまったり、簡単に書き換えられてしまうので、データの安全が保証され難いからです。

    データの安全性を保証するために、データを保持しておくモジュールレベルの変数は、全てPrivateスコープで宣言し、その参照や設定といった処理は、プロシージャを介して行います。

    その様にする事で、データの保全性が高まり、結果としてプログラム全体の保全性が高まることになります。

    この様にデータに直接アクセスできない様にすることを、カプセル化もしくは隠蔽といいます。

    上で作成したクラスを下のコードの様に書き換えて下さい。

    Option Explicit
    
    Public Enum PrSex
        prSexMale = 0
        prSexFemale = 1
    End Enum
    
    Private myName As String
    Private myAge As Long
    Private mySex As PrSex
    Private myAddress As String
    
    '名前の取得
    Public Property Get Name() As String
        Name = myName
    End Property
    
    '名前の設定
    Public Property Let Name(myNewName As String)
        '既に名前が設定されている場合は設定できない
        If Len(myName) > 0 Then Exit Property
        myName = myNewName
    End Property
    
    '年齢の取得
    Public Property Get Age() As Long
        Age = myAge
    End Property
    
    '年齢の設定
    Public Property Let Age(ByVal myNewAge As Long)
        '負の値や150を超える値は設定できない
        If myNewAge < 0 Or myNewAge > 150 Then Exit Property
        myAge = myNewAge
    End Property
    
    '性別の取得
    Public Property Get Sex() As PrSex
        Sex = mySex
    End Property
    
    '性別の設定
    Public Property Let Sex(ByVal myNewSex As PrSex)
        '男または女以外には設定できない
        If myNewSex <> prSexMale And myNewSex <> prSexFemale Then Exit Property
        mySex = myNewSex
    End Property
    
    '住所の取得
    Public Property Get Address() As String
        Address = myAddress
    End Property
    
    '住所の設定
    Public Property Let Address(myNewAddress As String)
        myAddress = myNewAddress
    End Property

    書き換えたクラスでは、「名前」「年齢」「性別」「住所」といったデータは、「myName」「myAge」「mySex」「myAddress」といった変数として保持しておき、外部からは、「Name」「Age」「Sex」「Address」の4つのプロパティを使用してアクセスする事になります。

    カプセル化により、データ設定時のチェック機能を加えただけですが、コードのボリュームが増えて、面倒だなと感じたのではないでしょうか?

    実はこの様にする事で、逆にプログラム全体のボリュームを抑える事ができるのです。

    カプセル化しなかった場合を考えてみると、人に使ってもらうプログラムを作成する場合、イレギュラーな操作に対する処理を行います。

    上記コードの場合、イレギュラーな操作への対策をプロパティとして内包しています。

    一番上のクラスではそれを行っていないので、設定のたびに、毎回同様のチェック機能を付加する必要があります。

    チェック用の関数を作れば賄えると考える人がいるかもしれませんが、サブルーチンを呼ぶコードを忘れてしまったら?など考えると、オブジェクトそのものに内包されて、意識しないうちにチェックを済ませてくれる方が確実だと思います。

    カプセル化をする事で、データへのアクセスに専用のメンバを使用する事になるので、クラスの独立性も高まり、編集効率が向上します。

    つまり、データへのアクセス方法を変更したい場合などは、そのクラスを変更するだけで良い事になります。

    標準モジュールに下のコードを貼り付けて実行して見て下さい。

    Option Explicit
    
    Sub Encapsulation()
        Dim mySex As String
        With New clsPerson
            .Name = "山田太郎"
            .Age = 200          '異常な年齢は設定できないはず
            .Sex = prSexFemale
            .Address = "岡山県津山市"
            GoSub PrintLbl
            .Name = " 太郎"   '名前は一度しか設定できないはず
            .Age = 30
            .Sex = prSexMale
            .Address = "大阪府茨木市"
            GoSub PrintLbl
            .Age = -5         '異常な年齢は設定できないはず
            .Sex = 2          'クラスにない性別は設定できないはず
            .Address = "静岡県富士市"
            GoSub PrintLbl
            Exit Sub
    PrintLbl:
            If .Sex = prSexMale Then
                mySex = "男性"
            Else
                mySex = "女性"
            End If
            Debug.Print .Name, .Age & "歳", mySex, .Address
            Return
        End With
    End Sub

    実行結果は下の様になります。

    VBACLASS

    ちゃんと予想通りチェックがかかりましたね!

    1つずつ属性を設定するのが面倒な方は、下記の様なメソッドで代替えできます。

    'まとめて属性設定
    Public Sub SetIdentity( _
        Optional myNewName As String _
        , Optional ByVal myNewAge As Long = -1 _
        , Optional ByVal myNewSex As PrSex = -1 _
        , Optional myNewAddress As Variant)
        Name = myNewName
        Age = myNewAge
        Sex = myNewSex
        If IsMissing(myNewAddress) Then Exit Sub
        Address = CStr(myNewAddress)
    End Sub

    EXCELVBAクラス入門まとめ

    長い記事を読んでくださり、ありがとうございます。
    クラスは考え方が少し難しいので疲れたのではないでしょうか。

    ここまでの説明した通り、クラスモジュールとはオブジェクトを作成するためにあります。 

    オブジェクトを自作するのは大変です。
    ただ、メリットはあるかと思います。

    クラスを使用するメリット

    ・プログラムの保全性が高まる
    ・独立性の高いクラスは再利用せいが高い
    ・クラスは複製を幾つでも作れるので、生産性の向上に繋がる
    ・ユーザー定義型とは異なり、データの安全性を確保できる

    あまり、VBAではクラスを使用しているサンプルなどを紹介しているサイトは少ないと思いますが、クラスを習得しておく事で、他のプログラム言語を触らなくてはいけなくなったときに、必ず役に立ちます。

    今回のサンプルでは、あまり使いどころがわからなかったかも知れませんが、今後、使いどころをよくありそうな業務などに絡めて紹介していきたいと思います。

    最後までお読みいただきありがとうございました!!