[カプセル化の1例]アクセス修飾子 private/publicの使い分け

スポンサーリンク

対象読者

  • クラスを使いこなしたい方

結論

「privateにすること = カプセル化」のような説明がネット上に溢れている。しかし、これはかなり狭い見方である。private化はカプセル化のほんの一例に過ぎない。

人間が理解しやすくするために、情報量が多すぎたり、複雑すぎたり、意味が不明瞭だったりするものを見ずに済むように詳細を隠蔽すること”全て”が「カプセル化」だ

アクセス修飾子とは、クラスのメンバ(プロパティやメソッド)へのアクセスを制限するための仕組みだ。static修飾子やprotected修飾子などもあるが、private/public修飾子さえ押さえていれば大体OKだ。

そもそもアクセスを制限することの何が嬉しいのだろうか。なぜカプセル化するべきなのだろうか。

そこで、そもそもプログラミング言語自体がカプセル化の産物であることから説明する。

カプセル化はプログラミングにありふれている

「カプセル化とは、隠蔽すること(ドヤ)」などと言われても何のこっちゃ分からん。何をどこから隠すのだろうか。なぜ隠す必要があるのだろうか。そこで、プログラミング言語のカプセル化の側面を説明する。

そもそも、コンピュータの動作は突き詰めると、0と1の羅列(ビット列)の演算に還元できる。ビット列によるプログラムの表現を「機械語」という。

ビット列を見て人間が理解できるだろうか。断じて否!

0と1がたくさん並んでいるのを見ても意味が分からない。ヒューマンフレンドリでないが故にビット列は機械語と呼ばれているのだ。

そこで、人間でも理解しやすい記述で機械語を代替するために生まれたのが「(高級)プログラミング言語」だ。

プログラミング言語は、英語ライクなコードから機械語へ変換する仕組み(コンパイル)によって、機械語を人間の目に見えないところへ隠蔽する。いわば、プログラミング言語は機械語をカプセル化するのだ!

プログラミングの分かりやすさの進歩のおかげで、未経験者でもプログラミングが簡単に行えるようになった(良いプログラムを書けるようになるには依然、修練が必要だが…)。

故に、機械語に近い部分を扱えるC言語は難解なのだ。

プログラミング言語の成り立ち自体がカプセル化であり、その当然の帰結としてプログラミングにはカプセル化が溢れている。例を羅列すると、

  • 「データ型」はメモリ上でのデータの表現(ビット列の並び方とその解釈)を、
  • 「変数」はデータ型とメモリ上のアドレスを、
  • 「関数」は具体的な手続きの羅列を

カプセル化する。

privateはカプセル化のほんの1例に過ぎない

上記のように、プログラミング自体がカプセル化であるという事実がある中で「プロパティをprivate化することこそがカプセル化である」みたいな解説がネット上で溢れている。しかも、オブジェクト指向の三大要素の1つとしてカプセル化が挙げられている。

笑止!

このような誤った解説はカプセル化がオブジェクト指向のエッセンスであるかのような誤解を招く。カプセル化はプログラミング一般的において重要なのだ。特に関数は手続き型やオブジェクト指向などのプログラミングスタイルを問わない重要な基礎だ。

手続き型プログラミングのカプセル化の手段を拡張したオブジェクト指向の基礎的な仕組みであるクラスはグローバル変数と関数の相互作用をカプセル化し、プログラムを理解しやすくするための重要な仕組みだ。

クラスのカプセル化の能力を十全に利用するためにアクセス修飾子が重要な役割を持つことを以下で示す。

アクセス修飾子 private/public

アクセス修飾子とは、クラスのメンバ(プロパティやメソッド)へのアクセスを制限するための仕組みだ。

public/private以外にも修飾子が存在するが、それらはカプセル化する上で重要ではないのでここでは詳細まで述べない。一言だけ述べるとすると、

  • static修飾子は、カラススコープにおける要素を宣言するものであり、クラスをインスタンス化せずとも使用できるメンバを定義し、
  • protected修飾子は、派生クラス(派生元含む)以外からのアクセスを禁止する

これらのアクセス修飾子はあるものの、基本となるのはprivate/public修飾子だ。

クラスとは関数もメンバとして持たせられるように構造体を拡張したユーザ定義型である。

さらに別の状況を見てクラスのメリットを浮き彫りにしていく。実践レベルのコードだと、三角形の処理の間にも色んな処理が必要かもしれない。

//グローバル変数
p1 = x
p2 = y
p3 = z

//色んな処理
area = calc_triangle_area()
//色んな処理
deg  = calc_degree()
//色んな処理
norm = calc_normal()

色んな処理には様々な記述が含まれると思って欲しい。このとき、一つの不安が生じる。すなわち、グローバルな座標の値が意図せず変更されているのではないか、という不安である。意図しない値の変更ははバグの原因となる。

この不安を払拭するためには、色んな処理をそれぞれ確認しなければならない。であれば、関数によって処理を分離したことの意義が薄れてしまう。クライアントが詳細を気にせずに済むような仕組みとしての関数の意義に反するからだ。

クラスの場合、この問題を解決する方法がある。それがアクセサ(アクセス修飾子)だ。アクセサはクラスのメンバへの

プログラマが注意してグローバル変数を変更しないようにすればいいと思うかもしれない。しかし、人間の努力による担保(グローバル変数の値が変更されていないという保証)よりも、コンピュータによる担保の方が優れていることは火を見るよりも明らかだ。クラスは人間に頼らずとも、プログラミング言語の仕様によって自動的に影響範囲を制限する。

基本はprivate

privateが多すぎるのは赤信号

インスタンススコープ

クラス内で保持される変数はグローバルではなく、クラス内にしか影響を与えない。故に、色んな処理で変数としてメモリ上に載ったクラス(インスタンス)内の変数が変更される心配はない。故に、色んな処理を確認する必要がなく、クライアントの処理が理解しやすくなる。

クラスに含まれた関数を特にメソッドと呼ぶ。この例では面積計算しかしていないが、他のメソッドも呼び出すような、より多様な処理を行う場合、クラスによるグローバル変数の制限のメリットはより顕著となる。

上記のような例文は短すぎて有難さが伝わらないと思う。しかし、職場での大規模なコードの文脈で考えるならば、クラスによるグローバル変数と関数のグルーピングは重要である。グローバル変数の影響範囲をクラス定義内に限定できるからだ(インスタンススコープ)。

名前の衝突を避けられ、短い名前にできる prefixやsuffixを名前に付与するにより区別する。

引数を削減

コメント

タイトルとURLをコピーしました