什么是Ruby中的java接口等价物?

时间:2010-12-14 08:40:10

标签: ruby interface

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

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

10 个答案:

答案 0 :(得分:82)

Ruby拥有接口,就像任何其他语言一样。

请注意,您必须小心不要混淆 Interface 的概念,这是一个单位的责任,保证和协议的抽象规范,具有{{1}的概念。这是Java,C#和VB.NET编程语言中的关键字。在Ruby中,我们一直使用前者,但后者根本就不存在。

区分这两者非常重要。重要的是接口,而不是interfaceinterface告诉你几乎没什么用处。没有什么能比Java中的标记接口更好地证明这一点,它们是完全没有成员的接口:只需看看java.io.Serializablejava.lang.Cloneable;这两个interface意味着非常不同的东西,但它们具有完全相同的签名。

那么,如果两个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在集合

哪些实际出现在element?没有! interface中没有任何内容表明interface方法甚至必须添加,它也可能中移除元素集合。

这是Add的完全有效的实现:

interface

另一个例子:在java.util.Set<E>中,它实际上是说它是吗?无处!或者更确切地说,在文档中。用英语。

在几乎所有来自Java和.NET的class MyCollection<E> implements java.util.List<E> { void add(int index, E element) throws UnsupportedOperationException, ClassCastException, NullPointerException, IllegalArgumentException, IndexOutOfBoundsException { remove(element); } } 的情况下,所有相关的信息实际上都在文档中,而不是在类型中。那么,如果类型不告诉你任何有趣的东西,为什么要保留它们呢?为什么不坚持文档?而这正是Ruby所做的。

请注意,其他语言中可以实际以有意义的方式描述接口。但是,这些语言通常不会调用描述接口interfaces”的构造,他们称之为interface。例如,在依赖类型的编程语言中,您可以表达type函数返回与原始函数长度相同的集合的属性,即原始函数中的每个元素也在已排序的集合中并且在较小的元素之前不会出现更大的元素。

因此,简而言之:Ruby没有Java sort的等价物。然而,它 具有与Java Interface 相同的功能,它与Java中的完全相同:文档。

此外,就像在Java中一样, Acceptance Tests 也可用于指定 Interfaces

特别是在Ruby中,对象的接口取决于它可以做什么,而不是interface是什么,或者是什么{{ 1}}它混入。任何具有class方法的对象都可以附加到。这在单元测试中非常有用,您可以简单地传入module<<而不是更复杂的Array,即使String和{{1}除了他们都有一个名为Logger的方法之外,不要共享一个明确的Array

另一个示例是StringIO,它实现与Logger相同的接口,因此interface接口的很大一部分除了<<之外,没有共享任何共同的祖先。

答案 1 :(得分:45)

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

答案 2 :(得分:34)

  

我们可以像在java中那样公开Ruby中的接口吗?强制执行 Ruby模块或类来实现接口定义的方法。

Ruby没有这个功能。原则上,它不需要它们,因为Ruby使用了所谓的duck typing

您可以采取的方法很少。

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

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

除此之外,你应该编写执行合同的测试代码(这里的其他帖子错误地称之为接口

如果您发现自己一直在编写类似上面的void方法,那么编写一个捕获该模块的辅助模块

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一样简单......是的,超过顶部:)

答案 3 :(得分:9)

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

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

答案 4 :(得分:4)

Java方式中没有接口这样的东西。但是你可以在红宝石中享受其他的东西。

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

上面提到的不是生活项目,虽然一开始目标看起来不错,但似乎大多数红宝石开发人员都可以在没有严格类型检查的情况下生活。但ruby的灵活性使得能够实现类型检查。

如果您想通过某些行为扩展对象或类(在ruby中相同的东西)或者有多重继承的ruby方式,请使用includeextend机制。使用include,您可以将另一个类或模块中的方法包含到对象中。使用extend,您可以向类添加行为,以便其实例具有添加的方法。这是一个非常简短的解释。

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

答案 5 :(得分:4)

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

通常,测试是使用模拟和存根单独定义的。但也有像Bogus这样的工具,允许定义合同测试。这些测试不仅定义了&#34;主要&#34;的行为。 class,还要检查协作类中是否存在stubbed方法。

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

答案 6 :(得分:3)

这里的所有示例都很有趣,但是缺少接口契约的验证,我的意思是如果你希望你的对象实现所有的接口方法定义,而只有你不能这样做。所以我建议你一个简单的例子(可以肯定地改进),以确保你有完全符合你期望通过你的接口(合同)。

使用已定义的方法(例如

)考虑您的界面
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

然后你可以编写一个至少包含接口契约的对象:

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

您还可以确保您的Object实现所有的接口方法定义

class BadFooImplementation
end

#  > FooInterface.new(BadFooImplementation.new)
# => NotImplementedError: BadFooImplementation must implement the method foo

答案 7 :(得分:2)

我已经对carlosayam的答案进行了一些扩展,以满足我的额外需求。这为Interface类添加了一些额外的强制执行和选项:required_variableoptional_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

根据我的需要,这要求实现“interface”的类将其子类化。

class MyPlugin < Plugin

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

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

答案 8 :(得分:2)

Ruby本身与Java接口不完全相同。

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

它叫class_interface

它非常简单。首先通过gem install class_interface安装gem或将其添加到您的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

答案 9 :(得分:0)

我意识到我正在使用模式&#34;未实现错误&#34;对于我想要特定行为的对象的安全检查太多了。结束编写一个基本上允许使用这样的界面的宝石:

require 'playable' 

class Instrument 
  implements Playable
end

Instrument.new #will throw: Interface::Error::NotImplementedError: Expected Instrument to implement play for interface Playable

它不检查方法参数。它的版本为0.2.0https://github.com/bluegod/rint

的更详细示例