読者です 読者をやめる 読者になる 読者になる

t-hom’s diary

主にVBAネタを扱っているブログです。

VBA クラスモジュールの使いどころ ~ Excelの一覧表とクラスモジュールは相性がいい

VBAには標準モジュールのほかに、クラスモジュールというものがある。
クラスモジュールについて調べようと思っても日本の書籍ではあまりきちんと解説されておらず、主にネットから情報を得ることになる。
使い方についてはまだ情報が存在するのだが、「使いどころ」については実際に活用している方が少ないせいか、ほとんど紹介しているサイトを見かけない。

今回は比較的身近に活用されているであろうExcelで作られた一覧表を題材に、クラスモジュールの使いどころを紹介したい。

※使い方が分からないという方はこちらをご参照ください。
thom.hateblo.jp


さて、今回は適当なデータが無かったので「なんちゃって個人情報」を使用して説明する。
これほんと便利。
f:id:t-hom:20151216224209p:plain

このような一覧表のそれぞれの行をレコードと呼ぶ。
今回利用するなんちゃって個人情報では、ひとつのレコードは、ひとりの人物を表す。

このひとりひとりの人物情報をオブジェクトとして扱い、コレクションに格納してしまうと、あとあと活用が楽になる。

その解説の前にまず、クラスモジュールを使わないバージョンを紹介しておこう。
コードはこちら。

Sub カレーの食べ方アンケート()
    For i = 2 To 100
        Debug.Print Range("I" & i).Value; "在住";
        Debug.Print DateDiff("yyyy", CDate(Range("F" & i)), Date); "歳";
        Debug.Print Range("D" & i).Value; "性 "; Range("M" & i); "性"
    Next
End Sub

実行すると、このように表示される。
f:id:t-hom:20151216231537p:plain


(あ…最後の性は余計だった。。まあ、SS取ってしまったのでこのままにしておく)

これはステップ数も少なくて分かりやすいコードだ。
年齢を誕生日から計算で求めているのは、表に書かれている年齢はどんどん古くなっていくためだ。

次にオブジェクトコレクションを作成するバージョン。
まずはクラスモジュールPersonを用意し、次のコードを記入する。

Public 名前 As String
Public ふりがな As String
Public アドレス As String
Public 性別 As String
Public 誕生日 As Date
Public 婚姻 As String
Public 血液型 As String
Public 都道府県 As String
Public 電話番号 As String
Public 携帯 As String
Public キャリア As String
Public カレーの食べ方 As String

Public Property Get 年齢() As Integer
    年齢 = DateDiff("yyyy", 誕生日, Date)
End Property

Public Property Get Self() As Person
    Set Self = Me
End Property

次に標準モジュールに書くメインのコード。

Enum 列
    名前 = 1
    ふりがな
    アドレス
    性別
    年齢
    誕生日
    婚姻
    血液型
    都道府県
    電話番号
    携帯
    キャリア
    カレーの食べ方
End Enum

Sub カレーの食べ方アンケート結果()
    Dim sh As Worksheet
    Set sh = ThisWorkbook.Sheets("なんちゃって個人情報")
    
    Dim Persons As New Collection

    Dim i As Long
    For i = 2 To 100
        With New Person
            .名前 = sh.Cells(i,.名前)
            .ふりがな = sh.Cells(i,.ふりがな)
            .アドレス = sh.Cells(i,.アドレス)
            .性別 = sh.Cells(i,.性別)
            .誕生日 = sh.Cells(i,.誕生日)
            .婚姻 = sh.Cells(i,.婚姻)
            .血液型 = sh.Cells(i,.血液型)
            .都道府県 = sh.Cells(i,.都道府県)
            .電話番号 = sh.Cells(i,.電話番号)
            .携帯 = sh.Cells(i,.携帯)
            .キャリア = sh.Cells(i,.キャリア)
            .カレーの食べ方 = sh.Cells(i,.カレーの食べ方)
            Persons.Add .Self
        End With
    Next
    
    'Personsコレクションからひとりずつ取り出してプロパティを出力
    Dim p As Person
    For Each p In Persons
        Debug.Print p.都道府県; "在住"; p.年齢; "歳"; p.性別; "性 ", p.カレーの食べ方
    Next
End Sub

最初のEnumからしてよく分からないかもしれないが、過去記事で解説しているのでそちらを参照してほしい。
thom.hateblo.jp

