Tuesday, March 15, 2011

Reblessing objects

Yesterday I've read the example from Refactoring transcripted into Perl. Nice work (I am not sure about the legal status of this - but I am sure that it is a great advert for the original book). The last refactoring there starts with:

We have several types of movie that have different ways of answering the same question. This sounds like a job for subclasses. We can have three subclasses of movie, each of which can have its own version of charge (Figure 1.14).

This allows me to replace the switch statement by using polymorphism. Sadly it has one slight flaw - it doesn't work. A movie can change its classification during its lifetime. An object cannot change its class during its lifetime.

It then goes on to show how the State Pattern can solve the problem thus stated. I am not so sure about this argumentation - typically the movie data would be stored in a database and the object would be created just for the one transaction - I think it is reasonable to assume that the movie does not change it's classification during the transaction. But OK - all this is just to show how the State Pattern solves the problem even if in this case the problem itself would not be real - for sure there are cases where it would.

Then it got me thinking - well in Perl you can change the class of an object during it's lifetime. I've even seen it done, I don't quite remember where. In general it is considered to be nasty - but is it always? In real life it is normal that things change - a caterpillar becomes a butterfly, a child becomes an adult.


Cosimo said...

Yes, I've done that :-)

I think in some unit test suite, to transform existing objects, for example, a custom object to become a fake Apache::Cookie, or something along those lines. Not pretty, I agree, but...

LeoNerd said...

Two occasions come to my mind.

First is my plan to write an IO::Handle subclass that provides the full set of termios(3) wrappings; 'use'ing this module would upgrade any of STDIN,STDOUT,STDERR that were appropriate.

Second is the message objects that IO::Socket::Netlink connections use to represent netlink messages. These message objects rebless themselves into subclasses to represent the message hierarchy once their message type is known, possibly recursively down more and more specific message types.

Peter Rabbitson said...

The entire storage system of DBIC is based on reblessing. In _determine_driver one starts with a ::Storage::DBI object. DBIC looks at the DSN - if it finds a matching subclass - it reblesses the object, and calls _rebless() *again*. The current driver may decide to specialize further (e.g. ODBC) and calls _rebless again and so on and so forth until the driver is satisfactory narrowed down.

It looks scary, but it is in fact a very elegant design, allowing us to differentiate storage drivers at a whim by simply adding code - the upper "layers" do not change whatsoever.

Here's a typical "flow" for MSSQL over ODBC

1) http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=dbsrgits/DBIx-Class.git;a=blob;f=lib/DBIx/Class/Storage/DBI.pm;h=2dc005c8f08eb86d6230319b5e00dcdb4dbba2e5;hb=HEAD#l1211

2) http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=dbsrgits/DBIx-Class.git;a=blob;f=lib/DBIx/Class/Storage/DBI/ODBC.pm;h=8f0b41824e0af2ebef69711130bde5f88c7c3d81;hb=HEAD#l7

3) http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=dbsrgits/DBIx-Class.git;a=blob;f=lib/DBIx/Class/Storage/DBI/ODBC/Microsoft_SQL_Server.pm;h=88627d364ae497a1e321638bb28df07649ea9b94;hb=HEAD

Glen said...

I've never considered doing this, but dagnabit if it isn't cool.

I accept this as technical credit; probably not intended, but a wonderful side effect of Perl's OO flexibility. Surely, given DBIC's usage, it's apparent that this can be used wisely.