専用クラスを作ってキーワード引数の代わりにする
引数をハッシュにするより専用クラスを作って(ry - きみのハートを8ビットキャスト
class Freq def initialize( freq ) ; @freq = freq ; end def to_i ; @freq ; end end class Bit def initialize( bit ) ; @bit = bit ; end def to_i ; @bit ; end end class Channel def initialize( ch ) ; @ch = ch ; end def to_i ; @ch ; end end
おもしろいテクニックだが、DRY原則に反してるからあかん。
せめてまとめなくちゃ。
def define_unit(methname) Class.new do define_method(:initialize) {|x| instance_variable_set("@#{methname}", x) } attr_reader methname end end Freq = define_unit :to_i Bit = define_unit :to_i Channel = define_unit :to_i # Numericからサクッと作れるように仕込む class Numeric def hz ; Freq.new( self ) ; end def bit ; Bit.new( self ) ; end def channel ; Channel.new( self ) ; end end # 実際のWavクラスの初期化(抜粋) class Wav def initialize( *args ) args.each { |arg| case arg when Channel ; @channels = arg.to_i when Freq ; @sample_rate = arg.to_i when Bit ; @sample_bit = arg.to_i end } end end Wav.new 44100.hz,16.bit, 1.channel # => #<Wav:0x83896ec @sample_rate=44100, @sample_bit=16, @channels=1>
クラス名と単位を管理しないといけない上、クラス名での分岐が出てるのがいやだ。
Rubyにおいて、クラスで分岐しているのはリファクタリングのサインであることが多い。
もうちょっと抽象化してみる。レッツ、DSL!
Numeric#hz等は value と unit の2つのメンバを持つ構造体オブジェクトを返す。
値を格納するだけのクラスは構造体を使えばよい。
で、Object#set_instance_variable_by_unit では、インスタンス変数名→単位名のハッシュに基いてインスタンス変数をセットする。
module Unit Value = Struct.new :value, :unit def self.define(unit) Numeric.module_eval do define_method(unit) { Value.new(self, unit) } end end end class Object def set_instance_variable_by_unit(args, hash) unit_to_var = hash.invert args.each do |arg| instance_variable_set unit_to_var[arg.unit], arg.value end end private :set_instance_variable_by_unit end # 黒魔術はここでおしまい。 ################################################################ Unit.define :hz Unit.define :bit Unit.define :channel class Wav def initialize( *args ) set_instance_variable_by_unit args, :@channels => :channel, :@sample_rate => :hz, :@sample_bit => :bit end end Wav.new 44100.hz,16.bit, 1.channel # => #<Wav:0xb7dd365c @channels=1, @sample_bit=16, @sample_rate=44100> Wav.new 1.channel, 44100.hz, 16.bit # => #<Wav:0xb7dd23c4 @channels=1, @sample_bit=16, @sample_rate=44100> 1.hz # => #<struct Unit::Value value=1, unit=:hz> 2.hz # => #<struct Unit::Value value=2, unit=:hz> 1.bit # => #<struct Unit::Value value=1, unit=:bit>
すっきりしたでしょ?
しかし、同じ単位の引数が複数ある場合は使えないなぁ…
っつーか、active-supportだとNumeric#secとかNumeric#minuteとか、Numeric#megabyteとか単位名メソッドがあるんだよなー。かぶってしまう恐れがあるかも…