루비에서 추상 클래스를 구현하는 방법은 무엇입니까?


121

루비에는 추상 클래스의 개념이 없다는 것을 알고 있습니다. 하지만 구현해야한다면 어떻게해야할까요? 나는 다음과 같은 것을 시도했다.

class A
  def self.new
    raise 'Doh! You are trying to write Java in Ruby!'
  end
end

class B < A
  ...
  ...
end

그러나 B를 인스턴스화하려고하면 내부적으로 A.new예외를 발생시킬 호출 이 발생합니다.

또한 모듈은 인스턴스화 할 수 없지만 상속 될 수도 없습니다. 새 메서드를 비공개로 설정하는 것도 작동하지 않습니다. 어떤 포인터라도?


1
모듈을 혼합 할 수 있지만 다른 이유로 인해 고전적인 상속이 필요하다고 생각합니까?
Zach

6
추상 클래스를 구현할 필요 가 없습니다 . 필요하다면 어떻게해야하는지 궁금했습니다. 프로그래밍 문제. 그게 다야.
Chirantan

127
raise "Doh! You are trying to write Java in Ruby".
Andrew Grimm 2011 년

답변:


61

저는 Ruby에서 추상 클래스를 사용하는 것을 좋아하지 않습니다 (거의 항상 더 좋은 방법이 있습니다). 그래도 상황에 가장 적합한 기술이라고 생각한다면 다음 코드를 사용하여 어떤 메서드가 추상적인지에 대해 더 선언 할 수 있습니다.

module Abstract
  def abstract_methods(*args)
    args.each do |name|
      class_eval(<<-END, __FILE__, __LINE__)
        def #{name}(*args)
          raise NotImplementedError.new("You must implement #{name}.")
        end
      END
      # important that this END is capitalized, since it marks the end of <<-END
    end
  end
end

require 'rubygems'
require 'rspec'

describe "abstract methods" do
  before(:each) do
    @klass = Class.new do
      extend Abstract

      abstract_methods :foo, :bar
    end
  end

  it "raises NoMethodError" do
    proc {
      @klass.new.foo
    }.should raise_error(NoMethodError)
  end

  it "can be overridden" do
    subclass = Class.new(@klass) do
      def foo
        :overridden
      end
    end

    subclass.new.foo.should == :overridden
  end
end

기본적으로 abstract_methods추상적 인 메서드 목록을 사용하여 호출 하고 추상 클래스의 인스턴스에 의해 호출되면 NotImplementedError예외가 발생합니다.


이것은 실제로 인터페이스와 비슷하지만 아이디어를 얻었습니다. 감사.
Chirantan

6
이것은 NotImplementedError본질적으로 "플랫폼에 따라 다르며 사용자가 사용할 수 없음"을 의미 하는 유효한 사용 사례로 들리지 않습니다. 문서를 참조하십시오 .
skalee 2014 년

6
한 줄 방법을 메타 프로그래밍으로 대체하고 있으므로 이제 믹스 인을 포함하고 메서드를 호출해야합니다. 나는 이것이 더 선언적이라고 생각하지 않습니다.
Pascal

1
이 답변을 편집하여 코드의 일부 버그를 수정하고 업데이트 했으므로 이제 실행됩니다. 또한 코드를 약간 단순화하여 include 대신 extend를 사용합니다. 이유는 다음과 같습니다. yehudakatz.com/2009/11/12/better-ruby-idioms
Magne

1
@ManishShrivastava : 여기에서 END 사용의 중요성에 대한 의견을 보려면 지금이 코드를 참조하십시오
Magne

113

늦게 여기에 차임으로써 누군가가 추상 클래스를 인스턴스화하는 것을 막을 이유가 없다고 생각합니다. 특히 그들은 즉석에서 메서드를 추가 할 수 있기 때문 입니다.

Ruby와 같은 덕 타이핑 언어는 런타임에 메서드의 존재 / 부재 또는 동작을 사용하여 호출 여부를 결정합니다. 따라서 귀하의 질문은 추상 방법에 적용 되므로 의미가 있습니다.

def get_db_name
   raise 'this method should be overriden and return the db name'
end

그리고 그것은 이야기의 끝쯤에있을 것입니다. Java에서 추상 클래스를 사용하는 유일한 이유는 특정 메서드가 "채워진"반면 다른 메서드는 추상 클래스에서 동작을 갖도록하기 위해서입니다. 덕 타이핑 언어에서는 클래스 / 타입이 아닌 메서드에 초점을 맞추므로 걱정을 그 수준으로 옮겨야합니다.

귀하의 질문에 기본적으로 abstractJava 에서 키워드 를 다시 만들려고 노력하고 있습니다 . 이것은 Ruby에서 Java를 수행하는 코드 냄새입니다.


