[Python] Pythonでオブジェクト指向を完全理解してみる

### そもそもオブジェクト指向プログラミング（OOP）とは?

オブジェクト指向プログラミング（OOP）には、次のような特徴があります。

**1. オブジェクト指向**
  オブジェクトベースのプログラミング言語であり、プロパティや振る舞いを持つオブジェクトを基にプログラムを構築します。
**2. 継承**
クラスが他のクラスからプロパティや振る舞いを継承することができます。
**3. ポリモーフィズム**
同じメソッドが異なる型のオブジェクトに対して適用され、それぞれのオブジェクトによって異なる実装を持つことを意味します。
**4. カプセル化**
オブジェクトのプロパティや振る舞いをカプセル化し、外部からの直接アクセスからオブジェクトの内部プロパティを保護することができます。
**5. 抽象化**
オブジェクトの実装の詳細をユーザーから切り離し、ユーザーはオブジェクトのパブリックインターフェースだけを知っていればよく、その実装を知る必要はないようにします。

### オブジェクトがプロパティと振る舞いを持つとは
オブジェクトが2つの異なる特性を持つということです。
プロパティはオブジェクトの特性を表し、振る舞いはオブジェクトのメソッドを表します。

例えば、"Car "というクラスがあったとして、このクラスは "color"、"speed"、"brand "などのプロパティを持っているかもしれません。
振る舞いは "ドライブ"、"ブレーキ"、"ターン "などのメソッドを持つことがあります。

このクラスをPythonで実装すると、次のようになります。
```python
class Car:
    def __init__(self, color, speed, brand):
        self.color = color
        self.speed = speed
        self.brand = brand
        
    def drive(self):
        self.speed += 10
        
    def brake(self):
        self.speed -= 10
        
    def turn(self, direction):
        print(f"The car is turning {direction}")
        
my_car = Car("red", 0, "Tesla")
my_car.drive()
my_car.turn("left")
print(my_car.speed) # 10
```
上の例では、Carというクラスは、color, speed, brandという3つのプロパティと、drive(), brake(), turn()という3つのメソッドを持っています。
これらのメソッドやプロパティは、my_carというインスタンスを作成する際に使用されます。

### 続いて、上記のCarクラスを使って、継承の例を書きます。
Carクラスを継承する新しいクラスを作成し、新しいクラスにCarクラスのプロパティとメソッドを継承させればよいのです。

例えば、Carクラスを継承した新しいクラス「ElectricCar」を作成し、ElectricCarクラスに新しいプロパティとメソッドを追加することができます。
```python
class ElectricCar(Car):
    def __init__(self, color, speed, brand, battery_size):
        super().__init__(color, speed, brand)
        self.battery_size = battery_size
        
    def charge(self):
        print("The electric car is charging...")
        
    def drive(self):
        if self.battery_size > 0:
            self.speed += 20
            self.battery_size -= 10
        else:
            print("The battery is empty, please charge the car.")
            
my_electric_car = ElectricCar("blue", 0, "Tesla", 100)
my_electric_car.drive()
my_electric_car.drive()
my_electric_car.drive()
print(my_electric_car.speed) # 60
print(my_electric_car.battery_size) # 70
```
この例では、ElectricCar クラスは Car クラスのすべてのプロパティとメソッドを継承し、新たに battery_size プロパティとメソッド charge() を定義しています。

Car クラスの init メソッドは super() 関数で継承することができます。

my_electric_car = ElectricCar("blue", 0, "Tesla", 100) を使ってインスタンスを作成します。
ElectricCarクラスの新しいプロパティとメソッドを使用することができます。

### 続いて、上のコードはポリモーフィズムの使い方を書きます。
ポリモーフィズムは、一般に継承されたクラスを使用することで実現することができます。
上のコードの例では、ElectricCarクラスでCarクラスを継承し、driveメソッドを再実装することで、ポリモーフィズムを実現することができます。
```python
class ElectricCar(Car):
    def __init__(self, color, speed, brand, battery_size):
        super().__init__(color, speed, brand)
        self.battery_size = battery_size
        
    def charge(self):
        print("The electric car is charging...")
        
    def drive(self):
        if self.battery_size > 0:
            self.speed += 20
            self.battery_size -= 10
        else:
            print("The battery is empty, please charge the car.")
            
class GasCar(Car):
    def __init__(self, color, speed, brand, gas_level):
        super().__init__(color, speed, brand)
        self.gas_level = gas_level
        
    def fill_gas(self):
        self.gas_level = 100
    
    def drive(self):
        if self.gas_level > 0:
            self.speed += 15
            self.gas_level -= 10
        else:
            print("The gas is empty, please fill the gas.")

my_electric_car = ElectricCar("blue", 0, "Tesla", 100)
my_gas_car = GasCar("black", 0, "Ford", 60)

def drive_car(car):
    car.drive()

drive_car(my_electric_car)
drive_car(my_gas_car)

print(my_electric_car.speed) # 20
print(my_gas_car.speed) # 15
```
この例では、ElectricCar と GasCar の両クラスに drive メソッドを再実装し、drive_car 関数に Car クラスのみを渡しています。 drive_car 関数に異なるクラスのインスタンスを渡すと、異なるドライブ動作が行われることが分かります。

これは、クラスごとに異なる関数やメソッドを書くのではなく、同じ関数やメソッドで複数のクラスのオブジェクトに対応できるポリモーフィズムを利用したもので、このポリモーフィズムを利用すると、クラスごとに異なる関数やメソッドを書く必要がなくなります。 これにより、コードの柔軟性と可読性が向上します。

### 続いて、上記のCarクラスを使って、カプセル化の例を書きます。
カプセル化とは、オブジェクトのプロパティや振る舞いをカプセル化し、オブジェクトの内部プロパティを外部からの直接アクセスから保護することです。

