SOLID クラスの原則 SOLID



1. S: 単一責任原則 (SRP)

2. O: オープン/クローズ原則 (OCP)

3. L: リスコフ置換原則 (LSP)

4. I: インターフェース分離原則 (ISP)

##5. D: 依存性反転原則 (DIP)

SOLID

SOLID は覚えやすい頭字語です。 Michael Feathers によって推奨されており、Robert Martin によって命名された 5 つの最も重要なオブジェクト指向コーディング設計原則を表しています

    #S: 単一責任原則 (SRP)
  • O: 開閉原理 (OCP)
  • L: リスコフ置換原理 (LSP)
  • I: インターフェース分離原理 ( ISP)
  • D: 依存性反転原則 (DIP)
#1. 単一責任原則

単一責任原則 (SRP)

クリーン コードで述べられているように、「クラスの変更は理由がある場合にのみ行うべきです」。飛行機にスーツケースを 1 つしか持ち込むことができない (スーツケースにすべてを詰め込む) のと同じように、クラスに多数のメソッドを詰め込むのは常に簡単です。これの問題は、このようなクラスは概念的に凝集性が低く、変更する理由がたくさん残っていることです。クラスを変更する必要がある回数を最小限に抑えることが重要です。これは、クラス内に多数のメソッドがある場合、そのうちの 1 つを変更すると、コード ベース内のどの依存モジュールが影響を受けるかを知ることが困難になるためです。

悪い:

class UserSettings
{
    private $user;
 
    public function __construct(User $user)
    {
        $this->user = $user;
    }
 
    public function changeSettings(array $settings): void
    {
        if ($this->verifyCredentials()) {
            // ...
        }
    }
 
    private function verifyCredentials(): bool
    {
        // ...
    }
}
良い:

class UserAuth
{
    private $user;
 
    public function __construct(User $user)
    {
        $this->user = $user;
    }
    
    public function verifyCredentials(): bool
    {
        // ...
    }
}
 
class UserSettings
{
    private $user;
    private $auth;
 
    public function __construct(User $user)
    {
        $this->user = $user;
        $this->auth = new UserAuth($user);
    }
 
    public function changeSettings(array $settings): void
    {
        if ($this->auth->verifyCredentials()) {
            // ...
        }
    }
}

2. 開閉の原則


オープン/クローズ原則 (OCP)

Bertrand Meyer が述べたように、「ソフトウェアの成果物 ( クラス、モジュール、関数

など) .) 拡張の場合はオープン、変更の場合はクローズする必要があります。」 しかし、これは何を意味するのでしょうか?この原則は一般に、既存のコードを変更せずに新しい機能を追加できるようにする必要があることを意味します。

Bad:

abstract class Adapter
{
    protected $name;
 
    public function getName(): string
    {
        return $this->name;
    }
}
 
class AjaxAdapter extends Adapter
{
    public function __construct()
    {
        parent::__construct();
 
        $this->name = 'ajaxAdapter';
    }
}
 
class NodeAdapter extends Adapter
{
    public function __construct()
    {
        parent::__construct();
 
        $this->name = 'nodeAdapter';
    }
}
 
class HttpRequester
{
    private $adapter;
 
    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }
 
    public function fetch(string $url): Promise
    {
        $adapterName = $this->adapter->getName();
 
        if ($adapterName === 'ajaxAdapter') {
            return $this->makeAjaxCall($url);
        } elseif ($adapterName === 'httpNodeAdapter') {
            return $this->makeHttpCall($url);
        }
    }
 
    private function makeAjaxCall(string $url): Promise
    {
        // request and return promise
    }
 
    private function makeHttpCall(string $url): Promise
    {
        // request and return promise
    }
}
Good:

interface Adapter
{
    public function request(string $url): Promise;
}
 
class AjaxAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}
 
class NodeAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}
 
class HttpRequester
{
    private $adapter;
 
    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }
 
    public function fetch(string $url): Promise
    {
        return $this->adapter->request($url);
    }
}

##3. リスコフ置換原理

リスコフ置換原理 (LSP)

これは単純な原理ですが、わかりにくい用語が使われています。その正式な定義は、「S が T のサブタイプである場合、プログラムの元の確立されたプロパティ (タスクのチェック、実行など) を変更することなく、型 T のオブジェクトを型 S のオブジェクトに置き換えることができます (たとえば、 S のオブジェクトを使用すると、T のオブジェクトを置き換えることができます)」 この定義は理解するのがさらに困難です :-)。

この概念を最もよく説明すると、親クラスと子クラスがある場合、元の結果の正確さを変えることなく、親クラスと子クラスを交換できるということです。これでもまだ少しわかりにくいように聞こえるので、古典的な正方形と長方形の例を見てみましょう。数学的には、正方形は長方形ですが、モデルが継承を通じて「is-a」関係を使用している場合、これは当てはまりません。

悪い:

