🚀 ニフティ’s Notion

カプセル化

目次

カプセル化

  • オブジェクト内部のデータや振る舞いを隠蔽してオブジェクト外部からの操作を制御することで、オブジェクトの独立性を保つための仕組み
例) AT自動車とMT自動車の発進時の違い
AT自動車
  • アクセルを踏むだけでどんどん加速してくれる
  • クラッチやギアの操作は全く必要ありません!というわけではなく、その部分は勝手にコンピュータがいい感じにしてくれる
😃
簡単

MT自動車
  • まずクラッチペダルを踏む
  • ギアを1速に入れる
  • 少しアクセルペダルを踏みながら半クラッチの状態にする
  • 速度が出てきたら徐々にクラッチペダルを戻す
  • 失敗するとエンジンストップしてまた最初から
😭
難しい
🚨
MT車の方が一般的に難しい。
それは、車の内部構造(クラッチ・ギア)を気にしないといけないから。
AT車はアクセルを踏むだけで、加速していくので、気にする必要はない。
内部構造を全く知らなくても、そのオブジェクトの中でいい感じにしてくれる。
これが、カプセル化だと言える。
例) スマートフォン
📱
他にもスマートフォンには、電磁気学、情報学、半導体工学など様々な学術分野の知識が凝縮されている。
無線技術には電磁気学の知識が必要になるが、電磁気学を知らないと、電話やネットサーフィンができないのか?というとそうではない。
これは、背景知識を知らなくても使えるように、エンジニア達がうまく隠蔽(カプセル化)してくれているからである。
このように世の中にはカプセル化で溢れている
カプセル化のメリット・デメリット
メリット
  • 内部の実装を変更しやすい
    • 内部の変更の影響が外側に波及せず、別の場所が壊れるみたいなことが起きづらい
      • 振る舞いが変わったからといって、呼び出し元も書き換える必要があっては困る
      • 呼び出し元は入力と出力の形式だけを気にすればいい
  • 可読性が高くなる
    • クラスのコードを読めば内部状態にどういったものを持つのか、どのように状態が遷移するのかがわかる
    • コードを読まなければならない範囲が閉じているため、コードリーディングの負担が減る
  • 中の実装が分からなくても、簡単にオブジェクトを使用することができる
    • もし仮に中の実装を気にしながら使わないといけないとしたら、辛い
    • 家電を使うのに中身がどうなっているかなんていちいち気にしていると辛い
デメリット
  • 強いて言えば、簡単な処理でもメソッドに切り出す必要があるので、コード量が増える


カプセル化の方法

Python
  • Pythonはデフォルトですべてのデータやメソッドは外部に公開されている
    • このままだと外部からデータが見えてしまうので、オブジェクト内部からしかアクセスできないようにアクセス範囲を制御する必要がある
  • そこで、慣習的な命名規則として変数やメソッドの先頭に"__"をつけることで ネームマングリング(難号化) を適用し、アクセスできないようにすることができる
    • メソッドや変数を外から参照できなくすることができる
    • (実は、工夫するとアクセスできてしまうが、やらないよりは硬い実装にはなる)
  • "_"だと ネームマングリング(難号化) はされないが、外部からアクセスしないように周囲に主張するというものになる。
    • アクセスしないでね〜という印になっているだけで、実際には外部からアクセスすることが可能
ネームマングリングを行わない場合(python)
  • クラスの外から値を書き換えることができてしまう
  • 以下の例ではSampleクラスのname変数を直接参照することができている。
class Sample:
    def __init__(self):
        self.name = "Funakubo"

    def hello(self):
        return "Hello! I'm " + self.name

    def print_hello(self):
        print(self.hello())


a = Sample()
print(a.name)       # インスタンス変数nameを直接参照して表示
print(a.hello())    # hello()メソッドの戻り値を表示
a.print_hello()     # print_hello()メソッドを呼び出す
$ python sample_capsule_1.py
Funakubo
Hello! I'm Funakubo
Hello! I'm Funakubo

インスタンス変数に対してネームマングリングを行った場合
  • 直接インスタンス変数を参照することはできず、メソッドを介して参照できる
    class Sample:
        def __init__(self):
            self.__name = "Funakubo"
    
        def hello(self):
            return "Hello! I'm " + self.__name
    
        def print_hello(self):
            print(self.hello())
    
    
    a = Sample()
    print(a.__name)    # インスタンス変数__nameを直接参照して表示
    print(a.hello())   # hello()メソッドの戻り値を表示
    a.printhello()     # printhello()メソッドを呼び出す
    $ python sample_capsule_2.py
    Traceback (most recent call last):
      File "sample_capsule_2.py", line 12, in <module>
        print(a.__name)    # インスタンス変数__nameを直接参照して表示
    AttributeError: 'Sample' object has no attribute '__name'
    メソッドに対してネームマングリングを行った場合
    • メソッドを呼び出すことはできない(クラス内のメソッドからは呼び出せる)
      class Sample:
          name = "Funakubo"    # プライベート変数で定義
      
          def __hello(self):   # プライベートメソッドで定義
              return "Hello! I'm " + self.name
      
          def print_hello(self):
              print(self.__hello())
      
      
      a = Sample()
      
      print(a.name)
      print(a.__hello())
      a.printhello()
      $ python sample_capsule_3.py
      Funakubo
      Traceback (most recent call last):
        File "sample_capsule_3.py", line 14, in <module>
          print(a.__hello())
      AttributeError: 'Sample' object has no attribute '__hello'
Java
  • Javaは外部への公開範囲をアクセス修飾子によって指定できる
  • 他のクラスから変更されたくないフィールドやメソッドのアクセス修飾子をprivate にする
特に何もしない場合(java)
public class User {
    String name;
    int book;

    public User(String name, int book) {
        this.name = name;
        this.book = book;
    }
}
外部から変更されたくない場合(java)
public class User {
    private String name;
    private int book;

    public User(String name, int book) {
        this.name = name;
        this.book = book;
    }
}

まとめ

カプセル化は、オブジェクト内部のデータや振る舞いを隠蔽しメソッドを通して操作することで、不用意なデータの変更を防いだり、変更を容易にすることができる。
データを隠蔽するためには、フィールドのアクセス範囲を制御する必要がある。
Pythonにおいてはネームマングリングを用いて、アクセス範囲を制御する。
Javaにおいてはアクセス修飾子を変数宣言時に使用して、アクセス範囲を制御する。