和風スパゲティのレシピ

日本語でコーディングするExcelVBA

ExcelVBAで学ぶオブジェクト指向

このブログの記事数が600を突破しました。

いつもお読みいただいている皆様ありがとうございます!


100記事に1回くらいは読み物をということで、
今日は「ExcelVBAで学ぶオブジェクト指向」というテーマで書いていきます。

興味がある方はお付き合いください。

オブジェクト指向とExcelVBA

プログラミングを学んでいくと必ず突き当たるのが、
「オブジェクト指向とはなんぞや?」というテーマです。

今回はこのテーマについて、
「VBAユーザーだからこそわかる解説」をしたいと思います。



まずはじめに、ExcelVBAはオブジェクト指向言語ではありません。

VBAにはオブジェクト指向に必要な機能が足りていないため、
VBAでオブジェクト指向プログラミングを行うことは残念ながらできません。

※ より正確には、オブジェクト指向言語ではないと言い切ることもできず、
「不完全なオブジェクト指向言語」と解釈すべきという意見もあるようです。


このため「VBAではオブジェクト指向を勉強できない」と考えてしまいがちですが、
実はそうではないのです。



ExcelVBAの主役といえば、もちろん「ワークシートとセル」ですよね。


黒い画面に結果を表示して勉強するような他の言語に比べると、
VBAユーザーは初心者の頃から大量の「オブジェクト」を扱います。

多くのVBAユーザーはその過程で知らず知らずのうちに、
オブジェクト指向の概念に自然と触れているんですよ。



これらWorksheetやRangeなどのオブジェクトは非常に洗練されており、
オブジェクト指向プログラミングの原則に則って設計されています。

オブジェクト指向言語で書かれているだけの、
あまり質の良くない業務システムのソースコードなんかよりもはるかに。



つまりExcelVBAとは、

  • 自分でオブジェクト指向に則ったプログラムを書くことはできない
  • しかしオブジェクト指向に則って洗練された組込オブジェクトに触れる機会は多い

という言語であり、オブジェクト指向のコーディングを体験することはできませんが、
オブジェクト指向の完成品に触れるにはむしろ最適の言語かもしれないのです。


そんな風にとらえて以降の説明をお読みいただければと思います。

オブジェクトとは

突然ですが、

Worksheets("集計表").Value

というコードが「.」を打った後の選択肢から選べるとします。


そしていざ実行すると

このオブジェクトはこのプロパティをサポートしていません。

って出たらどう思います?


「じゃあ選択肢に入れておくなよ!」

って思いますよね。


さらに不便な例として、もしこのValueプロパティが関数で作られていて、

Value(Range("A1")) = 1 ' A1セルに1を代入

このように書く必要があったとします。


かなり面倒と思いませんか?

プロパティやメソッドから入力を始める必要があるということは、
「.」の後の選択肢が使えないわけですからね。


Rangeオブジェクトに何の関数が使えるのかを覚える必要が出てしまいます。



そう考えると、

Range("A1").Value

これ、すごく便利に作られているんだなと実感しませんか?



このように、多くのプログラミング言語の「.」機能はとても洗練されています。


例えばWorksheetオブジェクトでは、

  • Worksheetが持つ情報(NameやIndex)
  • Worksheetの中にある子オブジェクト(RangeやShapes)
  • Worksheetに実行できる処理(CopyやDelete)

だけが「.」の後の候補に表示されてくれます。

このおかげで「存在しないプロパティやメソッド」を書いてしまうことも減りますし、
プロパティ名やメソッド名(の綴り)を覚える必要も激減します。


この『情報と処理を「.」から呼べるモノ』をオブジェクトと呼び、
超乱暴な説明をすると、
このオブジェクトを自作するのがオブジェクト指向プログラミングです。


もちろんただ作ればいいという訳ではなく、

  • オブジェクトの作り方におけるルールや方針
  • それらを守ったプログラミングをやりやすくしてくれる機能

がたくさん用意されており、
それらに則ってプログラミングしていくことをオブジェクト指向と呼ぶのです。


これらルール・方針・機能について
「こういう風に設計すると便利なオブジェクトになるよ!」
という3大要素がいわゆる「オブジェクト指向三大要素」として定義されています。


今日はExcelVBAを題材にこの三大要素を学んでいきましょう。

クラス・メソッド・プロパティ

ここで簡単に用語の説明だけ挟んでおきます。


先ほど以下の通りWorksheetオブジェクトを解説しました。

シートの情報やシート対する処理、シート内にあるオブジェクトを、

  • Worksheetが持つ情報(NameやIndex)
  • Worksheetの中にある子オブジェクト(RangeやShapes)
  • Worksheetに実行できる処理(CopyやDelete)

