programing

Ruby에서 추상 클래스를 구현하는 방법

topblog 2023. 6. 27. 21:37
반응형

Ruby에서 추상 클래스를 구현하는 방법

루비에는 추상적인 수업이라는 개념이 없는 것으로 알고 있습니다.하지만 구현이 필요하다면 어떻게 해야 할까요?저는 다음과 같은 것을 시도했습니다.

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

class B < A
  ...
  ...
end

하지만, 제가 B를 인스턴스화하려고 하면, 그것은 내부적으로 전화를 걸 것입니다.A.new이는 예외를 제기할 것입니다.

또한 모듈은 인스턴스화할 수 없지만 상속할 수도 없습니다.새 방법을 비공개로 설정해도 작동하지 않습니다.

누구 조언자 있어요?

여기서 늦게까지 시간을 내기 위해, 저는 누군가가 추상적인 수업을 인스턴스화하는 것을 막을 이유가 없다고 생각합니다. 특히 그들은 그것에 방법을 즉시 추가할 수 있기 때문입니다.

Ruby와 같은 오리형 언어는 런타임에 메서드의 존재/존재 또는 동작을 사용하여 메서드를 호출할지 여부를 결정합니다.따라서 추상적인 방법에 적용되는 당신의 질문은 타당합니다.

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

그리고 그것이 이야기의 끝에 관한 것이어야 합니다.Java에서 추상 클래스를 사용하는 유일한 이유는 특정 메서드가 "채움"되는 반면 다른 메서드는 추상 클래스에서 동작을 수행한다는 것을 주장하기 위한 것입니다.오리형 언어는 수업/유형이 아닌 방법에 중점을 두기 때문에 고민을 그 수준으로 옮겨야 합니다.

질문에 따르면, 당신은 기본적으로 다음과 같은 것을 재현하려고 합니다.abstract루비에서 Java를 수행하기 위한 코드 냄새인 Java의 키워드입니다.

저는 루비에서 추상 수업을 사용하는 것을 좋아하지 않습니다(거의 항상 더 좋은 방법이 있습니다).상황에 가장 적합한 기술이라고 생각한다면 다음 스니펫을 사용하여 추상적인 방법을 보다 명확하게 설명할 수 있습니다.

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예외가 발생합니다.

사용해 보십시오.

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

class B < A
  def initialize
  end
end

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

self.abstract_class = true
class A
  private_class_method :new
end

class B < A
  public_class_method :new
end

루비를 프로그래밍한 지난 6년 반 동안 추상 수업이 한 번도 필요하지 않았습니다.

만약 여러분이 추상적인 수업이 필요하다고 생각한다면, 여러분은 그것들을 제공하거나 필요로 하는 언어로 너무 많이 생각하고 있는 것이지 루비가 아닙니다.

다른 사람들이 제안했듯이, 믹스인은 인터페이스가 되어야 하는 것들(자바가 정의하는 것처럼)에 더 적합하며, C++와 같은 다른 언어의 추상 클래스가 "필요한" 것들에 더 적합합니다.

2022년 기준 업데이트: 20년 동안 루비에서 추상 수업이 필요하지 않았습니다.제 답변에 대해 언급하는 모든 사람들이 말하는 모든 것은 Ruby를 실제로 배우고 모듈과 같은 적절한 도구를 사용함으로써 해결됩니다(일반적인 구현까지 제공).에는 (처럼) , 은 대부분 입니다.NoMethodError정확하게 동일한 결과를 생성할 것입니다.AbstractClassError생산 중인

나의 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

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

추상적인 수업으로 어떤 목적을 수행하려고 합니까?루비에서 더 좋은 방법이 있을 수 있지만, 당신은 어떤 세부사항도 제시하지 않았습니다.

제 포인터는 이것입니다. 상속이 아닌 혼합을 사용하십시오.

당신은 3개의 루비젬을 시도할 수 있습니다.


추상적인 것

저는 개인적으로 키웁니다.NotImplementedError추상 클래스의 방법으로.하지만 당신은 그것을 제외하는 것이 좋을 수도 있습니다.new방법, 당신이 언급한 이유로.

또 다른 대답:

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>

다른 포스터들이 말했듯이, 여러분은 아마도 추상적인 수업보다는 믹스인을 사용해야 할 것입니다.

메소드에서 class와 , 당신의 A.new 에서, uninstantable class를 확인하세요.self == A오류를 범하기 전에

하지만 실제로 모듈은 여러분이 여기서 원하는 것에 더 가깝습니다.예를 들어 Enumerable은 다른 언어에서 추상 클래스일 수 있는 종류입니다.기술적으로는 하위 분류를 할 수 없지만 전화를 걸면include SomeModule거의 동일한 목표를 달성합니다.이것이 당신에게 효과가 없을 이유가 있나요?

저는 이런 식으로 했습니다. 그래서 추상적이지 않은 수업에 대한 새로운 것을 찾기 위해 아동 수업에 대한 새로운 것을 재정의합니다.저는 아직도 루비에서 추상 수업을 사용하는 것이 실용적이라고 생각하지 않습니다.

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

이렇게 작은 것도 있습니다.abstract_typegem, 방해되지 않는 방식으로 추상 클래스 및 모듈을 선언할 수 있습니다.

(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

두 줄 보석: https://rubygems.org/gems/abstract

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>
>>

당신의 접근 방식에는 아무런 문제가 없습니다.모든 하위 클래스가 재정의되는 한 이니셜라이저에서 오류를 발생시키는 것은 문제가 없는 것 같습니다.initialize물론이야.하지만 당신은 정의하기를 원하지 않습니다.self.new이런 거죠.제가 할 일은 다음과 같습니다.

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

또 다른 접근 방식은 모듈에 모든 기능을 넣는 것입니다. 말씀하신 것처럼 이 기능은 결코 인스턴스화될 수 없습니다.그런 다음 다른 클래스에서 상속하는 대신 모듈을 클래스에 포함합니다.하지만, 이것은 다음과 같은 것들을 깨뜨릴 것입니다.super.

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

언급URL : https://stackoverflow.com/questions/512466/how-to-implement-an-abstract-class-in-ruby

반응형