Webを支える技術:3章:REST – Webのアーキテクチャスタイル
Webを支える技術とは? 技術評者社から出されている技術書で、以下は公式サイトからの概要引用です。 Webは誕生から...
-2019.01.9
-技術書
-Ruby, メタプログラミングRuby
スポンサーリンク
内容
出版社である O’Reilly Japanのサイトからの引用です。
本書はRubyを使ったメタプログラミングについて解説する書籍です。メタプログラミングとは、プログラミングコードを記述するコードを記述することを意味します。前半では、メタプログラミングの背景にある基本的な考えを紹介しながら、動的ディスパッチ、ゴーストメソッド、フラットスコープといったさまざまな「魔術」を紹介します。後半では、ケーススタディとしてRailsを使ったメタプログラミングの実例を紹介します。今回の改訂では、Ruby 2とRails 4に対応し、ほぼすべての内容を刷新。Rubyを使ったメタプログラミングの魔術をマスターし、自由自在にプログラミングをしたい開発者必携の一冊です。
引用:https://www.oreilly.co.jp/books/9784873117430/
3章:メソッドを写経し終えたので内容を振り返ってまとめてみました。
コードの繰り返しが多いメソッド群をどう解決するか?
mouseメソッド、cpuメソッド、keyboardメソッドの中身は殆ど同じ。。
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def mouse
info = @data_source.get_mouse_info(@id)
price = @data_source.get_mouse_price(@id)
result = "Mouse: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
def cpu
info = @data_source.get_cpu_info(@id)
price = @data_source.get_cpu_price(@id)
result = "Cpu: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
def keyboard
info = @data_source.get_keyboard_info(@id)
price = @data_source.get_keyboard_price(@id)
result = "Keyboard: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
end
動的言語の特性を活かして
がある。
結論から言うと、可能な限り動的メソッドで対応するべき
class MyClass
def my_method(my_arg)
my_arg * 2
end
end
obj = MyClass.new
puts obj.send(:my_method, 3) # => 6
class MyClass
define_method :my_method do |my_arg|
my_arg * 3
end
end
obj = MyClass.new
puts obj.my_method(2) # => 6
①動的ディスパッチにより、メソッドを引数として呼び出す
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def mouse
component(:mouse)
end
def cpu
component(:cpu)
end
def keyboard
component(:keyboard)
end
def component(name)
info = @data_source.send("get_#{name}_info", @id)
price = @data_source.send("get_#{name}_price", @id)
result = "#{name.capitalize}: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
end
②メソッド定義も動的メソッドにする
この時、Computerクラスに対してdefine_componentを呼ぶためにクラスメソッドとしている。
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def self.define_component(name)
define_method(name) do
info = @data_source.send("get_#{name}_info", @id)
price = @data_source.send("get_#{name}_price", @id)
result = "#{name.capitalize}: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
end
define_component(:mouse)
define_component(:cpu)
define_component(:keyboard)
end
③イントロスペクションでメソッド定義も排除
イントロスペクションとはオブジェクトの情報を参照すること。
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component($1) }
end
def self.define_component(name)
define_method(name) do
info = @data_source.send("get_#{name}_info", @id)
price = @data_source.send("get_#{name}_price", @id)
result = "#{name.capitalize}: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
end
end
class Lawyer
end
nick = Lawyer.new
nick.send(:method_missing, :my_method)
# => undefined method `my_method' for # (NoMethodError)
method_missingをオーバーライドすることで存在しないメソッド(ゴーストメソッド)を呼び出せる
class Lawyer
def method_missing(method, *args)
puts "呼び出した:#{method}(#{args.join(', ')})"
puts "(ブロックも渡した)" if block_given?
end
end
bob = Lawyer.new
bob.talk_simple('a', 'b') do
# ブロック
end
# =>
# 呼び出した:talk_simple(a, b)
# (ブロックも渡した)
重複問題もゴーストメソッドで処理できる。
しかし、ゴーストメソッドはrespond_to?で認識できないので
respond_to_missingもオーバーライドしないといけないので注意。
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def method_missing(name)
super if !@data_source.respond_to?("get_#{name}_info")
info = @data_source.send("get_#{name}_info", @id)
price = @data_source.send("get_#{name}_price", @id)
result = "#{name.capitalize}: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
def respond_to_missing?(method, include_private = false)
@data_source.respond_to?("get_#{method}_info") || super
end
end
computer = Computer.new
computer.mouse
class Roulette
def method_missing(name, *args)
person = name.to_s.capitalize
# ↓3つのみゴーストメソッドとして許可する
super unless %w[Bob Frank Bill].include? person
number = 0 # ローカル変数のスコープに注意する
3.times do
number = rand(10) + 1
puts "#{number}..."
end
"#{person} got a #{number}"
end
end
number_of = Roulette.new
puts number_of.bob
puts number_of.frank
puts number_of.john
そこで、必要最低限のメソッドしか持たないBasicObjectを継承して回避する。
これを「ブランクスレート」と呼ぶ。
BasicObjectを継承するために、Object#respond_to?を気にする必要がないので、
respond_to_missingのオーバーライドが不要となる利点もある。
class Computer < BasicObject
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
end
def method_missing(name, *args)
super if !@data_source.respond_to?("get_#{name}_info")
info = @data_source.send("get_#{name}_info", @id)
price = @data_source.send("get_#{name}_price", @id)
result = "#{name.capitalize}: #{info} (#{price})"
return "* #{result}" if price >= 100
result
end
end
スポンサーリンク