什么是Ruby中的等价接口?
我们可以像在java中那样暴露Ruby中的接口,并强制Ruby模块或类来实现由接口定义的方法。
一种方法是使用inheritance和method_missing来实现,但有没有其他更合适的方法可用?
Ruby与其他语言一样具有接口 。
请注意,不要将接口的概念与Java,C#和VB.NET中作为关键字的interface
的概念相混淆,这是对单元的职责,保证和协议的抽象说明。编程语言。 在Ruby中,我们始终使用前者,但后者根本不存在。
区分两者是非常重要的。 重要的是接口 ,而不是interface
。 interface
告诉你几乎没有用。 没有什么比Java中的标记接口更好的performance出来,它们是没有成员的接口:只要看看java.io.Serializable
和java.lang.Cloneable
; 这两个interface
意味着非常不同的东西,但他们有完全相同的签名。
所以,如果两个interface
意味着不同的东西,具有相同的签名,甚至保证你的interface
到底是什么?
另一个好例子:
package java.util; interface List<E> implements Collection<E>, Iterable<E> { void add(int index, E element) throws UnsupportedOperationException, ClassCastException, NullPointerException, IllegalArgumentException, IndexOutOfBoundsException; }
什么是java.util.List<E>.add
的接口 ?
- 收集的长度不会减less
- 之前收集的所有物品都还在那里
- 该
element
在集合中
那些在interface
实际显示的是哪一个? 没有! interface
中没有什么说Add
方法甚至必须添加 ,也可能只是从集合中删除一个元素。
这是该interface
的完全有效的实现:
class MyCollection<E> implements java.util.List<E> { void add(int index, E element) throws UnsupportedOperationException, ClassCastException, NullPointerException, IllegalArgumentException, IndexOutOfBoundsException { remove(element); } }
另一个例子:在java.util.Set<E>
中它实际上是说它是一个集合 ? 无处! 或者更确切地说,在文档中。 用英语。
在几乎所有的Java和.NET interfaces
情况下,所有相关信息实际上都是在文档中,而不是在types中。 所以,如果这些types没有告诉你任何有趣的事情,为什么要保持它们呢? 为什么不坚持文档? 这正是Ruby所做的。
请注意,还有其他一些语言,实际上可以用一种有意义的方式来描述接口 。 但是,这些语言通常不会调用描述接口 “ interface
”的构造,他们称之为type
。 例如,在一个依赖types的编程语言中,可以表示一个sort
函数返回一个与原始长度相同长度的集合的属性,即原始中的每个元素也都在sorting后的集合中,元素出现在较小的元素之前。
简而言之,Ruby没有与Java interface
等价的interface
。 但是,它具有与Java 接口相同的function ,并且与Java:文档中的完全相同。
另外,就像在Java中一样, 验收testing也可以用来指定接口 。
特别是在Ruby中,一个对象的接口是由它可以做什么决定的,而不是什么class
,或者它混入了哪个module
。任何具有<<
方法的对象都可以被附加到。 这在unit testing中非常有用,在这种情况下,您可以简单地传递一个Array
或一个String
而不是一个更复杂的Logger
,即使Array
和Logger
不共享明确的interface
除了它们都有一个名为<<
。
另一个例子是StringIO
,它实现与IO
相同的接口 ,因此是File
的接口的一大部分,但是除了Object
之外没有共享任何共同的祖先。
尝试rspec的“共享示例”:
https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/example-groups/shared-examples
你为你的界面编写一个规范,然后在每个实现者的规范中放一行,例如。
it_behaves_like "my interface"
完整的例子:
RSpec.shared_examples "a collection" do describe "#size" do it "returns number of elements" do collection = described_class.new([7, 2, 4]) expect(collection.size).to eq(3) end end end RSpec.describe Array do it_behaves_like "a collection" end RSpec.describe Set do it_behaves_like "a collection" end
我们可以像在java中一样暴露Ruby中的接口,并强制 Ruby模块或类来实现由接口定义的方法。
Ruby没有这个function。 原则上,它不需要它们,因为Ruby使用所谓的鸭子打字 。
你可以采取的方法很less。
编写引发exception的实现; 如果一个子类试图使用未实现的方法,它将会失败
class CollectionInterface def add(something) raise 'not implemented' end end
除了上面的内容外,你还应该编写强制执行合同的testing代码(这里的其他post不正确地调用Interface )
如果你发现自己编写的方法总是像以前一样,那就写一个帮助模块来捕获这个方法
module Interface def method(name) define_method(name) { |*args| raise "interface method #{name} not implemented" } end end class Collection extend Interface method :add method :remove end
现在,结合上面的Ruby模块,你接近你想要的…
module Interface def method(name) define_method(name) { |*args| raise "interface method #{name} not implemented" } end end module Collection extend Interface method :add method :remove end col = Collection.new # <-- fails, as it should
然后你可以做
class MyCollection include Collection def add(thing) puts "Adding #{thing}" end end c1 = MyCollection.new c1.add(1) # <-- output 'Adding 1' c1.remove(1) # <-- fails with not implemented
让我再次强调:这是一个基础,因为Ruby中的所有内容都是在运行时发生的。 没有编译时间检查。 如果你将它与testing结合起来,那么你应该能够找出错误。 更进一步的,如果你进一步考虑以上的话,你可能可以编写一个接口来在类的第一次检查时创build该类的一个对象。 使您的testing像调用MyCollection.new
一样简单…是的,在顶部:)
正如大家所说,没有ruby界面系统。 但通过自省,你可以很容易地实现它。 这里有一个简单的例子,可以通过很多方式来帮助你开始:
class Object def interface(method_hash) obj = new method_hash.each do |k,v| if !obj.respond_to?(k) || !((instance_method(k).arity+1)*-1) raise NotImplementedError, "#{obj.class} must implement the method #{k} receiving #{v} parameters" end end end end class Person def work(one,two,three) one + two + three end def sleep end interface({:work => 3, :sleep => 0}) end
删除在Person上声明的方法之一或者改变它的参数个数将引发一个NotImplementedError
。
Java方式中没有接口这样的东西。 但还有其他的东西,你可以享受ruby。
如果你想实现某种types和接口 – 这样就可以检查对象是否有一些你需要的方法/消息 – 然后你可以看看rubycontracts 。 它定义了一个类似于PyProtocols的机制。 这里是一个关于rubytypes检查的博客。
上述提到的并不是生活中的项目,尽pipe目标看起来似乎很好,但似乎大多数Ruby开发者可以在没有严格的types检查的情况下生活。 但是ruby的灵活性可以实现types检查。
如果你想通过某些行为来扩展对象或类(在ruby中是同样的事情),或者有一些Rubyinheritance的方式,可以使用include
或extend
机制。 使用include
可以将来自其他类或模块的方法包含到对象中。 通过extend
您可以将行为添加到类中,以便其实例将具有添加的方法。 尽pipe这是一个非常简短的解释。
我认为解决Java接口需求的最好方法是理解ruby对象模型(参见Dave Thomas讲座 )。 可能你会忘记Java接口。 或者你有一个特殊的应用程序在你的时间表。
这里的所有例子都很有趣,但是缺less接口合约的validation,我的意思是如果你想让你的对象实现所有的接口方法定义,只有这个你不能。 所以我build议你一个简单的例子(可以肯定的改进),以确保你有你期望通过你的接口(合同)。
考虑你的界面与定义的方法
class FooInterface class NotDefinedMethod < StandardError; end REQUIRED_METHODS = %i(foo).freeze def initialize(object) @object = object ensure_method_are_defined! end def method_missing(method, *args, &block) ensure_asking_for_defined_method!(method) @object.public_send(method, *args, &block) end private def ensure_method_are_defined! REQUIRED_METHODS.each do |method| if !@object.respond_to?(method) raise NotImplementedError, "#{@object.class} must implement the method #{method}" end end end def ensure_asking_for_defined_method!(method) unless REQUIRED_METHODS.include?(method) raise NotDefinedMethod, "#{method} doesn't belong to Interface definition" end end end
然后你可以写一个至less有Interface接口的对象:
class FooImplementation def foo puts('foo') end def bar puts('bar') end end
您可以通过您的界面安全地调用您的对象,以确保您正是界面所定义的
# > FooInterface.new(FooImplementation.new).foo # => foo # > FooInterface.new(FooImplementation.new).bar # => FooInterface::NotDefinedMethod: bar doesn't belong to Interface definition
而且你也可以确保你的对象实现你所有的接口方法定义
class BadFooImplementation end # > FooInterface.new(BadFooImplementation.new) # => NotImplementedError: BadFooImplementation must implement the method foo
正如许多答案指出的那样,Ruby中没有办法通过inheritance一个类,包括一个模块或类似的东西来迫使一个类实现一个特定的方法。 原因可能是Ruby社区中TDD的stream行,这是定义接口的一种不同方式 – testing不仅指定方法的签名,而且指定行为。 因此,如果你想实现一个不同的类,它实现了一些已经定义的接口,你必须确保所有的testing都通过了。
通常使用模拟和存根来隔离testing。 但也有一些工具,如伪装 ,允许定义合同testing。 这样的testing不仅定义了“主”类的行为,而且还检查了合作类中存在的存根方法。
如果你真的关心Ruby中的接口,我会推荐使用一个实现合同testing的testing框架。
我意识到我使用的模式“未实现的错误”太多,以安全检查对象,我想特定的行为。 结束了写一个基本上允许使用这样一个接口的gem:
require 'playable' class Instrument implements Playable end Instrument.new #will throw: Interface::Error::NotImplementedError: Expected Instrument to implement play for interface Playable
它不检查方法参数 。 它的版本是0.2.0
。 更详细的例子在https://github.com/bluegod/rint