Ruby Mixin依赖于Objects

时间:2014-12-04 15:11:44

标签: ruby

我对ruby中模块的使用感到困惑。具体来说,当模块混合到类中时,如何在模块中访问对象实例变量/方法。请考虑以下示例。

class A
  include B

  def initialize(my_var)
    @my_var = my_var
    @result = nil
  end

  def get_result
    @result = foo
  end

end

module B

  def foo
    "Result of #{@my_var}"
  end

end

在上面的例子中,模块B的设计方式是,它期望封装对象定义变量my_var。这是一个很好的设计吗?感觉不对,因为在这种情况下,模块B以某种方式依赖于类A.构建模块B的另一种方法是这样的。

module B

  def foo(my_var)
    "Result of #{my_var}"
  end

end

在此版本中,该方法接受一个参数并执行必要的计算并返回一个值。这更加清晰,但这可能是一个人为的例子,因为在许多现实世界的场景中,事情要复杂得多。

此外,它不只是关于实例变量。我认为同样的问题也适用于实例方法。我们是否可以构造一个模块,期望封装对象具有某些方法和/或实例变量。在这种情况下设计解决方案的正确方法是什么?

我已经查看了几个与此问题密切相关的stackoverflow线程,但找不到满意的答案。提前感谢您的所有答案

3 个答案:

答案 0 :(得分:2)

在你的第二个例子中,foo不是一个真正的方法,它是一个程序。区分方法的一个原因是它具有对接收者的特权访问,即self。但是你的第二个foo没有使用它!那么,为什么要把它作为一种方法呢?您可以使用BASIC中的过程执行相同的操作。

在方法中访问self是完全没问题的,那就是方法。

除了文档之外,Ruby本身没有办法声明mixin期望self完成其工作的内容。但这很常见:Comparable期望self回复<=>Enumerable期望self回复each,例如。 / p>

我将这些称为“杠杆混合”,因为它们就像一个杠杆:你只需要自己提供一个小力量(一个each方法),但它们允许你做批次<重举。

与Ruby中的其他各种期望没什么不同。 Array#join(sep)期望sep回复to_str并回复to_s的数组元素,Array#[](idx)期望idx回复to_int }。 Range成员资格测试方法期望左元素响应<=>Range迭代方法(eachto_astep,... )期望左元素响应succEnumerable#sort期望枚举的元素能够响应<=>,依此类推。

将期望放在self上与将它们放在任何其他对象上并没有多大区别。

这些“期望”通常称为“协议”。对象符合协议(它们支持的消息集以及它们对它们的反应),并依赖于其他对象的协议。例如,您可以说Enumerable mixin取决于Iteration协议。但是,Ruby语言中没有用于表达除简单文档之外的其他协议的构造。

但是,在Ruby中使用实例变量通常 并不是非常OO。 OO是关于消息传递的,但是Ruby只为方法而不是变量进行消息传递(不像Self,例如,通过消息查找实例变量),因此您应该优先选择方法而不是变量。如果您担心吸气剂泄漏信息或设置者违反某些不变量,请将它们private

答案 1 :(得分:1)

这只是红宝石duck-typed的另一个方面。

就像将参数传递给方法一样,并且方法假定参数响应某些方法时,模块可能会假设有关包含类的内容。

答案 2 :(得分:0)

我认为本书将为您解答很多问题:Practical Object-Oriented Design in Ruby.

关于实例变量,不,在模块中使用它们不是一个好主意。模块应该提取通用行为,并且通常在多于1个类中混合(并且包含它们的类越多,实际变量名称冲突的可能性就越大。更好的方法(在上面的书)是这样的:

module B
  def foo
    "Result of #{my_var}"
  end
end

class A
  include B
  attr_reader :my_var

  def initialize(my_var)
    @my_var = my_var
  end
end

如果您不希望程序崩溃,例如,my_var不是混合类中的方法,您可以执行以下操作来检查方法是否存在:

def foo
    puts 'hi' if respond_to?(:my_var)
end