【问题标题】:What is java interface equivalent in Ruby?Ruby 中的 java 接口等价物是什么?
【发布时间】:2011-05-25 03:36:32
【问题描述】:

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

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

【问题讨论】:

  • 你应该再次问自己为什么你甚至需要这个。通常使用足够多的接口来编译该死的东西,这在 ruby​​ 中不是问题。
  • 这个问题可能会或可能不会被认为是 [In Ruby, what is equivalent to an interface in C#? ](StackOverflow.Com/q/3505521/#3507460)。
  • 为什么我需要这个?我想实现一些你可以称之为“版本化”的东西,它使文档/文件可以版本化,但可以使用什么来实现版本化......例如我可以使用现有的存储库软件(如 SVN 或 CVS)使其可版本化。无论我选择什么不可靠的机制,它都应该提供一些基本的最小功能。我想使用类似接口的东西来通过任何新的底层存储库实现来强制实现这些最基本的功能。
  • Sandi Metz 在她的 POODR 书中使用测试来记录界面。这本书真的很值得一读。截至 2015 年,我会说 @aleksander-pohl 的答案是最好的。

标签: ruby interface


【解决方案1】:

红宝石 3​​ (2021)

Ruby 3.0 引入了一个名为 RBS 的类型系统,它支持接口。

interface _IntegerConvertible
   def to_int: () -> Integer
end

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

冰糕 (2020)

Stripe 构建了一个名为 Sorbet 的静态类型检查器,它支持接口。请参阅冰糕文档中的 Abstract Classes and Interfaces

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

【讨论】:

  • 我相信这应该是公认的答案。这是大多数类型弱语言可以提供类似 Java 的接口的方式。接受的解释解释了为什么 Ruby 没有接口,而不是如何模拟它们。
  • 我同意,作为一名迁移到 Ruby 的 Java 开发人员,这个答案对我的帮助比上述公认的答案要大得多。
  • 是的,但接口的全部意义在于它具有相同的方法名称,但具体的类必须是实现行为的类,这可能是不同的。那么我应该在共享示例中测试什么?
  • Ruby 让一切变得务实。如果您想要记录并编写良好的代码,请添加测试/规范,这将是一种静态类型检查。
【解决方案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程序执行之前会产生相应的错误。您甚至可以通过在接口中分配类型来确定常量的类型。如果为零,则允许任何类型。

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

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

【讨论】:

    【解决方案3】:

    针对我的其他需求,我对 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

    根据我的需要,这要求实现“接口”的类对其进行子类化。

    class MyPlugin < Plugin
    
      @name = 'My Plugin'
      @description = 'I am a plugin'
      @safe = true
    
      def self.run
        puts 'Do Stuff™'
      end
    end
    

    【讨论】:

      【解决方案4】:

      我意识到我过多地使用“未实现错误”模式来对我想要特定行为的对象进行安全检查。最后写了一个基本上允许使用这样的界面的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

      【讨论】:

        【解决方案5】:

        这里的所有示例都很有趣,但缺少对接口契约的验证,我的意思是,如果你希望你的对象实现所有接口方法定义,而只有这些你不能。因此,我向您推荐一个简单的示例(肯定可以改进),以确保您通过您的界面(合同)获得您期望的内容。

        考虑你的接口与定义的方法类似

        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
        

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

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

        【讨论】:

          【解决方案6】:

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

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

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

          那么,如果两个 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&lt;E&gt;.add接口是什么?

          • 集合的长度不会减少
          • 之前集合中的所有项目仍然存在
          • element 在集合中

          其中哪些实际上出现在interface 中?没有! interface 中没有任何内容表明 Add 方法甚至必须添加,它也可以从集合中删除一个元素。 p>

          这是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&lt;E&gt; 中,它实际上在哪里说它是一个集合?无处!或者更准确地说,在文档中。英文。

          在几乎所有interfaces 的情况下,无论是来自Java 还是.NET,所有相关 信息实际上都在文档中,而不是在类型中。所以,如果这些类型无论如何都不能告诉你任何有趣的事情,为什么还要保留它们呢?为什么不只关注文档?而这正是 Ruby 所做的。

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

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

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

          特别是,在 Ruby 中,对象的 接口 取决于它可以做什么,而不是 class 是什么,或者 module 它是什么混入。任何具有&lt;&lt; 方法的对象都可以附加到。这在单元测试中非常有用,您可以简单地传入ArrayString 而不是更复杂的Logger,即使ArrayLogger 不共享显式interface因为他们都有一个名为&lt;&lt;的方法。

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

          【讨论】:

          • 虽然读得很好,但我没有找到有用的答案。它读起来像是一篇关于为什么interface 没用的论文,忽略了它的用途。更容易说 ruby​​ 是动态类型的,并且考虑到了不同的重点,并使 IOC 等概念变得不必要/不需要。如果您习惯于按合同设计,那将是一个艰难的转变。 Rails 可以从中受益,正如您在最新版本中看到的那样,核心团队意识到了这一点。
          • 后续问题:在 Ruby 中记录接口的最佳方式是什么? Java 关键字interface 可能无法提供所有相关信息,但它确实提供了放置文档的明显位置。我在 Ruby 中编写了一个实现(足够的)IO 的类,但我通过反复试验完成了它,并且对这个过程不太满意。我还编写了我自己的接口的多个实现,但是记录需要哪些方法以及它们应该做什么,以便我的团队的其他成员可以创建实现被证明是一个挑战。
          • interface construct 确实只需要在静态类型的单继承语言中将不同类型视为相同(例如将LinkedHashSetArrayList 都视为Collection),它与 ​​Interface 几乎没有任何关系,正如这个答案所示。 Ruby 不是静态类型的,因此不需要 construct
          • 我将其解读为“有些接口没有意义,因此接口不好。为什么要使用接口?”。它没有回答这个问题,坦率地说,这听起来像是一个不了解接口的用途和好处的人。
          • 您通过引用在名为“add”的函数中执行删除的方法来论证 List 接口的无效性,这是减少荒谬论点的经典示例。特别是可以用任何语言(包括 ruby​​)编写一个方法来做一些不同于预期的事情。这不是反对“接口”的有效论据,它只是糟糕的代码。
          【解决方案7】:

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

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

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

          【讨论】:

            【解决方案8】:

            我们能否像在 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 一样简单...是的,在顶部 :)

            【讨论】:

            • 好的,但是如果您的 Collection = MyCollection 实现了接口中未定义的方法,则此方法可以完美运行,因此您不能确保您的对象只有接口方法定义。
            • 这太棒了,谢谢。 Duck 打字很好,但有时可以明确地与其他开发人员交流界面的行为方式。
            【解决方案9】:

            正如大家所说,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

            【讨论】:

              【解决方案10】:

              Java 方式中没有接口之类的东西。但是你可以在 ruby​​ 中享受其他的东西。

              如果你想实现某种类型和接口——以便检查对象是否有你需要的方法/消息——然后你可以看看rubycontracts。它定义了一种类似于PyProtocols 的机制。一个关于 ruby​​ 类型检查的博客是here

              上面提到的方法不是活的项目,虽然起初目标似乎很好,但似乎大多数 ruby​​ 开发人员可以在没有严格的类型检查的情况下生活。但是 ruby​​ 的灵活性可以实现类型检查。

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

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

              【讨论】:

              • 戴夫·托马斯的那些讲座在付费墙后面。
              猜你喜欢
              • 1970-01-01
              • 2010-11-01
              • 2017-08-04
              • 2011-03-16
              • 2012-08-03
              • 1970-01-01
              • 2011-07-23
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多