このコードは、最初のFor文(For i = 2 To 100)が終わるまではすべて準備である。
準備だけで元のコードの何倍も長い。
これのどこが良いコードなんだろうか。

実は、一旦レコードをオブジェクトに格納することによって、各段にコーディングがしやすくなるのだ。

たとえば、大阪府在住の方だけ出力したいとすると、「If p.」と入力した時点で、プロパティの候補が出る。
f:id:t-hom:20151216232621p:plain
ここで都道府県を選んでTabキーで決定し、条件式を書き足す。

    Dim p As Person
    For Each p In Persons
        If p.都道府県 = "大阪府" Then
            Debug.Print p.都道府県; "在住"; p.年齢; "歳"; p.性別; "性 ", p.カレーの食べ方
        End If
    Next

これだけで次のとおり、大阪府在住の方だけが取り出せる。
f:id:t-hom:20151216232958p:plain

一方、非オブジェクト指向のコードを同様に変更しようと思ったらこのように条件を足してやれば良い。

Sub カレーの食べ方アンケート()
    For i = 2 To 100
        If Range("I" & i).Value = "大阪府" Then
            Debug.Print Range("I" & i).Value; "在住";
            Debug.Print DateDiff("yyyy", CDate(Range("F" & i)), Date); "歳";
            Debug.Print Range("D" & i).Value; "性 "; Range("M" & i)
        End If
    Next
End Sub

まだかろうじてシンプルさを維持している。
しかし、Range("XX")といった表記が増えてくると、それらが何を意味しているのかといった脳内変換でそのうち頭がパンクする。

では次に、苗字だけ取り出したい場合はどうするか。
データを見るとスペース区切りのようなので、Split関数に名前を渡して0番目のデータをとればよさそうだ。

オブジェクト指向の場合はこうなる。

Sub カレーの食べ方アンケート()
    For i = 2 To 100
        If Range("I" & i).Value = "大阪府" Then
            Debug.Print Range("I" & i).Value; "在住 ";
            Debug.Print Split(Range("A" & i).Value)(0); "さん";
            Debug.Print DateDiff("yyyy", CDate(Range("F" & i)), Date); "歳";
            Debug.Print Range("D" & i).Value; "性 "; Range("M" & i)
        End If
    Next
End Sub

なにやらコードがごちゃごちゃしてきた。

一方オブジェクト指向の場合はどうするか。

まず、Personクラスに名字というプロパティを追加する。

Public Property Get 名字() As String
    名字 = Split(名前)(0)
End Property

するとMainコードで「p.」を入力した時点で、プロパティのリストから名字が選べる。
f:id:t-hom:20151216234147p:plain

修正したメインコードはこちら。単にp.名字をとってくるだけで良い。

    'Personsコレクションからひとりずつ取り出してプロパティを出力
    Dim p As Person
    For Each p In Persons
        If p.都道府県 = "大阪府" Then
            Debug.Print p.都道府県; "在住 "; p.名字; "さん"; p.年齢; "歳"; p.性別; "性 ", p.カレーの食べ方
        End If
    Next

項目が一つ増えただけで、何も難しくはなっていない。
何が増えたのかも一目瞭然である。これがオブジェクト指向の良いところである。

その他工夫できる点としては、例えば敬称の「さん」や、年齢の「歳」もプロパティの内部に押し込んでしまえば、メインコードで書く必要はなくなる。

Personsコレクションを作るまでは割と面倒くさいけれど、一度オブジェクトに落とし込んでしまえばメインコードの簡潔さを保ったままメンテナンスできるということがお分かりいただけたかと思う。

オブジェクト指向なんて自分には関係ないよと思われていた方も、これを機にオブジェクト指向に興味を持っていただけたら嬉しい。

2016/12/25追記

SelfプロパティについてYahoo知恵袋で質問いただいているようなので追記。
detail.chiebukuro.yahoo.co.jp

この記事を書く前に直近で以下の記事を挙げており、そちらが前提になっていた。
thom.hateblo.jp

検索経由で今回の記事のほうがアクセスが増えてしまったため混乱させてしまったようだ。申し訳ない。

私の記事じゃないけれど、こちらでも解説してくれてるのでどうぞ。
ateitexe.com

テクニックとしてはこちらもお勧め。
thom.hateblo.jp

当ブログは、amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。