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


101

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

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



6
왜 이것이 필요한지 스스로에게 물어봐야합니다. 종종 루비에서는 문제가되지 않는 컴파일을하기 위해 충분한 인터페이스가 사용됩니다.
Arnis Lapsa

1
이 질문은 [ Ruby에서 C #의 인터페이스와 동등한 것은 무엇입니까? ] ( StackOverflow.Com/q/3505521/#3507460 ).
Jörg W Mittag

2
왜 이것이 필요한가요? 문서 / 파일을 버전화할 수 있지만 무엇을 사용하여 버전화할 수 있는지 "버전 화 가능"이라고 부를 수있는 것을 구현하고 싶습니다. 예를 들어 SVN 또는 CVS와 같은 기존 저장소 소프트웨어를 사용하여 버전화할 수 있습니다. 내가 선택하는 이상한 메커니즘이 무엇이든간에 기본적인 최소한의 기능을 제공해야합니다. 새로운 기본 저장소 구현에 의해 이러한 최소한의 기능을 구현하기 위해 인터페이스를 사용하고 싶습니다.
crazycrv

POODR 책의 Sandi Metz는 테스트를 사용하여 인터페이스를 문서화합니다. 이 책을 읽을 가치가 있습니다. 2015 년 현재 @ aleksander-pohl 답변이 최고라고 말하고 싶습니다.
Greg Dan

답변:


85

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


279
잘 읽으면서 도움이되는 답을 찾지 못했습니다. 왜 interface쓸모 없는지에 대한 논문처럼 읽혀서 사용 요점을 놓친 것입니다. 루비가 동적으로 입력되고 다른 초점을 염두에두고 IOC와 같은 개념을 불필요하거나 원치 않게 만든다고 말하는 것이 더 쉬웠을 것입니다. Design by Contract에 익숙하다면 어려운 변화입니다. Rails가 혜택을 볼 수있는 것은 최신 버전에서 볼 수 있듯이 핵심 팀이 깨달았습니다.
goliatone 2011

12
후속 질문 : Ruby에서 인터페이스 를 문서화 하는 가장 좋은 방법은 무엇 입니까? Java 키워드 interface는 모든 관련 정보를 제공하지는 않지만 문서를 넣을 수있는 명확한 위치를 제공합니다. (충분한) IO를 구현하는 Ruby로 클래스를 작성했지만 시행 착오를 거쳐 그 과정에 너무 만족하지 않았습니다. 나는 또한 내 인터페이스의 여러 구현을 작성했지만 어떤 메소드가 필요하고 다른 팀원이 구현을 만들 수 있도록해야하는지 문서화하는 것이 도전이었다.
Patrick

9
interface 구조는 참으로 전용 (예를 들면 치료 정적으로 입력 된 단일 상속 언어로 같은 서로 다른 종류의 치료를 위해 필요 LinkedHashSet하고 ArrayListA와 모두 Collection), 그것과 거의 아무것도 할 없습니다 인터페이스 이 답변 쇼 등을. Ruby는 정적으로 형식화되지 않으므로 구성이 필요하지 않습니다 .
Esailija

16
나는 이것을 "일부 인터페이스가 말이되지 않아서 인터페이스가 나쁘다. 왜 인터페이스를 사용하고 싶습니까?"라고 읽었습니다. 그것은 질문에 대한 답이 아니며 솔직히 인터페이스의 용도와 이점을 이해하지 못하는 사람처럼 들립니다.
Oddman

13
"add"라는 함수에서 제거를 수행하는 메소드를 인용하여 List 인터페이스의 무효성에 대한 귀하의 주장은 reductio ad absurdum 인수의 전형적인 예입니다. 특히 모든 언어 (루비 포함)에서 예상과 다른 작업을 수행하는 메서드를 작성할 수 있습니다. 이것은 "인터페이스"에 대한 유효한 주장이 아니며 단지 잘못된 코드 일뿐입니다.
Justin Ohms

58

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 문서의 추상 클래스 및 인터페이스 를 참조하십시오 .


15
나는 이것이 받아 들여진 대답이어야한다고 믿습니다. 이것은 대부분의 유형이 약한 언어가 Java와 같은 인터페이스를 제공 할 수있는 방법입니다. 허용되는 사람은 Ruby에 인터페이스가없는 이유를 설명하는 것이지 에뮬레이트하는 방법이 아닙니다.
SystematicFrank

1
동의합니다.이 답변은 Java 개발자가 위의 답변보다 Ruby로 이동하는 데 훨씬 더 많은 도움이되었습니다.
Cam

네,하지만 인터페이스의 요점은 메서드 이름이 같지만 구체적인 클래스는 동작을 구현하는 클래스 여야한다는 것입니다. 그렇다면 공유 예제에서 무엇을 테스트해야합니까?
Rob Wise

Ruby는 모든 것을 실용적으로 만듭니다. 문서화되고 잘 작성된 코드를 원한다면 테스트 / 사양을 추가하면 일종의 정적 타이핑 검사가 될 것입니다.
Dmitry Polushkin

41

우리는 우리가 자바처럼 루비 인터페이스를 노출 할 수 있습니다 시행 인터페이스에 의해 정의 된 메소드를 구현하는 루비 모듈 또는 클래스를.

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... 예, 맨 위에 :)


