Как реализовать абстрактный класс в ruby? - PullRequest
109 голосов
/ 04 февраля 2009

Я знаю, что в 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, что вызовет исключение.

Кроме того, модули не могут быть созданы, но они также не могут наследоваться. создание нового метода private также не будет работать. Есть указатели?

Ответы [ 16 ]

3 голосов
/ 08 сентября 2014

Есть также этот маленький камень 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
3 голосов
/ 29 октября 2010

Я сделал это так, поэтому он переопределяет новый для дочернего класса, чтобы найти новый для неабстрактного класса. Я до сих пор не вижу практического использования абстрактных классов в 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
3 голосов
/ 04 февраля 2009

Другой ответ:

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 для сообщения о невыполненных методах, но предотвращает реализацию абстрактных классов (даже если у них есть метод initialize)

class A
  include Abstract
end
class B < A
end

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

Как говорили другие авторы, вам, вероятно, следует использовать миксин, а не абстрактный класс.

3 голосов
/ 04 февраля 2009

Лично я поднимаю NotImplementedError в методах абстрактных классов. Но вы можете оставить его вне «нового» метода по указанным вами причинам.

1 голос
/ 05 июля 2011

2-х строчный камень: https://rubygems.org/gems/abstract

1 голос
/ 04 февраля 2009

Ничего плохого в вашем подходе. Вызывать ошибку при инициализации кажется нормальным, если конечно все ваши подклассы переопределяют инициализацию. Но вы не хотите определять self.new таким образом. Вот что я бы сделал.

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

Другой подход - поместить всю эту функциональность в модуль, который, как вы упомянули, никогда не может быть реализован. Затем включите модуль в свои классы, а не наследуйте от другого класса. Однако это сломало бы такие вещи, как супер.

Так что это зависит от того, как вы хотите его структурировать. Хотя модули кажутся более чистым решением для решения проблемы «Как написать некоторые вещи, которые предназначены для использования другими классами»

...