Как реализовать абстрактный класс в Ruby?
Я знаю, что в 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 который собирается вызвать исключение.
кроме того, модули не могут быть созданы, но они также не могут быть унаследованы. новый частный метод также не будет работать. Какие-нибудь указатели?
16 ответов:
мне не нравится использовать абстрактные классы в 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со списком абстрактных методов, и когда они вызываются экземпляром абстрактного класса, ANotImplementedErrorисключение.
просто запаздывая здесь, я думаю, что нет никаких причин, чтобы остановить кого-то от создания экземпляра абстрактного класса, тем более, что они могут добавлять методы к нему на лету.
языки Duck-typing, такие как Ruby, используют наличие/отсутствие или поведение методов во время выполнения, чтобы определить, следует ли их вызывать или нет. Поэтому ваш вопрос, как он относится к абстрактным метод, есть смысл
def get_db_name raise 'this method should be overriden and return the db name' endи это должно быть о конце истории. Единственная причина использовать абстрактные классы в Java-настаивать на том, чтобы некоторые методы были "заполнены", в то время как другие имеют свое поведение в абстрактном классе. В языке duck-typing основное внимание уделяется методам, а не классам/типам, поэтому вы должны переместить свои заботы на этот уровень.
в вашем вопросе, вы в основном пытаются воссоздать
abstractключевое слово из Java, которое является запахом кода для выполнения Java в Ruby.
попробуйте это:
class A def initialize raise 'Doh! You are trying to instantiate an abstract class!' end end class B < A def initialize end end
Мой 2¢: я выбираю простой, легкий DSL mixin:
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и, конечно, добавление еще одной ошибки для инициализации базового класса было бы тривиальным в этом случае.
за последние 6 1/2 лет программирования Ruby, я не нужны абстрактный класс.
Если вы думаете, что вам нужен абстрактный класс, вы слишком много думаете на языке, который обеспечивает/требует их, а не на Ruby как таковом.
Как предлагали другие, mixin более подходит для вещей, которые должны быть интерфейсами (как их определяет Java), и переосмысление вашего дизайна более подходит для вещей, которые" нуждаются " в абстрактных классах из другие языки, такие как C++.
для всех в мире rails реализация модели ActiveRecord как абстрактного класса выполняется с помощью этого объявления в файле модели:
self.abstract_class = true
вы можете попробовать 3 библиотек RubyGems:
интерфейс
аннотация
простые абстрактные
Если вы хотите пойти с классом uninstantiable, в А. новый метод, проверьте, если сам == а до возникновения ошибки.
но на самом деле модуль больше похож на то, что вы хотите здесь - например, Enumerable-это то, что может быть абстрактным классом на других языках. Вы технически не можете подкласс их, но вызов
include SomeModuleдостигает примерно той же цели. Есть ли какая-то причина, по которой это не сработает для вас?
какой цели вы пытаетесь служить с абстрактным классом? Есть, вероятно, лучший способ сделать это в Ruby, но вы не дали никаких подробностей.
мой указатель это; используйте mixin не наследование.
есть также Этот маленький
abstract_typegem, позволяющий ненавязчиво объявлять абстрактные классы и модули.пример (из 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
лично я поднимаю NotImplementedError в методах абстрактных классов. Но вы можете оставить его вне "нового" метода по причинам, которые вы упомянули.
еще один ответ:
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>
Я сделал это таким образом, так что он переопределяет новый на дочерний класс, чтобы найти новый на не абстрактный класс. Я до сих пор не вижу никакого практического использования абстрактных классов в 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
ничего плохого в вашем подходе. Поднять ошибку в инициализации кажется прекрасным, пока все ваши подклассы переопределяют инициализацию, конечно. Но вы не хотите определять себя.что-то новенькое. Вот что я бы сделал.
class A class AbstractClassInstiationError < RuntimeError; end def initialize raise AbstractClassInstiationError, "Cannot instantiate this class directly, etc..." end endдругой подход будет помещать всю эту функциональность в модуль, который, как вы упомянули, никогда не может быть создан. Затем включите модуль в свои классы, а не наследование от другого класса. Однако, это испортило бы супер.
Так это зависит от того, как вы хотите структурировать его. Хотя модули кажутся более чистым решением для решения проблемы "как мне написать некоторые вещи, которые соизволили использовать другие классы"
2-строчный камень:https://rubygems.org/gems/abstract
Comments