Ruby에서 "콜백"을 구현하는 방법은 무엇입니까?
Ruby에서 C 스타일 콜백에 대한 최고의 관용구가 있는지 또는 더 나은 것이 있는지 (C와 같은 것이 아닌) 확실하지 않습니다. C에서는 다음과 같이합니다.
void DoStuff( int parameter, CallbackPtr callback )
{
// Do stuff
...
// Notify we're done
callback( status_code )
}
좋은 루비는 무엇입니까? 기본적으로 "DoStuff"내에서 특정 조건이 충족 될 때 전달 된 클래스 메서드를 호출하고 싶습니다.
관용적이지 않은 루비 등가물은 다음과 같습니다.
def my_callback(a, b, c, status_code)
puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}"
end
def do_stuff(a, b, c, callback)
sum = a + b + c
callback.call(a, b, c, sum)
end
def main
a = 1
b = 2
c = 3
do_stuff(a, b, c, method(:my_callback))
end
관용적 접근 방식은 메서드에 대한 참조 대신 블록을 전달하는 것입니다. 블록이 독립된 방법에 비해 갖는 한 가지 장점은 컨텍스트입니다. 블록은 클로저 이므로 선언 된 범위의 변수를 참조 할 수 있습니다. 이렇게하면 do_stuff가 콜백에 전달해야하는 매개 변수의 수가 줄어 듭니다. 예를 들면 :
def do_stuff(a, b, c, &block)
sum = a + b + c
yield sum
end
def main
a = 1
b = 2
c = 3
do_stuff(a, b, c) { |status_code|
puts "did stuff with #{a}, #{b}, #{c} and got #{status_code}"
}
end
이 "관용적 블록"은 일상적인 Ruby의 핵심 부분이며 책과 튜토리얼에서 자주 다룹니다. 루비 정보 섹션은 유용한 [온라인] 학습 자료에 대한 링크를 제공합니다.
관용적 인 방법은 블록을 사용하는 것입니다.
def x(z)
yield z # perhaps used in conjunction with #block_given?
end
x(3) {|y| y*y} # => 9
또는 Proc 로 변환 할 수도 있습니다 . 여기에서를 사용하여 묵시적으로 Proc로 변환 된 "블록" &block
이 또 다른 "호출 가능"값 임을 보여줍니다 .
def x(z, &block)
callback = block
callback.call(z)
end
# look familiar?
x(4) {|y| y * y} # => 16
(나중에 사용하거나 오버 헤드와 구문 노이즈를 추가하기 때문에 다른 특수한 경우에만 block-now-Proc을 저장하려면 위의 양식을 사용하십시오.)
그러나 람다는 쉽게 사용할 수 있습니다 (그러나 이것은 관용적이지 않습니다).
def x(z,fn)
fn.call(z)
end
# just use a lambda (closure)
x(5, lambda {|y| y * y}) # => 25
위의 접근 방식은 클로저를 생성 할 때 "메서드 호출"을 모두 래핑 할 수 있지만 바인딩 된 메서드 는 일류 호출 가능 객체로 취급 될 수도 있습니다.
class A
def b(z)
z*z
end
end
callable = A.new.method(:b)
callable.call(6) # => 36
# and since it's just a value...
def x(z,fn)
fn.call(z)
end
x(7, callable) # => 49
또한 때때로 #send
메서드 를 사용하는 것이 유용합니다 (특히 메서드가 이름으로 알려진 경우). 여기에는 마지막 예제에서 생성 된 중간 Method 객체가 저장됩니다. Ruby는 메시지 전달 시스템입니다.
# Using A from previous
def x(z, a):
a.__send__(:b, z)
end
x(8, A.new) # => 64
즐거운 코딩 되세요!
주제를 조금 더 탐색하고 코드를 업데이트했습니다.
다음 버전은 기술을 일반화하려는 시도이지만 극도로 단순화되고 불완전합니다.
저는 DataMapper의 콜백 구현에서 영감을 얻었습니다. 저는 상당히 완전하고 아름답게 보입니다.
http://github.com/datamapper/dm-core/blob/master/lib/dm-core/support/hook.rb 코드를 살펴볼 것을 강력히 제안합니다.
어쨌든 Observable 모듈을 사용하여 기능을 재현하려는 시도는 매우 흥미롭고 유익했습니다. 몇 가지 참고 :
- 콜백을 등록 할 때 원래 인스턴스 메서드를 사용할 수 없기 때문에 추가 된 메서드가 필요한 것 같습니다.
- 포함하는 클래스는 관찰자와 자기 관찰자로 만들어집니다.
- 예제는 인스턴스 메서드로 제한되며 블록, 인수 등을 지원하지 않습니다.
암호:
require 'observer'
module SuperSimpleCallbacks
include Observable
def self.included(klass)
klass.extend ClassMethods
klass.initialize_included_features
end
# the observed is made also observer
def initialize
add_observer(self)
end
# TODO: dry
def update(method_name, callback_type) # hook for the observer
case callback_type
when :before then self.class.callbacks[:before][method_name.to_sym].each{|callback| send callback}
when :after then self.class.callbacks[:after][method_name.to_sym].each{|callback| send callback}
end
end
module ClassMethods
def initialize_included_features
@callbacks = Hash.new
@callbacks[:before] = Hash.new{|h,k| h[k] = []}
@callbacks[:after] = @callbacks[:before].clone
class << self
attr_accessor :callbacks
end
end
def method_added(method)
redefine_method(method) if is_a_callback?(method)
end
def is_a_callback?(method)
registered_methods.include?(method)
end
def registered_methods
callbacks.values.map(&:keys).flatten.uniq
end
def store_callbacks(type, method_name, *callback_methods)
callbacks[type.to_sym][method_name.to_sym] += callback_methods.flatten.map(&:to_sym)
end
def before(original_method, *callbacks)
store_callbacks(:before, original_method, *callbacks)
end
def after(original_method, *callbacks)
store_callbacks(:after, original_method, *callbacks)
end
def objectify_and_remove_method(method)
if method_defined?(method.to_sym)
original = instance_method(method.to_sym)
remove_method(method.to_sym)
original
else
nil
end
end
def redefine_method(original_method)
original = objectify_and_remove_method(original_method)
mod = Module.new
mod.class_eval do
define_method(original_method.to_sym) do
changed; notify_observers(original_method, :before)
original.bind(self).call if original
changed; notify_observers(original_method, :after)
end
end
include mod
end
end
end
class MyObservedHouse
include SuperSimpleCallbacks
before :party, [:walk_dinosaure, :prepare, :just_idle]
after :party, [:just_idle, :keep_house, :walk_dinosaure]
before :home_office, [:just_idle, :prepare, :just_idle]
after :home_office, [:just_idle, :walk_dinosaure, :just_idle]
before :second_level, [:party]
def home_office
puts "learning and working with ruby...".upcase
end
def party
puts "having party...".upcase
end
def just_idle
puts "...."
end
def prepare
puts "preparing snacks..."
end
def keep_house
puts "house keeping..."
end
def walk_dinosaure
puts "walking the dinosaure..."
end
def second_level
puts "second level..."
end
end
MyObservedHouse.new.tap do |house|
puts "-------------------------"
puts "-- about calling party --"
puts "-------------------------"
house.party
puts "-------------------------------"
puts "-- about calling home_office --"
puts "-------------------------------"
house.home_office
puts "--------------------------------"
puts "-- about calling second_level --"
puts "--------------------------------"
house.second_level
end
# => ...
# -------------------------
# -- about calling party --
# -------------------------
# walking the dinosaure...
# preparing snacks...
# ....
# HAVING PARTY...
# ....
# house keeping...
# walking the dinosaure...
# -------------------------------
# -- about calling home_office --
# -------------------------------
# ....
# preparing snacks...
# ....
# LEARNING AND WORKING WITH RUBY...
# ....
# walking the dinosaure...
# ....
# --------------------------------
# -- about calling second_level --
# --------------------------------
# walking the dinosaure...
# preparing snacks...
# ....
# HAVING PARTY...
# ....
# house keeping...
# walking the dinosaure...
# second level...
Observable 사용에 대한 간단한 설명이 유용 할 수 있습니다 : http://www.oreillynet.com/ruby/blog/2006/01/ruby_design_patterns_observer.html
그래서, 이것은 매우 "un-ruby"일 수 있습니다. 그리고 저는 "전문적인"Ruby 개발자가 아닙니다. 그러니 여러분들이 해보고 싶다면 부드럽게 해주세요 :)
Ruby에는 Observer라는 내장 모듈이 있습니다. 나는 그것이 사용하기 쉽다는 것을 알지 못했지만 공정하기 위해 많은 기회를주지 않았습니다. 내 프로젝트에서 고유 한 EventHandler 유형을 만드는 데 의존했습니다 (예, C #을 많이 사용합니다). 기본 구조는 다음과 같습니다.
class EventHandler
def initialize
@client_map = {}
end
def add_listener(id, func)
(@client_map[id.hash] ||= []) << func
end
def remove_listener(id)
return @client_map.delete(id.hash)
end
def alert_listeners(*args)
@client_map.each_value { |v| v.each { |func| func.call(*args) } }
end
end
따라서 이것을 사용하기 위해 클래스의 읽기 전용 멤버로 노출합니다.
class Foo
attr_reader :some_value_changed
def initialize
@some_value_changed = EventHandler.new
end
end
"Foo"클래스의 클라이언트는 다음과 같은 이벤트를 구독 할 수 있습니다.
foo.some_value_changed.add_listener(self, lambda { some_func })
나는 이것이 관용적 인 Ruby가 아니라고 확신하며 C # 경험을 새로운 언어로 단련하고 있지만 그것은 나를 위해 일했습니다.
(Rails에서) ActiveSupport 를 사용하고자한다면 간단하게 구현할 수 있습니다.
class ObjectWithCallbackHooks
include ActiveSupport::Callbacks
define_callbacks :initialize # Your object supprots an :initialize callback chain
include ObjectWithCallbackHooks::Plugin
def initialize(*)
run_callbacks(:initialize) do # run `before` callbacks for :initialize
puts "- initializing" # then run the content of the block
end # then after_callbacks are ran
end
end
module ObjectWithCallbackHooks::Plugin
include ActiveSupport::Concern
included do
# This plugin injects an "after_initialize" callback
set_callback :initialize, :after, :initialize_some_plugin
end
end
I often implement callbacks in Ruby like in the following example. It's very comfortable to use.
class Foo
# Declare a callback.
def initialize
callback( :on_die_cast )
end
# Do some stuff.
# The callback event :on_die_cast is triggered.
# The variable "die" is passed to the callback block.
def run
while( true )
die = 1 + rand( 6 )
on_die_cast( die )
sleep( die )
end
end
# A method to define callback methods.
# When the latter is called with a block, it's saved into a instance variable.
# Else a saved code block is executed.
def callback( *names )
names.each do |name|
eval <<-EOF
@#{name} = false
def #{name}( *args, &block )
if( block )
@#{name} = block
elsif( @#{name} )
@#{name}.call( *args )
end
end
EOF
end
end
end
foo = Foo.new
# What should be done when the callback event is triggered?
foo.on_die_cast do |number|
puts( number )
end
foo.run
I know this is an old post, but others that come across this may find my solution helpful.
http://chrisshepherddev.blogspot.com/2015/02/callbacks-in-pure-ruby-prepend-over.html
참고URL : https://stackoverflow.com/questions/1677861/how-to-implement-a-callback-in-ruby
'Development Tip' 카테고리의 다른 글
word2vec : 네거티브 샘플링 (일반 용어)? (0) | 2020.10.28 |
---|---|
Visual Studio에서 명명 규칙 위반 메시지를 제거하는 방법은 무엇입니까? (0) | 2020.10.28 |
NameValueCollection을 통해 C # 반복 (0) | 2020.10.28 |
판다 집계 수 구별 (0) | 2020.10.28 |
Java에서 오토 박싱과 언 박싱을 사용하는 이유는 무엇입니까? (0) | 2020.10.28 |