開放閉鎖原則についてのまとめ

はじめに

対象読者は初心者です。

開放閉鎖原則とは

定義

Robert C.Martinの著書「アジャイルソフトウェア開発の奥義」では、

ソフトウェアの構成要素(クラス、モジュール、関数など)は拡張に対して開いていて、修正に対して閉じていなければならない

と説明されています。

「拡張に対して開いている」とは、雑に言い換えると「機能追加がしやすい」です。

「修正に対して閉じている」とは、雑に言い換えると「変更が他の場所に影響を及ぼさない」です。

厳密ではないけどわかりやすいであろう言い方としては、「クラスや関数は機能追加しやすく、変更が他の場所に影響を及ぼさない作りになっているべき」です。

開放閉鎖原則の具体例として、自分が考えたものは、くじ引きです。

一般くじとプレミアムくじというものがあり、それぞれが違う抽選ロジックを持っているとします。

開放閉鎖原則に従って実装したくじ引きのコードは、下記のようになります(インターフェース(publicなメソッド)部分が重要なので細かい部分は除いています)。

class GeneralLotteryLogic # 一般くじの抽選ロジック
  def initialize(params)
    @params = params
  end
  
  def exec
    # 抽選ロジックの実装がある
  end
end

class PremiumLotteryLogic # プレミアムくじの抽選ロジック
  def initialize(params)
    @params = params
  end
  
  def exec
    # プレミアム抽選ロジックの実装がある
  end
end

class Lottery # 実際にくじ引きを行うクラス
  def initialize(logic)
    @logic = logic
  end

  def draw(count)
    raise ArgumentError.new('count must be positive integer') if count <= 0 
    Array.new(count).map do
      @logic.exec
    end
  end
end
# それぞれどこかで下記のような形で使用されているとする
lottery = Lottery.new(GeneralLotteryLogic.new({})) # 一般くじのロジックを注入
lottery.draw(10) # 一般くじを引く

premium_lottery = Lottery.new(PremiumLotteryLogic.new({})) # プレミアムくじのロジックを注入
premium_lottery.draw(10) # プレミアムくじを引く

なぜ、上記のコードが開放閉鎖原則に従っていると言えるのでしょうか?

要件の追加やバグの修正の例を用いて説明します。

新たに要件が追加され、スーパープレミアムくじを追加することになりました。異なっているのは抽選ロジックの部分のみのため、新たにSuperPremiumLotteryLogicクラスを実装しました。

class SuperPremiumLotteryLogic # スーパープレミアム抽選ロジック
  def initialize(params)
    @params = params
  end
  
  def exec
    # スーパープレミアム抽選ロジックの実装がある
  end
end

# どこかで下記のような形で使われている
super_premium_lottery = Lottery.new(SuperPremiumLotteryLogic.new({})) # スーパープレミアムくじのロジックを注入
super_premium_lottery.draw(10) # スーパープレミアムくじを引く

このとき、既存のコードには一切変更を加えずに、新機能を追加できました。
これが、「拡張に対して開いている」ということです。

機能実装後、スーパープレミアムくじのテストを行っている際に、バグが発見されたため、SuperPremiumLotteryLogicを修正しました。
このとき、既存の他のコードに一切影響を及ぼさずに修正を行うことができました。
GeneralLotteryLogicでもPremiumLotteryLogicでも同様に問題が発生して修正することになっても(execの引数と戻り値が変わらない限り)他に影響を及ぼしません。
これが、「修正に対して閉じている」ということです。

なぜ開放閉鎖原則を守るべきなのか

当たり前のことなのですが、そもそも要求が変わるからです。

すべてのプログラムが人の要求を満たすために作られています。そして、人の要求は変わります。

要求が変わったときには、機能の追加や変更が必要になります。そのとき、いかに容易に安全にできるかがこの原則を守っているかどうかで変わってきます。

どうすれば開放閉鎖原則を満たすように作れるのか

自分は、下記3つを押さえることが大事だと考えてます。

  • 変更されやすい部分を見抜く
  • 変更発生時に追加する
  • 原則を知る

変更されやすい部分を見抜く

作るものにもよると思いますが、似たようなビジネスを他が行っていたり、抽象化して捉えたときに同じようなものが存在しているときには、変更されやすい部分がわかります。

上記で例として上げたくじ引きはわかりやすいと思います。よくソシャゲなどで見られるガチャやくじといったものにはパターンが存在しているので、そういったものから考えて変更されやすそうだという当たりをつけることができます。

* ただの勘ではなく、分析した結果として適用する必要があります。Rober C. Martinは「アジャイルソフトウェア開発の奥義」の中で、「OCPの適用は起こる可能性の高い変更に限定すべきなのは明らかだ。」と述べています(詳しくは著書をお読みください)。

変更発生時に追加する

全ての「変更されやすい箇所を見抜く」ことは困難なので、変更が発生されてから抽象を追加して、開放閉鎖原則に従うようにするというのもありだと思います。
ただし、ちゃんとテストコードが書かれていたり、強い静的型付けに頼れる状態であったりしないと厳しいと思います。

原則を知る

今回、開放閉鎖原則に従っている例として上げたコードは、デザインパターンで言うところのストラテジーパターンに該当します。
そういったデザインパターンについて知っておいたり、抽象(インターフェース)に依存するように作るということを知っておくと開放閉鎖原則に従ったコードが書けるようになると思います。

終わりに

今回はSOLIDのO(Open Closed Principle)である開放閉鎖原則についてまとめてみました。

「アジャイルソフトウェア開発の奥義」も「Clean Architecture」もおすすめなので、ぜひ読んでみてください。

(自分の書いたコード含めて)人を不幸にしないコードが普及することを願う。

参考

  • アジャイルソフトウェア開発の奥義 Robet C. Martin
  • Clean Architecture Robert C. Martin

コメント

  1. Top 10 Casinos in Washington - MapYRO
    › › 밀양 출장샵 Washington D › Washington D Washington D Casinos · Casinos: 34 Casinos 속초 출장안마 & Hotels in Washington · D.C. Casinos: 6 Tribes · Casinos: 충주 출장안마 9 Tribes · Casinos: 문경 출장샵 6 안양 출장샵 Tribes Casino · D.C. Casinos:

    返信削除

コメントを投稿

このブログの人気の投稿

RubyではテストしやすくするためにDIを使わない

文字列が適切か考える

単一責任の原則についてのまとめ