Ruby 动态生成代码
我有一大堆结构类似的方法,每一种方法看起来都像这样:Ruby 动态生成代码,ruby,dynamic,methods,eval,Ruby,Dynamic,Methods,Eval,我有一大堆结构类似的方法,每一种方法看起来都像这样: 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_m
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
我有我的方法2、3、4等等。我要做的是避免为我所有的方法都键入这些内容,因为大多数代码都是重复的。我只想键入在方法1、2、3、4等中实际变化的代码位
我尝试使用eval(),它可以工作,当然会使我所有的方法都干涸,但让我感到不舒服。基本上,我有一个helper方法,它接受键-值对,键是“上下文”,值是指定为字符串的“语句”:
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
现在,我的单个方法可以是超级干式的,只需调用helper方法并传入代码字符串:
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
避免对散列键进行求值)。您还可以让helper方法定义方法本身,这会稍微缩短使用时间
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" }
)
代码中重复的分支告诉我,您的类可以使用一些重构来消除对多个if语句的需要。听起来您的类需要委托给另一个类来实现特定的功能。虽然我不知道您的类看起来像什么,或者它的意图是什么,但下面是一个示例,您可以将其应用到代码中,这样就根本不需要生成动态方法 假设的
顺序
类,带有重复的if语句
考虑这个Order
类,它包含多个外观相似的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 stations,并将这些计算委托给折扣类
重构Order
类以利用委托方法
首先,让我们清理一下订单
类,然后我们将实现一个折扣
类:
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
类现在实际上可以无限扩展。订单#订单类型
值决定折扣。现在,让我们定义我们的折扣类
实现折扣
类
根据我们的(假)业务规则,只有三种折扣:
折扣20%,2014年底到期
50%折扣,截止2014年3月底
0%折扣(无折扣),从今天起100年内到期,基本上意味着它永远不会过期
我们不希望人们创建任意折扣,因此让我们将折扣
实例限制为仅使用私有构造函数定义的实例,然后为每种折扣声明静态方法:
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
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.half_off
Discount.twenty_percent_off
Discount.default_discount
考虑到Order#Order(u type
用于确定折扣,我们通过Order#折扣
基于Order#Order(u type
返回适当的discount
实例进行仿真。此外,我们通过定义自己的折扣来防止人们玩系统游戏,并且所有折扣的逻辑都在一个类中
order = Order.new
order.order_type = 1
puts order.discount_amount # -> .2
order = Order.new
order.order_type = 2
puts order.discount_amount # -> .5
您可以使用子分类创建更具体的业务逻辑,例如“随机”折扣:
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
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
类折扣
受保护的\u类\u方法:新建
...
def self.random
@随机折扣| |=随机折扣。新(无)
结束
类别折扣<折扣
def量
兰特/2
结束
结束
结束
现在折扣.random.amount
每次输出不同的折扣。可能性变得无穷无尽
这如何适用于你的问题
重复if语句的存在意味着你的类做得太多了。它应该委托给另一个专门处理这些代码分支之一的类。您不必在运行时操纵Ruby中的方法来实现这一点。这太“神奇”了,让新开发人员感到困惑。通过我上面概述的方法,您可以得到这些折扣是什么的强类型定义,并且您可以让每个类专注于一个任务(不,如果正确使用,“强类型”在Ruby中不是一个四个字母的单词)。您可以获得对象之间定义良好的关系、更易于测试的代码以及业务规则的强大实施。所有这些都没有“魔力”。缺少信息。到目前为止,我不知道为什么不调用处理相同代码并接受参数的方法。或者创建一个实用程序类来为您实现这一点,并允许以某种方式基于(最有可能的)类型进行子类化或方法调度。到目前为止,我不认为需要任何过于动态的东西,这往往会使事情变得相当复杂。是的,如果不提供#在我的方法#u 1、2、3、4等中保持不变的代码。#在我的方法#u 1、2、3、4等中变化的代码。
不清楚如何清理它。我不会使用eval
或方法#u missing
。我会非常谨慎地使用任何元编程,因为它会使代码的可读性大大降低。你应该可以使用其他的Ruby结构。我不明白这个问题。例如,对于params[:user_id]
是#在我的_方法_1、2、3、4等中保持不变的代码。
可以放入每个方法1…4调用的方法中的东西?我建议你提供