避免 Ruby 中的方法重载
Posted
技术标签:
【中文标题】避免 Ruby 中的方法重载【英文标题】:Avoiding method overloading in Ruby 【发布时间】:2015-01-23 23:08:34 【问题描述】:由于 Ruby 不支持重载(由于几个微不足道的原因),我正试图找到一种“模拟”它的方法。
在静态类型语言中,你不能使用instanceof
,(当然某些特殊情况除外......)来指导应用程序。
所以,请记住这一点,这是重载我关心变量类型的方法的正确方法吗? (在这种情况下,我不关心参数的数量)
class User
attr_reader :name, :car
end
class Car
attr_reader :id, :model
end
class UserComposite
attr_accessor :users
# f could be a name, or a car id
def filter(f)
if (f.class == Car)
filter_by_car(f)
else
filter_by_name(f)
end
end
private
def filter_by_name(name)
# filtering by name...
end
def filter_by_car(car)
# filtering by car id...
end
end
【问题讨论】:
【参考方案1】:在种情况下这是一种很好的方法,而 Ruby 为您提供了处理它的工具。
但是,您的案例不清楚,因为您的示例自相矛盾。如果f.class == Car
则filter_by_car
接受_car
,而不是_car_id
。
我假设您实际上是在传递类的实例,如果是这样,您可以这样做:
# f could be a name, or a car
def filter(f)
case f
when Car
filter_by_car(f)
else
filter_by_name(f)
end
end
case [x]
查看它的每个when [y]
子句并执行[y] === [x]
的第一个子句
实际上这是在运行Car === f
。当您在类对象上调用#===
时,如果参数是该类的实例,则返回true。
这是一个非常强大的结构,因为不同的类可以定义不同的“大小写相等”。例如,如果参数与表达式匹配,Regexp 类将大小写相等定义为 true,因此以下工作:
case "foo"
when Fixnum
# Doesn't run, the string isn't an instance of Fixnum
when /bar/
# Doesn't run, Regexp doesn't match
when /o+/
# Does run
end
【讨论】:
【参考方案2】:就我个人而言,我认为以这种方式进行分支并没有什么大问题。虽然使用case
会看起来更干净
def filter(f)
case f
when Car
filter_by_car(f)
else
filter_by_name(f)
end
end
稍微复杂一点的例子涉及用对象替换分支(毕竟,ruby 是 oop 语言:))。在这里,我们为特定格式(类)的数据定义处理程序,然后通过传入的数据类查找这些处理程序。大致如下:
class UserComposite
def filter(f)
handler(f).filter
end
private
def handler(f)
klass_name = "#f.classHandler"
klass = const_get(klass_name) if const_defined?(klass_name)
klass ||= DefaultHandler
klass.new(f)
end
class CarHandler
def filter
# ...
end
end
class DefaultHandler # filter by name or whatever
def filter
# ...
end
end
end
【讨论】:
实际上,我发现后者的建议更像 Ruby-way。只需创建一个响应调用对象的方法。 :) 好像你在使用反射,不是吗?你们在 Ruby 方面的经验比我多,所以我不知道这是否来自“Ruby 的世界”,但我必须相信你,尽管我不喜欢对这种简单的事情进行反思。那里没有设计模式吗? @Quarktum:是的,这是反射。我也不是它的忠实粉丝。在你的情况下,我可能只使用case
(除非你过于简单化)
@Quarktum:ruby 代码(尤其是 rails)充满了反射。它是 ruby 的强大功能之一,因此它通常比在 java 中使用得更多。 :)
@SergioTulentsev:是的。只有在不超过 7 到 8 行的情况下,我才会使用 case
。但是,一如既往的好答案。荣誉.. :)【参考方案3】:
您的架构中可能潜伏着一个问题 - UserComposite
需要对 Car
和 User
了解太多。假设您需要添加更多类型? UserComposite
会逐渐变得臃肿。
但是,很难给出具体的建议,因为过滤背后的业务逻辑并不清晰(架构应该始终适应您的实际用例)。
您真的需要对Car
s 和User
s 执行共同的操作吗?
如果不是,请不要将行为合并到单个 UserComposite
类中。
如果是这样,您应该使用具有通用接口的装饰器。大致是这样的:
class Filterable
# common public methods for filtering, to be called by UserComposite
def filter
filter_impl # to be implemented by subclasses
end
end
class FilterableCar < Filterable
def initialize(car)
@car = car
end
private
def filter_impl
# do specific stuff with @car
end
end
class DefaultFilterable < Filterable
# Careful, how are you expecting this generic_obj to behave?
# It might be better replace the default subclass with a FilterableUser.
def initialize(generic_obj)
# ...
end
private
def filter_impl
# generic behavior
end
end
然后UserComposite
只需要关心它是否传递了Filterable
,它所要做的就是在该对象上调用filter
。拥有通用的可过滤接口使您的代码可预测,并且更易于重构。
我建议您避免动态生成可过滤的子类名称,因为如果您决定重命名子类,将很难找到生成代码的代码。
【讨论】:
以上是关于避免 Ruby 中的方法重载的主要内容,如果未能解决你的问题,请参考以下文章