これらを「.」から選んで実行・取得できるモノがWorksheetオブジェクト。


まずはこれら情報・処理などを、

  • Worksheetが持つ情報や状態のことを「プロパティ」
  • Worksheetが/に実行できる処理を「メソッド」
  • これらを合わせてWorksheetオブジェクトの「メンバー」

と呼びます。


続いてシート内のセルやシェイプなどは「子オブジェクト」と呼びます。

ただし実際は「子オブジェクトを取得するプロパティ」で取得していますので、
余裕があればこの構造も覚えておいてください。
 

Worksheets(1).Range("A1")

↑このコードは
「第1シートの子オブジェクトA1セルをRangeプロパティで取得」
しています。

オブジェクトを取得するプロパティがあるということですね。



そしてここからがちょっと難しいのですが、
「プロパティやメソッドをまとめたもの」をオブジェクトと呼ぶ中で、
より正確に、その設計図のことをクラスと呼び分けます。


例えばWorksheetを例にとると、「データ」シート、「集計表」シートなど、
実際に存在する各シートはWorksheetオブジェクトと呼びます。


これらのシートはそれぞれでNameプロパティやCopyメソッドを動かせますが、
そのためにはNameやCopyのソースコードを先に用意しておく必要がありますよね?

その用意されたオブジェクト用のソースコード集≒設計図のことをクラスと呼びます。

※ ちなみに実際に存在する各シートについては、
クラスと呼び分けてインスタンスと呼んだりもします。


このあたりはすごく概念的なものなので理解するが結構難しいです。

  • Worksheetオブジェクトで使うメソッド・プロパティのソースコード集
  • まだオブジェクトが存在しない架空のWorksheetオブジェクト

みたいなものとしてとらえると少しは捉えやすいでしょうか。


最初はクラス=オブジェクトと思っておいても特に問題ありません。

この違いはクラスモジュールを自分で組むと自然と分かってきますので、
とりあえずクラスをそのままオブジェクトに置き換えて読み進めてみてください。

オブジェクト指向三大要素「カプセル化」

さて先ほど、Worksheetオブジェクトでは、

  • Worksheetが持つ情報(NameやIndex)
  • Worksheetの中にある子オブジェクト(RangeやShapes)
  • Worksheetに実行できる処理(CopyやDelete)

これらが「.」から選択できると説明しました。


まずはこの『オブジェクトが使える情報と処理を「.」内にまとめる』ことを、
広義にオブジェクト指向三大要素のひとつ、「カプセル化」と呼びます。


先ほどの「クラス」という用語も使って説明すると、
「オブジェクトの処理をクラス内にしっかりまとめること=カプセル化」
と捉えてもいいですね。


ただし、この「広義」「しっかり」といった部分が結構大事で、
ただクラスにまとめるだけでは厳密にはカプセル化と呼べません。


例えばWorksheetのNameプロパティを思い浮かべてください。

このプロパティは

  • シート名はWorksheet.Nameからしか変更ができない
  • 既存シート名と被ったり31文字以上を渡すとエラーとなる
  • ユーザーはNameプロパティを実装している裏のコードを読む必要はない

ように設計されています。

「32文字目が次のシート名の先頭にはみ出してしまう」
といった昔のゲームみたいなバグは起きませんからね。


このような「正しい使い方以外の実行を許さないことでユーザーを守る」
仕組みを搭載して初めて、狭義に「カプセル化」と呼びます。


こうすることで、我々VBAユーザーは、

  • Worksheet.Valueのように持っていない機能は選べない
  • 選んだ機能の使い方が間違っていたらエラーで教えてくれる
  • Worksheetのソースコードまで読みにいく必要はない

といった恩恵を得ることができています。

そうなるように設計することを「カプセル化」と呼ぶということですね。


ちなみに

  • シート名はWorksheet.Nameからしか変更ができない
  • 既存シート名と被ったり31文字以上を渡すとエラーとなる
  • ユーザーはNameプロパティを実装している裏のコードを読む必要はない

これらの防御壁を作り、誤ったメンバーの使用をシャットアウトすることを、
オブジェクト指向用語で「隠蔽」と呼びます。


「隠蔽」はカプセル化を実現するための一つの要素として捉えられていますので、
余裕があったらこの用語も一緒に覚えてしまってください。

オブジェクト指向三大要素「継承」

さてカプセル化に続いて次の要素に入りましょう。


ExcelVBAで図形やグラフを扱う際にとても便利なのが、
すべてひっくるめてShapeオブジェクトとして動かせる点です。


シート内のオブジェクトを扱う際、

