Ruby中的java接口等效什么?


101

我们可以像在Java中一样在Ruby中公开接口并强制Ruby模块或类来实现interface定义的方法吗?

一种方法是使用继承和method_missing来实现相同,但是还有其他更合适的方法吗?



6
您应该双重自问,为什么您甚至需要这个。通常,足够多的接口仅用于编译该死的东西,这在ruby中不是问题。
阿尼斯·拉普萨

1
这个问题可能会或可能不会被视为[ 在Ruby中,等同于C#中的接口?](StackOverflow.Com/q/3505521/#3507460)。
约尔格W¯¯米塔格

2
为什么我需要这个?我想实现一些您可以称为“可版本化”的功能,这可以使文档/文件可版本化,但可以使用...来实现版本化。例如,我可以使用现有的存储库软件(如SVN或CVS)来实现版本化。无论我选择哪种机制,它都应提供一些基本的最低功能。我想使用诸如接口之类的接口,以通过任何新的基础存储库实现来强制执行这些最低限度的最小功能。
crazycrv

Sandi Metz在她的POODR书中使用测试来记录接口。确实值得读这本书。截至2015年,我会说@ aleksander-pohl的回答是最好的。
格雷格·丹

Answers:


85

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

请注意,您必须注意不要将Interface的概念(它是单元的职责,保证和协议的抽象规范)interface与Java,C#和VB.NET编程中的关键字的概念混为一谈。语言。在Ruby中,我们一直都在使用前者,但后者根本不存在。

区分两者非常重要。重要的是接口,而不是接口interface。该interface告诉您几乎没有什么有用的。没有什么比Java中的标记器接口更好地证明了这一点,标记器接口根本没有成员:只需看一下java.io.Serializableand java.lang.Cloneable;这两个interface有什么恶意非常不同的事情,但他们有完全相同的签名。

所以,如果两个interfaces表示不同的含义,具有相同的签名,有什么确切的是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

  • 收集时间不会减少
  • 之前收藏中的所有物品仍然存在
  • 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>它实际上在哪里表示它是一个集合?无处!或更确切地说,在文档中。用英语讲。

interfacesJava和.NET的几乎所有情况下,所有相关信息实际上都在文档中,而不是类型中。因此,如果这些类型仍然不能告诉您任何有趣的事情,为什么还要保留它们呢?为什么不仅仅坚持文档呢?而这正是Ruby所做的。

注意,还有其他一些语言可以实际地以有意义的方式描述接口。然而,这些语言通常不叫,它描述了构建接口interface”,他们称之为type。例如,在依赖类型的编程语言中,您可以表达以下属性:sort函数返回与原始长度相同的集合的集合,原始中的每个元素也都在排序的集合中,而没有更大的元素出现在较小元素之前。

简而言之:Ruby没有Java的等效项interface。但是,它确实具有与Java Interface等效的功能,并且与Java:文档中的功能完全相同。

同样,就像在Java中一样,验收测试也可以用于指定接口

特别是在Ruby中,对象的接口由它可以执行的操作,不是存在的内容class或其module混合的内容决定。任何具有<<方法的对象都可以附加到该对象上。这是在单元测试中,在那里你可以简单地传递一个非常有用的ArrayString代替更复杂Logger,即使ArrayLogger不共享一个明确interface的事实,他们都有一个名为方法分开<<

另一个例子是StringIO,它实现了相同的接口作为IO和的这样一个大的部分接口File,但不共享任何共同的祖先除了Object


279
虽然阅读不错,但我认为答案无济于事。它读起来就像一篇关于为什么interface无用的论文,错过了它的使用要点。可以说红宝石是动态键入的,并且在头脑中有不同的关注点,这使IOC这样的概念变得不必要/不受欢迎。如果您习惯按合同设计,这是一个艰难的转变。Rails可以从中受益,核心团队如您在最新版本中看到的那样。
goliatone 2011年

12
后续问题:在Ruby中记录接口的最佳方法是什么?Java关键字interface可能未提供所有相关信息,但确实提供了放置文档的明显位置。我已经在Ruby中编写了一个实现(足够)IO的类,但是我是通过反复试验来做到这一点的,并且对该过程不太满意。我还编写了自己的接口的多个实现,但是记录所需的方法以及应该执行的方法以使团队中的其他成员可以创建实现被证明是一个挑战。
帕特里克(Patrick)

9
interface 结构确实只需要对待不同类型,在静态类型的单继承语言相同的(例如治疗LinkedHashSet,并ArrayList同时作为Collection),它与几乎没有做接口为这个答案节目。Ruby不是静态类型的,因此不需要构造
Esailija 2013年

16
我将其读为“一些接口没有意义,因此接口很糟糕。为什么要使用接口?”。它没有回答问题,坦率地说,听起来像是谁不了解接口的用途及其益处。
Oddman

13
通过引用一种在称为“ add”的函数中执行删除的方法来说明List接口的无效性,这是一个还原性荒谬参数的经典示例。特别是可以用任何一种语言(包括红宝石)编写一种方法,该方法执行的功能与预期的不同。这不是针对“接口”的有效参数,它只是错误的代码。
贾斯汀·欧姆

58

尝试使用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

更新:八年后(2020年),ruby现在支持通过冰糕的静态类型界面。请参见冰糕文档中的抽象类和接口


15
我确实认为这应该是公认的答案。这是大多数类型弱语言可以提供类似Java的接口的方式。被接受的解释了为什么Ruby没有接口,而不是如何模拟它们。
SystematicFrank

1
我同意,这个答案对我成为Java开发人员而言,向Ruby迁移的帮助远超过上述公认的答案。
2013年

是的,但是接口的全部要点是它具有相同的方法名称,但是具体类必须是实现行为的类,这大概是不同的。那么在共享示例中我应该测试什么?
罗布·怀斯

Ruby使一切变得务实。如果您想要编写成文档的代码并编写良好的代码,请添加测试/规范,这将是一种静态的类型检查。
Dmitry Polushkin

41

我们可以像在Java中一样在Ruby中公开接口并强制 Ruby模块或类来实现interface定义的方法吗?

Ruby没有该功能。原则上,不需要它们,因为Ruby使用所谓的鸭子类型

您可以采取几种方法。

编写引发异常的实现;如果子类尝试使用未实现的方法,它将失败

class CollectionInterface
  def add(something)
    raise 'not implemented'
  end
end

除上述内容外,您还应编写可强制执行合同的测试代码(此处的其他内容误称为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中的所有事情都在运行时发生。没有编译时检查。如果将此与测试结合起来,那么您应该能够发现错误。更进一步,如果您进一步介绍上述内容,则可能可以编写一个接口,该接口在第一次创建该类的对象时对该类执行检查。使您的测试像调用一样简单MyCollection.new...是的,在顶部:)


好的,但是如果您的Collection = MyCollection实现了在接口中未定义的方法,则此方法可以很好地工作,因此您不能确保您的对象仅具有接口方法定义。
Joel AZEMAR

太棒了,谢谢。鸭子类型很好,但有时可以很好地与其他开发人员明确交流界面的行为方式。
Mirodinho

10

正如这里每个人所说的,没有红宝石接口系统。但是通过自省,您可以很容易地实现它。这是一个简单的示例,可以通过多种方式进行改进以帮助您入门:

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


5

Java中没有接口之类的东西。但是,您还可以在红宝石中享受其他乐趣。

如果要实现某种类型和接口-以便可以检查对象是否具有您需要的某些方法/消息,则可以查看rubycontracts。它定义了一种类似于PyProtocols的机制。关于ruby中类型检查的博客在这里

上面提到的方法并不是活着的项目,尽管起初目标似乎不错,但似乎大多数红宝石开发人员都可以活着而无需严格的类型检查。但是ruby的灵活性使得可以执行类型检查。

如果要通过某些行为扩展对象或类(在ruby中是同一事物)或某种程度上具有ruby的多重继承方式,请使用includeor extend机制。随着include您可以包括从另一个类或模块的方法为对象。使用,extend您可以为类添加行为,以便其实例具有添加的方法。不过,这只是一个简短的解释。

我认为解决Java接口需求的最佳方法是了解ruby对象模型(例如,参见Dave Thomas的讲座)。可能您会忘记Java接口。或者您在日程安排中有一个特殊的应用程序。


那些戴夫·托马斯(Dave Thomas)的演讲背后是收费壁垒。
Purplejacket

5

正如许多答案所表明的那样,Ruby中没有办法通过从类(包括模块或类似的东西)继承来强制类实现特定的方法。原因可能是TDD在Ruby社区中盛行,这是定义接口的另一种方式-测试不仅指定方法的签名,还指定行为。因此,如果要实现另一个类,该类实现一些已经定义的接口,则必须确保所有测试都通过。

通常,使用模拟和存根来独立定义测试。但是,还有像Bogus这样的工具,可以用来定义合同测试。这样的测试不仅定义了“主要”类的行为,而且还检查了协作类中是否存在存根方法。

如果您真的关心Ruby中的接口,我建议您使用实现合同测试的测试框架。


3

这里的所有示例都很有趣,但是缺少对Interface Contract的验证,这意味着如果您希望您的对象实现所有Interface方法的定义,而仅实现此定义,则不能。因此,我为您提供一个简单的示例(可以肯定地加以改进),以确保您完全拥有通过接口(合同)获得的期望。

考虑使用这样的已定义方法的接口

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

然后,您可以至少使用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

2

对于我的其他需求,我对carlosayam的回答做了一些扩展。这为Interface类添加了两个附加的强制实施和选项: required_variable并且optional_variable支持默认值。

我不确定您是否想使用此元编程太大的东西。

正如其他答案所指出的那样,最好是编写能够正确执行所要查找内容的测试,尤其是当您要开始执行参数并返回值时。

请注意,此方法只会在调用代码时引发错误。在运行时,仍然需要进行测试才能正确实施。

代码示例

interface.rb

module Interface
  def method(name)
    define_method(name) do
      raise "Interface method #{name} not implemented"
    end
  end

  def required_variable(name)
    define_method(name) do
      sub_class_var = instance_variable_get("@#{name}")
      throw "@#{name} must be defined" unless sub_class_var
      sub_class_var
    end
  end

  def optional_variable(name, default)
    define_method(name) do
      instance_variable_get("@#{name}") || default
    end
  end
end

plugin.rb

我将单例库用于正在使用的给定模式。这样,任何子类在实现此“接口”时都将继承单例库。

require 'singleton'

class Plugin
  include Singleton

  class << self
    extend Interface

    required_variable(:name)
    required_variable(:description)
    optional_variable(:safe, false)
    optional_variable(:dependencies, [])

    method :run
  end
end

my_plugin.rb

对于我的需求,这要求实现“接口”的类将其子类化。

class MyPlugin < Plugin

  @name = 'My Plugin'
  @description = 'I am a plugin'
  @safe = true

  def self.run
    puts 'Do Stuff™'
  end
end

2

Ruby本身与Java接口没有完全等效的接口。

但是,由于这样的接口有时可能非常有用,因此我自己为Ruby开发了gem,它以非常简单的方式模拟Java接口。

叫做class_interface

它的工作非常简单。首先安装gem by gem install class_interface或将其添加到您的Gemfile中并运行bundle install

定义接口:

require 'class_interface'

class IExample
  MIN_AGE = Integer
  DEFAULT_ENV = String
  SOME_CONSTANT = nil

  def self.some_static_method
  end

  def some_instance_method
  end
end

实现该接口:

class MyImplementation
  MIN_AGE = 21
  DEFAULT_ENV = 'dev' 
  SOME_CONSTANT = 'some_value'

  def specific_method
    puts "very specific"
  end

  def self.some_static_method
    puts "static method is implemented!"
  end

  def some_instance_method
    # implementation
  end

  def self.another_methods
    # implementation
  end

  implements IExample
end

如果您未实现某个常量或方法,或者参数编号不匹配,则在执行Ruby程序之前会引发相应的错误。您甚至可以通过在接口中分配类型来确定常量的类型。如果为nil,则允许任何类型。

必须在类的最后一行调用“ implements”方法,因为这是上面已实现的方法已被检查的代码位置。

更多信息请参见:https : //github.com/magynhard/class_interface


0

我意识到,对于需要特定行为的对象进行安全检查时,我过多地使用了“未实现错误”模式。最终编写了一个gem,该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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.