読者です 読者をやめる 読者になる 読者になる

継承可能なクラス属性

Rubyのクラス属性まわりは個人的には結構ややこしいと思っています。

クラスに保存できるデータには以下のようなものがあります。

  • クラス変数
  • クラスローカル変数( クラスインスタンス変数? )
  • メソッド

クラス変数

@@variableのように表記します。

以下1.8での挙動。

class Foo
  @@foo = 'foo'

  def self.foo
     @@foo
  end
end

class Bar < Foo
   def self.bar
     @@foo
   end

   def self.update val
     @@foo = val
   end
end


Foo.foo
  #=> "foo"

Bar.foo
  #=> "foo"

Bar.bar
  #=> "foo"

Bar.update 'hoge'
  #=> "hoge"

Bar.bar
  #=> "hoge"

Bar.foo
  #=> "hoge"

Foo.foo
  #=> "hoge"

継承下のクラスで書きかえると、その親クラスに影響がでていることが見受けられます。

ライブラリを継承して作るようなクラスでクラス変数がかぶってしまうようなことがあると悲惨なことになります。

ちなみに1.9だと継承ツリーには影響を及ぼさず、継承すると値にセットされていた値は継承したサブクラスではnilになっているようです。

クラスローカル変数

Rubyで@variableな変数は通常インスタンス変数と呼ばれますが、実はクラスメソッドのコンテキストなどでも使うことができます。( この使いかたはruby使ってる人にはクラスインスタンス変数とか言うと通じやすかったりするのですが、rubyを知らない人には混乱を招きやすい名前なのであまり正確な呼び名ではないかなぁと思ってます。あえていうとクラスローカル変数とでもいえばいいんですかねぇ。。。)

class Foo
  @foo = 'foo'
  def self.foo
    @foo
  end
end

class Bar < Foo
  def self.bar
    @foo
  end
  def self.update val
    @foo = val
  end
end

Foo.foo
  #=> "foo"

Bar.foo
  #=> nil

Bar.update 'bar'
  #=> "bar"

Bar.foo
  #=> "bar"

Foo.foo
  #=> "foo"

クラス変数を使った場合と違い継承されません。

メソッド

メソッドの挙動はrubyを使っている皆さんには使いなれていて、非常に直感的な挙動をしていると思います。

class Foo
  def self.foo
    "foo"
  end
end

class Bar < Foo
end

Foo.foo #=> "foo"
Bar.foo #=> "foo"

こちらは親クラスで定義しておいたデータを継承することができるかわりに、クラスレベルでの書きかえがしにくいです。(Rubyのオープンクラスを駆使すればさほど難しくはないですが;)

わりと複雑なライブラリのようなものを書いているとごくたまに継承可能なクラス属性のようなものが欲しくなることがあります。(例えば、モードを変更するとクラスの挙動が変わったり、クラスの属性にあわせてインスタンスの挙動をまとめて変更させたいとか。)

そのようなことのために簡単に定義できる実装はいくつかあって、現時点で最もよく使われてるのはactivesupportのclass_inheritable_accessorでしょう。

個人的にはclass_inheritable_accessorを使いたいがためにactivesupportのような巨大で通常のRubyのクラスを拡張しまくるライブラリには依存したくないなぁというのがあって、classxでやっていることを応用して、クラス属性をclassxの通常のアクセサと同様に定義できるようにしてみました。

 require 'classx'

 class YourClass
   extend ClassX::CAttrs
   class_has :x, 
     :writalbe => true,
     :default  => proc {|klass| klass.to_s.split(/::/).last }

 end

 YourClass.x #=> "YourClass"

 YourClass.x = "HogeFuga"
 YourClass.x #=> "HogeFuga"

class YourClass2 < YourClass
end
  #=> nil

# 今のYourClass.x属性の値は継承されず、
# デフォルトのProcに従ってデフォルト値ができる。
YourClass2.x
  #=> "YourClass2"

機能的には、このようにclass_inheritable_accessorを使いやすいようにDSLでラップしたmaihaさんのdsl_accessorとほぼ同様の使い勝手で、classXのアクセサと同様の機能が実現できるようになってます。(すなわち、アクセサの定義と同時にデフォルト値、Validationのルール、delegateの設定などが柔軟に定義できる)

さきほどリリースしたclassx 0.0.4で入っています。

SEE ALSO