Abstract classes and modules

Hello,
for some code I am writing, I’d like to have a couple of abstract
classes. However I am not very sure how to go about it in Ruby.

Ideally all I would need is:

  1. The class cannot be instantiated
    and/or
  2. The class is not visible outside of the module it comes in

What would be the canonical way to go about this in Ruby? Should I use
a mix-in module?

Ideally I would prefer to use a class since all the subclasses have a
similar instantiation so I’d like to code it only once and use
super(), but then the user himself may try to instantiate it breaking
the whole “magic”. :slight_smile:

Thank you
Diego

(1) can be achieved by overwriting “new” on the class itself:
class Foo
class << self
def new
raise “Don’t do that!”
end
end
end
class Bar < Foo
end

Foo.new #=> raises
Bar.new #=> doesn’t raise

By (2) you probably mean simple namespacing…

module X
class Y
end
end

Y #=> raises NameError
X::Y #=> that’s it.

On Sunday, November 28, 2010 04:15:19 am Diego V. wrote:

Hello,
for some code I am writing, I’d like to have a couple of abstract
classes. However I am not very sure how to go about it in Ruby.

Just do it.

Ideally all I would need is:

  1. The class cannot be instantiated
    and/or
  2. The class is not visible outside of the module it comes in

The canonical Ruby solution is either to

  1. Document the class as abstract, and warn people not to instantiate it
    or
  2. Document the class as a private implementation detail

The simplest way to do this would be to put “Abstract” in the name of
your
class, but you’ll want to go a bit farther than that.

What would be the canonical way to go about this in Ruby? Should I use
a mix-in module?

No, if it’s a class, you should use a class. Modules are for collections
of
behavior you might mix in to a class, but sometimes, a superclass
actually
does make sense.

So you should do whichever actually makes sense to you. Do what makes it
easiest to do things the right way. Don’t even consider how to prevent
people
from doing things the wrong way.

Ideally I would prefer to use a class since all the subclasses have a
similar instantiation so I’d like to code it only once and use
super(),

If you call super, while the behavior is a bit weird, it will hit module
methods.

but then the user himself may try to instantiate it breaking
the whole “magic”. :slight_smile:

This is Ruby. The user may try to do ANYTHING, and you CANNOT stop them.

Let’s say you use a module. How do you stop the user from inheriting
from that
module? Or say you use a class – even if you stop the user from
instantiating
it, how do you stop them from simply inheriting from that class and
instantiating anyway?

So stop trying. Document the proper way to use your code, and don’t try
to
prevent users from doing it the wrong way.

Just in case I haven’t convinced you, here’s a few examples of how you
might
do what you’re accomplishing, and why they don’t work.

You might do this:

class Abstract
def self.new
raise “Can’t initialize abstract class!”
end
end

But then you’ll have a hard time instantiating it yourself in
subclasses, and
the user can still do this:

Class.instance_method(:new).bind(Abstract).call

That’s assuming they can’t figure out (and duplicate) exactly how you
manage
to instantiate subclasses. You could do something fancier:

class Abstract
def self.new
if self == Abstract
raise “Can’t instantiate abstract class!”
else
super
end
end
end

But then they can do this:

Class.new(Abstract).new

Which is basically an anonymous version of this:

class Foo < Abstract; end
Foo.new

You could try to hide the class away by making it anonymous:

module MyModule
k = Class.new do
def some_method
“Don’t call me without super!”
end
end

class Foo < k
def some_method
super
“some_method called in child”
end
end
end

Now no one outside that block can see k, right?

Wrong.

MyModule::Foo.ancestors[1].new

I suppose you could override ancestors, but that’s just an arms race
that
you’re very likely to lose. Just for fun, let’s add that in:

class MyModule::Foo
def self.ancestors
a = super
[a.first] + a[2…a.length]
end
end

So now I just do this:

Class.instance_method(:ancestors).bind(MyModule::Foo).call[1].new

You’d pretty much have to dig into all the core classes like Class and
Object
to really prevent me from doing this kind of trick. I don’t think that’s
worth
the time and effort, and I think it’s very likely I could still find a
way
around it.

If you really want, you could do something like one of those raise-in-
the-‘new’-method approaches to at least force your users to know what
they’re
doing, but I don’t really see the point. If your users want to do things
properly, they’ll follow the docs. If they don’t, you can’t stop them.

Thank you for a very exhaustive reply. Much appreciated. :slight_smile:

Just to explain what I meant a bit more clearly, I don’t have a
problem with someone who feels like snooping and modifying things: as
you point out this is Ruby. I simply meant that I would want him/her
not to encounter the class in everyday work.

I can use :nodoc: to remove it from the documentation, but it would
still be way to easy to encounter. So, yeah, your “solutions” are just
the thing: and I feel a little bit better seeing that there’s no real
canonical way, or something easier.

No, if it’s a class, you should use a class. Modules are for collections of
behavior you might mix in to a class, but sometimes, a superclass actually
does make sense.

Good to hear someone say that. :slight_smile: I’ve been programming for a while
but I have no formal training/knowledge, so it’s good to read that my
gut feeling (that I really needed a superclass rather than a mix-in
module) made some sense. :wink:

Thanks again
Diego

On Sunday, November 28, 2010 10:24:36 am niklas | brueckenschlaeger
wrote:

Foo.new #=> raises
Bar.new #=> doesn’t raise

Yes it does. Try it.

It’s a bad idea, anyway.

On Sunday, November 28, 2010 12:25:22 pm Diego V. wrote:

Thank you for a very exhaustive reply. Much appreciated. :slight_smile:

Just to explain what I meant a bit more clearly, I don’t have a
problem with someone who feels like snooping and modifying things: as
you point out this is Ruby. I simply meant that I would want him/her
not to encounter the class in everyday work.

I’m not sure how they would, anyway. After all…

I can use :nodoc: to remove it from the documentation, but it would
still be way to easy to encounter.

I think if you’ve got a number of classes which are actually
well-documented,
and this one isn’t, you never instantiate it, your unit tests never
instantiate it, and it’s clearly a base class for other things you do
provide, it seems like if the user instantiates it, they get to keep
both
pieces.

And it may well be that you want users to discover this class, that you
want
to document common behaviors of subclasses here, but you want to make it
clear
that they should be instantiating subclasses if anything. For instance,
Nokogiri has this:

http://nokogiri.org/Nokogiri/XML/Node.html

I can’t think of any case where it makes sense to instantiate this class
directly, as opposed to, say, Element or Text. However, it’s not hidden,
nor
(as far as I can tell) is there anything preventing me from doing so.

So my recommendation is still to just be clear what it is and what it’s
for,
or at the very least that it’s not for the end-user. Enforcing the
abstract-
ness of something that’s clearly abstract smells a bit to me like the
kind of
“safety” you’d get with strict type-checking.

But if you still want to use my pseudo-solutions, happy to help, I
guess…