”SOLID原則”って何?これは5つの設計原則の頭文字!

プログラミング
スポンサーリンク

今回はSOLID原則について紹介していきます。

他の原則、法則についてはこちらのページから!
プログラミングの原則とは?知っておくべき原則5選!
スポンサーリンク

SOLID原則とは?

SOLIDは5つの設計原則の頭文字を取ったものです。オブジェクト指向プログラミングと設計における5つの基本原則のことを指します。

  • S: 単一責任の原則 (Single Responsibility Principle)
  • O: 開放/閉鎖の原則 (Open/Closed Principle)
  • L: リスコフの置換原則 (Liskov Substitution Principle)
  • I: インターフェース分離の原則 (Interface Segregation Principle)
  • D: 依存関係逆転の原則 (Dependency Inversion Principle)

以下に、それぞれの原則について詳しく説明します。

S: 単一責任の原則 (SRP)

  • 説明: クラスは一つの責任だけを持つべきであり、その責任を全うするために必要なすべての機能を持つべきです。
  • 目的: クラスの変更理由を一つに限定することで、コードの保守性と理解しやすさを向上させます。
  • : ユーザー管理クラスがユーザーのデータベース操作とユーザーインターフェースのロジックを同時に持つのではなく、これらを別々のクラスに分ける。

この原則を飲食店の例で簡潔に示すと

料理を作る人、配膳する人、皿を洗う人に別れた方が、それぞれが果たすべき役割がわかりやすいよね!

といった感じです。

具体例: ユーザー管理システム

    • 違反例Userクラスがユーザーのデータベース操作、認証、メール送信のすべてを担当している。
      class User:
          def save_to_db(self):
              # データベースにユーザーを保存する
          def authenticate(self):
              # ユーザーを認証する
          def send_email(self):
              # ユーザーにメールを送信する
      
    • 改善例: 各責任を別々のクラスに分ける。
      class User:
          def save_to_db(self):
              # データベースにユーザーを保存する
      
      class Authenticator:
          def authenticate(self, user):
              # ユーザーを認証する
      
      class EmailSender:
          def send_email(self, user):
              # ユーザーにメールを送信する

O:オープン/クローズドの原則 (OCP)

  • 説明: ソフトウェアのエンティティ(クラス、モジュール、関数など)は拡張に対して開かれているべきであり、修正に対して閉じられているべきです。
  • 目的: 既存のコードを変更せずに、新しい機能を追加できるようにすることで、コードの安定性と再利用性を高めます。
  • : 新しい機能を追加する際に、既存のクラスを変更するのではなく、新しいクラスを継承して機能を追加する。

この原則を先ほどの例の続きで簡潔に示すと

皿を洗う人はレジ対応もできそうだよね!お願い!

といった感じです。

具体例: 支払いシステム

    • 違反例: 支払い方法を追加するたびにPaymentProcessorクラスを変更する必要がある。
      class PaymentProcessor:
          def process_payment(self, payment_type):
              if payment_type == "credit_card":
                  # クレジットカードで支払いを処理する
              elif payment_type == "paypal":
                  # PayPalで支払いを処理する
      
    • 改善例: 新しい支払い方法を追加する際に既存のコードを変更せずに済むようにする。
      class PaymentProcessor:
          def process_payment(self, payment_method):
              payment_method.process()
      
      class CreditCardPayment:
          def process(self):
              # クレジットカードで支払いを処理する
      
      class PayPalPayment:
          def process(self):
              # PayPalで支払いを処理する

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

  • 説明: 派生クラスは、その基底クラスと置き換え可能でなければなりません。つまり、基底クラスのオブジェクトが期待される場所で、派生クラスのオブジェクトも問題なく動作するべきです。

こちらの原則は理解しづらいと思うので端的に言うと、派生したクラスを派生元と入れ替えても問題なく動くべき!といった感じです。

  • 目的: 継承関係における一貫性を保ち、コードの予測可能性と信頼性を向上させます。
  • : 基底クラスのメソッドをオーバーライドする際に、元のメソッドの契約(入力、出力、例外処理など)を破らないようにする。

この原則を先ほどの例の続きで簡潔に示すと

もし洗う皿が増えてきたら、皿洗いもできる君にレジ対応任せる!

といった感じです。

具体例: 図形クラス

  • 違反例Rectangleクラスを継承したSquareクラスが、Rectangleクラスの期待を破る。
    Square クラスが Rectangle クラスを継承し、幅と高さをそれぞれ設定するメソッドがオーバーライドされている。

    class Rectangle:
        def set_width(self, width):
            self.width = width
        def set_height(self, height):
            self.height = height
    
    class Square(Rectangle):
        def set_width(self, width):
            self.width = width
            self.height = width
        def set_height(self, height):
            self.width = height
            self.height = height
    
  • 改善例RectangleSquareを別々のクラスにし、共通のインターフェースを持たせる。
    class Shape:
        def area(self):
            pass
    
    class Rectangle(Shape):
        def __init__(self, width, height):
            self.width = width
            self.height = height
        def area(self):
            return self.width * self.height
    
    class Square(Shape):
        def __init__(self, side):
            self.side = side
        def area(self):
            return self.side * self.side

