我们可以像在Java中一样在Ruby中公开接口并强制Ruby模块或类来实现interface定义的方法吗?
一种方法是使用继承和method_missing来实现相同,但是还有其他更合适的方法吗?
我们可以像在Java中一样在Ruby中公开接口并强制Ruby模块或类来实现interface定义的方法吗?
一种方法是使用继承和method_missing来实现相同,但是还有其他更合适的方法吗?
Answers:
Ruby 和其他语言一样具有接口。
请注意,您必须注意不要将Interface的概念(它是单元的职责,保证和协议的抽象规范)interface
与Java,C#和VB.NET编程中的关键字的概念混为一谈。语言。在Ruby中,我们一直都在使用前者,但后者根本不存在。
区分两者非常重要。重要的是接口,而不是接口interface
。该interface
告诉您几乎没有什么有用的。没有什么比Java中的标记器接口更好地证明了这一点,标记器接口根本没有成员:只需看一下java.io.Serializable
and java.lang.Cloneable
;这两个interface
有什么恶意非常不同的事情,但他们有完全相同的签名。
所以,如果两个interface
s表示不同的含义,具有相同的签名,有什么确切的是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>
它实际上在哪里表示它是一个集合?无处!或更确切地说,在文档中。用英语讲。
在interfaces
Java和.NET的几乎所有情况下,所有相关信息实际上都在文档中,而不是类型中。因此,如果这些类型仍然不能告诉您任何有趣的事情,为什么还要保留它们呢?为什么不仅仅坚持文档呢?而这正是Ruby所做的。
注意,还有其他一些语言可以实际地以有意义的方式描述接口。然而,这些语言通常不叫,它描述了构建接口 “ interface
”,他们称之为type
。例如,在依赖类型的编程语言中,您可以表达以下属性:sort
函数返回与原始长度相同的集合的集合,原始中的每个元素也都在排序的集合中,而没有更大的元素出现在较小元素之前。
简而言之:Ruby没有Java的等效项interface
。但是,它确实具有与Java Interface等效的功能,并且与Java:文档中的功能完全相同。
同样,就像在Java中一样,验收测试也可以用于指定接口。
特别是在Ruby中,对象的接口由它可以执行的操作,不是存在的内容class
或其module
混合的内容决定。任何具有<<
方法的对象都可以附加到该对象上。这是在单元测试中,在那里你可以简单地传递一个非常有用的Array
或String
代替更复杂Logger
,即使Array
和Logger
不共享一个明确interface
的事实,他们都有一个名为方法分开<<
。
另一个例子是StringIO
,它实现了相同的接口作为IO
和的这样一个大的部分接口的File
,但不共享任何共同的祖先除了Object
。
interface
无用的论文,错过了它的使用要点。可以说红宝石是动态键入的,并且在头脑中有不同的关注点,这使IOC这样的概念变得不必要/不受欢迎。如果您习惯按合同设计,这是一个艰难的转变。Rails可以从中受益,核心团队如您在最新版本中看到的那样。
interface
可能未提供所有相关信息,但确实提供了放置文档的明显位置。我已经在Ruby中编写了一个实现(足够)IO的类,但是我是通过反复试验来做到这一点的,并且对该过程不太满意。我还编写了自己的接口的多个实现,但是记录所需的方法以及应该执行的方法以使团队中的其他成员可以创建实现被证明是一个挑战。
interface
结构确实只需要对待不同类型,在静态类型的单继承语言相同的(例如治疗LinkedHashSet
,并ArrayList
同时作为Collection
),它与几乎没有做接口为这个答案节目。Ruby不是静态类型的,因此不需要构造。
尝试使用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现在支持通过冰糕的静态类型界面。请参见冰糕文档中的抽象类和接口。
我们可以像在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
...是的,在顶部:)
正如这里每个人所说的,没有红宝石接口系统。但是通过自省,您可以很容易地实现它。这是一个简单的示例,可以通过多种方式进行改进以帮助您入门:
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中没有接口之类的东西。但是,您还可以在红宝石中享受其他乐趣。
如果要实现某种类型和接口-以便可以检查对象是否具有您需要的某些方法/消息,则可以查看rubycontracts。它定义了一种类似于PyProtocols的机制。关于ruby中类型检查的博客在这里。
上面提到的方法并不是活着的项目,尽管起初目标似乎不错,但似乎大多数红宝石开发人员都可以活着而无需严格的类型检查。但是ruby的灵活性使得可以执行类型检查。
如果要通过某些行为扩展对象或类(在ruby中是同一事物)或某种程度上具有ruby的多重继承方式,请使用include
or extend
机制。随着include
您可以包括从另一个类或模块的方法为对象。使用,extend
您可以为类添加行为,以便其实例具有添加的方法。不过,这只是一个简短的解释。
我认为解决Java接口需求的最佳方法是了解ruby对象模型(例如,参见Dave Thomas的讲座)。可能您会忘记Java接口。或者您在日程安排中有一个特殊的应用程序。
正如许多答案所表明的那样,Ruby中没有办法通过从类(包括模块或类似的东西)继承来强制类实现特定的方法。原因可能是TDD在Ruby社区中盛行,这是定义接口的另一种方式-测试不仅指定方法的签名,还指定行为。因此,如果要实现另一个类,该类实现一些已经定义的接口,则必须确保所有测试都通过。
通常,使用模拟和存根来独立定义测试。但是,还有像Bogus这样的工具,可以用来定义合同测试。这样的测试不仅定义了“主要”类的行为,而且还检查了协作类中是否存在存根方法。
如果您真的关心Ruby中的接口,我建议您使用实现合同测试的测试框架。
这里的所有示例都很有趣,但是缺少对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
对于我的其他需求,我对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
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
我意识到,对于需要特定行为的对象进行安全检查时,我过多地使用了“未实现错误”模式。最终编写了一个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