読み込み中...

SOLID原則の単一責任原則(SRP)についてRubyで解説

プログラミング #ruby #SOLID #オブジェクト指向 #クリーンコード
2024年5月9日
2024年6月6日
SOLID原則の単一責任原則(SRP)についてRubyで解説

オブジェクト指向プログラミングにおいて、コードの保守性、可読性、再利用性を高めるために、SOLID原則と呼ばれる5つの重要な設計原則があります。

今回は、このSOLID原則のうち、単一責任原則 (Single Responsibility Principle, SRP)についてRubyのサンプルコードを用いて解説していきます。

SOLIDとは

SOLIDとは、5つの原則の頭文字を取った略語です。

  1. Single Responsibility Principle(SRP): 単一責任原則
  2. Open Closed Principle(OCP): オープン・クローズドの原則
  3. Liskov Substitution Principle(LSP): リスコフの置換原則
  4. Interface Separation Principle(ISP): インターフェース分離の原則
  5. Dependency Inversion Principle(DIP): 依存性逆転の原則

単一責任原則 (Single Responsibility Principle, SRP)とは

単一責任原則は、たいへんシンプルで1つのクラスは1つの責務のみを持つべきであるという原則です。

つまり、クラスは1つの機能や目的に特化し、それ以外の責任を持たないようにします。

これにより、機能ごとにクラスを分離することができ、変更を加える際に関係のない箇所へ意図しない影響を与えないようにすることが出来ます。

ここで疑問となるのが、どこまでを一つの責務とすべきか?だと思います。

こちら解説していきます。

責務の定義方法

これはニーズによって定義出来ると考えております。

例えばですが、外部APIを使用してデータを作成・読み込みが出来る以下のクラスがあったとします。

class SampleApiClient
  URL = 'https://example.com/api/v1'.freeze

  def initialize
    # URLを元にを初期化
  end

  def fetch(**args)
    # ...
  end

  def create(**args)
    # ...
  end
end

class SampleUseCase
  def initialize(api_client: SampleApiClient.new)
    @api_client = api_client
  end

  def execute
    @api_client.fetch
    @api_client.create
  end
end

こちら SampleApiClient にはfetchcreateがあり、機能としては2つ存在しています。

これを単一の責務にしなきゃとクラスを分けると、もし、バージョンが変わった場合、利用する側は分けたクラス分だけ変更を強いられます。(URL定数で定義されているURL先のAPIを使用する際、fetchcreateも同じバージョンでないといけないという前提で説明)

この場合は、SampleApiClientの責務を「外部API用のインターフェースを提供」という風に定義し、クラスは分けないほうが扱いやすいです。

今回の例のように、クラスを利用する側がどんな時に「別のクラスや新しいバージョンに交換したいか」や「期待する処理が変わるか」を想像することが大事です。この想像こそが、責務を定義する際に役に立ちます。

責務を分けたほうが良い例

以下のような場合は分けた方が良いです。

なぜなら、下書きと公開処理は分かれていても問題がないのと、将来的に変更が入るのが想像でき、分かれていたほうが影響を考慮しなくて良く変更しやすいです。

class PostOperator
  class << self
    def draft(post, writer)
      # ...
    end

    def publish(post, manager)
      # ...
    end
  end
end

# 下書き作成画面からの呼び出し
PostOperator.draft(post, writer)
# 公開画面からの呼び出し
PostOperator.publish(post, manager)

分けるとしたら以下のようになると思います。

class DraftOperator
  def self.call(post, writer)
    # ...
  end
end

class PublishingOperator
  def self.call(post, manager)
    # ...
  end
end

# 下書き作成画面からの呼び出し
DraftOperator.call(post, writer)

# 公開画面からの呼び出し
PublishingOperator.call(post, manager)

こうすることで、下書きを作成する時の事情が変わったけれど、公開する時は従来通りということが実現できます。

これは今は処理が少ないので、そこまでメリットが感じにくいと思いますが、様々な条件分岐が入って処理が複雑になったり、privateなメソッドが増えてくると、クラスが分かれている方が他の処理への影響を意識しなくて良いため管理がしやすいです。

最後に

これはクラスではなく、メソッド単位でも同じことが言える原則だと思います。

単一責任原則を意識してコーディングすると、一つの責務だけに意識を集中できるため、テストコードを書く際の負担も減り、結果保守性を向上出来ます。

単一責任原則を習得し、保守性の高いシステムを開発していけると良いですね。



システム開発やDX戦略などでお困りの際はお気軽にご相談ください。

※通常1営業日以内に回答致します。

お問い合わせはこちら
Top