DM> It won't tell you how many Foo objects actually exist. It's more a
count of DM> how many have ever been created.
Cool.
Is there a way to tell how many instances actually exist? Is there
anything like a destructor for class instances? Just curious.
These are separate questions.
In MRI, you can tell how many instances exist by actually counting them. Look
at the documentation for ObjectSpace.
And yes, you can register a destructor -- that's also in ObjectSpace, and it's
called a "finalizer". But remember, Ruby is garbage-collected -- that means
you have absolutely no guarantee of _when_ that object will be removed. The
finalizer is guaranteed to be called before the program exits, but that's the
only guarantee you get.
Both of these are relatively advanced topics, and something that shouldn't be
needed in most programs. Keep in mind that in C++, then main purpose of a
destructor is to free resources used by the object in question -- and most of
these resources are simply other objects. So, the fact that Ruby is garbage-
collected removes most of the reason for destructors/finalizers.
An exception would be a resource that is more than just an object -- for
example, an open file, or a database handle. But these are generally much
scarcer resources than memory, so if at all possible, you want to manually
close these as soon as you can, rather than waiting for something to get
garbage collected. For example:
open 'some_file' do |file|
file.each_line do |line|
# do some stuff with each line in the file
end
end
What might not be obvious here is that the 'open' call will automatically
close the file when the block exits.
So, basically, the reason that instance_variable_get is private to
Module is that one does not wish make breaking encapsulation too easy?
Others have answered this already...
In a very basic sense, yes, that seems like a valid answer. There is really no
good reason why code outside a class should have to call
instance_variable_get, most of the time. It's private so that when you try,
you'll rethink your architecture so you don't need it.
On the other hand, Ruby has open classes. While there are ways to supposedly
lock it down and run untrusted code in a safe sandbox, in general, any code
running in the same interpreter can do all sorts of things to existing
classes. In other words, if you want to access the variable @bar in an object
of class Foo, you could always do something like this:
class Foo
attr_reader :bar
end
There are many other ways of getting the same result.
Now, Florian mentioned another aspect, which is that you still can only know
anything about an object by calling methods on it. So even if
instance_variable_get was public, there's nothing from stopping an especially
paranoid class from redefining it or removing it.
I think that's more than enough to answer your question, but if you're
curious, you could always have a sort of "arms race" of trying to enforce
encapsulation. For example, even if someone redefines instance_variable_get, I
can always do this:
Class.instance_method
instance_method).bind(Foo).call
instance_variable_get).bind(foo).call
@bar)
That means there's nothing the Foo class itself can do to hide the @bar
variable from you, including redefining instance_method on itself.
But wait! How do you know Class itself hasn't been modified?
The lesson to learn here is that while Ruby is very good at encapsulation by
convention, it's not very good at all at enforced encapsulation, as C++ is. At
the end of the day, you have to remember that this is a language which lets
you define null to be not null. Here, try this in irb:
class NilClass
def nil?
false
end
end
Now type any other command, and watch irb crash.
I mean, it seems easy enough to break encapsulation by adding an
accessor, right?
Well, yes and no.
Yes, you can break encapsulation by just adding an accessor to any instance
variable that anyone was trying to hide from you. You can also break it by
using one of several tricks to call the private instance_variable_get, and
probably a few ways I haven't thought of. Ruby doesn't enforce encapsulation.
On the other hand, accessors are the ultimate in encapsulation -- partly
because they're the only sane way to get at instance variables, and partly
because they're so easy to define. I remember wishing for them in Java, where
good coding style led to lots of these:
private int foo;
public int getFoo() {
return foo;
}
public void setFoo(int value) {
foo = value;
}
I can see why Java people use tools like Eclipse -- there's no way you want to
type all that stuff yourself. But it's still a good idea, because it allows
you to change your internal structure to no longer require that variable,
without changing your external interface.
The point here is, if you actually expose an accessor, no one has to use
instance_variable_get or anything like it -- they won't be mucking about with
the internals of your class. (If they do, they're asking for trouble when you
change something.) That means you can, among other things, override the
behavior of the accessor on subclasses, replace it entirely with a method,
etc.
Just as an example: Suppose you have a class like this:
class Line
attr_reader :a, :b, :slope
def initialize(point_a, point_b)
@a = point_a
@b = point_b
@slope = (a.x - b.x) / (a.y - b.y)
end
end
Now, maybe later on, you notice this isn't working out too well -- maybe not
everyone cares about the slope, so you're wasting cycles calculating it every
time. Maybe the points are changing frequently, and you don't want to have to
re-calculate the slope on every change, only when someone requests it. Since
you defined slope as a reader method, you can just change your implementation:
class Line
attr_reader :a, :b
def slope
(a.x - b.x) / (a.y - b.y)
end
end
No one has to know that @slope no longer exists.
Granted, you might have some coders who deliberately break things by talking
to @slope directly, but if you've defined an accessor, they have to be a
masochist to want to do it that way. Similarly, because it's just one command
for you to define them, you really have no excuse not to.
I suppose that's the fundamental difference -- in Java and C++, encapsulation
can be enforced to some extent, while in Ruby, it can be easily circumvented.
But Ruby makes it easy to write well-encapsulated code, while Java and C++
make it annoying and tedious.