オブジェクト指向プログラミングの基本的な概念の話【カプセル化・抽象化・ポリモーフィズム・継承】


オブジェクト指向プログラミングにおける基本的な概念「カプセル化」「抽象化」「ポリモーフィズム」「継承」のまとめです。

参考書：[独学プログラマー Python言語の基本から仕事のやり方まで]
(https://www.amazon.co.jp/dp/B07BKVP9QY/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)（第13章）
# カプセル化
### やること・出来ること
- 複数の変数とメソッド、つまり複数のデータと操作をまとめて扱えること。
- 外から直に参照したり操作をする必要のないデータなど内部の状態を、クラス内に隠蔽してアクセスできないようにすること。

### 具体例

```python:Encapsulation.py
class Encapsulation:
    ## 初期化
    def __init__(self, w, h):
        self.width  = w
        self._height = h
        self.option = []
    ## 面積を返す関数
    def area(self):
        return self.width * self._height
    ## オプションの配列に追加する関数
    def add_option(self, op):
        self.option.append(op)
    ## クライアントから操作
    def change_size(self, w,h):
        self.width  = w
        self._height = h

Capsul_1 = Encapsulation(10, 20)
Capsul_2 = Encapsulation(10, 20)

# クラスのメソッドを利用する
print(Capsul_1.area())

# クラスのメソッドを利用して内部のデータを操作する
Capsul_1.add_option("3D")
print(Capsul_1.option)

# インスタンスの変数がパブリック変数なのでメソッドを使う以外に、
# インスタンス変数を直接変更することもできる（Pythonはプライベート変数がない）
# ただし、名前付けの既約として、変数やメソッドがアンダースコアで始まっているものは
# 「自己責任で」クライアントから直接アクセスすること。
Capsul_1.change_size(3, 6)
print("Capsul_1.width:"  + str(Capsul_1.width))
print("Capsul_1._height:" + str(Capsul_1._height))
Capsul_2.width  = 3
Capsul_2._height = 6
print("Capsul_2.width:"  + str(Capsul_2.width))
print("Capsul_2._height:" + str(Capsul_2._height))

```

実行結果

```python:
200
['3D']
Capsul_1.width:3
Capsul_1._height:6
Capsul_2.width:3
Capsul_2._height:6
```

# 抽象化
### やること・出来ること
- 本質的な特徴だけを集めた状態にすること。オブジェクトをモデル化する際に不要な詳細を省略することで、扱いたい問題のみのフォーマットにし、問題を単純化できる。

　問題を単純化することは、大事。

### 具体例
人間をモデル化するため、人間の特徴を考える。

- 身長
- 体重
- 年齢
- 民族
- 目の色
- 髪の色
- 性別
　etc...

⇒例えば社員情報を扱うためのクラスであれば、目の色や髪の色は多くの場合不要となる。
　必要な特徴のみを備えたクラスを作成する。

# ポリモーフィズム
### やること・出来ること
- 同じインターフェース（主に関数やメソッドなど）でありながら、inputとなるデータ型に合わせて異なる動作をする機能。関数をまとめることができる。また型の違いを吸収し、プログラミングを補助する。

### 具体例

```python:Polymorphism.py
## print関数は文字列や整数、浮動小数点のどれでも同じインターフェースで動作する
print("String!")
print(2020)
print(129.3)   # ドラえもんの身長
print("---------------")

## ポリモーフィズムの有無によるコードの差異
class Triangle:
    def __init__(self, w, h):
        self.width    = w
        self.height   = h
        self.mycolor  = "white"
    def area(self):
        return self.width * self.height / 2
    def color(self, choice_color):
        self.mycolor  = choice_color

class Square:
    def __init__(self, w, h):
        self.width    = w
        self.height   = h
        self.yourcolor  = "White"
    def area(self):
        return self.width * self.height
    def coloring(self, choice_color):
        self.yourcolor  = choice_color

triangle_1 = Triangle(10, 3)
square_1   = Square(10, 3)
shapes     = [triangle_1, square_1]

for shape in shapes:
    # 面積を返す関数はポリモーフィズムが使える。
    print(shape.area())
    # 色を付ける関数はポリモーフィズムが使えないので、
    # 確認して適切なメソッドを呼ばなければならない。
    if isinstance(shape, Triangle):
        shape.color("Blue")
        print(shape.mycolor)
    if isinstance(shape, Square):
        shape.coloring("Red")
        print(shape.yourcolor)
```

実行結果

```python:
## Triangle
15.0
Blue
## Square
30
Red
```

# 継承
### やること・出来ること
- 既存のクラス（親クラス）を使ってメソッドや変数を受け継いだクラス（子クラス）を作成する。コードの削減が図られ、プログラム全体が扱いやすくなる。

### 具体例

```python:Inheritance.py
## 親クラス
class Shape:
    def __init__(self, w, h):
        self.width  = w
        self.height = h
    def print_size(self):
        print("{} by {}".format(self.width, self.height))
## 子クラスその１
class Square(Shape):
    def area(self):
        return self.width * self.height
## 子クラスその２
class Triangle(Shape):
    def print_size(self):
        print("My triangle is {} by {}".format(self.width, self.height))

# Shapeクラスを継承しているので子クラスの定義に記載していなくても
# 以下のようにオブジェクトを作成できる
square_1   = Square(20, 20)
triangle_1 = Triangle(10, 10)

# 親クラスのメソッドを利用
square_1.print_size()
# 子クラスのメソッドを利用
print(square_1.area())
# 親クラスのメソッドを子クラスが別の実装で置き換えて使用（メソッドオーバーライド）
triangle_1.print_size()
```

実行結果

```python:
# 親クラスのメソッドを利用
20 by 20
# 子クラスのメソッドを利用
400
# メソッドオーバーライド
My triangle is 10 by 10
```

メソッドオーバーライドを利用することにより、同一のメソッド名で違う動作を実現しやすくなる。
これにより、ポリモーフィズムが実装しやすくなる。