我对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线程,但找不到满意的答案。提前感谢您的所有答案
答案 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
迭代方法(each
,to_a
,step
,... )期望左元素响应succ
。 Enumerable#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