メインコンテンツへスキップ
  1. 第09講 クラス(2/2)/

カプセル化

クラス カプセル化 Private Public
目次

カプセル化
#

教科書 p.229 (Lesson 8.4)

カプセル化とは,オブジェクト指向プログラミングの特徴の一つで,単純であろうと見せかける仕組みのことです. 内部の細かな仕様を理解しなくとも,外部から利用できるインターフェースだけで利用できるようにすることが目的です. そのため,不要なデータや手続きは公開せず,公開すべきものを限定することが重要です.

そこでPythonでは習慣的に,以下のような命名規則を使って,外部からアクセスすべきでないメンバ変数やメソッドを表現します.

  • _(アンダースコア)で始まるメンバ変数やメソッドは,外部からアクセスすべきでないことを示す.
    • アンダースコア1つの場合は,外部からアクセスできますが,習慣的にアクセスしないようにしています.
  • __(アンダースコア2つ)で始まるメンバ変数やメソッドは,外部からアクセスできないことを示す.
    • __ が名前の最初に付けられると,マングリング(mangling)と呼ばれる名前修飾により名前が変更されます. 変更後の名前でなければアクセスできないようになります.

他言語では,public/private のような物理的にアクセスを制御するアクセス修飾子がありますが,Python にはありません. そこで,上記のような決まりを用いてアクセス制御を行います.

参考資料
#

カプセル化の例
#

例えば,3つの辺の長さを持つ三角形を表すクラスを考えます. 公開しているメソッドは次の通りです.

  • is_right: 直角三角形かどうかを判定するメソッド.
  • is_valid: 有効な三角形かどうかを判定するメソッド.
  • area: 三角形の面積を計算するメソッド.

それぞれのメソッドでは,外部からアクセスすべきでないメンバ変数 _line1, _line2, _line3 を使っています. また,内部のアルゴリズムは外部に公開しておらず,アルゴリズムを知らなくても利用できるようになっています. 加えて,3つの辺の長さから面積を求めるメソッド _calculate_area_helons_formula は外部からアクセスできないようにしていますが,メソッド名からヘロンの公式を利用していることがわかります. このように,カプセル化を行うことで,外部からの利用を簡単にし,内部の実装を隠せるようになります.

ただし前述の通り,Python には,物理的にアクセス制限をかける機能はありません. Triangle のオブジェクトを生成した後で,_line1, _line2, _line3 に直接アクセスすることは可能です. しかし,習慣的に_で始まっているメンバ(フィールド,メソッド)にはアクセスしない,ということになっているので従うようにしましょう.

なお,この Triangle は(第08講 例題2is_rightメソッドを追加し,必要なカプセル化を行なっています.

class Triangle:
    def __init__(self, line1, line2, line3): # 底の長さ,高さを受け取るコンストラクタ.
        self._line1 = line1 # 底辺の長さを保持するメンバ変数.
        self._line2 = line2 # 高さを保持するメンバ変数.
        self._line3 = line3 # 斜辺の長さを保持するメンバ変数.

    def is_right(self): # 直角三角形かどうかを判定するメソッド.
        l1 = max(self._line1, self._line2, self._line3)        # 最長の辺(斜辺)を求める.
        l2 = min(self._line1, self._line2, self._line3)        # 最短の辺を求める
        l3 = self._line1 + self._line2 + self._line3 - l1 - l2 # もう一つの辺の長さを求める.
        return l1 * l1 == l2 * l2 + l3 * l3   # ピタゴラスの定理が成り立てば直角三角形である.

    def is_valid(self): # 有効な三角形かどうかを判定するメソッド.
        return self._line1 + self._line2 > self._line3 and \
            self._line2 + self._line3 > self._line1 and \
            self._line3 + self._line1 > self._line2

    def area(self): # 三角形の面積を計算するメソッド.
        if not self.is_valid(): # 三角形が成立しない場合は 0 を返す.
            return 0
        return self._calculate_area_helons_formula(self._line1, self._line2, self._line3)
    
    def _calculate_area_helons_formula(self, a, b, c): # ヘロンの公式により三角形の面積を計算するメソッド.
        s = (a + b + c) / 2
        return (s * (s - a) * (s - b) * (s - c)) ** 0.5

setter/getter
#

Python には setter/getter という機能がありませんが,メンバ変数にアクセスするためのメソッドを用意することで,メンバ変数の値を制御することができます. メンバ変数を取得するためのメソッドを getter,設定するためのメソッドを setter と呼びます. getter/setter は get_xxxxx, set_xxxx のような名前で設定されることが一般的です. しかし今日のプログラムでは,setter/getter はカプセル化を壊すものであり,使うべきではないとされています.

setter/getter は使うべきではない
#

オブジェクト指向のカプセル化の話で,setter/getter を用意しようというのは昔の話で, 今日のプログラムではむしろ,setter/getter はカプセル化を壊すと言われています. 今日のプログラムの変数は,イミュータブル(変更不可)であるべきと言われていますので,setter を呼び出す機会がないわけです.

加えて,getter でオブジェクト内部の変数を取得することは,カプセル化でもなんでもないわけです. 上記の例の Triangle では,三角形にまつわる様々な処理をメソッドに隠蔽しています. つまり,getter を必要としないように設計されています. このような例で getter を用意してしまうと,アルゴリズムをクラスの外側で記述する必要があり,カプセル化が壊れてしまいます. もし,getter を用意したいというときには,getter ではなく,メソッドを用意するべきです.

これは,オブジェクト指向の原則として言われている「求めるな,命じよ(Tell, Don’t Ask)」に通じます. オブジェクトには,複雑な処理は任せて結果だけを聞き(tell),決して様々な内容を根掘り葉掘り聞いてはいけない(don’t ask)ということを表しています.

これらのことから,setter/getter は今日のプログラムでは使うべきではありません.

setter/getterはどこから出てきたのか

setter/getter を使うべきではないのであれば,なぜそもそも setter/getter というような呼び方があり, これまで使われていた,もしくは,いまだに使われているのでしょうか.

昔,Java言語でプログラムの自動作成の考え方のもと,JavaBeansという考え方が提案されました. JavaBeansでは,クラスのフィールドは private にして,getter/setter を用意することが推奨されました. プログラムの一部ではありますが,自動的にプログラムを作成するため,オブジェクト内のフィールドを操作したかったわけです. しかし,変数に直接アクセスすることは,アクセス修飾子を持つ言語としては避けたいので,getter/setter が使われるようになりました. setter/getter があると,オブジェクト内の変数を直接操作するわけではないので,カプセル化になっているという誤解が生まれ,広まったのです. もちろん,setterにより不正な値の設定を防げるという利点もあります. しかしそれ以上に,setter/getter はカプセル化を壊すという弊害をもたらすものであるということを理解しておく必要があります.