For Each シェイプ In 対象シート.Shapes

とすることで図形・画像・テキストボックス・グラフをすべてループでき、
位置の移動やコピーなどは全く同じコードで実装できます。


Top、Left、Hight、Width、Copy、Deleteなど、
Shapeオブジェクト共通のプロパティ・メソッドは全員が持っていますからね。


それでいて「写真」や「グラフ」特有の処理をしたいときは、
Picture型やChart型の変数に格納することで専用のプロパティたちが使えます。


さらに細かい仕様を言うと、同じCopyメソッドであっても、
「グラフオブジェクトの場合はデータエリアやプロットの書式もコピーする」
といった、Shapeオブジェクトにはない処理も追加で行ってくれます。


このようにあるオブジェクトを設計する上で、

  • 共通のプロパティやメソッドは親であるShapeオブジェクトのものを使える
  • その上で自分専用のプロパティやメソッドを追加実装できる
  • 共通のプロパティやメソッドを自分用に書き替えることもできる

という機能があると、ユーザーにとっても便利なオブジェクトになりますし、
このオブジェクトを設計する側にとってもプログラミングしやくなります。


この「共通オブジェクトをベースに専用オブジェクトを設計する機能」のことを、
オブジェクト指向の三大要素のひとつ、「継承」と呼びます。


ChartオブジェクトはShapeオブジェクトを継承したオブジェクトということですね。

※ 正確には「共通クラスをベースに専用クラスを設計」するのが継承です。
「ChartクラスはShapeクラスを継承したクラス」と呼称してください。
実際に存在するシェイプをコピーしてグラフを作るわけでは当然ないため、
厳密にはオブジェクトがオブジェクトを継承するという記述は誤りです。


ついでに用語を付け足しておくと、先ほど出てきた「継承」の機能のうち、
「共通のプロパティやメソッドを自分用に書き替える」
ことをオブジェクト指向用語で「オーバーライド」と呼びます。


Shapeクラスを継承してChartクラスを作るときに、
Copyメソッドの中身をグラフ用に書き替えたのがオーバーライドということですね。



さてこの継承、とても便利なのですが、
残念ながらVBAのクラスモジュールにはこの継承機能がありません。


ShapeやChartなどVBAの組込オブジェクトは継承を利用して作られているのですが、
自作のクラスでこの機能を使うことはできないのです。


要するに、

  • VBAを作る人(Microsoftさん)は継承を利用してVBAを作っている
  • VBAのクラスには継承機能がないため、VBAを使う人は継承が利用できない

