자바 에서처럼 Ruby에서 인터페이스를 노출하고 Ruby 모듈이나 클래스를 적용하여 인터페이스에서 정의한 메소드를 구현할 수 있습니까?
한 가지 방법은 상속과 method_missing을 사용하여 동일한 결과를 얻는 것이지만 더 적절한 방법이 있습니까?
자바 에서처럼 Ruby에서 인터페이스를 노출하고 Ruby 모듈이나 클래스를 적용하여 인터페이스에서 정의한 메소드를 구현할 수 있습니까?
한 가지 방법은 상속과 method_missing을 사용하여 동일한 결과를 얻는 것이지만 더 적절한 방법이 있습니까?
답변:
Ruby에는 다른 언어와 마찬가지로 인터페이스 가 있습니다.
단위의 책임, 보증 및 프로토콜에 대한 추상적 인 사양 인 Interface 개념과 interface
Java, C # 및 VB.NET 프로그래밍의 키워드 인 which 개념을 혼동하지 않도록주의 해야 합니다. 언어. Ruby에서는 항상 전자를 사용하지만 후자는 단순히 존재하지 않습니다.
둘을 구별하는 것은 매우 중요합니다. 중요 한 것은입니다 인터페이스 가 아닌 interface
. 은 interface
유용하지 않다는 것을 거의 알려줍니다. 멤버가 전혀없는 인터페이스 인 Java 의 마커 인터페이스 보다 더 좋은 것은 없습니다 . java.io.Serializable
and java.lang.Cloneable
; 이 두 가지는 매우 다른 것을 interface
의미 하지만 똑같은 서명을 가지고 있습니다.
이 개 경우에 따라서, interface
평균 여러 가지가 동일한 서명을 가지고들, 무엇을 정확히 입니다 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에서와 마찬가지로 Acceptance Tests 를 사용하여 인터페이스 를 지정할 수도 있습니다 .
특히, 루비에서, 인터페이스 객체의 그것 수 있는지에 의해 결정된다 수행 하지 무엇 class
이고, 또는 어떤 module
그것의 혼합물. 가지는 모든 객체에 <<
방법에 추가 될 수있다. 이것은 단순히 전달할 수있는 단위 테스트에 매우 유용 Array
또는 String
대신 더 복잡의 Logger
에도 불구 Array
하고 Logger
명시를 공유하지 않는 interface
둘 다라는 방법을 가지고 있다는 사실 그렇다 <<
.
또 다른 예는의 인터페이스 와 StringIO
동일한 인터페이스 를 구현 IO
하므로 다른 공통 조상을 공유하지 않고 의 인터페이스 의 많은 부분 을 구현합니다 .File
Object
interface
쓸모 없는지에 대한 논문처럼 읽혀서 사용 요점을 놓친 것입니다. 루비가 동적으로 입력되고 다른 초점을 염두에두고 IOC와 같은 개념을 불필요하거나 원치 않게 만든다고 말하는 것이 더 쉬웠을 것입니다. Design by Contract에 익숙하다면 어려운 변화입니다. Rails가 혜택을 볼 수있는 것은 최신 버전에서 볼 수 있듯이 핵심 팀이 깨달았습니다.
interface
는 모든 관련 정보를 제공하지는 않지만 문서를 넣을 수있는 명확한 위치를 제공합니다. (충분한) IO를 구현하는 Ruby로 클래스를 작성했지만 시행 착오를 거쳐 그 과정에 너무 만족하지 않았습니다. 나는 또한 내 인터페이스의 여러 구현을 작성했지만 어떤 메소드가 필요하고 다른 팀원이 구현을 만들 수 있도록해야하는지 문서화하는 것이 도전이었다.
interface
구조는 참으로 전용 (예를 들면 치료 정적으로 입력 된 단일 상속 언어로 같은 서로 다른 종류의 치료를 위해 필요 LinkedHashSet
하고 ArrayList
A와 모두 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
업데이트 : 8 년 후 (2020) 루비는 이제 셔벗을 통해 정적으로 형식화 된 인터페이스를 지원합니다. sorbet 문서의 추상 클래스 및 인터페이스 를 참조하십시오 .
우리는 우리가 자바처럼 루비 인터페이스를 노출 할 수 있습니다 시행 인터페이스에 의해 정의 된 메소드를 구현하는 루비 모듈 또는 클래스를.
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
다시 한 번 강조하겠습니다. 이것은 루비의 모든 것이 런타임에 발생하므로 초보적인 것입니다. 컴파일 시간 검사가 없습니다. 이것을 테스트와 결합하면 오류를 포착 할 수 있습니다. 더 나아가, 위의 내용을 더 살펴보면 해당 클래스의 객체가 처음 생성 될 때 클래스에 대한 검사를 수행 하는 인터페이스 를 작성할 수있을 것 입니다. 당신의 테스트를 호출하는 것처럼 간단하게 만들기 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 와 유사한 메커니즘을 정의합니다 . 루비의 유형 검사에 대한 블로그는 여기에 있습니다 .
언급 된 접근 방식은 살아있는 프로젝트가 아니지만, 처음에는 목표가 좋은 것처럼 보이지만 대부분의 루비 개발자는 엄격한 유형 검사없이 살 수있는 것 같습니다. 그러나 루비의 유연성으로 인해 유형 검사를 구현할 수 있습니다.
특정 동작에 의해 객체 또는 클래스 (루비에서도 동일)를 확장하거나 루비 방식의 다중 상속을 사용하려면 include
또는 extend
메커니즘을 사용하십시오 . 를 사용 include
하면 다른 클래스 또는 모듈의 메서드를 개체에 포함 할 수 있습니다. 을 사용 extend
하면 클래스에 동작을 추가하여 해당 인스턴스에 추가 된 메서드를 가질 수 있습니다. 그것은 매우 짧은 설명이었습니다.
Java 인터페이스 요구를 해결하는 가장 좋은 방법은 루비 객체 모델을 이해하는 것 입니다 (예를 들어 Dave Thomas 강의 참조 ). 아마 당신은 자바 인터페이스를 잊어 버릴 것입니다. 또는 일정에 예외적 인 응용 프로그램이 있습니다.
많은 답변에서 알 수 있듯이 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
또한 Object가 모든 인터페이스 메서드 정의를 구현하도록 할 수 있습니다.
class BadFooImplementation
end
# > FooInterface.new(BadFooImplementation.new)
# => NotImplementedError: BadFooImplementation must implement the method foo
추가 요구 사항에 대해 carlosayam의 답변에 대해 약간 확장했습니다. 이 인터페이스 클래스에 몇 가지 추가 강제 조치 및 옵션을 추가 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을 직접 개발했습니다.
라고 class_interface
합니다.
아주 간단하게 작동합니다. 먼저 gem을 설치 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"메서드는 클래스의 마지막 줄에서 호출되어야합니다. 위의 구현 된 메서드가 이미 확인 된 코드 위치이기 때문입니다.
특정 동작을 원했던 객체에 대한 안전 점검에 "실행되지 않은 오류"패턴을 너무 많이 사용하고 있음을 깨달았습니다. 기본적으로 다음과 같은 인터페이스를 사용할 수있는 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 에서 더 자세한 예