1. 程式人生 > >支援Open Class特性的程式語言中的開閉原則(Open-Closed Principle)

支援Open Class特性的程式語言中的開閉原則(Open-Closed Principle)

我們知道Ruby的語法特性支援Open Class,可以讓使用者重新定義系統中已經存在的類,給其新增方法或屬性。例如:

# foo.rb
class Foo
def method1 *args
...
end
end

你定義了上面的類Foo之後,可以重新Open它,往其中新增方法:

# foo2.rb
class Foo
def method2 *args
...
end
end

使用Foo的時候,就可以用這兩個方法,與其初始就定義兩個方法沒有區別:

foo = Foo.new
foo.method1 :arg1, :arg2
foo.method2 :arg1, :arg2

那麼這麼做是違背了Open-Close原則嗎?Bertrand Meyer第一個闡述了Open-Close,他的定義是:

軟體實體(如類Class,模組Module,函式Function等),對擴充套件要開放,對修改要封閉。

他在這裡有詳細的闡述:

這就是開閉原則,我認為這是面向物件的核心變革:你可以完整地使用一個元件,並且以後還可以通過繼承擴充套件其功能。和傳統的結構體的實現方式不一樣,面向物件既可以Close又可以Open:Close是因為我們可以在上層程式碼(clients)中直接使用;Open是因為我們可以擴充套件其屬性和方法而不影響上層程式碼(clients)。
關於更多的開閉原則,參考這篇文章:

《言簡意賅,隱喻的力量》

因此,若client只依賴foo.rb且只用method1,那client就根本不關注method2。但是,如果client同時依賴foo.rb和foo2.rb,這時候就會有問題,除非client不關注foo2.rb中的改動。好像可以使用標準的繼承來解決這種問題?

實際不是的,我們並沒有違背開閉原則,我們只是使用Open Class規則來擴充套件一個類,和標準繼承是一樣的。

如果我們使用標準的繼承:

# foo.rb
class Foo
def method1 *args
...
end
end
...
class DerivedFoo < Foo def method2 *args ... end end ... foo = SubFoo.new # Instantiate different class... foo.method1 :arg1, :arg2 foo.method2 :arg1, :arg2


繼承與Open Class的顯著區別就是:繼承多建立了一個類。如果你經常使用繼承的話你可以會首選繼承來實現,要用繼承有一個條件就是你必須能夠完全控制你要建立的類(Open Class就不一樣,可以擴充套件現有的類庫而不需要完全維護這個類)並且能夠很容易的更換你使用的類。當然,繼承是最好的方法,如果你想同時改變類的所有的行為。

有時候你想改變某個類所有例項的行為,且對client是透明的,不會影響已經建立了的例項。
我們舉一個紀錄日誌方法呼叫的例子,來說說明如何實現上面所說的。我們使用Ruby中著名的alias_method方法,該方法是將類中已經定義好的方法再取一個別名(Ruby中的AOP也通常是使用alias_method來實現的)。

# foo.rb
class Foo
def method1 *args
...
end
end
# logging_foo.rb
class Foo
alias_method :old_method1, :method1
def method1 *args
p "Inside method1(#{args.inspect})"
old_method1 *args
end
end
...
foo = Foo.new
foo.method1 :arg1, :arg2

Foo.method1的行為與上面DerivedFoo繼承實現是一致的,且遵循了LSP原則(關於Ruby中的LSP原則參考淺談ruby core library 與 Liskov Substitution Principle原則)。

因此我認為開閉原則可以稍加修正並重申為:

軟體實體(如類Class,模組Module,函式Function等),對擴充套件要開放,對修改原始碼要封閉。

我們不能修改其原始碼,但是通過一個新的檔案,增加新的功能是可以的。

軟體實體(如類Class,模組Module,函式Function等),對擴充套件要開放,對修改原始碼和約定要封閉。

關於約定是LSP原則的冗餘或加強定義,我不認為這個加強的定義不好,這個約定是介面提供者和使用者之間約定好的介面的行為方式。我們在使用Open-Class時不能違背這個介面約定規則,就像我們在使用繼承的時候不能違背LSP原則一樣。

開閉原則(OCP)和LSP是面嚮物件語言設計中兩條很重要的原則,繼承是符合這兩個原則的一種擴充套件方法,我們今天討論的Open Class也是符合這兩個原則的另外一種。面向方面的程式設計(AOP)是第三種。