t-hom’s diary

主にVBAネタを扱っているブログ…とも言えなくなってきたこの頃。

VBAでインターフェースを使う

VBAはクラスを継承することができないが、インターフェースを作成することでポリモーフィズムを実現させることができる。

元ネタはこちらのサイト

VBAに関しては入門者向けの情報が多いなか、高度な情報を提供してくれる大変貴重なサイトである。

これを使って、何か役に立つコードが書けないか検証してみた。
できたのが、以下のコード群。
内容は、Word・Excel・テキストの3種類を統一的な方法で扱うというもの。

こちらがクラスモジュール(IDocument)のコード

Public DocumentPath As String

Public Sub OpenItem()
End Sub

Public Function GetPages()
End Function

Public Function UnitOfPage()
End Function

これがクラスモジュール(ExcelDocument)のコード

Implements IDocument

Private DocumentPath As String
Private file As Workbook

Sub IDocument_OpenItem()
    Set file = Workbooks.Open(DocumentPath)
End Sub

Private Property Get IDocument_DocumentPath() As String
    IDocument_DocumentPath = DocumentPath
End Property
Private Property Let IDocument_DocumentPath(ByVal filepath As String)
    DocumentPath = filepath
End Property

Public Function IDocument_GetPages()
    IDocument_GetPages = file.Sheets.Count
End Function
Public Function IDocument_UnitOfPage()
    IDocument_UnitOfPage = "シート"
End Function

これがクラスモジュール(WordDocument)のコード

Implements IDocument

Private DocumentPath As String
Private file As Object

Sub IDocument_OpenItem()
    With CreateObject("Word.Application")
        Set file = .Documents.Open(DocumentPath)
        .Visible = True
    End With
End Sub

Private Property Get IDocument_DocumentPath() As String
    IDocument_DocumentPath = DocumentPath
End Property
Private Property Let IDocument_DocumentPath(ByVal filepath As String)
    DocumentPath = filepath
End Property

Public Function IDocument_GetPages()
    IDocument_GetPages = file.Range.Information(4)
    '引数の4は、WordVBAの定数「wdNumberOfPagesInDocument」の値
End Function

Public Function IDocument_UnitOfPage()
    IDocument_UnitOfPage = "ページ"
End Function

これがクラスモジュール(TextDocument)のコード

Implements IDocument

Private DocumentPath As String

Sub IDocument_OpenItem()
    Dim WSH
    Set WSH = CreateObject("Wscript.Shell")
    WSH.Run """" & DocumentPath & """", 3
    Set WSH = Nothing
End Sub

Private Property Get IDocument_DocumentPath() As String
    IDocument_DocumentPath = DocumentPath
End Property
Private Property Let IDocument_DocumentPath(ByVal filepath As String)
    DocumentPath = filepath
End Property

Public Function IDocument_GetPages()
    IDocument_GetPages = 1
End Function

Public Function IDocument_UnitOfPage()
    IDocument_UnitOfPage = "ファイル"
End Function

これが標準モジュールに書いたテスト

Sub test()
    Dim x(1 To 3) As IDocument
    
    Set x(1) = New ExcelDocument
    x(1).DocumentPath = "C:\work\exceltest.xlsm"
    
    Set x(2) = New WordDocument
    x(2).DocumentPath = "C:\work\wordtest.docx"
    
    Set x(3) = New TextDocument
    x(3).DocumentPath = "C:\work\texttest.txt"
    
    For i = 1 To 3
        x(i).OpenItem
        Debug.Print x(i).GetPages & x(i).UnitOfPage
    Next i
End Sub

3種類の異なったファイルを、IDocument型の配列に入れて統一的に扱うことができる。

実行すると

4シート
2ページ
1ファイル

と表示された。

まぁでもインターフェースが無くてもObject型に代入してやれば、ポリモーフィズムができてしまうので、あまりありがたみは分からないかもしれない。

あえてインターフェースを使うメリットとしては、コードの信頼性が増すこと。
インターフェース型の変数を介した場合、メソッドが存在することをコンパイル段階で保証してくれるため、より堅牢なコードになる。また、具像クラスにのみ存在するメソッドは、インターフェース型の変数を介した場合はアクセスできなくなる。

今回のサンプルを改良すれば、特定フォルダに入っているすべてのファイルから文字列を検索したりできると思う。(ポリモーフィズムにより、テキストならそのまま検索・Officeファイルならオートシェイプ内のテキストもチェックするなど)

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