こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

singletonメソッドへのアクセス

インスタンスメソッドからsingletonメソッドへのアクセスが、思ったようにできません。

はじめは、次のように記述すれば動作すると思っていました。
(MyClass.get_hello_wordをインスタンスメソッドから呼び出し)

class MyClass
 def hello
  puts self.get_hello_word # エラー発生源
 end
 def self.get_hello_word
  'hello every one'
 end
end

MyClass.new.hello # エラー


しかし、実際には次のように記述しなければ動きませんでした。
class MyClass
 def hello
  puts self.class.get_hello_word # self.classのワンクッション
 end
 def self.get_hello_word
  'hello every one'
 end
end

MyClass.new.hello


そもそも、
def self.get_hello_word
と定義したんだから、参照する時も同様にできてもいいのではないかと思うんですけれど、何か認識が間違っているのでしょうか。

だとしたら、(勘で言いますと)
class << MyClass
 def get_hello_word
  'hello every one'
 end
end
の文法の考え方がからんでいるような気がするのですが・・
ちんぷんかんぷんです。

どうかこの辺りの知識を教えて頂けないでしょうか。
あと、self.class.get_hello_wordの記述方法よりも簡単な(そうすべき)書き方があれば教えて下さい。

投稿日時 - 2009-11-24 23:22:50

QNo.5473655

暇なときに回答ください

質問者が選んだベストアンサー

> ちなみに、javaや.NETではstatic宣言すれば、同クラス内からだと、メソッド名にプレフィクスを付けなくても使用できるので、それに対してrubyはちょっと不便に感じたことが質問の動機です。

そうですね。Javaや.NETでは確かにこのような小さなユーティリティ関数の類をよくクラスメソッドにしますし、文法上そのように扱えるので便利です。ただ、改めて考えてみると、こうした関数はRubyでは普通はprivateなインスタンスメソッドにしてしまう気がします。

なぜならば、
> もしvalid_password?メソッドがインスタンスメソッドだと、@saltや@passwordに何らかの変更を加えた場合、 valid_password?メソッドが万が一これらを参照している可能性があるので

クラスメソッドであっても、クラス変数やクラスのインスタンス変数を参照しているかも知れません。その場合はもっと酷い副作用/非純粋性がありえます。結局@saltや@passwordなどの他の状態に依存していないかどうかは他の部分、例えば規約やドキュメントや、設計上の常識、あるいはソースを見る、などによって担保しなければならないのであって、文法上は保証できません。Javaや.NETでもその気になれば期待に反するコードはいくらでも書けることでしょう。特にRubyの場合はそのDynamismからクラスメソッドにしたところであまり保証度が上がった気がしません。

この手のユーティリティでstaticメンバ関数/staticメソッドを用いるのは、保守性というよりはどちらかというと、多態性を必要としない場合のC++におけるパフォーマンスチューニングの流儀が継承されてきた設計ではないかと思います。

> 実態が1つしか作られないので実行速度が上がる等な理由もありますが、

Javaや.NETでは静的なinvokeは速度が向上する場合もあろうかと思いますが、Rubyではクラスメソッドもクラスに対するシングルトンメソッドなのであって、実行時に動的に解決されますし多態に振る舞いもします。

どちらかというと、特異クラスに対するメソッド呼び出しがメソッドキャッシュに載っていない分パフォーマンスが低下するケースもあるかと思います。ケースバイケースで、プラスになる場合もありマイナスになる場合もあり、少なくとも一般的にパフォーマンスが向上するとは期待できません。

ついでに言うと、
class MyClass
(snip)
 private

 def self.valid_password?(passwd)
  (/\A\w{4,12}\Z/ =~ passwd) != nil ? true : false
 end
end

今、valid_password?はMyClassのpublicなクラスメソッドです。なぜならば、privateはそれ以下の、そのクラスのメソッドをprivateにするものだからです。クラスメソッドはそのクラスの特異クラスのインスタンスメソッドであって、そのクラスのメソッドではないです。従って、privateはdef self.valid_password?には影響を与えていません。

無理に多言語のパフォーマンス特性やオブジェクトモデルを流用した設計にせずともよいのではないでしょうか。この場合素直にMyClassのprivate instance methodにしてはどうでしょう。私ならそうしますし、保守の点はunit testとrdocでカバーします。

それにしても、確かにC++ならvalid_password?はstaticにしていたところです。面白い例をありがとうございました。

投稿日時 - 2009-11-26 10:28:18

お礼

> こうした関数はRubyでは普通はprivateなインスタンスメソッドにしてしまう気がします。

できればインスタンス内との影響が全く無くて、入力と出力で全てが完結していることが一目瞭然なメソッド定義のしかたがあればと思ったのですが、慣習がそうなら仕方がないですね。

ググってみると、private_class_methodというのがありましたので、privateなクラスメソッドをクラス定義から直に宣言することは可能なようです。
が、やはりMyClassの特異クラスのメソッドとなるので、MyClassのインスタンスメソッドからアクセスすることはできないことに変わりはありませんが・・。