という構図になっており、
継承ができないためExcelVBAは完全なオブジェクト指向言語にはなれないのです。
残念(´・ω・`)


このExcelVBAの仕様についても覚えておいてください。

オブジェクト指向三大要素「ポリモーフィズム」

「カプセル化」「継承」ときてオブジェクト指向最後の要素です。


ExcelVBAは規則だたしくプロパティやメソッド名が決められており、
先ほどからよく例に挙げるNameプロパティCopyメソッドは、
Workbook、Worksheet、Shapeなどで同名になっています。


この「メソッドやプロパティが同名」であることでひとつ面白いコード、

Selection.Copy

こんなコードを書くことができます。


このコード、よく考えるとなかなかすごい仕様のコードであり、
ExcelVBAさんは実行前に「どのオブジェクトをコピーするか」がわかりません。


  • セルを選択していればセルのコピー
  • 図形を選択していれば図形のコピー
  • グラフを選択していればグラフのコピー

が実行されますからね。


このように「○○.Copyと書くと○○の種類によってそれぞれのコピーを実行する」機能のことを、オブジェクト指向の三大要素のひとつ「ポリモーフィズム」と呼びます。


Microsoftさんがこのようにクラスを設計しておいてくれたおかげで、

Selection.Copy
Selection.Paste

はSelectionが差し変わっても動かすことができます。

使う側にとっては、
メソッドやプロパティ名でどんな処理かを予想しやすいというメリットもありますね。


これを応用して自作クラスで同じような実装をすることも可能で、

Variant型変数.自作メソッド

このコードを複数の自作クラスで動くように設計することができます。

同名の自作メソッドにしておけば、
Variant変数に入れるクラスが変わってもいい感じに動かせるということですね。



ここで2つ追加の用語を説明しておきます。


まず「VBAは規則正しくプロパティやメソッド名が決められている」という点。

このプロパティ名やメソッド名にルールを作る機能のことを、
オブジェクト指向用語で「インターフェース」と呼びます。


インターフェースをしっかり定義しておくことでプロパティやメソッド名が統一され、
ポリモーフィズムが最大限に効果を発揮できると捉えてください。


続いてもうひとつ、これはRangeプロパティを例に挙げるとわかりやすいのですが、
Rangeプロパティは渡した引数によって動きを変えてくれ、

  • Range("A1")と書いたときはそのアドレスのセルを
  • Range(Cells, Cells)と書いたときはそのセルからそのセルまでの矩形範囲

を取得してくれるプロパティとして動きます。


このように渡された引数の種類によってメソッドやプロパティの動きを変える機能を、
オブジェクト指向用語で「オーバーロード」と呼びます。

こちらは「インターフェース」を守りつつ実装に柔軟性を持たせる機能と捉えておけばOKですね。


これら機能に支えられる形で「ポリモーフィズム」が実現されています。

余裕があればまとめて覚えてください。

補足

ここまで説明を簡潔にするために正確性を犠牲にしてきましたので、
嘘をついた部分だけ一応訂正しておきます。

まずShapeクラスを継承してChartクラスを作っていると説明しましたが、
内部実装的には全く別のクラスを似せて作っているだけのようです。


つまり継承は使っておらず、
「Shapeクラスと同名のメンバーを用意する規則でChartクラスを作成した」
という点で、どちらかと言えばインターフェースを用いてポリモーフィズムを実現した例と言えるクラスになっています。

グラフを「As Shape」の変数で宣言しても基本的な処理はこなしてくれる点で、
かなり高度なポリモーフィズムを実現していますよね。


といってもユーザーは「継承されてそうなクラス」と思って使うこともできますので、
そういう意味では「中身を気にせず使えるカプセル化の実現」とも言えそうです。


続いて先ほどの「オーバーロード」ですが、より正確には、

  • 誤:引数の種類によってメソッドやプロパティの動きを変える機能
  • 正:同名のメソッドを別の引数で複数定義できる機能

こんな説明をしなければいけませんでした。


Rangeプロパティを例にとると、

Property Get Range(セルアドレス As String)
Property Get Range(1セル As Range,2セル As Range)

というPropertyを二つ作成することができ、
渡された引数によって呼ばれるRangeが変わるのがオーバーロードです。


しかし実際は、

Property Get Range(Cell1 As Variant, Optional Cell2 As Variant)

と、

  • Variant型の引数にしてRangeとStringをどちらも受け取れるようにし
  • Optionalで省略可能にして引数の数を動的にした

実装になっているようです。


VBAにおいては組込クラスであっても自作クラスであっても、
Variant・Optional・ParamArrayを用いた疑似オーバーロードになります。

余裕があればこの違いも覚えておいてください。

まとめ

ここまで出てきた「カプセル化」「継承」「ポリモーフィズム」を合わせて、
オブジェクト指向三大要素と定義しています。

「VBAユーザーはオブジェクト指向プログラミングをすることはできません」
が、
「VBA自体はしっかりオブジェクト指向で作られている」
のがなんとなく分かりましたでしょうか?


ものすごくざっくりまとめると

  • オブジェクトの情報と処理を「.」内にまとめて定義したものが「クラス」
  • ユーザーが中身を気にせず使えるレベルのクラスを作る「カプセル化」
  • ShapeクラスをカスタマイズしてChartクラスを作るのが「継承」
  • Selection.Copyで各々のコピーを実行できるのが「ポリモーフィズム」

というイメージになります。


ついでに出てきた用語もまとめておくと、

  • オブジェクトの持つ情報や子オブジェクトを取得する「プロパティ」
  • オブジェクトに対して処理を実行する「メソッド」
  • これらを合わせてクラス(オブジェクト)の「メンバー」
  • 誤ったメンバーの使い方をシャットアウトする「隠蔽」
  • 継承時に同名メソッドを自身専用に書き替える「オーバーライド」
  • メソッド・プロパティ名にルールを設ける「インターフェース」
  • 同名プロパティが引数の種類によって動きを変える「オーバーロード」

という感じになります。


まあこのあたりの用語は別に覚える必要はなく、
使っていくうちに感覚的につかんでいけば十分なんじゃないかなと思います。


要は

  • とても使いやすいオブジェクトを作るためのルールと方針が決められている
  • その方針を効率よく守るための便利な機能が用意されている
  • これをひっくるめてオブジェクト指向プログラミングと呼んでいる

と思っておけばいいんじゃないでしょうか。


前述の通りExcelVBAでオブジェクト指向プログラミングはできないのですが、
このあたりを理解するといい設計のコードが書けるようになるかもしれません。


興味がわいたらクラスモジュールをしっかり設計して作ってみたり、
実際のオブジェクト指向言語に触れて遊んでみてください。