1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

クラス設計 Open Closed 〜トレードオフを考えよう〜

Posted at

今回は前回のSingleResponsibilityの続きです。

Open Closed

OpenClosdとは。「変更が発生した際に、なるべく既存のコードを修正せずにコードの追加だけで対応できるようにすべき」という考え方のこと。

つまり、「Open to adding new features & Closed to changing existing codes」ということです。

ここでいう拡張と変更は以下の意味を持ちます。

  • 拡張
    インタスタンス変数やメソッドの追加

  • 変更
    既存のインスタンス変数やメソッドの削除

rubyではOpen Closedを完全無視している

しかしながら、rubyではOpen Closedの原則を無視しています。(むしろ推奨している?)

では、Open Closedを実現可能か?という問いに対しては以下の方法があります。

拡張と変更の両方に閉じる

これはシンプルで以下のように書けば拡張と変更が禁止されます。

クラスに新しいインスタンスメソッドを定義することはできません。また、既存のメソッドを上書きしたり、クラス変数を定義することもできません。

ClassName.freeze

しかし、これではOpenClosedを満たしていません。

では、OpenClosedをどのように満たせるのか?

Rubyでクラスメソッドを追加する方法は3つあります。これらによる変更にOpenClosedを適用させてみましょう。

  1. モジュールをincludeしてクラスにメソッドを定義する方法
  2. モジュールをprependしてクラスにメソッドを定義する方法
  3. メソッドをクラスに直接定義する方法

includeとprependによる変更を禁止する

includeとprependよる変更を禁じることでOpen Closedを可能にできます。
具体的には、includeとprependメソッドをオーバーライドして、「クラスの既存のインスタンスメソッドを上書きするようなインスタンスメソッド」を持つモジュールが渡されたらraiseで例外を発生させる方法です。

class ImmutableModule
#モジュールが別のクラスで呼び出された時に発火
  def self.include(klass)
    raise "#{self} cannot be included in #{klass}."
  end
#モジュールが別のクラスで呼び出された時に発火
  def self.prepend(klass)
    raise "#{self} cannot be prepended to #{klass}."
  end
end

class MyClass
  include ImmutableModule
end

class AnotherClass
  prepend ImmutableModule
end
ImmutableModuleをインクルードしようとすると、例外が発生する
=> ImmutableModule cannot be included in AnotherClass.

ImmutableModuleをプリペンドしようとすると、例外が発生する
=> ImmutableModule cannot be prepended to YetAnotherClass.

補足
self.includeやself.prependメソッドは、ImmutableModuleがinclude(prepend)メソッドを定義しているわけではなく、Module#include(prepend)が呼び出されたときにImmutableModule#include(prepend)メソッドが呼び出される。

直接定義はmethod_addedフック

クラスを直接追加されるのを防ぐ方法はrubyには存在しません。
したがって、次善策を取る必要があり、それが"method_addedフック"です。

method_addedはクラスに定義されるメソッドで、メソッドが定義されたら呼び出されます。
なので、もうすでにメソッドは書かれてしまっているんですが...

しかし、メソッドが定義された時点でエラーを返したり、既に作成したメソッドをオーバーライドするなどすれば、まるでOpenClosedにできるわけですね。

class ImmutableClass
  def self.method_added(name)
    raise "#{self} cannot add new methods"
  end
  
  def foo
    puts "This is a method"
  end
end

class MyClass < ImmutableClass
end

method_addedの例

class X
    # #method_added はクラスメソッドとして定義
    def self.method_added name
        puts "== method_added =="
        puts name
    end

    def method
    end
end
== method_added ==
method

しかし、結論としてはrubyでOpenClosedを求め過ぎてもあまり意味がないような気がします。
必要な場合は使用を検討しても良いかもしれません。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?