可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I know there is no concept of abstract class in ruby. But if at all it needs to be implemented, how to go about it? I tried something like...
class A
def self.new
raise 'Doh! You are trying to write Java in Ruby!'
end
end
class B < A
...
...
end
But when I try to instantiate B, it is internally going to call A.new
which is going to raise the exception.
Also, modules cannot be instantiated but they cannot be inherited too. making the new method private will also not work. Any pointers?
回答1:
I don't like using abstract classes in Ruby (there's almost always a better way). If you really think it's the best technique for the situation though, you can use the following snippet to be more declarative about which methods are abstract:
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
Basically, you just call abstract_methods
with the list of methods that are abstract, and when they get called by an instance of the abstract class, a NotImplementedError
exception will be raised.
回答2:
Just to chime in late here, I think that there's no reason to stop somebody from instantiating the abstract class, especially because they can add methods to it on the fly.
Duck-typing languages, like Ruby, use the presence/absence or behavior of methods at runtime to determine whether they should be called or not. Therefore your question, as it applies to an abstract method, makes sense
def get_db_name
raise 'this method should be overriden and return the db name'
end
and that should be about the end of the story. The only reason to use abstract classes in Java is to insist that certain methods get "filled-in" while others have their behavior in the abstract class. In a duck-typing language, the focus is on methods, not on classes/types, so you should move your worries to that level.
In your question, you're basically trying to recreate the abstract
keyword from Java, which is a code-smell for doing Java in Ruby.
回答3:
Try this:
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¢: I opt for a simple, lightweight 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
And, of course, adding another error for initializing the base class would be trivial in this case.
回答6:
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++.
回答7:
for anyone in the rails world, implementing an ActiveRecord model as an abstract class is done with this declaration in the model file:
self.abstract_class = true
回答8:
You can try 3 rubygems:
interface
abstract
simple abstract
回答9:
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?
回答10:
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.
回答11:
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.
回答12:
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.
回答13:
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
回答14:
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
回答15:
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"
回答16:
2-lines gem : https://rubygems.org/gems/abstract