この記事はエイチーム引越し侍 / エイチームコネクトの社員による、Ateam Hikkoshi samurai Inc.× Ateam Connect Inc. Advent Calendar 2020 13日目の記事です。2記事めの投稿!
オープン・クローズドの原則
この記事のゴール
- 10分で、オープン・クローズドの原則(OCP)の概要を理解する
前提:なぜ設計やアーキテクチャを考える必要があるのか
システムに求められるニーズを満たすために、必要な労力をできるだけ少なくしなければならない
オブジェクト指向設計(SOLID)について
Robert C. Martinによって作り出された、オブジェクト指向プログラミングにおける5つのガイドライン。
- SRP 単一責任の原則
- OCP オープン・クローズドの原則
- LSP リスコフの置換原則
- ISP インターフェイス分離の原則
- DIP 依存関係逆転の原則
それぞれの頭文字を一文字ずつとって、SOLID原則とも呼ばれます。
オープン・クローズドの原則とは
「ソフトウェアの構成要素(クラス、モジュール、関数など)は拡張に対しては開いていて、修正に対して閉じていなければならない。」
(Bertrand Meyer. Object Oriented Software Construction, Printice Hallm 1988m p.23.)
つまりどういうこと?
変更が発生した場合に、既存のコードには修正を加えずに、新しくコードを追加するだけで対応できるような設計にしましょう!
ということ。
どのような具体例があるの?
オープン・クローズドの原則に則らず設計すると...?
Badなクラス😑
<?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
/**
* 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
// 正方形
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
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 13日目の記事でした!(めちゃめちゃ遅れての投稿になってしまいました)
14日目のアドベントカレンダー記事は、尊敬する先輩エンジニア、@anneauさんです!