
I don't want anyone constructing a Party object on its own, so I want to make the constructor protected. I basically want these tests to pass:
assert_raise(NoMethodError) {Party.new("some name") }
end
d = Department.new("name")
assert_equal "name", d.name
end
e = Employee.new("name", 5, 12)
assert_equal "name", e.name
end
I first tried this:
attr_reader :name
@name = name
end
protected :initialize
end
But the initialize method can happily be called when constructing Party directly, so the first test fails. I then tried this:
attr_reader :name
@name = name
end
end
end
super(name)
@id, @annual_cost = id, annual_cost
end
end
super
end
end
which does prevent me from constructing Party directly. Unfortunately, I can't construct the concrete classes without getting a NoMethodError because I'm calling the protected method :new, so this isn't much good to me. But then Andy Kotlinski showed me that if I implement :new on both of the concrete classes to simply call super:
super(name)
@id, @annual_cost = id, annual_cost
end
; super; end
end
super
end
; super; end
end
And now all of the tests pass, and my superclass constructor is protected.
Now, if you don't want to jump through all of those hoops, you can include a module like this:
klass.module_eval do
end
end
end
end
include MakeConstructorProtected
attr_reader :name
@name = name
end
end
And you don't need to modify any of your subclasses. Because the inclusion of the module documents the intention far better than the obscure code, I quite like this solution. It still feels a little un-ruby-like, but it does document that Party wasn't written with the intention of being constructed on its own, and I quite like that. If anyone has an easier way to do this, then I'd love to hear it.

4 comments:
http://grahamis.com/blog/2007/08/23/ruby-protected-constructors/
I would rather see a method added to Class than the module approach.
I agree, Shane, an abstract base class is not a very Ruby thing to be doing. But if you have to, I would prob skip all the protected madness and go right for:
class Party
attr :name
def initialize name
@name = name
raise NoMethodError.new("Party is an abstract class and cannot be instantiated") if self.class == Party
end
end
just my two cents. Might use a different Error subclass.
http://github.com/bjeanes/make_abstract
I used your example to create a simple way to make abstract classes. Just:
require 'make_abstract'
then to make any class abstract (and leave it's child classes untouched):
class ImAbstractNow
make_abstract
end
Post a Comment