以下は、改善例のポイントです。

  1. 共通のインターフェース: Shape クラスを作成して、area メソッドを定義することで、今後異なる形状を追加する際にも容易に対応できます。

  2. それぞれの特性の明確化: RectangleSquare がそれぞれ独自の属性を持ち、その特性に基づいて動作します。これにより、オブジェクト指向の設計におけるクラスの役割が明確に保たれます。

  3. テストの簡略化: 各クラスが独立しているため、それぞれ個別にテストが可能であり、問題の切り分けが容易になります。

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

  • 説明: クライアントは、使用しないメソッドに依存してはならない。大きなインターフェースを小さな特化したインターフェースに分割するべきです。
  • 目的: クラスが不要なメソッドを実装することを避け、インターフェースの利用を簡素化します。
  • : 一つの大きなインターフェースを、特定の機能に焦点を当てた複数の小さなインターフェースに分割する。

この原則を先ほどの例の続きで簡潔に示すと

君は皿洗いもできるけど、レジ対応に専念してもらった方がいいね!皿洗いはしなくていいよ。

といった感じです。

具体例: プリンタインターフェース

  • 違反例: すべてのプリンタが不要なメソッドを実装しなければならない。
    class Printer:
        def print(self):
            pass
        def scan(self):
            pass
        def fax(self):
            pass
    
  • 改善例: インターフェースを分割し、必要なメソッドだけを実装する。
    class Printer:
        def print(self):
            pass
    
    class Scanner:
        def scan(self):
            pass
    
    class Fax:
        def fax(self):
            pass

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

  • 説明: 高レベルのモジュールは低レベルのモジュールに依存してはならない。両者は抽象に依存すべきである。抽象は詳細に依存してはならない。詳細は抽象に依存すべきである。
  • 目的: モジュール間の依存関係を減らし、システムの柔軟性と保守性を向上させます。
  • : 具体的なクラスに依存するのではなく、インターフェースや抽象クラスに依存するように設計する。

具体例: メッセージ送信システム

  • 違反例: 高レベルのモジュールが低レベルのモジュールに依存している。
    Notification クラスが直接 EmailSender クラスに依存しています。これにより、変更が難しくなり、テストも難しくなる可能性があります。

    class EmailSender:
        def send(self, message):
            # メールを送信する
    
    class Notification:
        def __init__(self):
            self.email_sender = EmailSender()
        def notify(self, message):
            self.email_sender.send(message)
    
  • 改善例: 高レベルのモジュールが抽象に依存するようにする。
    MessageSender という抽象クラスを導入し、その抽象クラスに基づく具体的な実装(EmailSender)が依存する形になっています

    class MessageSender:
        def send(self, message):
            pass
    
    class EmailSender(MessageSender):
        def send(self, message):
            # メールを送信する
    
    class Notification:
        def __init__(self, sender: MessageSender):
            self.sender = sender
        def notify(self, message):
            self.sender.send(message)

この方法にはいくつかの利点があります。

  1. 柔軟性: 他の送信手段(SMS, プッシュ通知など)を追加するのが簡単になります。新たな送信方式は MessageSender を継承し、notify メソッドを変更するだけで済みます。

  2. テストの容易さ: モックやスタブを使用して、MessageSender を模擬したサブクラスを作成することで、ユニットテストが容易になります。これにより、Notification クラスをテストする際に実際のメール送信を行わずに済みます。

  3. 単一責任の原則の遵守: Notification クラスは通知の管理だけを担当し、送信方法の具体的な実装には関与しません。

まとめ

今回のポイントをまとめます。

  • S: 単一責任の原則 (Single Responsibility Principle)
  • O: 開放/閉鎖の原則 (Open/Closed Principle)
  • L: リスコフの置換原則 (Liskov Substitution Principle)
  • I: インターフェース分離の原則 (Interface Segregation Principle)
  • D: 依存関係逆転の原則 (Dependency Inversion Principle

SOLID原則は、ソフトウェア設計の品質を高めるための強力なツールです。これらの原則を適用することで、コードの保守性、再利用性、拡張性が向上し、より堅牢なシステムを構築することができます。

・他原則についても知りたい方はこちら

間違いがあればご指摘いただけると幸いです。

 

スポンサーリンク

コメント

タイトルとURLをコピーしました