Как реализовать абстрактный класс в рубине?
Я знаю, что в 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
, который собирается поднять исключение.
Кроме того, модули не могут быть созданы, но они также не могут быть унаследованы. что делает новый метод частным тоже не работает. Любые указатели?
Ответы
Ответ 1
Мне не нравится использовать абстрактные классы в 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
.
Ответ 2
Просто, чтобы перезвонить в конце здесь, я думаю, что нет причин помешать кому-либо создавать экземпляр абстрактного класса особенно потому, что они могут добавлять к нему методы на летать.
Языки утиной печати, такие как Ruby, используют присутствие/отсутствие или поведение методов во время выполнения, чтобы определить, следует ли их вызывать или нет. Поэтому ваш вопрос, применительно к абстрактному методу , имеет смысл
def get_db_name
raise 'this method should be overriden and return the db name'
end
и это должно быть о конце истории. Единственная причина использования абстрактных классов в Java - настоять на том, чтобы определенные методы получили "заполнение", в то время как другие имеют свое поведение в абстрактном классе. На языке утиного ввода основное внимание уделяется методам, а не классам/типам, поэтому вы должны переместить свои заботы на этот уровень.
В вашем вопросе вы в основном пытаетесь воссоздать ключевое слово abstract
из Java, которое является запахом кода для выполнения Java в Ruby.
Ответ 3
Попробуйте следующее:
class A
def initialize
raise 'Doh! You are trying to instantiate an abstract class!'
end
end
class B < A
def initialize
end
end
Ответ 4
class A
private_class_method :new
end
class B < A
public_class_method :new
end
Ответ 5
My 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
И, конечно, добавление еще одной ошибки для инициализации базового класса было бы тривиально в этом случае.
Ответ 6
для любого в мире рельсов, реализация модели ActiveRecord в качестве абстрактного класса выполняется с этим объявлением в файле модели:
self.abstract_class = true
Ответ 7
За последние 6 с половиной лет программирования на Ruby мне ни разу не понадобился абстрактный класс.
Если вы думаете, что вам нужен абстрактный класс, вы слишком много думаете на языке, который их предоставляет/требует, а не на Ruby как таковом.
Как и предполагали другие, миксин больше подходит для вещей, которые должны быть интерфейсами (как их определяет Java), а переосмысление вашего дизайна больше подходит для вещей, которые "нуждаются" в абстрактных классах из других языков, таких как C++.
Обновление 2019: мне не нужны абстрактные классы в Ruby за 16 с половиной лет использования. Все, что говорят все, кто комментирует мой ответ, решается путем изучения Ruby и использования соответствующих инструментов, таких как модули (которые даже дают вам общие реализации). В командах, которыми я управлял, есть люди, которые создали классы, у которых базовая реализация не работает (например, абстрактный класс), но в основном это пустая трата кода, потому что NoMethodError
даст тот же результат, что и AbstractClassError
в производстве ,
Ответ 8
Вы можете попробовать 3 рубина:
интерфейс
абстрактные
простой аннотация
Ответ 9
Если вы хотите пойти с неинтегрируемым классом, в вашем методе A.new, проверьте, если self == A перед выбросом ошибки.
Но действительно, модуль кажется более похожим на то, что вы хотите здесь - например, Enumerable - это нечто вроде абстрактного класса на других языках. Вы технически не можете подклассифицировать их, но вызов include SomeModule
достигает примерно одной и той же цели. Есть ли причина, по которой это не сработает для вас?
Ответ 10
С какой целью вы пытаетесь служить абстрактным классом? Вероятно, есть лучший способ сделать это в рубине, но вы не указали никаких подробностей.
Мой указатель - это; используйте mixin не наследование.
Ответ 11
Лично я поднимаю NotImplementedError в методах абстрактных классов. Но вы можете оставить его вне "нового" метода по причинам, о которых вы говорили.
Ответ 12
Другой ответ:
module Abstract
def self.append_features(klass)
# access an object copy of its class 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>
Как и другие плакаты, вы, вероятно, должны использовать mixin, а не абстрактный класс.
Ответ 13
Я сделал это таким образом, поэтому он переопределяет новый класс child для поиска нового в не абстрактном классе.
Я до сих пор не вижу практического использования абстрактных классов в 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
Ответ 14
Там также этот маленький камень 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
Ответ 15
Ничего плохого в вашем подходе. Повысить ошибку при инициализации кажется прекрасным, если все ваши подклассы переопределяют инициализацию, конечно. Но вы не хотите определять self.new, как это. Вот что я буду делать.
class A
class AbstractClassInstiationError < RuntimeError; end
def initialize
raise AbstractClassInstiationError, "Cannot instantiate this class directly, etc..."
end
end
Еще один подход будет включать все функциональные возможности модуля, которые, как вы упомянули, никогда не могут быть инициированы. Затем включите модуль в свои классы, а не наследуйте его от другого класса. Однако это нарушит такие вещи, как супер.
Так что это зависит от того, как вы хотите его структурировать. Хотя модули выглядят как более чистое решение для решения проблемы "Как написать материал, который будет использоваться для использования другими классами"
Ответ 16
2-строчный драгоценный камень: https://rubygems.org/gems/abstract