Programming

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

procodes 2020. 7. 26. 13:24
반응형

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


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

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에서 추상 클래스를 사용하는 것을 좋아하지 않습니다 (거의 항상 더 좋은 방법이 있습니다). 실제로 이것이 상황에 가장 적합한 기술이라고 생각하면 다음 스 니펫을 사용하여 추상적 인 메소드에 대해 더 선언적 일 수 있습니다.

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


여기서 늦게 차임하기 위해 누군가 클래스를 인스턴스화하지 못하게 할 이유가 없다고 생각합니다. 특히 메소드를 즉시 추가 할 수 있기 때문 입니다.

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

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

그리고 그것은 이야기의 끝에 관한 것이어야합니다. Java에서 추상 클래스를 사용하는 유일한 이유는 특정 메소드가 "채워지는"반면 다른 클래스는 추상 클래스에서 동작을하는 것입니다. 오리 타이핑 언어에서는 클래스 / 타입이 아닌 메서드에 중점을 두므로 걱정을 해당 수준으로 이동해야합니다.

귀하의 질문에, 당신은 기본적으로 abstractJava 에서 키워드 를 다시 작성하려고합니다 . 이는 Ruby에서 Java를 수행하기위한 코드 냄새입니다.


이 시도:

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

class B < A
  def initialize
  end
end

class A
  private_class_method :new
end

class B < A
  public_class_method :new
end

내 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

물론이 경우 기본 클래스를 초기화하는 데 다른 오류를 추가하는 것은 쉽지 않습니다.


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

self.abstract_class = true

In the last 6 1/2 years of programming Ruby, I haven't needed an abstract class once.

If you're thinking you need an abstract class, you're thinking too much in a language that provides/requires them, not in Ruby as such.

As others have suggested, a mixin is more appropriate for things that are supposed to be interfaces (as Java defines them), and rethinking your design is more appropriate for things that "need" abstract classes from other languages like C++.

Update 2019: I haven’t needed abstract classes in Ruby in 16½ years of use. Everything that all of the folks commenting on my response are saying is addressed by actually learning Ruby and using the appropriate tools, like modules (which even give you common implementations). There are people on teams I have managed who have created classes that have base implementation that fail (like an abstract class), but these are mostly a waste of coding because NoMethodError would produce the exact same result as an AbstractClassError in production.


You can try 3 rubygems:
interface
abstract
simple abstract


If you want to go with an uninstantiable class, in your A.new method, check if self == A before throwing the error.

But really, a module seems more like what you want here — for example, Enumerable is the sort of thing that might be an abstract class in other languages. You technically can't subclass them, but calling include SomeModule achieves roughly the same goal. Is there some reason this won't work for you?


What purpose are you trying to serve with an abstract class? There is probably a better way to do it in ruby, but you didn't give any details.

My pointer is this; use a mixin not inheritance.


Personally I raise NotImplementedError in methods of abstract classes. But you may want to leave it out of the 'new' method, for the reasons you mentioned.


Another answer:

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

This relies on the normal #method_missing to report unimplemented methods, but keeps abstract classes from being implemented (even if they have an initialize method)

class A
  include Abstract
end
class B < A
end

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

Like the other posters have said, you should probably be using a mixin though, rather than an abstract class.


I did it this way, so it redefines new on child class to find a new on non abstract class. I still don't see any practical in using abstract classes in ruby.

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

There's also this small abstract_type gem, allowing to declare abstract classes and modules in an unobstrusive way.

Example (from the README.md file):

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

Nothing wrong with your approach. Raise an error in initialize seems fine, as long as all your subclasses override initialize of course. But you dont want to define self.new like that. Here's what I would do.

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

Another approach would be put all that functionality in a module, which as you mentioned can never be instiated. Then include the module in your classes rather than inheriting from another class. However, this would break things like super.

So it depends on how you want to structure it. Although modules seem like a cleaner solution for solving the problem of "How do I write some stuff that is deigned for other classes to use"


2-lines gem : https://rubygems.org/gems/abstract

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

반응형