3
@Christopher Perry : 이유는 무엇입니까?
SasQ

10
@ChristopherPerry 나는 아직도 그것을 얻지 못한다. 결국 부모와 형제 관련 되어 있고이 관계를 명시 적으로 지정 하려면이 종속성을 원하지 않는 이유는 무엇 입니까? 또한 어떤 클래스의 객체를 다른 클래스 내에서 구성하려면 그 정의도 알아야합니다. 상속은 일반적으로 컴포지션으로 구현되며, 컴포지션 된 객체의 인터페이스를 포함하는 클래스의 인터페이스의 일부가됩니다. 그럼에도 불구하고 포함되거나 상속 된 개체의 정의가 필요합니다. 아니면 다른 얘기를하고 있습니까? 그것에 대해 좀 더 자세히 설명해 주시겠습니까?
SasQ

2
@SasQ, 당신은 그것을 구성하기 위해 부모 클래스의 구현 세부 사항을 알 필요가 없으며 API 만 알면됩니다. 그러나 상속하는 경우 부모의 구현에 의존합니다. 구현이 변경되면 코드가 예기치 않은 방식으로 중단 될 수 있습니다. 자세한 내용은 여기
Christopher Perry

16
죄송합니다. "상속보다 구성 선호"는 "항상 사용자 구성"이 아닙니다. 일반적으로 상속은 피해야하지만 더 잘 맞는 몇 가지 사용 사례가 있습니다. 맹목적으로 책을 따르지 마십시오.
Nowaker

1
@Nowaker 매우 중요한 포인트입니다. 종종 우리는 "이 경우에 실용적인 접근 방식이 무엇인가"라고 생각하는 대신 우리가 읽거나들은 내용에 눈이 멀게되는 경향이 있습니다. 완전히 검은 색이거나 흰색 인 경우는 거의 없습니다.
Per Lundberg

44

이 시도:

class A
  def initialize
    raise 'Doh! You are trying to instantiate an abstract class!'
  end
end

class B < A
  def initialize
  end
end

38
#initializeB의 super in을 사용하려면 실제로 A # initialize에서 무엇이든 올릴 수 있습니다 if self.class == A.
mk12

17
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end

7
또한 부모 클래스의 상속 된 후크를 사용하여 생성자 메서드를 모든 하위 클래스에서 자동으로 표시 할 수 있습니다. def A.inherited (subclass); subclass.instance_eval {public_class_method : new}; 종료
t6d

1
아주 좋은 t6d. 주석으로, 그것이 놀라운 행동이므로 문서화되어 있는지 확인하십시오 (최소한의 놀라움을 위반 함).
bluehavana

16

Rails 세계의 모든 사람을 위해 ActiveRecord 모델을 추상 클래스로 구현하는 것은 모델 파일의 다음 선언으로 수행됩니다.

self.abstract_class = true

12

내 2 ¢ : 간단하고 가벼운 DSL 믹스 인을 선택합니다.

module Abstract
  extend ActiveSupport::Concern

  included do

    # Interface for declaratively indicating that one or more methods are to be
    # treated as abstract methods, only to be implemented in child classes.
    #
    # Arguments:
    # - methods (Symbol or Array) list of method names to be treated as
    #   abstract base methods
    #
    def self.abstract_methods(*methods)
      methods.each do |method_name|

        define_method method_name do
          raise NotImplementedError, 'This is an abstract base method. Implement in your subclass.'
        end

      end
    end

  end

end

# Usage:
class AbstractBaseWidget
  include Abstract
  abstract_methods :widgetify
end

class SpecialWidget < AbstractBaseWidget
end

SpecialWidget.new.widgetify # <= raises NotImplementedError

물론이 경우 기본 클래스를 초기화하기 위해 다른 오류를 추가하는 것은 사소한 일입니다.


1
편집 : 좋은 측정을 위해,이 접근 방식은 define_method를 사용하기 때문에 역 추적이 손상되지 않았는지 확인할 수 있습니다. 예 : err = NotImplementedError.new(message); err.set_backtrace caller()YMMV
Anthony Navarre

저는이 접근 방식이 매우 우아하다고 생각합니다. 이 질문에 기여 해주셔서 감사합니다.
wes.hysell

12

루비를 프로그래밍 한 지난 6 년 반 동안 저는 추상 클래스가 한 번도 필요 하지 않았습니다 .

추상 클래스가 필요하다고 생각하는 경우 Ruby가 아닌이를 제공 / 요구하는 언어로 너무 많이 생각하고있는 것입니다.