話変わって、慣習ではどうであろうと、やはりなんだかもやもやしていましたので、試行錯誤してみました。これならばvalid_password?メソッドはPasswordHolderのメンバにアクセスしないような気がし(実際には、Enumerableモジュールがincludeされるクラスにeachメソッドが定義されている事を期待するように、あくまでも"気がする”程度ですが・・)、さらにprivateも実現しました。
こんなのは専門家なyuguiさんの目にはどう写るでしょうか。
moduleを実際使ったのは初めてなのですが、普通の使い方なのか、よい使い方なのか、邪道な使い方なのか・・

module PasswordHolderSupport
 private
 def valid_password?(passwd)
  (/\A\w{4,12}\Z/ =~ passwd) != nil ? true : false
 end
end

class PasswordHolder
 include PasswordHolderSupport

 def initialize(passwd)
  if valid_password?(passwd) == false
   raise ArgumentError, "使用できないパスワード:#{passwd}"
  end
  @salt = [rand(9), rand(9)].join
  @password = passwd.crypt @salt
 end

 def authorized?(passwd)
  @password == passwd.crypt(@salt)
 end
end

投稿日時 - 2009-11-26 23:19:42

ANo.2

このQ&Aは役に立ちましたか?

6人が「このQ&Aが役に立った」と投票しています

回答(2)

ANo.1

 def self.get_hello_word
は、定義時点におけるselfに対してシングルトンメソッドget_hello_wordを定義します。今def式はMyClassのclass式の中にあるので、def式が実行された時点でのselfはMyClassです。

一方hello実行時のselfはMyClassのインスタンスであってMyClassではありません。よって定義時のselfであったMyClassにはget_hello_wordがあるが、実行時のselfにはget_hello_wordはないので、self.get_hello_wordはエラーを発生します。

> self.class.get_hello_wordの記述方法よりも簡単な(そうすべき)書き方があれば教えて下さい。
意図することがクラスメソッドであるならば、恐らくこれがそのまま望ましい書き方です。
概念としてのクラスメソッドはRubyの場合クラスに対するシングルトンメソッドとして実現されるわけです。設計上におけるクラスメソッドを実現する分にはシングルトンメソッドや特異クラスについて理解する必要はないので、ご自分で見いだした上のような書き方を「クラスメソッドの決まり事」として利用すると良いでしょう。

クラスメソッドの場合以外のより一般的なシングルトンメソッドを扱うならばシングルトンメソッドとは何かという事を理解して使うべきです。シングルトンメソッドは個別のインスタンスに属するものなので、上に述べたようにそれが定義されたインスタンスは何であるかを考えないといけないわけです。

ただ、もしかしてこの場合シングルトンメソッドは必要ないのではないでしょうか。もう少し具体的な意図をお聞かせいただければ何か付け加えるべきことを言えるかも知れません。

投稿日時 - 2009-11-25 17:52:16

補足

メソッド定義時と実行時でselfが別物とは驚きました。
しかし、selfはそのオブジェクト自身を指すと考えるとうまく噛み合いますね。
クラスの定義時には、インスタンス化されていないクラス自身の中でメソッド等の解析を実行するのでクラス自身のオブジェクトをselfとして参照し、定義されたクラスがいったんインスタンス化された後はインスタンス化されたメソッドの中での実行なので、selfはインスタンス自身を指す。
こんな感じでしょうか。

それと、シングルトンメソッドが必要になる具体的な例を考えてみました。(妙に時間使ってしまいました・・)

class PasswordHolder
 def initialize(passwd)
  if self.class.valid_password?(passwd) == false
   raise ArgumentError, "使用できないパスワード:#{passwd}"
  end
  @salt = [rand(9), rand(9)].join
  @password = passwd.crypt @salt
 end

 def authorized?(passwd)
  @password == passwd.crypt(@salt)
 end

 private

 def self.valid_password?(passwd)
  (/\A\w{4,12}\Z/ =~ passwd) != nil ? true : false
 end
end

begin
 password_holder = PasswordHolder.new 'hagiwara'
 puts password_holder.authorized?('muroi') # -> false
 puts password_holder.authorized?('hagiwara') # -> true
rescue ArgumentError => e
 puts e.message
end

begin
 password_holder = PasswordHolder.new 'kentarou!' # 最後の文字が使用不可
 puts password_holder.authorized?('miidori') # 到達しないコード
rescue ArgumentError => e
 puts e.message # -> 使用できないパスワード:kentarou!
end

もしvalid_password?メソッドがインスタンスメソッドだと、@saltや@passwordに何らかの変更を加えた場合、valid_password?メソッドが万が一これらを参照している可能性があるので、その影響を考えなくてはなりません(つまり毎回メソッドの中身を見て影響が無いことを確認しなければならない)。その点、シングルトンメソッドはインスタンス変数を一切見ることができないので、self.valid_password?と定義してあった場合は影響が及ぶことが無いことが一目瞭然で、保守性が上がります。

実態が1つしか作られないので実行速度が上がる等な理由もありますが、やはり保守性が一番の理由ですね。

ちなみに、javaや.NETではstatic宣言すれば、同クラス内からだと、メソッド名にプレフィクスを付けなくても使用できるので、それに対してrubyはちょっと不便に感じたことが質問の動機です。

投稿日時 - 2009-11-25 21:25:16

あなたにオススメの質問