避免 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 == Carfilter_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 需要对 CarUser 了解太多。假设您需要添加更多类型? UserComposite 会逐渐变得臃肿。

但是,很难给出具体的建议,因为过滤背后的业务逻辑并不清晰(架构应该始终适应您的实际用例)。

您真的需要对Cars 和Users 执行共同的操作吗?

如果不是,请不要将行为合并到单个 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 中的方法重载的主要内容,如果未能解决你的问题,请参考以下文章

为啥“避免方法重载”?

使用重载方法等待

替换函数 - 没有重载方法错误 C# - 试图删除字符串中的部分

PHP 重载方法 __call()

JAVA怎样重载操作符?

说出几条 Java 中方法重载的最佳实践?