我找到了一个mixin的例子,它假设包含类的实例变量。像这样:
module Fooable
def calculate
@val_one + @val_two
end
end
class Bar
attr_accessor :val_one, :val_two
include Fooable
end
我发现支持和反对是否是一种好的做法。显而易见的替代方法是将val_one
和val_two
作为参数传递,但这似乎并不常见,并且拥有更多参数化方法可能是一个缺点。
关于mixin对阶级状态的依赖,是否存在传统观点?从实例变量中读取值与将它们作为参数传递有什么优点/缺点?或者,如果您开始修改实例变量而不是仅仅读取它们,答案是否会改变?
答案 0 :(得分:5)
在模块中假设一些关于包含/预先包含它的类的属性,这根本不是问题。通常这样做。实际上,Enumerable
模块假定包含/预先包含它的类具有each
方法,并且有许多依赖于它的方法。同样,Comparable
模块假定包含/前置类具有<=>
。我不能立即想出一个实例变量的例子,但是关于这一点的方法和实例变量之间没有关键的区别;关于实例变量也应如此。
不使用实例变量传递参数的缺点是您的方法调用将是冗长且不太灵活的。
答案 1 :(得分:2)
经验法则:Mixins不应该对它们可能包含的类/模块做任何假设。但是,正如通常所做的那样,任何规则都有例外。
但首先,让我们谈谈第一部分。具体来说,访问(取决于)包括类实例变量。如果你的mixin依赖于包含类中的任何东西,那么这意味着你无法改变那些&#34;任何东西&#34;在父类中保证它不会破坏某些东西。此外,您必须记录mixin的依赖性,不仅在与mixin相关的文档中,而且在包含mixin的类/模块的文档中。因为,在未来的道路上,需求可能会发生变化,或者有人可能会看到重构您的类/模块代码的机会。显然,该人不会挖掘该课程的文档或知道该特定的课程/模块在您的文档中有一个部分。
无论如何,通过依赖包括类内部,不仅你的mixin使自己依赖,而且最终使任何包含它的类/模块成为依赖。这绝对不是一件好事。因为,您无法控制谁或哪个类/模块包含您的mixin,您永远不会有信心引入更改。没有打破任何东西就不会有变化的信心是项目的糟糕!
&#34;解决方法&#34;可能是 - &#34; 用测试&#34;覆盖它。但是,请考虑自己或其他人在2年内维护该代码。你会记得报道你的新课程,包括mixin,以确保它符合所有mixin依赖要求吗?我相信你或新的维护者不会。
因此,根据维护或基本OOP原则,您的mixin 不得依赖任何包含类/模块。
现在,让我们谈谈规则位总是有例外。
你可以例外,前提是mixin依赖不会引入&#34;惊喜&#34;你的代码。所以,没关系,如果mixin依赖关系在你的团队中是众所周知的,或者它们是一个约定。另一种情况可能是在内部使用mixin并且您可以控制谁使用它(基本上,当您在自己的项目中使用它时)。
OOP在开发可维护系统方面的关键优势是它能够隐藏/封装实现细节。让mixin依赖于包含它的任何类,将所有多年的OOP经验抛到窗外。
答案 2 :(得分:2)
我会说mixin不应该对它所包含的特定类做出假设,但是对公共父类(分别是它的公共方法)做出假设是完全没问题的。
很好的例子:可以在一个将包含在控制器中的mixin中调用transition: all 0.3s ease-in-out;
。
或者,根据你的例子更准确,我认为这样的事情会很好:
params
答案 3 :(得分:2)
当情况需要时,你应该毫不犹豫地在mixin模块中包含实例变量。
例如,假设您写道:
<div class="section">
<div class="left">
<img class="image" src="http://sysomos.com/sites/default/files/map_twitter.png" />
</div>
</div>
<div class="section">
<div class="right">
<img class="image" src="http://sysomos.com/sites/default/files/map_twitter.png" />
</div>
</div>
现在假设有人要求检查体重。我们可以添加方法
class A
def initialize(h)
@h = h
end
def confirm_colour(colour)
@h[:colour] == colour
end
def confirm_size(size)
@h[:size] == size
end
def confirm_all(colour, size)
confirm_colour(colour) && confirm_size(size)
end
end
a = A.new(:colour=>:blue, :size=>:medium, :weight=>10)
a.confirm_all(:blue, :medium)
#=> true
a.confirm_all(:blue, :large)
#=> false
并将def confirm_weight(weight)
@h[:weight] == weight
end
更改为
confirm_all
但有更好的方法:将所有支票放入模块中。
def confirm_all(colour, size)
confirm_colour(colour) && confirm_size(size) && confirm_weight(size)
end
然后在module Checks
def confirm_colour(g)
@h[:colour] == g[:colour]
end
def confirm_size(g)
@h[:size] == g[:size]
end
def confirm_weight(g)
@h[:weight] == g[:weight]
end
end
中包含该模块并执行所有检查。
A
这样做的好处是,当要添加或删除检查时,只会影响模块;不需要对课程进行任何更改。这个例子无疑是人为的,但它是现实世界情况的一小步。
答案 4 :(得分:0)
尽管进行这些假设是很常见的,但是您可能需要考虑使用不同的模式来编写更具可组合性和可测试性的代码:依赖注入。
module Fooable
def add(one, two)
one + two
end
end
class Bar
attr_accessor :val_one, :val_two
include Fooable
def calculate
add @val_one, @val_two
end
end
尽管它增加了一个额外的间接层,但通常值得这样做,因为它有可能在大量类中使用该关注点,并使测试代码更容易。