좋습니다.하지만 Collection = MyCollection이 인터페이스에 정의되지 않은 메서드를 구현하면 완벽하게 작동하므로 개체에 인터페이스 메서드 정의 만 있는지 확인할 수 없습니다.
Joel AZEMAR

정말 대단해요, 감사합니다. 덕 타이핑은 괜찮지 만 때로는 인터페이스가 어떻게 동작해야하는지 명시 적으로 다른 개발자에게 전달하는 것이 좋습니다.
Mirodinho

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에 선언 된 메서드 중 하나를 제거하거나 인수 수를 변경하면 NotImplementedError.


5

Java 방식에는 인터페이스와 같은 것이 없습니다. 하지만 루비에서 즐길 수있는 다른 것들이 있습니다.

어떤 종류의 유형과 인터페이스를 구현하고 싶은 경우-객체에 필요한 메소드 / 메시지가 있는지 확인할 수 있도록-그런 다음 rubycontracts를 살펴볼 수 있습니다 . PyProtocols 와 유사한 메커니즘을 정의합니다 . 루비의 유형 검사에 대한 블로그는 여기에 있습니다 .

언급 된 접근 방식은 살아있는 프로젝트가 아니지만, 처음에는 목표가 좋은 것처럼 보이지만 대부분의 루비 개발자는 엄격한 유형 검사없이 살 수있는 것 같습니다. 그러나 루비의 유연성으로 인해 유형 검사를 구현할 수 있습니다.

특정 동작에 의해 객체 또는 클래스 (루비에서도 동일)를 확장하거나 루비 방식의 다중 상속을 사용하려면 include또는 extend메커니즘을 사용하십시오 . 를 사용 include하면 다른 클래스 또는 모듈의 메서드를 개체에 포함 할 수 있습니다. 을 사용 extend하면 클래스에 동작을 추가하여 해당 인스턴스에 추가 된 메서드를 가질 수 있습니다. 그것은 매우 짧은 설명이었습니다.

Java 인터페이스 요구를 해결하는 가장 좋은 방법은 루비 객체 모델을 이해하는 것 입니다 (예를 들어 Dave Thomas 강의 참조 ). 아마 당신은 자바 인터페이스를 잊어 버릴 것입니다. 또는 일정에 예외적 인 응용 프로그램이 있습니다.


그 Dave Thomas 강의는 Paywall 뒤에 있습니다.
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

또한 Object가 모든 인터페이스 메서드 정의를 구현하도록 할 수 있습니다.

class BadFooImplementation
end

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

2

추가 요구 사항에 대해 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

2

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"메서드는 클래스의 마지막 줄에서 호출되어야합니다. 위의 구현 된 메서드가 이미 확인 된 코드 위치이기 때문입니다.

추가 정보 : 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.