5分で理解するオープン・クローズドの原則


[この記事はエイチーム引越し侍 / エイチームコネクトの社員による、Ateam Hikkoshi samurai Inc.× Ateam Connect Inc. Advent Calendar 2020](https://qiita.com/advent-calendar/2020/hikkoshizamurai-connect) 13日目の記事です。２記事めの投稿！
# オープン・クローズドの原則

この記事のゴール

- 10分で、オープン・クローズドの原則（OCP）の概要を理解する

--- 
# 前提：なぜ設計やアーキテクチャを考える必要があるのか

![Screenshot 2020-12-23 13.11.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261575/fa4927af-4647-e667-bd71-181767365c3e.png)

システムに求められるニーズを満たすために、必要な労力をできるだけ少なくしなければならない

---
# オブジェクト指向設計（SOLID）について
Robert C. Martinによって作り出された、オブジェクト指向プログラミングにおける５つのガイドライン。

- SRP　単一責任の原則
- OCP　オープン・クローズドの原則
- LSP　リスコフの置換原則
- ISP　インターフェイス分離の原則
- DIP　依存関係逆転の原則

それぞれの頭文字を一文字ずつとって、SOLID原則とも呼ばれます。

---
## オープン・クローズドの原則とは
「ソフトウェアの構成要素(クラス、モジュール、関数など)は拡張に対しては開いていて、修正に対して閉じていなければならない。」

(Bertrand Meyer. *Object Oriented Software Construction, Printice Hallm 1988*m p.23.)

---

## つまりどういうこと？
変更が発生した場合に、**既存のコードには修正を加えずに、新しくコードを追加する**だけで対応できるような設計にしましょう！

ということ。

---

# どのような具体例があるの？
## オープン・クローズドの原則に則らず設計すると...?
### Badなクラス😑
```php
<?php

/**
 * AreaCalcService
 * 面積を計算するクラス
 */
class AreaCalcService
{
  private $length;

  public function __construct(int $length)
  {
    $this->length = $length;
  }

  /**
   * call
   * $lengthを二乗した値を返す
   * @return integer
   */
  public function call(): int
  {
    return $this->length ** 2;
  }
}
```
---
### なぜBadなのか
- **円の面積など、新しいケースを計算できない**。
  - 「面積を計算するクラス」という役割に相応しくない

- 正方形以外の面積を計算できるようにするために、**改修コストがかかる。**
  - 既存コードを読む、書き直す... という作業が発生する

---

### Case: 👦<円の面積を計算したいです！
```php
<?php

/**
 * AreaCalcService
 * 面積を計算するクラス
 */
class AreaCalcService
{
  private $length;

  public function __construct(int $length, string $shape)
  {
    $this->length = $length;
    $this->shape = $shape;
  }

  /**
   * call
   * @return integer
   */
  public function call(): int
  {
    if ($this->shape === 'square') {
      return $this->length ** 2;
    } elseif ($this->shape === 'circle') {
      return $this->length ** 2 * 3.14;
    } else {
      return $this->length;
    }
  }
}

```
対応ケースが増えるごとに条件文が増えてしまい、AreaCalcServiceが肥大化してしまう😑

---

## オープン・クローズドの原則にのっとって設計してみる🧑🏻‍💻
```php
<?php

// 正方形
class Square
{
  public $length;

  public function __construct(int $length)
  {
    $this->length = $length;
  }

  public function area(): int
  {
    return $this->length ** 2;
  }
}

class AreaCalcService
{
  private $shape;

  public function __construct(object $shape)
  {
    $this->shape = $shape;
  }

  public function call(): mixed
  {
    return $this->shape->area();
  }
}

```

---
### Case: 👦<円の面積を計算したいです！

```php
<?php

class Square
{
  public $length;

  public function __construct(int $length)
  {
    $this->length = $length;
  }
  
  public function area(): int
  {
    return $this->length ** 2;
  }
}

// 円のクラスを新しく定義
class Circle
{
  public $length;

  public function __construct(int $length)
  {
    $this->length = $length;
  }

  public function area(): float
  {
    return $this->length ** 2 * 3.14;
  }
}

// ↓のクラスは変更しなくてOK！
class AreaCalcService
{
  private $shape;

  public function __construct(object $shape)
  {
    $this->shape = $shape;
  }

  public function call(): mixed
  {
    return $this->shape->area();
  }
}
```
このように記述することで、Circleクラスを定義するだけで改修がOK！

（追記：このままだと長方形などのパターンに対応できないので、例としてよろしくない設計です..インターフェースを使うとかが良いかもです）

---

# まとめ
- システムに求められるニーズを満たすために、必要な労力をできるだけ少なくしなければならない。

- オープン・クローズドの原則にのっとってクラス設計を行うことによって、リファクタ時に労力が少なくなる🥂

- この原則をすぐに既存コードに応用！は難しいかもしれないが、新しくクラスを作る際や、メソッド作成時に頭の片隅においておくと👍

## 次回予告
[Ateam Hikkoshi samurai Inc.× Ateam Connect Inc. Advent Calendar 2020](https://qiita.com/advent-calendar/2020/hikkoshizamurai-connect) 13日目の記事でした！（めちゃめちゃ遅れての投稿になってしまいました:qiitan-cry:）

14日目のアドベントカレンダー記事は、尊敬する先輩エンジニア、@anneauさんです！