RubyでSOLID原則を満たしたコードを書いてみる

Engineering
この記事を書いた人

PharmaXというオンライン薬局のスタートアップで薬剤師・エンジニアとして働いています。Rails・React・TypeScriptなどを書きます。英語が得意でTOEIC900点・通訳案内士資格取得。主に薬剤師の働き方やプログラミング、英語学習について書きます。当サイトではアフィリエイトプログラムを利用して商品を紹介しています。
>> 詳しいプロフィール

Tomoyuki Katoをフォローする

 

SOLID原則とは

 

SOLIDとは、オブジェクト指向設計の5つの基本的な原則を指す。

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

 

これらの原則は、Robert C. Martinによって提唱され、

  • プログラムの可読性
  • 再利用性
  • 保守性
  • の向上を目的としています。

の向上を目的としている。

 

単一責任の原則

 

一つのクラスは、一つの責任だけを持つべきという原則。

つまり、特定の責任に関連する変更を行う際に、その変更が他の部分に意図しない影響を及ぼす可能性が低くなるということだ。

 

NG:単一責任の原則を満たしていないコード

以下のコードは単一責任の原則を満たしていない。

なぜかというと、経理部と人事部で使われるロジックが1つのクラスに書かれているからだ。

 

仮に、経理部だけ労働時間を取得するロジック(get_working_timeメソッド)を修正したいとなった場合、人事部で使っているcalculate_salaryメソッドはバグってしまう。

 

class Employee
  attr_reader :name, :department

  def initialize(name, department)
    @name = name
    @department = department
  end

  # 経理部で使われるメソッド
    # 給与の計算をする
  def calculate_salary
    salary = get_working_time * 時給
    
    puts "#{@name}の給与は#{salary}円です"
  end

  # 人事部で使われるメソッド
  # 労働時間を計算する
  def report_working_time
    puts "#{@name}の労働時間は#{get_working_time}です"
  end

  private

  def get_working_time
    # 労働時間の計算をするロジック
  end
end

 

GOOD:単一責任の原則を満たしているコード

SalaryCalculatorクラスは給与の計算に関する責任を持ち、WorkingTimeReporterクラスは労働時間の報告に関する責任を持つ。

これにより、各クラスが一つの責任だけを持つことになった。

class Employee
  attr_reader :name, :department

  def initialize(name, department)
    @name = name
    @department = department
  end
end

class SalaryCalculator
  def initialize(employee)
    @employee = employee
  end

  def calculate_salary
    salary = get_working_time * 時給
    puts "#{@employee.name}の給与は#{salary}円です"
  end

  private

  def get_working_time
    # 人事部で使う労働時間の計算をするロジック
  end
end

class WorkingTimeReporter
  def initialize(employee)
    @employee = employee
  end

  def report_working_time
    puts "#{@employee.name}の労働時間は#{get_working_time}時間です"
  end

  private

  def get_working_time
    # 経理部で使う労働時間の計算をするロジック
  end
end

employee = Employee.new('kato', 'engineering')
salary_calculator = SalaryCalculator.new(employee)
working_time_reporter = WorkingTimeReporter.new(employee)

salary_calculator.calculate_salary
working_time_reporter.report_working_time

 

オープンクローズドの原則

 

クラスは拡張に対して開かれていて、変更に対して閉じているべき。

つまり、既存コードの変更なしに新しい機能や振る舞いを追加できるように設計すべきということ。

 

 

 

リスコフの置換原則

 

サブクラスは、スーパークラスと置換可能であるべき。

つまり、スーパークラスをサブクラスに置換したとしても、プログラムの振る舞いが正しく保たれるべきということ。

 

サブクラスをスーパークラスに置き換えてもプログラムが正常に動作することが期待されるため、コードの挙動が予測可能になり、バグの発生リスクを低減できる。

 

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

クラスは不必要なインターフェースを持つべきではない。

つまり、クラスが実際に使用しないメソッドを実装する必要がないということを意味する。

 

最小限のインターフェースのみを持つことで、クラスの役割と責任が明確になり、システムの変更や拡張が容易になることがメリット。

 

依存関係逆転の原則

高レベルのモジュールは、低レベルのモジュールに依存すべきではなく、両方とも抽象に依存すべき。

具体的な実装ではなく、抽象に依存することで、モジュール間の結合度を低く保ち、再利用性や拡張性を向上させることができる