Ruby中的設計模式——《松本行弘的程式世界》

turingbooks發表於2020-04-07

    《 設計模式 一書是用C++ Smalltalk 介紹模式例項的。看了那些例子,大家都會感覺到,絕大多數的模式用 Smalltalk 實現起來非常簡單。這是為什麼呢?

         因為Smalltalk 沒有靜態型別,所以也就不需要匹配型別的模板等機制,也不需要僅僅為滿足型別要求而進行繼承,這就是 Smalltalk 簡單的理由。而且,由於語言本身的動態性質,有些模式根本不必要以模式的形式來抽象出來就可以得到簡潔的實現,這也是 Smalltalk 顯得簡單的原因之一。

         Ruby在很多方面很像 Smalltalk ,實現設計模式也毫無困難。一般說來,用 Ruby 來實現設計模式的場合,要比 C++ 簡潔得多,有些模式用現成的庫就足以表現。

         下面以Ruby 為中心,讓我們來看幾個在實際設計中 用設計模式的例子。


Singleton( 單件 模式

 

    首先, 讓我們來看一下最簡單的設計模式之一,Singleton 模式。 Singleton 模式用來保證某個類的例項只有一個。

         為什麼需要Singleton 模式呢?比如作為其他物件的雛形而存在的物件(用於 Prototype 模式),系統全體只存在唯一一個的物件等,都要用到 Singleton 模式。

         用Ruby 實現 Singleton 模式的方法有幾個 讓我們按順序來逐一說明。

使用singleton 庫的方法

         Ruby已經以庫的形式實現了 Singleton 模式。如圖 4-1 所示,使用singleton 庫的話,在任意的類裡只要包含( include )上 Singleton 模組,那個類就變成了 Singleton 模式的物件。

         要想取得Singleton 模式的類的物件,像圖 4-1 最後一行那樣,使用該類的 instance 方法。如果該類物件還沒有生成, instance 方法會生成該類物件並返回。如果該類物件已經生成, instance 方法就返回既有物件。

使用類或模組

         C++ Java 是不能把類作為物件來使用的,與之不同的是, Smalltalk Ruby 能把類也作為物件來處理。因此,在類或模組裡定義一個方法就可以實現 Singleton 模式( 參見 4- 2)。

4-1  使用 singleton 庫的 Ruby 程式碼

4-2  利用類定義來實現 Singleton 模式的程式碼

把一般的物件作為Singleton 來使用

        為了把一個類的物件限制成只有一個,並不一定需要對物件的一般生成方法加以限制。我們可以生成一個一般的物件,然後遵守紳士協定,不要再生成 其他更 多個物件,也就 了( 參見 4- 3)。

使用物件和特異方法

         其實還有不用類就可以實現的方法。Ruby 可以在物件生成之後再增加新的方法,這樣我們就可以生成一個 Object 類的物件,然後給它追加必要的功能( 參見 4- 4)。

4-3  在程式設計上下點功夫來實現 Singleton 模式

4-4  利用特殊方法來實現 Singleton 模式的程式碼

         這種使用特異方法的辦法是很符合Ruby 特徵的。 Ruby 自身的 main (最高層的 self ARGF 虛擬檔案,用來代表引數所指定的檔案 等也都是用這種方法實現的。


Proxy( 代理 模式

          Proxy模式是為某個物件提供代理物件的模式。為什麼需要 Proxy 模式呢?

         假設有個生成代價非常大的物件。 如果 在還不知道是否真正需要該物件的時候就事先生成它的話,可能會帶來很大的浪費。但話雖這麼說,不生成物件的話什麼事也做不了。這時候代理物件就有用武之地了。

         比如字處理軟體,它利用Proxy 物件來處理嵌入影像,把嵌入影像的生成處理延遲到需要表示的瞬間才來進行。

         Ruby的庫中也有使用 Proxy 模式的。比如 tempfile 庫,它不用指定檔名就可以生成臨時的工作檔案( 參見 4- 5)。

         Tempfile類與實際負責檔案輸出的 IO 類沒有繼承關係,它的有關輸入、輸出處理的方法都通過 Proxy 轉送到實際的 IO 類物件。因此,通過使用 Tempfile 類的物件,在任何有必要的時候也都可以使用相關的 IO 物件。

         Proxy模式也可以用 Ruby 的庫來實現。使用 delegate 庫就可以了。 delegate 是委託的意思。 Tempfile 類也是用 delegate 庫來實現的( 參見 4- 6)。

4-5  採用 Proxy 模式的 tempfile 庫的使用示例

4-6  使用 delegate 庫來實現 Proxy 模式的例子

 

         看一下就知道,delegate 庫的原始碼是相當複雜的,但基本上只是把被呼叫的方法都轉送到本來的物件那裡去。這裡使用的是 Ruby method_   missing 方法。

          Ruby中對物件 A 呼叫它所不知道的方法的時候, A method_missing 方法就會被呼叫。傳遞給 method_mis sing 的引數是在原來調 用方法的引數之前加上不存在的方法名。利用這一框架就可以很簡單地實現Proxy 模式( 參見 4-7 )。

         怎麼樣,真的是非常簡單吧。但是,這種實現方式也有不靈光的時候。Proxy 類固有的方法被呼叫的時候,是不會轉送到 method_missing 方法的。也就是說,Proxy 類的父類 Object 類的方法是轉送不了的。

4-7  使用 method_missing 方法來實現Proxy 模式的例子

         如果這樣的情況也要對應的話,就會稍微麻煩一些。實際上,delegate 庫除了空行和註釋以外,長達 114 行。比圖 4-7 要複雜得多。

         delegate庫使用起來雖然很簡單,但方法轉送的物件僅限於既有的物件。因此,在最開始舉的字處理軟體例子中,要想達到延遲影像生成的目的,直接使用 delegate 是不行的。我們可以像圖 4-8 那樣,從Delegator 派生一個子類 ImageProxy 來達到這一目的。

    __getobj__ 方法是Delegator 物件取得方法轉送物件的方法。通過 重寫 這個方法,ImageProxy 會在實際訪問影像物件的時候才來生成影像物件。 C++ 會用 operator-> 或者 operator* 來代替 __getobj__

4-8   Proxy 模式延遲物件生成的例子


Iterator( 迭代器 模式

         Iterator模式提供按順序訪問包含有多個物件的集合物件中各元素的方法。即使不知道物件的內部構造,也可以按順序訪問其中的每個元素。 Iterator 模式是為集合物件另外準備用來控制迴圈處理的物件,就像 C++ Java 一樣。我們稱這個迴圈控制物件為 Iterator 。也稱為游標。

         圖 4-9 Iterator 模式的類構成圖。呼叫集合物件(圖 4-9 Iteratable )的 Create Iterator() 方法,就會返回自己對應的Iterator 物件。 Iterator 物件會記住現在所指向的 Iteratable 元素,呼叫 Next() 方法可以返回集合的下一個元素。要想知道集合中是否還有別的元素,可以呼叫 IsDone() 方法來確認。圖 4-10 是利用Iterator 模式的程式示例。 Iterator 模式實現的是所謂外部迭代器的迴圈控制抽象化。

4-9   J ava Iterator 模式的類構成圖

4-10   Java 版外部迭代器的用法

         而Ruby 是用塊來對集合的各元素進行迴圈處理的。作為設計模式,使用塊進行迴圈的抽象化屬於 Visitor (訪問者)模式。但因為語言本身就支援這樣的迴圈,所以也就不需要 Iterator 這樣的物件了。這實在是太基本的東西了,也許都不應該稱之為設計模式了。

相關文章