[ruby] Ruby에서 Java 인터페이스에 해당하는 것은 무엇입니까?

자바 에서처럼 Ruby에서 인터페이스를 노출하고 Ruby 모듈이나 클래스를 적용하여 인터페이스에서 정의한 메소드를 구현할 수 있습니까?

한 가지 방법은 상속과 method_missing을 사용하여 동일한 결과를 얻는 것이지만 더 적절한 방법이 있습니까?



답변

Ruby에는 다른 언어와 마찬가지로 인터페이스 가 있습니다.

단위의 책임, 보증 및 프로토콜에 대한 추상적 인 사양 인 Interface 개념과 interfaceJava, C # 및 VB.NET 프로그래밍의 키워드 인 which 개념을 혼동하지 않도록주의 해야 합니다. 언어. Ruby에서는 항상 전자를 사용하지만 후자는 단순히 존재하지 않습니다.

둘을 구별하는 것은 매우 중요합니다. 중요 한 것은입니다 인터페이스 가 아닌 interface. 은 interface유용하지 않다는 것을 거의 알려줍니다. 멤버가 전혀없는 인터페이스 인 Java 의 마커 인터페이스 보다 더 좋은 것은 없습니다 . java.io.Serializableand 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>실제로 어디에 그것이 세트 라고 말 합니까? 아무데도! 또는 더 정확하게는 문서에서. 영어로.

interfacesJava 및 .NET 의 거의 모든 경우 에서 모든 관련 정보는 실제로 유형이 아니라 문서에 있습니다. 따라서 유형이 어쨌든 흥미로운 것을 말하지 않으면 왜 그것들을 아예 유지합니까? 문서화에만 집착하지 않는 이유는 무엇입니까? 이것이 바로 Ruby가하는 일입니다.

있다는 것을 참고 다른 하는 언어로 인터페이스가 실제로 의미있는 방식으로 설명 될 수이. 그러나 이러한 언어는 일반적으로 인터페이스interface” 를 설명하는 구성을 호출하지 않고 type. 예를 들어 종속 형식의 프로그래밍 언어에서는 sort함수가 원본과 동일한 길이의 컬렉션을 반환하고 원본에있는 모든 요소도 정렬 된 컬렉션에 있으며 더 큰 요소는 없다는 속성을 표현할 수 있습니다. 더 작은 요소 앞에 나타납니다.

간단히 말해서 Ruby에는 Java와 동등한 기능이 없습니다 interface. 그것은 않습니다 , 그러나, 자바에 해당하는이 인터페이스 , 그리고 정확하게 자바에서와 동일합니다 : 문서.

또한 Java에서와 마찬가지로 Acceptance Tests 를 사용하여 인터페이스 를 지정할 수도 있습니다 .

특히, 루비에서, 인터페이스 객체의 그것 수 있는지에 의해 결정된다 수행 하지 무엇 class이고, 또는 어떤 module그것의 혼합물. 가지는 모든 객체에 <<방법에 추가 될 수있다. 이것은 단순히 전달할 수있는 단위 테스트에 매우 유용 Array또는 String대신 더 복잡의 Logger에도 불구 Array하고 Logger명시를 공유하지 않는 interface둘 다라는 방법을 가지고 있다는 사실 그렇다 <<.

또 다른 예는의 인터페이스StringIO동일한 인터페이스 를 구현 IO하므로 다른 공통 조상을 공유하지 않고 의 인터페이스 의 많은 부분 을 구현합니다 .FileObject


답변

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