Rubyで同等のJavaインターフェースとは何ですか?


101

Javaで行うようにRubyでインターフェースを公開し、Rubyモジュールまたはクラスがインターフェースで定義されたメソッドを実装するように強制できますか?

1つの方法は、継承とmethod_missingを使用して同じことを達成することですが、他にもっと適切な方法がありますか?



6
なぜこれが必要なのかを自分自身に問い直す必要があります。多くの場合、Rubyの問題ではないものをコンパイルするためだけに十分なインターフェースが使用されます。
Arnis Lapsa 2010

1
この質問は、[ RubyではC#のインターフェイスに相当するものは何ですか?](StackOverflow.Com/q/3505521/#3507460)。
イェルクWミッターク

2
なぜこれが必要なのですか?「バージョン管理可能」と呼べるものを実装したいのですが、ドキュメント/ファイルをバージョン管理可能にしますが、何を使用してバージョン管理可能にします。たとえば、SVNやCVSなどの既存のリポジトリソフトウェアを使用してバージョン管理可能にできます。私がそれを選択する根本的なメカニズムが何であれ、それはいくつかの基本的な最小限の機能を提供するはずです。インターフェイスのようなものを使用して、基礎となる新しいリポジトリ実装によるこれらの最小限の機能の実装を強制します。
crazycrv 2010

彼女のPOODRブックのSandi Metzは、テストを使用してインターフェースを文書化しています。この本を読むのは本当に価値がある。2015年の時点で、@ aleksander-pohlの回答が最適です。
グレッグダン

回答:


85

Rubyには、他の言語と同じようにインターフェースがあります。

ユニットの責任、保証、プロトコルの抽象的な仕様であるインターフェースの概念をinterface、Java、C#、およびVB.NETプログラミングのキーワードであるの概念と混同しないように注意する必要があることに注意してください。言語。Rubyでは前者を常に使用していますが、後者は存在しません。

2つを区別することは非常に重要です。重要なのはインターフェースではなく、インターフェースinterfaceです。interfaceあなたに便利なほとんど何も伝えていません。何もこれよりも優れ示していないマーカインタフェースまったくのメンバーを持たないインタフェースであるJavaで、:だけを見てみましょうjava.io.Serializablejava.lang.Cloneable。これら2つinterfaceのは非常に異なる意味ですが、シグネチャはまったく同じです。

2あれば、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>実際にセットだと言っているのですか?どこにもない!より正確には、ドキュメントで。英語で。

interfacesJavaと.NETの両方のほとんどすべてのケースで、すべての関連情報は実際にはタイプではなくドキュメントにあります。それで、型がとにかく興味深いことを何も伝えていないのなら、なぜそれらをまったく保持するのですか?ドキュメントだけに固執しないのはなぜですか?そして、まさにそれがRubyが行うことです。

インターフェースを実際に意味のある方法で記述できる他の言語があることに注意してください。ただし、これらの言語は通常、インターフェイス " " を記述する構成を呼び出さず、それを呼び出します。依存型プログラミング言語では、たとえば、元の関数と同じ長さのコレクションを関数が返すというプロパティを表現できます。元のすべての要素は、並べ替えられたコレクションにもあり、それより大きな要素はありません。小さい要素の前に表示されます。interfacetypesort

つまり、RubyにはJavaに相当するものがありませんinterface。それはない、しかし、Javaのと同等の持つインタフェースを、そしてそれは、Javaと全く同じです:ドキュメントを。

また、Javaと同様に、受け入れテストを使用してインターフェイスを指定することもできます。

具体的には、Rubyで、インタフェースオブジェクトのは、それができるかによって決定される行い、ないものclassである、または何moduleそれに混合される。有している任意のオブジェクト<<メソッドは、に付加することができます。これは、単純に渡すことができますユニットテスト、に非常に有用であるArrayか、String代わりに、より複雑なのLoggerにもかかわらず、ArrayLogger明示的に共有していないinterface彼らの両方が呼ばれるメソッドを持っているという事実から離れて<<

別の例は、StringIO同じ実装し、インタフェースとしてIO、従って、大部分のインターフェイスFile、しかし他に任意の共通の祖先を共有せずObject


278
良い読み物ではありますが、その答えは役に立ちません。それinterfaceはなぜそれが役に立たないのかについての論文のように読み、その使用のポイントを逃しています。rubyは動的に型付けされており、異なることに焦点が当てられており、IOCのような概念が不要/不要であると言う方が簡単でしょう。契約による設計に慣れている場合、ハードシフトになります。Railsが恩恵を受ける可能性があるものは、コアチームが最新バージョンで確認できるように実現しました。
goliatone 2011

12
追加質問:Rubyでインターフェースを文書化する最良の方法は何ですか?Javaキーワードinterfaceはすべての関連情報を提供するわけではありませんが、ドキュメントを置くための明白な場所を提供します。私は(十分な)IOを実装するクラスをRubyで作成しましたが、試行錯誤でそれを行い、プロセスにあまり満足していませんでした。また、独自のインターフェースの複数の実装を作成しましたが、チームの他のメンバーが実装を作成できるようにするために必要なメソッドとその実行方法を文書化すると、課題が判明しました。
Patrick

9
interface 構築物は実際にのみ(例えば御馳走静的型付けの単一継承言語で同じようさまざまな種類の治療に必要とされているLinkedHashSetArrayListの両方Collection)、それはかなり何もしてませんしていないインタフェースこの答えが示すように。Rubyは静的に型付けされていないため、構成は必要ありません。
エサイリヤ2013年

16
私はこれを「一部のインターフェースは意味をなさないため、インターフェースが悪いです。なぜインターフェースを使いたいのですか?」と読みました。それは質問に答えません、そして率直に言って、インターフェースが何のためであるか、そして彼らの利益のために理解していない誰かのように聞こえます。
オッドマン2014

13
"add"と呼ばれる関数で削除を実行するメソッドを引用することによるListインターフェースの無効性に関するあなたの議論は、reductio ad absurdum引数の古典的な例です。特に、任意の言語(Rubyを含む)で、予想とは異なる何かを行うメソッドを書くことが可能です。これは「インターフェース」に対する有効な議論ではなく、単に悪いコードです。
ジャスティンオームス

58

rspecの「共有サンプル」を試してください:

https://www.relishapp.com/rspec/rspec-core/v/3-5/docs/example-groups/shared-examples

インターフェースの仕様を作成し、各実装者の仕様に1行挿入します。

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

更新:8年後(2020年)、rubyはシャーベットを介して静的に型付けされたインターフェースをサポートするようになりました。シャーベットのドキュメントの抽象クラスとインターフェースを参照してください。


15
私はこれが受け入れられる答えであると信じています。これは、ほとんどの型の弱い言語がJavaのようなインターフェースを提供できる方法です。受け入れられたものは、Rubyにインターフェースがない理由を説明しています。インターフェースをエミュレートする方法ではありません。
SystematicFrank 2014年

1
私は同意します。この回答は、JavaデベロッパーがRubyに移行する上で、上記の回答よりもはるかに役立ちました。
Cam

そうですが、インターフェースの全体的なポイントは、同じメソッド名を持っているということですが、具体的なクラスは、おそらく異なる動作を実装するものでなければなりません。では、共有されている例で何をテストするのでしょうか?
ロブ・ワイズ

Rubyはすべてを実用的なものにします。ドキュメント化された適切に記述されたコードが必要な場合は、テスト/仕様を追加します。これは、静的型付けチェックの一種になります。
Dmitry Polushkin

41

Javaで行うようにRubyでインターフェースを公開、Rubyモジュールまたはクラスがインターフェースで定義されたメソッドを実装するように強制できますか?

Rubyにはその機能はありません。Rubyはいわゆるダックタイピングを使用するため、原則としてそれらは必要ありません。

あなたが取ることができるいくつかのアプローチがあります。

例外を発生させる実装を記述します。サブクラスが実装されていないメソッドを使用しようとすると、失敗します

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

上記に加えて、契約を実施するテストコードを作成する必要があります(ここの他の投稿では、誤ってInterfaceと呼んでいます)

上記のような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がインターフェイスで定義されていないメソッドを実装する場合、これは完全に機能するため、オブジェクトにインターフェイスメソッドの定義のみがあることを確認できません。
Joel AZEMAR、2015年

これはかなり素晴らしいです、ありがとう。アヒルのタイピングは問題ありませんが、インターフェイスの動作を他の開発者に明示的に伝えるのが良い場合もあります。
ミロディーニョ

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で宣言されたメソッドの1つを削除するか、引数の数を変更すると、が発生しNotImplementedErrorます。


5

Javaのようにインターフェースなどはありません。しかし、ルビーには他にも楽しめるものがあります。

ある種の型とインターフェースを実装したい場合-オブジェクトに必要なメソッド/メッセージがあるかどうかをオブジェクトがチェックできるようにする場合-次に、rubycontractsを調べます。PyProtocolsと同様のメカニズムを定義します。Rubyでの型チェックに関するブログはこちらです。

上記のアプローチは生きているプロジェクトではありませんが、最初は目標は良いようですが、ほとんどのRuby開発者は厳密な型チェックなしで生きることができるようです。しかし、rubyの柔軟性により、型チェックを実装できます。

特定の振る舞いによってオブジェクトまたはクラス(ルビでも同じこと)を拡張したい場合、またはルビの方法で多重継承を行う場合は、includeまたはextendメカニズムを使用します。ではinclude、別のクラスまたはモジュールのメソッドをオブジェクトに含めることができます。を使用extendすると、クラスに動作を追加できるので、そのインスタンスにメソッドが追加されます。しかし、それは非常に短い説明でした。

Javaインターフェースの必要性を解決する最良の方法はルビーオブジェクトモデルを理解することだと私は思います(たとえば、Dave Thomasの講義を参照)。おそらく、Javaインターフェースについては忘れてしまいます。または、あなたのスケジュールに優れたアプリケーションがあります。


これらのデイブ・トーマスの講義はペイウォールの背後にあります。
Purplejacket 2017年

5

多くの回答が示すように、モジュールなどを含むクラスから継承することにより、Rubyにクラスに特定のメソッドを実装させることはできません。その理由は、おそらくRubyコミュニティでのTDDの普及です。これは、インターフェイスを定義する別の方法です。テストでは、メソッドのシグネチャだけでなく、動作も指定します。したがって、すでに定義されているインターフェイスを実装する別のクラスを実装する場合は、すべてのテストに合格する必要があります。

通常、テストはモックとスタブを使用して分離して定義されます。ただし、契約テストを定義できるBogusなどのツールもあります。このようなテストは、「プライマリ」クラスの動作を定義するだけでなく、連携するクラスにスタブ化されたメソッドが存在することも確認します。

Rubyのインターフェースに本当に関心がある場合は、契約テストを実装するテストフレームワークを使用することをお勧めします。


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

また、オブジェクトがすべてのインターフェイスメソッド定義を実装するようにすることもできます。

class BadFooImplementation
end

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

2

追加のニーズに合わせて、カルロサヤムの答えを少し拡張しました。これは、インターフェイスのクラスにカップルの追加施行し、オプションを追加します 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

2

Ruby自体には、Javaのインターフェースとまったく同じものはありません。

ただし、このようなインターフェースは非常に役立つ場合があるため、Javaインターフェースを非常に簡単にエミュレートするRubyのgemを自分で開発しました。

と呼ばれていclass_interfaceます。

それは非常に簡単に動作します。まずGemをインストールするgem install class_interfaceか、Gemfileに追加してrundします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を作成することになりました。

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のより詳細な例

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.