动态生成代码
Dynamically Generating Code
我有一大堆结构相似的方法,每个方法看起来都像这样:
def my_method_1
if params[:user_id]
#code that stays the same across my_method_1, 2, 3, 4, etc.
#code that varies across my_method_1, 2, 3, 4, etc.
elsif params[:tag_id]
#code that stays the same across my_method_1, 2, 3, 4, etc.
#code that varies across my_method_1, 2, 3, 4, etc.
else
#code that stays the same across my_method_1, 2, 3, 4, etc.
#code that varies across my_method_1, 2, 3, 4, etc.
end
end
我有 my_method_2、3、4 等。我想做的是避免为我拥有的每个方法输入所有这些,因为大部分代码都是重复的。我只想输入在方法 1、2、3、4 等中实际不同的代码位
我在这方面的尝试使用了 eval(),它有效,并且肯定会耗尽我所有的个人方法,但让我感到不舒服。基本上,我有一个接受键值对的辅助方法,键是 "context",值是 "statement",指定为字符串:
def helper_method
hash.each do |context, statement|
if params[eval(":#{context}_id")]
#code that stays the same
eval(statement)
return
end
end
eval(hash[:none])
end
现在我的个人方法可以超级枯燥,只需调用辅助方法并传入代码字符串:
def my_method_1
helper_method(
user: '#code that varies',
tag: '#code that varies',
none: '#code that varies'
)
end
再次强调,在字符串中输入大块代码让我感到不舒服。任何以另一种方式解决这个问题的帮助将不胜感激!
您需要使用动态方法:
def method_missing(method_name)
if method_name.to_s =~ /context_(.*)/
#Some code here that you want
# ...
end
end
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?('context_') || super
end
您可以通过使用 instance_eval
来改进这一点,它只是在您传递的块内更改 self
(同时 String#to_sym
避免了哈希键的评估)。您还可以让辅助方法定义方法本身,这样使用起来会更短一些。
def self.define_structured_method(name, hash)
define_method(name) do
hash.each do |context, block|
if params["#{context}_id".to_sym]
#code that stays the same
instance_eval &block
return
end
end
instance_eval &hash[:none]
end
end
define_structured_method(:my_method_1,
user: proc { puts "user code" },
tag: proc { puts "tag code" },
none: proc { puts "else code" }
)
代码中的重复分支告诉我您的 class 可以使用一点重构来消除对多个 if 语句的需要。听起来您的 class 需要委派给另一个 class 以实现特定功能。虽然我不知道您的 class 看起来像,或者它的意图,但以下是一个示例,您可以将其应用于您的代码,这样您 就不需要 完全生成动态方法。
具有重复 if 语句的假设 Order
class
考虑这个 Order
class 和多个看起来相似的 if 语句:
class Order
attr_accessor :order_type
def discount_amount
if order_type == 1
.2
elsif order_type == 2
.5
else
0
end
end
def discount_end_date
if order_type == 1
DateTime.new(2014, 12, 31)
elsif order_type == 2
DateTime.new(2014, 3, 31)
else
# Always expires 100 years from now
DateTime.new(DateTime.now.year + 100, 1, 1)
end
end
end
我们有三种折扣:2014 年底到期的 20% 折扣; 50%,将于 2014 年 3 月底到期。最后,0% 的默认折扣始终在 100 年后到期。让我们清理一下以删除 if 语句,并将这些计算委托给 Discount
class。
重构 Order
class 以利用委托方法
首先,让我们清理一下 Order
class,然后我们将执行一个 Discount
class:
class Order
attr_accessor :order_type
def discount
@discount ||=
if order_type == 1
Discount.twenty_percent_off
elsif order_type == 2
Discount.half_off
else
Discount.default_discount
end
end
def discount_amount
discount.amount
end
def discount_end_date
discount.end_date
end
end
干净整洁。 Order
对象需要 Discount
对象来获取折扣金额和结束日期。 Order
class 现在几乎可以无限扩展,因为计算折扣的逻辑完全卸载到另一个 class。 Order#order_type
值决定折扣。现在,让我们定义 Discount
class.
实施 Discount
class
根据我们的(假)商业规则,只有三种折扣:
- 20% 折扣,2014 年底到期
- 50% 折扣,2014 年 3 月底到期
- 0%-off(无折扣)从今天起 100 年后总是过期,本质上意味着它永远不会过期
我们不希望人们创建任意折扣,所以让我们将 Discount
实例限制为仅使用私有构造函数定义的实例,然后为每种折扣声明静态方法:
class Discount
private_class_method :new
def self.default_discount
@@default_discount ||= new(0)
end
def self.half_off
@@half_off_discount ||= new(.5, DateTime.new(2014, 3, 31))
end
def self.twenty_percent_off
@@twenty_percent_off ||= new(.2, DateTime.new(2014, 12, 31))
end
def initialize(amount, end_date = nil)
@amount = amount
@end_date = end_date
end
def amount
@amount
end
def end_date
@end_date ||= DateTime.new(DateTime.now.year + 100, 1, 1)
end
end
尝试 运行 Discount.new(...)
应该会引发错误。我们只有三个折扣实例可用:
Discount.half_off
Discount.twenty_percent_off
Discount.default_discount
鉴于 Order#order_type
用于确定折扣,我们使用 Order#discount
模拟此方法,返回基于 Order#order_type
的正确 Discount
实例。此外,我们通过定义自己的折扣来防止人们玩弄系统,并且所有折扣的逻辑都在一个 class.
order = Order.new
order.order_type = 1
puts order.discount_amount # -> .2
order = Order.new
order.order_type = 2
puts order.discount_amount # -> .5
您可以使用 sub classing 创建更具体的业务逻辑,例如 "random" 折扣:
class Discount
protected_class_method :new
...
def self.random
@random_discount ||= RandomDiscount.new(nil)
end
class RandomDiscount < Discount
def amount
rand / 2
end
end
end
现在Discount.random.amount
每次输出不同的折扣。可能性变得无穷无尽。
这如何适用于您的问题
重复 if 语句的存在意味着您的 class 做得太多了。它应该委托给另一个 class,专门研究这些代码分支之一。您不必在 运行 时操作 Ruby 中的方法来实现此目的。它太多 "magic" 并且让新开发人员感到困惑。使用我上面概述的方法,您可以获得这些折扣的强类型定义,并且您让每个 class 专注于一项任务(不,"strongly typed" 不是 Ruby 正确使用时)。您将获得对象之间明确定义的关系、更易于测试的代码,并且可以强有力地执行业务规则。全部没有"magic."
我有一大堆结构相似的方法,每个方法看起来都像这样:
def my_method_1
if params[:user_id]
#code that stays the same across my_method_1, 2, 3, 4, etc.
#code that varies across my_method_1, 2, 3, 4, etc.
elsif params[:tag_id]
#code that stays the same across my_method_1, 2, 3, 4, etc.
#code that varies across my_method_1, 2, 3, 4, etc.
else
#code that stays the same across my_method_1, 2, 3, 4, etc.
#code that varies across my_method_1, 2, 3, 4, etc.
end
end
我有 my_method_2、3、4 等。我想做的是避免为我拥有的每个方法输入所有这些,因为大部分代码都是重复的。我只想输入在方法 1、2、3、4 等中实际不同的代码位
我在这方面的尝试使用了 eval(),它有效,并且肯定会耗尽我所有的个人方法,但让我感到不舒服。基本上,我有一个接受键值对的辅助方法,键是 "context",值是 "statement",指定为字符串:
def helper_method
hash.each do |context, statement|
if params[eval(":#{context}_id")]
#code that stays the same
eval(statement)
return
end
end
eval(hash[:none])
end
现在我的个人方法可以超级枯燥,只需调用辅助方法并传入代码字符串:
def my_method_1
helper_method(
user: '#code that varies',
tag: '#code that varies',
none: '#code that varies'
)
end
再次强调,在字符串中输入大块代码让我感到不舒服。任何以另一种方式解决这个问题的帮助将不胜感激!
您需要使用动态方法:
def method_missing(method_name)
if method_name.to_s =~ /context_(.*)/
#Some code here that you want
# ...
end
end
def respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?('context_') || super
end
您可以通过使用 instance_eval
来改进这一点,它只是在您传递的块内更改 self
(同时 String#to_sym
避免了哈希键的评估)。您还可以让辅助方法定义方法本身,这样使用起来会更短一些。
def self.define_structured_method(name, hash)
define_method(name) do
hash.each do |context, block|
if params["#{context}_id".to_sym]
#code that stays the same
instance_eval &block
return
end
end
instance_eval &hash[:none]
end
end
define_structured_method(:my_method_1,
user: proc { puts "user code" },
tag: proc { puts "tag code" },
none: proc { puts "else code" }
)
代码中的重复分支告诉我您的 class 可以使用一点重构来消除对多个 if 语句的需要。听起来您的 class 需要委派给另一个 class 以实现特定功能。虽然我不知道您的 class 看起来像,或者它的意图,但以下是一个示例,您可以将其应用于您的代码,这样您 就不需要 完全生成动态方法。
具有重复 if 语句的假设 Order
class
考虑这个 Order
class 和多个看起来相似的 if 语句:
class Order
attr_accessor :order_type
def discount_amount
if order_type == 1
.2
elsif order_type == 2
.5
else
0
end
end
def discount_end_date
if order_type == 1
DateTime.new(2014, 12, 31)
elsif order_type == 2
DateTime.new(2014, 3, 31)
else
# Always expires 100 years from now
DateTime.new(DateTime.now.year + 100, 1, 1)
end
end
end
我们有三种折扣:2014 年底到期的 20% 折扣; 50%,将于 2014 年 3 月底到期。最后,0% 的默认折扣始终在 100 年后到期。让我们清理一下以删除 if 语句,并将这些计算委托给 Discount
class。
重构 Order
class 以利用委托方法
首先,让我们清理一下 Order
class,然后我们将执行一个 Discount
class:
class Order
attr_accessor :order_type
def discount
@discount ||=
if order_type == 1
Discount.twenty_percent_off
elsif order_type == 2
Discount.half_off
else
Discount.default_discount
end
end
def discount_amount
discount.amount
end
def discount_end_date
discount.end_date
end
end
干净整洁。 Order
对象需要 Discount
对象来获取折扣金额和结束日期。 Order
class 现在几乎可以无限扩展,因为计算折扣的逻辑完全卸载到另一个 class。 Order#order_type
值决定折扣。现在,让我们定义 Discount
class.
实施 Discount
class
根据我们的(假)商业规则,只有三种折扣:
- 20% 折扣,2014 年底到期
- 50% 折扣,2014 年 3 月底到期
- 0%-off(无折扣)从今天起 100 年后总是过期,本质上意味着它永远不会过期
我们不希望人们创建任意折扣,所以让我们将 Discount
实例限制为仅使用私有构造函数定义的实例,然后为每种折扣声明静态方法:
class Discount
private_class_method :new
def self.default_discount
@@default_discount ||= new(0)
end
def self.half_off
@@half_off_discount ||= new(.5, DateTime.new(2014, 3, 31))
end
def self.twenty_percent_off
@@twenty_percent_off ||= new(.2, DateTime.new(2014, 12, 31))
end
def initialize(amount, end_date = nil)
@amount = amount
@end_date = end_date
end
def amount
@amount
end
def end_date
@end_date ||= DateTime.new(DateTime.now.year + 100, 1, 1)
end
end
尝试 运行 Discount.new(...)
应该会引发错误。我们只有三个折扣实例可用:
Discount.half_off
Discount.twenty_percent_off
Discount.default_discount
鉴于 Order#order_type
用于确定折扣,我们使用 Order#discount
模拟此方法,返回基于 Order#order_type
的正确 Discount
实例。此外,我们通过定义自己的折扣来防止人们玩弄系统,并且所有折扣的逻辑都在一个 class.
order = Order.new
order.order_type = 1
puts order.discount_amount # -> .2
order = Order.new
order.order_type = 2
puts order.discount_amount # -> .5
您可以使用 sub classing 创建更具体的业务逻辑,例如 "random" 折扣:
class Discount
protected_class_method :new
...
def self.random
@random_discount ||= RandomDiscount.new(nil)
end
class RandomDiscount < Discount
def amount
rand / 2
end
end
end
现在Discount.random.amount
每次输出不同的折扣。可能性变得无穷无尽。
这如何适用于您的问题
重复 if 语句的存在意味着您的 class 做得太多了。它应该委托给另一个 class,专门研究这些代码分支之一。您不必在 运行 时操作 Ruby 中的方法来实现此目的。它太多 "magic" 并且让新开发人员感到困惑。使用我上面概述的方法,您可以获得这些折扣的强类型定义,并且您让每个 class 专注于一项任务(不,"strongly typed" 不是 Ruby 正确使用时)。您将获得对象之间明确定义的关系、更易于测试的代码,并且可以强有力地执行业务规则。全部没有"magic."