在 Ruby 中 Java 接口等价于什么?

我们能否像在 Java 中那样在 Ruby 中公开接口,并强制 Ruby 模块或类来实现接口定义的方法。

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

67872 次浏览

Ruby 和其他语言一样有 接口

注意,你必须小心不要把 接口的概念混为一谈,接口是一个单元的职责、保证和协议的抽象规范,而 interface是 Java、 C # 和 VB.NET 编程语言中的关键字。在 Ruby 中,我们一直使用前者,但后者根本不存在。

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

So, if two interfaces that mean different things, have the same signature, what exactly is the interface even guaranteeing you?

另一个很好的例子:

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>中,它实际上在哪里说它是,你知道,一个 准备好了?哪儿也不去!或者更确切地说,在文档中。说英语。

interfaces的几乎所有情况下,包括 Java 和。NET 中,所有的 相关的信息实际上都在文档中,而不是在类型中。那么,如果这些类型不告诉你任何有趣的事情,为什么还要留着它们呢?为什么不坚持做文档工作呢?露比就是这么做的。

请注意,在 其他语言中,接口实际上可以用一种有意义的方式来描述。然而,这些语言通常不会将描述 接口的结构称为“ interface”,而是称之为 type。例如,在依赖类型的编程语言中,可以表示这样的属性: sort函数返回与原始元素长度相同的集合,原始元素中的每个元素也都在已排序的集合中,没有更大的元素出现在较小的元素之前。

因此,简而言之: Ruby 没有等价于 Javainterface的东西。但是,它的 does等价于 Java接口,而且它与 Java: 文档中的 does完全相同。

而且,就像 Java 一样,验收测试也可以用来指定 接口

In particular, in Ruby, the module0 of an object is determined by what it can module1, not what class is is, or what module it mixes in. Any object that has a << method can be appended to. This is very useful in unit tests, where you can simply pass in an Array or a String instead of a more complicated Logger, even though Array and Logger do not share an explicit interface apart from the fact that they both have a method called <<.

另一个例子是 StringIO,它实现与 IO相同的 接口,因此是 File接口的很大一部分,但是除了 Object之外没有共享任何共同的祖先。

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

如果您希望实现某种类型和接口——这样就可以检查对象是否具有您需要的方法/消息——那么您可以查看 rubycontracts。它定义了一种类似于 协议的机制。关于 Ruby 中的类型检查的博客是 给你

The mentioned approached are not living projects, although the goal seems to be nice at first, it seems that most of the ruby developers can live without strict type checking. But the flexibility of ruby enables to implement type checking.

If you want to extend objects or classes (the same thing in ruby) by certain behaviors or somewhat have the ruby way of multiple inheritance, use the include or extend mechanism. With include you can include methods from another class or module into an object. With extend you can add behavior to a class, so that its instances will have the added methods. That was a very short explanation though.

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

Ruby 3(2021)

Ruby 3.0引入了一个名为 苏格兰皇家银行的类型系统,它支持接口。

interface _IntegerConvertible
def to_int: () -> Integer
end

Source: https://blog.appsignal.com/2021/01/27/rbs-the-new-ruby-3-typing-language-in-action.html

果汁冰糕(2020)

Stripe 构建了一个名为 Sorbet 的静态类型检查器,它支持接口。

RSpec (原答案,2012)

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

正如这里的每个人所说,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

Removing one of the methods declared on Person or change it number of arguments will raise a NotImplementedError.

我们可以像在 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一样简单... 是的,超出了上面的范围:)

正如许多答案所表明的,在 Ruby 中没有办法强制一个类实现一个特定的方法,通过继承一个类,包括一个模块或任何类似的东西。原因可能是 TDD 在 Ruby 社区中的流行,这是定义接口的一种不同的方式——测试不仅指定了方法的签名,还指定了行为。因此,如果你想实现一个不同的类,实现一些已经定义的接口,你必须确保所有的测试通过。

通常使用模拟和存根隔离地定义测试。但是也有像 假的这样的工具,允许定义契约测试。这样的测试不仅定义“主”类的行为,而且还检查存根方法是否存在于协作类中。

If you are really concerned with interfaces in Ruby I would recommend using a testing framework that implements contract testing.

这里的所有例子都很有趣,但是缺少对 Interface 契约的验证,我的意思是,如果你想让你的对象实现所有 Interface 方法定义,只有这一个你不能实现。因此,我建议您使用一个简单的示例(可以肯定地加以改进) ,以确保您通过您的 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

Then you can write a object with at least the Interface contract:

class FooImplementation
def foo
puts('foo')
end
def bar
puts('bar')
end
end

您可以通过 Interface 安全地调用 Object,以确保您正是 Interface 定义的对象

#  > FooInterface.new(FooImplementation.new).foo
# => foo


#  > FooInterface.new(FooImplementation.new).bar
# => FooInterface::NotDefinedMethod: bar doesn't belong to Interface definition

您也可以确保您的 Object 实现了所有的 Interface 方法定义

class BadFooImplementation
end


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

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

为了满足我的额外需求,我已经延长了 Carlosaam 的答复。这为 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

对于我使用的给定模式,我使用了 singleton 库。这样,在实现这个“接口”时,任何子类都可以继承单例库。

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

我的插件

For my needs this requires that the class implementing the "interface" subclasses it.

class MyPlugin < Plugin


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


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

Ruby 本身没有与 Java 中的接口完全等价的东西。

然而,由于这样的接口有时非常有用,我自己为 Ruby 开发了一个 gem,它以一种非常简单的方式模拟 Java 接口。

叫做 class_interface

它的工作原理非常简单。首先通过 gem install class_interface安装 gem 或者将它添加到 Gemfile 并运行 bundle install

Defining an interface:

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,则允许任何类型。

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

More at: Https://github.com/magynhard/class_interface