Rubyと大クラス主義とダックタイピング、そして名前重要
最近、Javaを勉強したりしていて、RubyがJavaとの対比で昔より客観的に見られるようになったので、Rubyの記事を書きたいと思います。自分でも消化しきれていない話なので、反論は大歓迎です。
内容は、大クラス主義とダックタイピング、そして名前重要に関してです。
大クラス主義
Rubyの考えでよく言われるものに、大クラス主義があります。例えば、RubyのArrayは配列を表すクラスです。しかし、Arrayができることは、単純に値を複数持てるだけではありません。Arrayを調べてみると、popやpushやshiftやunshiftがあります。つまり、RubyのArrayはキューやスタックとしても使えます。一方、JavaのArrayやArrayListでは、配列の要素を扱うことはできますが、そのままではキューやスタックとしては使えません。Javaでキューやスタックが使いたいのであれば、例えば、QueueクラスやStackクラスが用意されています*1。
もう一つ例ですが、RubyのArrayにはeachメソッドがあります*2。一方、Javaでは、繰り返しの処理はIteratorクラスを使ったりします。
Javaでは、ArrayやQueueやStackやIteratorクラスのように、責務によってクラスが別れる傾向があるのに対し、RubyではすべてArrayクラスが引き受けます。このように、Rubyのクラスは、Javaよりもクラスが大きく、担う役割も大きなものになります。このような考え方は、大クラス主義と呼ばれています。
単一責任原則
Javaに多数のクラスがあって、クラスによって役割が異なるのは、単一責任原則によるものです。JavaのArrayクラスは、配列の要素の読み書きをするだけの責務を持っています。Arrayはその責務だけを全うします。もし、(Stackを使わずに)Arrayをスタックとして使う必要があるのならば、Arrayを利用した、あるいは継承したArrayStackのような別クラス作ることになります。
あたらしく作ったArrayStackでは、配列の読み書きにあたる役割はArrayに任せて、スタックとしての役割に専念できます。責務を分割してそれぞれを攻略する分割統治法は、プログラムの世界で重要なことですね。
そして、配列を利用するときにはArray型を指定し、新しく作ったスタックを使うにはArrayStack型を指定することで、安全に利用することができます。
「だとすると、それならRubyはこんなので大丈夫なの?」という気がしてきました。RubyヨリJavaノホウガイインジャナイノ?
そこでいろいろ考えてみると、実は「ダックタイピング」が重要な鍵なんじゃないかと思うようになってきました。
ダックタイピング
ダックタイピング(wikipedia)とは、「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ。」と形容される原理です。それだけだとよく分からないので、例をみてみましょう。
例えば、スタックからpopして1足してからpushするようなプログラムが書きたいとき、Arrayクラスに用意されているpushメソッドとpopメソッドを利用して下のようなプログラムが書けます。
def increment_last_element(array) array.push array.pop.to_i + 1 end increment_last_element [1, 2, 3] # => [1, 2, 4]
この時、increment_last_elementに渡す値は、popとpushさえ持っていればArrayクラスでなくても構いません。この場合、popとpushを持っているのならば、それはArrayと同じように扱えるということです。
例えば、文字列をスタックのように使うクラスMyStringStackを作って、そのインスタンスをincrement_last_elementを渡しても、最後の要素に1を足すことができます。
def increment_last_element(array) array.push array.pop.to_i + 1 end class MyStringStack def push(char) @string ||= '' # @stringが定義されていないときに、空文字列を代入する @string += char.to_s end def pop ret = @string[-1] #戻り値用の値として、@stringの最後の文字を取得 @string = @string[0..-2] #@stringの最後の文字を取り除いて@stringに代入 return ret end def to_s @string end end stack = MyStringStack.new stack.push '1' # => "1" stack.push '2' # => "12" stack.push '3' # => "123" increment_last_element stack # => "124"
長い上に分かりにくい例で申し訳ないですが、MyStringStackはpushメソッドとpopメソッドを持ってるので、increment_last_elementに渡すことができます。
私が言いたいことは、Rubyの世界では、オブジェクトを利用する段になったら、どのクラスのインスタンスかはそれほど重要ではないということです。
ダックタイピングの話をしたところで、単一責任原則の話に戻りたいと思います。
メソッド重要
Javaの世界では、クラスによって責務が分かれていました。配列を使うにはArrayクラスを使い、スタックを使うためにはStackクラスを使うのです。一方で、Rubyでは、スタックを使うにはArrayクラスを使うのではなく、pushメソッドとpopメソッドを使います*3。
となると、Javaではクラスの責務が重要になるように、Rubyではメソッドの責務が重要になるのではないかと思います。
JavaもRubyも同じ純粋なオブジェクト指向の言語ですが、Javaはクラスをより重視しているのに対し、Rubyはメソッドをより重視しているように感じます。
RubyのArrayクラスなんかを見ていると単一責任に見えないかもしれないですが、メソッドごとの責任はシンプルに分割されていると思います。
名前重要
Rubyの世界では、Arrayクラスのインスタンスであることよりも、「pushメソッドをもっている」ことや、「popメソッドをもっている」ことが重要ということでした。そうなると、「ボタンを押すという責務のpushメソッドを作っちゃったらどうするの?」という疑問が湧いてきます。
この疑問から見えてくるのは、メソッド名が非常に重要な要素だということです。Rubyではどの名前のメソッドを持っているかが重要です。そして、そのメソッド名が責務を表します。だからこそ、「名前重要」はRubyの世界では強く活きてくると思います。慎重に名前をつけてあげることで、その名前がプログラムを導いてくれます。