需要先了解以下主题:
此实作范例修改自五倍红宝石技术文章
目标是让下面的程序码:
1 class Example
2 before_action :method_a, :method_b do
3 |n| puts "the code before #{n}"
4 end
5
6 def method_a
7 puts 'this is method a'
8 end
9
10 def method_b
11 puts 'this is method b'
12 end
13 end
14
15 instance_1 = Example.new
16 instance_1.method_a
17 instance_2 = Example.new
18 instance_2.method_b
19
可以有这样的执行结果
1 the code before method_a
2 this is method a
3 the code before method_b
4 this is method b
首先是打开 Singleton Class 的起手式:
class 类别名称
class << self
def 方法名称
end
end
end
class Example
class << self
attr_accessor :methods, :block
def before_action
end
end
end
这里加入了 before_action 的类别方法,之後覆写方法时,会需要取得到相关的变数,所以也ㄧ并把 attr_acessor 所提供方法都带进去了。这代表了我们ㄧ共建立了五个类别方法:(1) :before_action, (2) :methods, (3) :methods=, (4) :block, (5) :block=
在定义 before_action() 方法会需要两个参数(parameter):
因为在执行 before_action() 方法时,还未定义 Example 类别内的 method_a() 方法,无法在这里执行覆写的编码,但可以把传进来的引数储存下来,而透过 attr_accessor 就可以让其他的方法取得存下来的属性。
class Example
class << self
attr_accessor :methods, :block
def before_action(*methods, &block)
@methods = methods
@block = block
end
end
end
接着我们建立 ExampleRefinement 模组,以 refine 的方式改写 Example 类别内要被覆写的方法。这时候之前储存下的的属性就派上用场了
透过 each 方法把每一个方法名称转出来,以 alias_method 建立方法的新别名,并且保留原有方法的功能。接着用动态方法 define_method 重新定义方法,要执行的内容会先呼叫 block ,再用 send 呼叫原本的 method。
在最後的步骤中,透过运用动态方法、method wrappers的技巧来达到覆写方法的目的。
module ExampleRefinement
refine Example do
block = Example.block
@methods = Example.methods
@methods.each do |method|
newname = "new_#{method}"
alias_method newname, method
define_method method do
block.call(method)
send(newname)
end
end
end
end
class Example
class << self
attr_accessor :methods, :block
def before_action(*methods, &block)
@methods = methods
@block = block
end
end
end
class Example
before_action(:method_a, :method_b){|n| p "the code before #{n}"}
def method_a
p 'this is method a'
end
def method_b
p 'this is method b'
end
end
module ExampleRefinement
refine Example do
block = Example.block
@methods = Example.methods
@methods.each do |method|
# 帮原有的方法取别名
newname = "new_#{method}"
alias_method newname, method
# 动态定义新方法要执行的内容
define_method method do
block.call(method)
send(newname)
end
end
end
end
using ExampleRefinement # => 拿掉这一行就不会有覆写方法的效果
instance_1 = Example.new
instance_1.method_a
instance_2 = Example.new
instance_2.method_b
执行结果会是:
the code before method_a
this is method a
the code before method_b
this is method b
目标是让下面的程序码:
1 class Example
2 extend BeforeAction
3
4 before :method_a, :method_b do
5 puts "the code before method"
6 end
7
8 def method_a
9 puts 'this is method a'
10 end
11
12 def method_b
13 puts 'this is method b'
14 end
15 end
16
17 instance_1 = Example.new
18 instance_1.method_a
19 instance_2 = Example.new
29 instance_2.method_b
可以有这样的执行结果
1 the code before method
2 this is method a
3 the code before method
4 this is method b
实作的过程在原文章的最後部分,详细的解说可以点选 这里
以下程序码(加上我自己的注解)是完整的答案:
module BeforeAction
def new
# 只有第一次执行 new 时要 execute_before
# 因为 execute_before 内的 alias_method(),在执行第二次时会有
# 无限回圈的问题
execute_before if first_time?
# 再呼叫 super 才能回到原有的 new 方法
super
end
def first_time?
return false if @not_first_time
@not_first_time = true
end
def before(*methods, &block)
# 先存起来之後用
@methods = methods
@block = block
end
def execute_before
@methods.each do |method|
# 帮原本的 method 取一个新名字
newname = "new_#{method}"
alias_method newname, method
block = @block
# 重新定义每个 method 要做什麽
define_method method do
# 执行 block
block.call
# 原本的 method
send(newname)
end
end
end
end
class Example
extend BeforeAction
before :method_a, :method_b do
puts 'the code before method'
end
def method_a
puts 'this is method a'
end
def method_b
puts 'this is method b'
end
end
instance_1 = Example.new
instance_1.method_a
instance_2 = Example.new
instance_2.method_b
Medium 文章连结:https://kevin0117.medium.com/
本文同步发布於 Kevin's Blog: https://chienhao.tw/
备注:之後文章修改更新,以个人部落格为主
<<: Amazon SageMaker 机器学习线上研讨会
除了AWS提供预设的RDS, 若觉得使用起来不顺手, 也可以建立虚拟机, 使用安装版的MSSQL资料...
上一篇我们简单的说明完如何使用 Event Storm 来完成 DDD 战略的三个产出 : 分析 D...
MSIX AppAttach 其实让我想到了十年前刚接触Citrix XenApp跟VMware V...
中秋节就是要烤肉阿! 台式烤肉吃腻了来换换口味吧, 韩剧及韩综中常常出现韩国烤五花肉,在家就可以吃!...
AppDelegate 为App 的主要入口点,Apple 会在一些应用程序级别的生命周期事件调用A...