다른 사람들이 제안했듯이 믹스 인은 인터페이스 (Java가 정의한대로)로 간주되는 것에 더 적합하고 디자인을 재고하는 것은 C ++와 같은 다른 언어의 추상 클래스를 "필요로하는"것에 더 적합합니다.

2019 년 업데이트 : 16 년 반 동안 Ruby에서 추상 클래스가 필요하지 않았습니다. 내 응답에 대해 언급 한 모든 사람들이 말하는 모든 것은 실제로 Ruby를 배우고 모듈 (일반적인 구현을 제공하는)과 같은 적절한 도구를 사용하여 해결됩니다. 내가 관리 한 팀에는 추상 클래스처럼 실패하는 기본 구현이있는 클래스를 만든 사람들이 있지만 프로덕션에서 NoMethodError와 똑같은 결과를 생성 하기 때문에 대부분 코딩 낭비입니다 AbstractClassError.


24
Java에서도 추상 클래스가 / need / 필요하지 않습니다. 기본 클래스이며 클래스를 확장하는 사람들을 위해 인스턴스화해서는 안된다는 것을 문서화하는 방법입니다.
fijiaaron 2010

3
IMO, 몇 가지 언어가 객체 지향 프로그래밍 개념을 제한해야합니다. 성능 관련 이유 (또는 더 설득력있는 것)가없는 한 주어진 상황에서 적절한 것이 언어에 의존해서는 안됩니다.
thekingoftruth 2013 년

10
@fijiaaron : 그렇게 생각한다면 추상 기본 클래스가 무엇인지 확실히 이해하지 못합니다. 그들은 클래스가 인스턴스화되어서는 안된다는 것을 "문서화"하는 데 그다지 중요하지 않습니다 (차라리 추상적 인 부작용 일뿐입니다). 이는 여러 파생 클래스에 대한 공통 인터페이스를 지정하는 것에 관한 것입니다. 그러면 구현 될 것임을 보장합니다 (그렇지 않으면 파생 클래스도 추상으로 유지됩니다). 그 목표는 인스턴스화가별로 의미가없는 클래스에 대해 Liskov의 대체 원칙을 지원하는 것입니다.
SasQ

1
(계속) 물론 몇 가지 일반적인 메서드와 속성을 사용하여 여러 클래스를 만들 수 있지만 컴파일러 / 인터프리터는 이러한 클래스가 어떤 식 으로든 관련되어 있다는 것을 알지 못합니다. 메서드 및 속성의 이름은 이러한 각 클래스에서 동일 할 수 있지만, 이것만으로는 아직 동일한 기능을 나타내는 것은 아닙니다 (이름 일치는 우연 일 수 있음). 컴파일러에게이 관계에 대해 알리는 유일한 방법은 기본 클래스를 사용하는 것이지만이 기본 클래스 자체의 인스턴스가 존재하는 것이 항상 의미가있는 것은 아닙니다.
SasQ


4

개인적으로 추상 클래스의 메서드에서 NotImplementedError를 발생시킵니다. 그러나 언급 한 이유로 '새로운'방법에서 제외 할 수 있습니다.


그러나 인스턴스화되지 않도록하는 방법은 무엇입니까?
Chirantan

개인적으로 저는 Ruby를 시작하고 있지만 Python에서 선언 된 __init ___ () 메서드가있는 하위 클래스는 수퍼 클래스의 __init __ () 메서드를 자동으로 호출하지 않습니다. 루비에도 비슷한 개념이 있기를 바라지 만 제가 말했듯이 이제 막 시작했습니다.
Zack

루비에서 부모의 initialize메서드는 명시 적으로 super로 호출되지 않는 한 자동으로 호출되지 않습니다.
mk12

4

인스턴스화 할 수없는 클래스를 사용하려면 A.new 메서드에서 오류를 발생하기 전에 self == A인지 확인하십시오.

그러나 실제로 모듈은 여기에서 원하는 것과 더 비슷해 보입니다. 예를 들어 Enumerable은 다른 언어의 추상 클래스 일 수 있습니다. 기술적으로 하위 클래스를 만들 수는 없지만 호출은 include SomeModule거의 동일한 목표를 달성합니다. 이것이 작동하지 않는 이유가 있습니까?


4

추상 클래스로 봉사하려는 목적은 무엇입니까? 아마도 루비로하는 더 좋은 방법이있을 것입니다. 그러나 당신은 어떤 세부 사항도주지 않았습니다.

내 포인터는 이것입니다. 상속이 아닌 믹스 인을 사용하십시오 .


과연. 모듈을 믹싱하는 것은 AbstractClass를 사용하는 것과 같습니다. wiki.c2.com/?AbstractClass PS : 모듈이 그 자체이고 믹싱이 그들과 함께하는 것이기 때문에 그것들을 믹스 인이 아니라 모듈이라고 부르 자.
Magne

