builder by ZDNet Japanをご愛読頂きありがとうございます。

builder by ZDNet Japanは2022年1月31日にサービスを終了いたします。

長らくのご愛読ありがとうございました。

Rubyのモジュールとイントロスペクション

文:Steve Hayes(Builder AU) 翻訳校正:村上雅章・野崎裕子
2007-12-13 10:00:00
  • このエントリーをはてなブックマークに追加

 Mix-inは、Rubyが提供している一般的なクラスにおいても重要な役割を果たしている。ObjectはKernelモジュールをMix-inしており(そしてKernelメソッドをprivateにするためのちょっとしたマジックも行っている)、前回のコラムで解説したイテレータのほとんどはEnumerableモジュールによって提供されている。Enumerableモジュールが要求するのは、インクルードするクラスが#eachの実装を提供するということだけである--つまり、Enumerableにおけるその他すべてのメソッドは、究極的には#eachにのみ依存しているのだ。このため、新たなクラスを実装する際に適切な#eachを記述しておけば、EnumerableをMix-inするだけでさまざまな繰り返しメソッドを無料で入手できるのだ。

 以下は、数値を数値桁の配列に変換するという簡単なクラスの例である。このコードは力任せのアプローチを採用しており、エレガントだとは到底言えないものの、Rubyの特徴をいくつか示すものとなっている。

class Digits
  attr_reader :digits
  def initialize(number)
    string = number.to_s
    @digits = (0..string.size-1).inject([]) {|digits, index| digits << string[index,1].to_i}
  end  
end

 このコードを用い、返されてきた数値桁の配列に対して繰り返し処理を行うことで、数値中の数値桁毎に何らかの処理を繰り返し適用することができる。つまり、以下のように記述することで各数値桁の2乗を取得することができるわけだ。

Digits.new(123).digits.collect{|each| each*each} # => [1, 4, 9]

 しかしdigitsという属性を人目にさらすのはオブジェクト指向の観点から見ると望ましいことではない。Digitsクラスが繰り返しを直接取り扱った方がずっとスマートであり、そういったことは、以下のようにEnumerableを用いることで簡単に実現できるのだ。

class Digits

  include Enumerable

  def initialize(number)
    string = number.to_s
    @digits = (0..string.size-1).inject([]) {|digits, index| digits << string[index,1].to_i}
  end

  def each(&block)
    @digits.each(&block)
  end
 
end

 これで、Digitsオブジェクトに対して直接繰り返し処理を指示できるようになったうえ、実装をどのようなかたちにでも変えられるようになった。

Digits.new(123).collect{|each| each*each} # => [1, 4, 9]

 モジュールに加えて、Rubyには柔軟性と拡張性をもたらすもう1つの特徴がある--その他多くの言語とは異なり、Rubyのクラスはすべて、拡張に対してオープンとなっているのだ。

 このことは、既存のRubyクラスに対して新しいメソッドを追加したり、既存のメソッドの振る舞いを変えたりすることが自由にできるということを意味している。例えばJavaのプロジェクトでは、開発者がStringクラスのインスタンスに対して起動する可能性のあるすべてのメソッドを保持した重装備なStringUtilsクラスを構築し、チーム全体で使用するのが一般的である。これは、JavaのStringクラスが拡張に対して閉じられているため、こういった機能をString上に直接実装することができないためだ。

 具体的な例を挙げると、Apache CommonsのStringUtilsクラスでは、文字列が空白、空文字列、nullのいずれかであるかを判定するisBlank()が提供されている。このメソッドは以下のようにして起動することになる。

StringUtils.isBlank(aString)

 Rubyの場合、このメソッドを直接Stringクラスに実装することができる(次のコードでは、空白と空文字列だけをチェックしている)。

class String
  def blank?
    self.strip.empty?
  end
end

 判定対象がStringである場合はこれで問題なく処理が行われるものの、判定対象がnullの場合には処理が行われない(String#blank?に制御が渡ってこない)。しかしまったく問題はない。Rubyにおいてnullに相当するもの、すなわちnilはNilClassのインスタンスであり、そのクラスも拡張に対してオープンとなっているため、以下のように実装しておけばよいのだ。

class NilClass
  def blank?
    true
  end
end

 こういった実装の方が、オブジェクト指向言語らしい自然な表現を行えるのだ。

's'.blank? # => false
''.blank? # => true
nil.blank? # => true

 このようにRubyという言語には、重複を避けるかたちでコードを簡単に組織化することで、表現を明確にして理解しやすくするための機能が数多く取りそろえられている。またRubyには、イントロスペクションを行うためのツールが言語の一部として組み込まれているため、既存の構造と実装を自由に調査できるようになっている。そしてこのことは、あなたのRubyに対する理解を深めるとともに、手元にある特定の問題を解決するためのより良い指針を提供してくれるのだ。

この記事は海外CNET Networks発のニュースをシーネットネットワークスジャパン編集部が日本向けに編集したものです。海外CNET Networksの記事へ

ブログの新規登録は、2021年12月22日に終了いたしました。

folllow builer on twitter
このサイトでは、利用状況の把握や広告配信などのために、Cookieなどを使用してアクセスデータを取得・利用しています。 これ以降ページを遷移した場合、Cookieなどの設定や使用に同意したことになります。
Cookieなどの設定や使用の詳細、オプトアウトについては詳細をご覧ください。
[ 閉じる ]