きれいなコードを書くためにSOLID原則を学びました③ ~リスコフの置換原則~

今回は**SOLIDのリスコフの置換原則**についてまとめました。

その他の記事は以下。
<a href="https://qiita.com/suzuki0430/items/b13c7c1f637e7cd2146d">きれいなコードを書くためにSOLID原則を学びました① ~単一責任の原則~</a>
<a href="https://qiita.com/suzuki0430/items/b0e2a2c294d81ecdefba">きれいなコードを書くためにSOLID原則を学びました② ~オープン・クローズドの原則~</a>
<a href="https://qiita.com/suzuki0430/items/040a186e5e8f51e6c11f">きれいなコードを書くためにSOLID原則を学びました④ ~インターフェース分離の原則~</a>
<a href="https://qiita.com/suzuki0430/items/c7a86520c0bd18742ea7">きれいなコードを書くためにSOLID原則を学びました⑤ ~依存関係逆転の原則~</a>

#リスコフの置換原則
>Object should be replaceable with their subtypes without affecting the correctness of the program

これを直訳すると、**「オブジェクトは、プログラムの正しさに影響を与えずにそのサブタイプと交換可能であるべきである。」**という意味になります。

サブタイプは、親クラスを継承したクラスのことをいいます。要は、[サブクラス]**は**[親クラス]**である**という関係のことですね。
ex.) コンパクトカー**は**車**である**、ダチョウ**は**鳥**である**
![スクリーンショット 2021-02-06 10.27.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/63a88508-3150-b260-5c98-3222dc61fb1b.png)


Bird(鳥)クラスとBirdクラスを継承したOstrich(ダチョウ)クラスを考えてみます。

```java
public class Bird {
  public void fly() {
    // Fly high!
  }
}

public class Ostrich extends Bird {
  public void fly() {
    // Unimplemented
    throw new RuntimeException();
  }
  
  public void run() {
    // Run fast!
  }
}
```

継承により、OstrichクラスはBirdクラスのメソッドをそのまま使うことができるのですが、ダチョウは空を飛ぶことができないので、flyメソッドを例外として上書きしています。

以下で、Birdクラスのインスタンスを2つ、Ostrichクラスのインスタンスを1つつくって、flyメソッドを出力するようなBirdHabitsというクラスを考えてみます。

```java
public class BirdHabits {
  public static void main(String[] args) {
    Bird first = new Bird();
    Bird second = new Bird();
    Bird third = new Ostrich();
  }

  List<Bird> myBirds = new ArrayList<>();
  myBirds.add(first);
  myBirds.add(second);
  myBirds.add(third);

  for(Bird bird: myBirds) {
    System.out.println(bird.fly());
  }
}
```
Ostrichクラスからつくられたthirdインスタンスのflyが呼び出されるときだけ、例外処理が走ります。

ここで親クラスをサブクラスと交換してみましょう。
Ostrichクラスのインスタンスを

```java
Bird third = new Ostrich();
```
Birdクラスのインタンスに置き換えたらどうなるでしょうか？

```java
Bird third = new Bird();
```
**以下の部分で3回ともBirdのflyメソッドが実行されてしまい、クラスを置き換える前とプログラムのふるまいがかわってしまいます。**

```java
for(Bird bird: myBirds) {
  System.out.println(bird.fly());
}
```

したがって、以上の例は、リスコフの置換原則に違反しています。
このように、ダチョウ(Ostrich)は鳥(Bird)であることは確かなのですが、鳥から継承できないふるまい(メソッド)が含まれている限り、リスコフの置換原則的には望ましくない設計であるということになります。

##解決法: さらに一般的な親クラスを考える
それでは、どうすれば原則を満たす理想的な設計となるのでしょうか。
解決法は、**更に一般的な親クラスを用意してあげることです。**

今までの例で考えれば、**「鳥もダチョウも動物である」のでAnimal（動物）クラスというものを新しく用意してあげることになります。**
![スクリーンショット 2021-02-06 10.43.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/de1048eb-881f-1a29-380b-514bd764e963.png)

Animalクラスでは新しくmoveメソッドというものをつくり、継承先のクラス(Bird, Ostrich)でそれぞれのメソッド(fly, run)を返してあげます。

```java
public class Animal {
  public void move() {
    // move
  }
}

public class Bird extends Animal {
  public void move() {
    return this.fly();
  }
  public void fly() {
    // Fly high!
  }
}

public class Ostrich extends Bird {
  public void move() {
    return this.run();
  }
  
  public void run() {
    // Run fast!
  }
}
```
こうすることで、AnimalHabitsクラスの中でつくったインスタンスがBirdクラスのものであろうとOstrichクラスのものであろうと、moveメソッドは問題なく呼び出されます。

```java
public class AnimalHabits {
  public static void main(String[] args) {
    Animal first = new Bird();
    Animal second = new Bird();
    Animal third = new Ostrich();
  }

  List<Animal> myAnimals = new ArrayList<>();
  myAnimals.add(first);
  myAnimals.add(second);
  myAnimals.add(third);

  for(Animal animal: myAnimals) {
    System.out.println(animal.move());
  }
}
```
これで原則を満たすような設計になりました。

#おわりに
むやみやたらと継承をつかうと痛い目をみる可能性があるという話ですね。今後気をつけます。

#参考書籍・動画
<a href="https://www.amazon.co.jp/dp/4048930656/ref=cm_sw_em_r_mt_dp_VTH6KQ7525WWPDZX78BY">Crean Architecture</a>　Robert.C.Martin
<a href="https://www.udemy.com/share/101KLUCEEZd11VRno=/">SOLID Principles: Introducing Software Architecture & Design</a>　Sujith George