Pythonでは、_や__を使用して、クラス内でのみアクセス可能なプライベート変数やメソッドを表すことができます。

例えば、ElectricCar クラスの battery_size プロパティを private 変数に変更することができます。
```python
class ElectricCar(Car):
    def __init__(self, color, speed, brand, battery_size):
        super().__init__(color, speed, brand)
        self.__battery_size = battery_size
        
    def charge(self):
        print("The electric car is charging...")
        
    def drive(self):
        if self.__battery_size > 0:
            self.speed += 20
            self.__battery_size -= 10
        else:
            print("The battery is empty, please charge the car.")
            
    def get_battery_size(self):
        return self.__battery_size
    
my_electric_car = ElectricCar("blue", 0, "Tesla", 100)
print(my_electric_car.get_battery_size()) #100
```
この例では、ElectricCar クラスの battery_size プロパティを、クラス内部でのみアクセス可能な private 変数として定義しています。 変数が外部からアクセスされた場合、エラーとなります。

その代わり、プライベート変数に対応するパブリックメソッド get_battery_size() を使って、プライベート変数にアクセスすることができます。 これにより、クラスの内部プロパティを外部からの直接アクセスから保護するとともに、publicメソッドによる適切な外部アクセスも提供します。

### 続いて、上記のCarクラスを使って、抽象化の例を書きます。
```python
from abc import ABC, abstractmethod

class Car(ABC):
    def __init__(self):
        self.__year_model = 0
        self.__make = ""
        self.__speed = 0

    @abstractmethod
    def drive(self):
        pass

class ElectricCar(Car):
    def __init__(self):
        super().__init__()
        self.__battery_type = ""

    def set_battery_type(self, battery_type):
        self.__battery_type = battery_type

    def get_battery_type(self):
        return self.__battery_type

    def drive(self):
        print("Electric car is driving.")

class GasCar(Car):
    def __init__(self):
        super().__init__()
        self.__fuel_type = ""

    def set_fuel_type(self, fuel_type):
        self.__fuel_type = fuel_type

    def get_fuel_type(self):
        return self.__fuel_type

    def drive(self):
        print("Gas car is driving.")

#create object
car = ElectricCar()
car.drive()

car = GasCar()
car.drive()
```
この例では、Carクラスは抽象クラスであり、抽象メソッドdrive()を定義しています。ElectricCarクラスとGasCarクラスは、いずれもCarクラスを継承し、drive()メソッドを実装しています。

この書き方により、カプセル化用途はもちろん、ポリモーフィズムに対応することができます。また、Carクラスは抽象クラスであるため、Carクラスのオブジェクトを直接作成することはできず、子クラスを使用することでしか作成できません。

### QA

#### 0. オブジェクト指向はなぜオブジェクト指向と呼ばれるのか？
オブジェクト指向のプログラミング言語は、オブジェクトという概念を重視しているため、オブジェクト指向と呼ばれます。 オブジェクト指向のプログラミング言語では、開発者はクラスを使ってオブジェクトの共通性を定義し、オブジェクトを使ってクラスをインスタンス化することができます。

オブジェクトはプロパティと振る舞いを持ち、互いに通信し、相互作用することができます。 オブジェクト指向プログラミングは、実世界をより視覚的にシミュレートし、コードの保守・拡張をより容易になります。

#### 1. Pythonでは、プライベート変数やメソッドに_や__を使用することができますが、_と__の違いは何ですか？
プライベート変数やメソッドにアンダースコア（_）を1つだけ使っても、実際には外部からのアクセスが遮断されるわけではなく、慣習的にそう書かれているだけなのです。

2つのアンダースコア（__）を使用すると、プライベート変数とメソッドが本当にカプセル化され、外部からのアクセスが遮断され、子クラスからはアクセスできなくなり、カプセル化の保証がより確実になることを意味します。

#### 2. 継承された機能があるのに、なぜ抽象化が必要なのか？
継承の目的は、子クラスに親クラスのプロパティやメソッドを継承させ、親クラスのコードを共有させることでコードの再利用を図ることであります。 これにより、コードがより簡潔になり、子クラスが親クラスの機能を共有できるようになります。

一方、抽象化とは、クラスに共通の機能や振る舞いを持たせることであります。 抽象クラスはインスタンス化できないが、他のクラスから継承して利用することができます。 抽象クラスは、抽象メソッドと通常のメソッドを定義できますが、抽象メソッドは定義されるだけで、実装されるわけではありません。 抽象的なメソッドは、子クラスで実装する必要があります。 これにより、すべての子クラスが同じ動作をすることが保証されますが、実装は異なる可能性があります。

#### 3. 実際の業務では、どのような場合に継承を使い、どのような場合に抽象化を使うのでしょうか?
- 継承
継承は、他のクラスの機能や動作を引き継ぎたい場合に使用します。 例えば、自動車クラスがあり、電気自動車とガソリン車クラスを作成する必要がある場合、どちらも同じ機能や振る舞いを多く持っているので、継承を使用して自動車クラスの機能や振る舞いを電気自動車とガソリン車クラスに引き継ぐことができます。

- 抽象化
抽象化は、同じインタフェースや振る舞いを共有するクラスが多数ある場合に使用されます。 例えば、carクラスがあり、電気自動車、ガソリン車、ディーゼル車など様々な種類の車を作成する必要がある場合、すべての車がdrive動作を持つようにするために、抽象化を使ってdriveメソッドを定義し、電気、ガソリン、ディーゼルなどのクラスでdriveメソッドを実装します。

### 最後に
今の内容はChatGPTとのやりとりでしたw