我有一大堆类似结构的方法,每个方法都是这样的:
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",值是"语句"指定为字符串:
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
再次,在字符串中键入代码块会让我感到不舒服。任何有关这种方式的帮助将非常感谢!
答案 0 :(得分:1)
您可以使用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" }
)
也会避免使用散列键的eval)。你也可以让helper方法定义方法本身,使用起来要短一些。
{{1}}
答案 1 :(得分:1)
代码中的重复分支告诉我,您的类可以使用一些重构来消除对多个if语句的需要。听起来您的类需要委托给另一个类来获取特定功能。虽然我不知道您的课程是什么样的,或者它的意图,但以下是您可以应用于您的代码的示例,以便您不需要生成动态方法。
Order
类考虑这个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-statments,然后委托Discount
类进行这些计算。
Order
类以使用委托方法首先,让我们清理Order
类,然后我们将实现Discount
类:
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
类现在几乎可以无限扩展,因为计算折扣的逻辑完全被卸载到另一个类。 Order#order_type
值确定折扣。现在,让我们定义我们的Discount
类。
Discount
类根据我们的(虚假)商业规则,只有三个折扣:
我们不希望人们创建任意折扣,所以让我们将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
模拟此问题,并根据Discount
返回正确的Order#order_type
实例。此外,我们通过定义自己的折扣来阻止人们游戏系统,并且所有折扣的逻辑都在一个类别中。
order = Order.new
order.order_type = 1
puts order.discount_amount # -> .2
order = Order.new
order.order_type = 2
puts order.discount_amount # -> .5
您可以使用子类来创建更具体的业务逻辑,例如" 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语句的存在意味着你的班级做得太多了。它应该委托给另一个专门研究其中一个代码分支的类。你不应该在运行时在Ruby中操作方法来实现这一点。它太多&#34;魔术&#34;并且让新开发者感到困惑。通过上面概述的方法,您可以获得这些折扣的强类型定义,并且您将每个课程都集中在一个任务上(并且不,#34;强类型&#34;在Ruby中不是四个字母的单词)使用得当)。您可以获得对象之间定义良好的关系,更易于测试的代码以及强大的业务规则实施。一切都没有&#34;魔术。&#34;
答案 2 :(得分:0)
您需要使用动态方法:
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