3

또 다른 대답 :

module Abstract
  def self.append_features(klass)
    # access an object's copy of its class's methods & such
    metaclass = lambda { |obj| class << obj; self ; end }

    metaclass[klass].instance_eval do
      old_new = instance_method(:new)
      undef_method :new

      define_method(:inherited) do |subklass|
        metaclass[subklass].instance_eval do
          define_method(:new, old_new)
        end
      end
    end
  end
end

이것은 구현되지 않은 메서드를보고하기 위해 일반적인 #method_missing에 의존하지만, 추상 클래스가 구현되지 않도록합니다 (초기화 메서드가 있더라도)

class A
  include Abstract
end
class B < A
end

B.new #=> #<B:0x24ea0>
A.new # raises #<NoMethodError: undefined method `new' for A:Class>

다른 포스터가 말했듯이 추상 클래스보다는 믹스 인을 사용해야합니다.


3

나는 이런 식으로 그것을 했으므로, 비 추상 클래스에서 새로운 것을 찾기 위해 자식 클래스에서 새로운 것을 재정의합니다. 나는 여전히 루비에서 추상 클래스를 사용하는 데 실용적이지 않다.

puts 'test inheritance'
module Abstract
  def new
    throw 'abstract!'
  end
  def inherited(child)
    @abstract = true
    puts 'inherited'
    non_abstract_parent = self.superclass;
    while non_abstract_parent.instance_eval {@abstract}
      non_abstract_parent = non_abstract_parent.superclass
    end
    puts "Non abstract superclass is #{non_abstract_parent}"
    (class << child;self;end).instance_eval do
      define_method :new, non_abstract_parent.method('new')
      # # Or this can be done in this style:
      # define_method :new do |*args,&block|
        # non_abstract_parent.method('new').unbind.bind(self).call(*args,&block)
      # end
    end
  end
end

class AbstractParent
  extend Abstract
  def initialize
    puts 'parent initializer'
  end
end

class Child < AbstractParent
  def initialize
    puts 'child initializer'
    super
  end
end

# AbstractParent.new
puts Child.new

class AbstractChild < AbstractParent
  extend Abstract
end

class Child2 < AbstractChild

end
puts Child2.new

3

이 작은 abstract_type보석도있어, 눈에 거슬리지 않는 방식으로 추상 클래스와 모듈을 선언 할 수 있습니다.

예 ( README.md 파일에서) :

class Foo
  include AbstractType

  # Declare abstract instance method
  abstract_method :bar

  # Declare abstract singleton method
  abstract_singleton_method :baz
end

Foo.new  # raises NotImplementedError: Foo is an abstract type
Foo.baz  # raises NotImplementedError: Foo.baz is not implemented

# Subclassing to allow instantiation
class Baz < Foo; end

object = Baz.new
object.bar  # raises NotImplementedError: Baz#bar is not implemented

1

접근 방식에 문제가 없습니다. 물론 모든 하위 클래스가 초기화를 재정의하는 한 초기화에서 오류를 발생시키는 것은 괜찮아 보입니다. 그러나 당신은 self.new를 그렇게 정의하고 싶지 않습니다. 여기 내가 할 일이 있습니다.

class A
  class AbstractClassInstiationError < RuntimeError; end
  def initialize
    raise AbstractClassInstiationError, "Cannot instantiate this class directly, etc..."
  end
end

또 다른 접근 방식은 모든 기능을 모듈에 넣는 것인데, 언급했듯이 절대 설명 할 수 없습니다. 그런 다음 다른 클래스에서 상속하는 대신 클래스에 모듈을 포함하십시오. 그러나 이것은 슈퍼와 같은 것을 깨뜨릴 것입니다.

따라서 구성 방법에 따라 다릅니다. 모듈은 "다른 클래스에서 사용할 수 있도록 지정된 내용을 어떻게 작성합니까"라는 문제를 해결하기위한 더 깨끗한 솔루션처럼 보이지만


나도 그렇게하고 싶지 않다. 그러면 아이들은 "슈퍼"라고 부를 수 없습니다.
Austin Ziegler


0

Ruby처럼 느껴지지는 않지만 다음과 같이 할 수 있습니다.

class A
  def initialize
    raise 'abstract class' if self.instance_of?(A)

    puts 'initialized'
  end
end

class B < A
end

결과 :

>> A.new
  (rib):2:in `main'
  (rib):2:in `new'
  (rib):3:in `initialize'
RuntimeError: abstract class
>> B.new
initialized
=> #<B:0x00007f80620d8358>
>>
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.