什么是Ruby中的等价接口?

我们可以像在java中那样暴露Ruby中的接口,并强制Ruby模块或类来实现由接口定义的方法。

一种方法是使用inheritance和method_missing来实现,但有没有其他更合适的方法可用?

Ruby与其他语言一样具有接口

请注意,不要将接口的概念与Java,C#和VB.NET中作为关键字的interface的概念相混淆,这是对单元的职责,保证和协议的抽象说明。编程语言。 在Ruby中,我们始终使用前者,但后者根本不存在。

区分两者是非常重要的。 重要的是接口 ,而不是interfaceinterface告诉你几乎没有用。 没有什么比Java中的标记接口更好的performance出来,它们是没有成员的接口:只要看看java.io.Serializablejava.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 ,即使ArrayLogger不共享明确的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的方式,可以使用includeextend机制。 使用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