class Rectangle
{
    protected $width = 0;
    protected $height = 0;
 
    public function setWidth(int $width): void
    {
        $this->width = $width;
    }
 
    public function setHeight(int $height): void
    {
        $this->height = $height;
    }
 
    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}
 
class Square extends Rectangle
{
    public function setWidth(int $width): void
    {
        $this->width = $this->height = $width;
    }
 
    public function setHeight(int $height): void
    {
        $this->width = $this->height = $height;
    }
}
 
function printArea(Rectangle $rectangle): void
{
    $rectangle->setWidth(4);
    $rectangle->setHeight(5);
 
    // BAD: Will return 25 for Square. Should be 20.
    echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()).PHP_EOL;
}
 
$rectangles = [new Rectangle(), new Square()];
 
foreach ($rectangles as $rectangle) {
    printArea($rectangle);
}

良い:

これら 2 種類の四角形を別々に扱い、適切な方を使用するのが最善です。代わりに、タイプのより一般的なサブタイプ。

正方形と長方形は似ていますが、異なります。正方形はひし形に近く、長方形は平行四辺形に近くなります。しかし、それらはサブタイプではありません。正方形、長方形、ひし形、平行四辺形は似ていますが、すべて異なる形状であり、それぞれ独自の特性があります。

interface Shape
{
    public function getArea(): int;
}
 
class Rectangle implements Shape
{
    private $width = 0;
    private $height = 0;
 
    public function __construct(int $width, int $height)
    {
        $this->width = $width;
        $this->height = $height;
    }
 
    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}
 
class Square implements Shape
{
    private $length = 0;
 
    public function __construct(int $length)
    {
        $this->length = $length;
    }
 
    public function getArea(): int
    {
        return $this->length ** 2;
    }
}
 
function printArea(Shape $shape): void
{
    echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL;
}
 
$shapes = [new Rectangle(4, 5), new Square(5)];
 
foreach ($shapes as $shape) {
    printArea($shape);
}

4. インターフェイス分離の原則

インターフェイス分離の原則 (ISP)

インターフェイス分離の原則とは、「呼び出し元」を意味します。必要のないインターフェイスに依存することを強制されるべきではありません。」

この原則を説明する明確な例があります。クラスで多数の設定が必要な場合、通常はすべての設定が必要ないため、呼び出し元に多数のオプションの設定を要求しない方が便利です。設定をオプションにすると、「ファット インターフェイス」の作成を回避できます。

悪い例:

interface Employee
{
    public function work(): void;
 
    public function eat(): void;
}
 
class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....working
    }
 
    public function eat(): void
    {
        // ...... eating in lunch break
    }
}
 
class RobotEmployee implements Employee
{
    public function work(): void
    {
        //.... working much more
    }
 
    public function eat(): void
    {
        //.... robot can't eat, but it must implement this method
    }
}

良い例:

すべてのワーカーがそうであるわけではありません。従業員ですが、すべての従業員は労働者です

interface Workable
{
    public function work(): void;
}
 
interface Feedable
{
    public function eat(): void;
}
 
interface Employee extends Feedable, Workable
{
}
 
class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....working
    }
 
    public function eat(): void
    {
        //.... eating in lunch break
    }
}
 
// robot can only work
class RobotEmployee implements Workable
{
    public function work(): void
    {
        // ....working
    }
}

5. 依存関係逆転の原則

依存関係逆転の原則 (DIP)

この原則は 2 つの基本的な点を示しています:

高レベルのモジュールは低レベルのモジュールに依存すべきではなく、すべて抽象化に依存する必要があります

抽象化は実装に依存すべきではなく、実装は依存する必要があります抽象化について

これは最初は少しわかりにくいように思えるかもしれませんが、PHP フレームワーク (Symfony など) を使用したことがある場合は、この概念の実装である依存関係注入 (DI) を見たことがあるはずです。これらは厳密に同等の概念ではありませんが、依存関係逆転の原則により、実装の詳細と上位モジュールの作成が下位モジュールから分離されます。これは、依存関係注入 (DI) を使用して実現できます。最大の利点は、モジュールを相互に分離できることです。結合はリファクタリングを困難にし、非常に悪い開発モデルです。

悪い:

class Employee
{
    public function work(): void
    {
        // ....working
    }
}
 
class Robot extends Employee
{
    public function work(): void
    {
        //.... working much more
    }
}
 
class Manager
{
    private $employee;
 
    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }
 
    public function manage(): void
    {
        $this->employee->work();
    }
}

良い:

interface Employee
{
    public function work(): void;
}
 
class Human implements Employee
{
    public function work(): void
    {
        // ....working
    }
}
 
class Robot implements Employee
{
    public function work(): void
    {
        //.... working much more
    }
}
 
class Manager
{
    private $employee;
 
    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }
 
    public function manage(): void
    {
        $this->employee->work();
    }
}