Как реализовать абстрактный класс в 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
со списком абстрактных методов, и когда они вызываются экземпляром абстрактного класса, A NotImplementedError
исключение.
просто чтобы перезвонить здесь поздно, я думаю, что нет причин останавливать кого-то от создания экземпляра абстрактного класса,тем более, что они могут добавлять методы к нему на лету.
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_type
gem, позволяющий ненавязчиво объявлять абстрактные классы и модули.
пример (из 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
лично я поднимаю 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 для отчета о нереализованных методах, но сохраняет абстрактные классы от реализации (даже если у них есть метод initialize)
class A
include Abstract
end
class B < A
end
B.new #=> #<B:0x24ea0>
A.new # raises #<NoMethodError: undefined method `new' for A:Class>
Как и другие плакаты, вы, вероятно, должны использовать mixin, а не абстрактный класс.
Я сделал это таким образом, поэтому он переопределяет new на дочернем классе, чтобы найти новый на не абстрактном классе. Я все еще не вижу практического использования абстрактных классов в 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
другой подход мог бы поместить все функции в модуль, который, как вы упомянули, не может быть instiated. Затем включите модуль в свои классы, а не наследование от другого класса. Однако, это испортило бы супер.
Так это зависит от того, как вы хотите структурировать его. Хотя модули кажутся более чистым решением для решения проблемы "как написать некоторые вещи, которые соизволили использовать другие классы"