今回は前回の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を適用させてみましょう。
- モジュールをincludeしてクラスにメソッドを定義する方法
- モジュールをprependしてクラスにメソッドを定義する方法
- メソッドをクラスに直接定義する方法
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を求め過ぎてもあまり意味がないような気がします。
必要な場合は使用を検討しても良いかもしれません。