Madogiwa Blog

主に技術系の学習メモに使っていきます。

Rubyで学オブジェクト指向 SOLID原則「リスコフの置換原則(Liskov substitution principle)」

Clean Architecture 達人に学ぶソフトウェアの構造と設計を読んで、オブジェクト指向の原則について学びがあったので、Rubyのコード例と共に内容を整理してみました。

Clean Architecture 達人に学ぶソフトウェアの構造と設計

Clean Architecture 達人に学ぶソフトウェアの構造と設計

※私の学びのメモなので理解が間違ってる可能性があります、間違ってたらすいません🙇‍♂️

リスコフの置換原則(Liskov substitution principle)とは?

プログラムの中にある任意のオブジェクトは、プログラムの正しさを変化させることなく、そのサブクラスのオブジェクトと置換できなければならない。 https://ja.wikipedia.org/wiki/SOLID

リスコフの置換原則は、サブクラスはスーパークラスの期待する要件を満たし、サブクラスのオブジェクトはスーパークラスのオブジェクトとしても振る舞えるように設計すべきという原則ですね。

単一責任の原則の例とコード

リスコフの置換原則を意識できていないコードと例

例えば下記のような、社員を扱うClass群があったとします。社員全体に関連するEmployee、それを継承した管理者用とメンバ用のClassがそれぞれあるという構成です。

class Employee
  def initialize(attributes)
    # build employeer object.
  end

  def work
    # do work.
  end
end

class Manager < Employee
  def initialize(attributes)
    super
    @subordinates = attributes[:subordinates]
    # build employeer object.
  end

  def manage
  end
end

class Member < Employee
  def initialize(attributes)
    super
    @manager = attributes[:manager]
    # build employeer object.
  end

  def report
  end
end

このようなClass群に対して、「メンバは正社員とは限らないのでパートの場合は別の振る舞いをさせたい」という仕様変更が入ったのでMemberを変更しました。

class Member < Employee
  def initialize(attributes)
    super
    @manager = attributes[:manager]
    @temporary = attributes[:temporary]
    # build employeer object.
  end

  def work
    if temporary
      super
    else
      temporary_work # diffarent progress...
    end
  end

  def report
  end
end

一見良さそうに見えますが、Employeeとして振る舞うことが期待されているMembertemporaryの値によっては全く違う挙動をするようになっていました。 この状態ではEmployeeのサブクラスでもtemporarytrueのものに関しては除外するといった処理がアプリケーション内の様々なところで発生して、修正漏れや考慮漏れを誘発しそうです。。。

このような状態がリスコフの置換原則が守られていない状態と言えそうです。

リスコフの置換原則を意識してリファクタリングしてみる

全く違う挙動をするのであれば、そもそも別物だと思いますのでEmployeeを継承せずに別のClassとして実装してあげたほうが良さそうですね。

class PartTimer
  def initialize(attributes)
    # build TemporaryMember object
  end

  def work
    # diffarent progress...
  end
end

また、あくまでEmployeeのサブクラスとして扱いたい場合はworkメソッドはサブクラスに実装するものとして、Employeeのサブクラスクラスは同様の振る舞いをするということは期待せずに設計を見直して上げたほうがよさそうです。

class Employee
  def initialize(attributes)
    # build employeer object.
  end
end

class Manager < Employee
  def initialize(attributes)
    super
    @subordinates = attributes[:subordinates]
    # build employeer object.
  end

  def work
  end

  def manage
  end
end

class Member < Employee
  def initialize(attributes)
    super
    @manager = attributes[:manager]
    @temporary = attributes[:temporary]
    # build employeer object.
  end

  def work
  end

  def report
  end
end

class PartTimer < Employee
  def initialize(attributes)
    super
    @manager = attributes[:manager]
    @temporary = attributes[:temporary]
    # build employeer object.
  end

  def work
  end

  def report
  end
end

サブクラスと定義している以上、スーパークラスに期待されている振る舞いは満たした上で拡張するようにしてあげると、ポリモーフィックにきれいに書けたりとメリットが非常に大きそうですね🙌

おわりに

今回はオブジェクト指向のSLID原則「リスコフの置換原則(Liskov substitution principle)」について自分の理解をRubyのコード例とともに説明してみました。

同様の振る舞いを期待しているはずなのにif文等でイレギュラーな処理をいれていくと、リスコフの置換原則に違反し、どんどんサブクラスとスーパークラスの関係が崩れていって、ぐちゃぐちゃになってしまいそうなので注意ですね💦

次回は「インターフェイス分離の原則(Interface segregation principle)」について書いてみようかと思います 🙇‍♂️

参考

Clean Architecture 達人に学ぶソフトウェアの構造と設計

Clean Architecture 達人に学ぶソフトウェアの構造と設計

qiita.com