|Think about Loose Coupling|
Re: Global Super Class (equivalent of java.lang.Object)by jimt (Chaplain)
|on Sep 26, 2006 at 16:39 UTC||Need Help??|
This was one of my original design goals when I created Basset. I wanted one object to bind them all.
Well, slightly earlier than that, I just wanted a persistent object store, ala Class::DBI or Tangram or whatever else was popular at the time. But the immediate problem I had was that the peristence layer didnt need to have anything in common with any other objects I was using or creating. Yes, yes, I could've reverse engineered and cloned out the API for one of them, but it really would have been two APIs (my cloned copy, and the original one), which I didnt care for.
So in a fit of enthusiasm, I created Basset::Object and haven't looked back. This is where I stuck all of the global stuff that all of my objects were going to need. So the constructor goes there, the default initializer goes there. Most importantly - error handling goes there. Methods for adding attributes go there. That way, if you change your under lying object type, you just change the method to add new attributes (which is a closure generator around the actual reference access), and everything updates for free. Note - it's not easy to use this approach if you want to vary up the internals for subclasses. So having a hashref inherit from an arrayref is still difficult, but changing all your stuff to an arrayref is relatively easy.
A few important helper methods (loading packages, an abstract factory, a copier) and some other crap that I don't use a whole lot. Pay careful attention to the "crap" part. It's really really tempting to put stuff in there that just doesn't belong, for simplicity's sake. I had the methods in there to do html & cgi escaping for a long time, 'cuz it was easy. I finally got rid of them a few months back. I still have stuff in there for simple time strings. That's debatable whether it should be present. Basically, you need to police your root class vigorously. If theres any doubt that it should be there, then hold off. Think it over more. If youre positive that absolutely everything anywhere in the world could use it, then think about it some more before putting it in. An unpoliced root class quickly becomes a function bucket and largely useless.
And the results? They're amazing. I've used it to build and provide for myself an API. If you're using anything in Basset, it's designed to work the same way. All your objects are created the same way, they all report errors the same way, they all get initialized the same way. It's all consistent1. I never end up with code where the persistence layer throws an exception inside the template which would otherwise return an error code. Everything always is where I think it will be.
Encapsulation is the other big advantage. For example, I do have a ->dump method which is just a wrapper around Data::Dumper. Utterly trivial, but it standardized everything in my world to dump in one and only one way. If you want to dump something else in a non-standard way? Go ahead and do it, but then it's your responsibility to deal with. But if I want to change the global dump method to not use Data::Dumper? Easy as pie. One method, everything gets it automatically.
I've gone to great pains over the years to define the root object to be as abstract as possible. This is so I can easily drop it into disparate situations and have it just work. You don't edit it, you change your root object in your system and make modifications there. You don't inherit from it, you inherit from your abstract object type (whatever it is you've defined in basset that you're using in your system). I strongly wanted to enforce a set of rules, but I wanted to make sure those rules were as flexible as possible.
For example, I use error codes everywhere (though I am strongly debating jumping to exceptions, finally). And Basset is set up to work that way. But if you want to use exceptions? Flip a flag and you've got exceptions. All of your code remains the same.
I'll hop down off of my advertising soapbox now. The point I'm trying to make is that its been wildly successful for me in standardizing everything I work on. I don't need to worry about figuring out how housekeeping stuff is handled, that's all dealt with for me. I can worry about important things. I think that's the strongest argument for any of the frameworks using abstract root objects. Let the framework worry about the nitty gritty, you've got real code to write.
And, in the spirit of my The history of a templating engine, I'll just point out to be careful what you're getting into if you write your own. The benefits are tremendous, as long as it works for you. But it will also become the most keyly critical piece of software you have anywhere. If everything inherits from it, then it has to work flawlessly 100% of the time. For me, Basset::Object is my most tested module, with somewhere around 95% coverage (I think). Changes to it can be tedious as they may have to propogate out to my entire world, so I'm always slow to move on changes to the external API. I'm slow to move on internal changes as well, due to the potential to break everything.
So proceed cautiously, but I highly highly recommend this approach. The potetntial gains are amazing. I'm pleased as punch.
1 Yes, I am aware of the irony of a system thats completely internally consistent, but not really consistent with the way a lot of the rest of the perl world works. Shaddup.