Javaで行うようにRubyでインターフェースを公開し、Rubyモジュールまたはクラスがインターフェースで定義されたメソッドを実装するように強制できますか?
1つの方法は、継承とmethod_missingを使用して同じことを達成することですが、他にもっと適切な方法がありますか?
Javaで行うようにRubyでインターフェースを公開し、Rubyモジュールまたはクラスがインターフェースで定義されたメソッドを実装するように強制できますか?
1つの方法は、継承とmethod_missingを使用して同じことを達成することですが、他にもっと適切な方法がありますか?
回答:
Rubyには、他の言語と同じようにインターフェースがあります。
ユニットの責任、保証、プロトコルの抽象的な仕様であるインターフェースの概念をinterface
、Java、C#、およびVB.NETプログラミングのキーワードであるの概念と混同しないように注意する必要があることに注意してください。言語。Rubyでは前者を常に使用していますが、後者は存在しません。
2つを区別することは非常に重要です。重要なのはインターフェースではなく、インターフェースinterface
です。interface
あなたに便利なほとんど何も伝えていません。何もこれよりも優れ示していないマーカインタフェースまったくのメンバーを持たないインタフェースであるJavaで、:だけを見てみましょうjava.io.Serializable
とjava.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>
実際にセットだと言っているのですか?どこにもない!より正確には、ドキュメントで。英語で。
interfaces
Javaと.NETの両方のほとんどすべてのケースで、すべての関連情報は実際にはタイプではなくドキュメントにあります。それで、型がとにかく興味深いことを何も伝えていないのなら、なぜそれらをまったく保持するのですか?ドキュメントだけに固執しないのはなぜですか?そして、まさにそれがRubyが行うことです。
インターフェースを実際に意味のある方法で記述できる他の言語があることに注意してください。ただし、これらの言語は通常、インターフェイス " " を記述する構成を呼び出さず、それを呼び出します。依存型プログラミング言語では、たとえば、元の関数と同じ長さのコレクションを関数が返すというプロパティを表現できます。元のすべての要素は、並べ替えられたコレクションにもあり、それより大きな要素はありません。小さい要素の前に表示されます。interface
type
sort
つまり、RubyにはJavaに相当するものがありませんinterface
。それはない、しかし、Javaのと同等の持つインタフェースを、そしてそれは、Javaと全く同じです:ドキュメントを。
また、Javaと同様に、受け入れテストを使用してインターフェイスを指定することもできます。
具体的には、Rubyで、インタフェースオブジェクトのは、それができるかによって決定される行い、ないものclass
である、または何module
それに混合される。有している任意のオブジェクト<<
メソッドは、に付加することができます。これは、単純に渡すことができますユニットテスト、に非常に有用であるArray
か、String
代わりに、より複雑なのLogger
にもかかわらず、Array
とLogger
明示的に共有していないinterface
彼らの両方が呼ばれるメソッドを持っているという事実から離れて<<
。
別の例は、StringIO
同じ実装し、インタフェースとしてIO
、従って、大部分のインターフェイスのFile
、しかし他に任意の共通の祖先を共有せずObject
。
interface
はなぜそれが役に立たないのかについての論文のように読み、その使用のポイントを逃しています。rubyは動的に型付けされており、異なることに焦点が当てられており、IOCのような概念が不要/不要であると言う方が簡単でしょう。契約による設計に慣れている場合、ハードシフトになります。Railsが恩恵を受ける可能性があるものは、コアチームが最新バージョンで確認できるように実現しました。
interface
はすべての関連情報を提供するわけではありませんが、ドキュメントを置くための明白な場所を提供します。私は(十分な)IOを実装するクラスをRubyで作成しましたが、試行錯誤でそれを行い、プロセスにあまり満足していませんでした。また、独自のインターフェースの複数の実装を作成しましたが、チームの他のメンバーが実装を作成できるようにするために必要なメソッドとその実行方法を文書化すると、課題が判明しました。
interface
構築物は実際にのみ(例えば御馳走静的型付けの単一継承言語で同じようさまざまな種類の治療に必要とされているLinkedHashSet
とArrayList
の両方Collection
)、それはかなり何もしてませんしていないインタフェースこの答えが示すように。Rubyは静的に型付けされていないため、構成は必要ありません。
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はシャーベットを介して静的に型付けされたインターフェースをサポートするようになりました。シャーベットのドキュメントの抽象クラスとインターフェースを参照してください。
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
...ええ、上に:)
ここで誰もが言ったように、ルビーのためのインターフェースシステムはありません。しかし、イントロスペクションを使用すると、非常に簡単に自分で実装できます。簡単な例を以下に示します。これは、さまざまな方法で改善して、始めるのに役立ちます。
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
ます。
Javaのようにインターフェースなどはありません。しかし、ルビーには他にも楽しめるものがあります。
ある種の型とインターフェースを実装したい場合-オブジェクトに必要なメソッド/メッセージがあるかどうかをオブジェクトがチェックできるようにする場合-次に、rubycontractsを調べます。PyProtocolsと同様のメカニズムを定義します。Rubyでの型チェックに関するブログはこちらです。
上記のアプローチは生きているプロジェクトではありませんが、最初は目標は良いようですが、ほとんどのRuby開発者は厳密な型チェックなしで生きることができるようです。しかし、rubyの柔軟性により、型チェックを実装できます。
特定の振る舞いによってオブジェクトまたはクラス(ルビでも同じこと)を拡張したい場合、またはルビの方法で多重継承を行う場合は、include
またはextend
メカニズムを使用します。ではinclude
、別のクラスまたはモジュールのメソッドをオブジェクトに含めることができます。を使用extend
すると、クラスに動作を追加できるので、そのインスタンスにメソッドが追加されます。しかし、それは非常に短い説明でした。
Javaインターフェースの必要性を解決する最良の方法はルビーオブジェクトモデルを理解することだと私は思います(たとえば、Dave Thomasの講義を参照)。おそらく、Javaインターフェースについては忘れてしまいます。または、あなたのスケジュールに優れたアプリケーションがあります。
多くの回答が示すように、モジュールなどを含むクラスから継承することにより、Rubyにクラスに特定のメソッドを実装させることはできません。その理由は、おそらくRubyコミュニティでのTDDの普及です。これは、インターフェイスを定義する別の方法です。テストでは、メソッドのシグネチャだけでなく、動作も指定します。したがって、すでに定義されているインターフェイスを実装する別のクラスを実装する場合は、すべてのテストに合格する必要があります。
通常、テストはモックとスタブを使用して分離して定義されます。ただし、契約テストを定義できるBogusなどのツールもあります。このようなテストは、「プライマリ」クラスの動作を定義するだけでなく、連携するクラスにスタブ化されたメソッドが存在することも確認します。
Rubyのインターフェースに本当に関心がある場合は、契約テストを実装するテストフレームワークを使用することをお勧めします。
ここでのすべての例は興味深いですが、インターフェイスコントラクトの検証がありません。つまり、オブジェクトにすべてのインターフェイスメソッドの定義を実装させたい場合、このメソッドのみを実装させることはできません。だから私はあなたがあなたのインターフェース(契約)を通してあなたが期待するものを正確に持っていることを確実にするための簡単な簡単な例(確かに改善することができます)を提案します。
そのような定義されたメソッドであなたのインターフェースを検討してください
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
追加のニーズに合わせて、カルロサヤムの答えを少し拡張しました。これは、インターフェイスのクラスにカップルの追加施行し、オプションを追加します 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のインターフェースとまったく同じものはありません。
ただし、このようなインターフェースは非常に役立つ場合があるため、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」メソッドは、クラスの最後の行で呼び出す必要があります。これは、上記の実装されたメソッドがすでにチェックされているコードの位置だからです。
特定の動作が必要なオブジェクトの安全性チェックのために、「未実装エラー」のパターンを使いすぎていることに気付きました。結局、基本的には次のようなインターフェースの使用を許可する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のより詳細な例