Implementing an object factory
From ClassDBI
It's all very well representing each table as a class, and each row as an object, but to really take advantage of the OO pradigm, you need to be able to sub-class and specialise.
In particular, you want the class you end up with to be determined by the data in the table itself.
Contents |
A Non-Object-Oriented Method
The simplest way to do this, would be to define seperate classes, each using the same table. You could look up the type from the database, and then call retrieve() on the relevant class. However, I suspect that the live object index wouldn't prevent this from causing two SQL queries every time you wanted to retrieve an object, and it's not a particularly elegant solution.
A Solution Using Triggers
The next simplest way, is to specify a select trigger that re-blesses the returned object into the specified class:
__PACKAGE__->add_trigger(select => \&rebless);
sub rebless
{
my $self = shift;
my $class = __PACKAGE__ . '::' . $self->type;
bless $self, $class;
$self->__service_init;
#return $self;
}
## Sub classes should overload this if they need any extra
## initialisation...
sub __service_init
{
}
This solution works fine if all you're doing is adding additional methods to the class. However, once you start to add additional relationships, you can run into problems.
Personally, this method didn't work for me, although other people have report success with it. Investigation by Lianna indicates that the problem is caused by the order in which classes are loaded. If this is the case, then this solution is still unworkable for me because I am loading all my modules automatically using Module::Pluggable.
Another Solution
The solution that I prefer, involves overriding the construct method for Class::DBI, which is called whenever (with the exception noted below) an object is created.
The code looks like this:
sub construct
{
my $proto = shift; ## class or obj
my $data = shift;
my $class = ref $proto || $proto;
## To prevenet infinite looping, we only override this
## method if called on this class (our subclasses construct
## should actually construct, not loop back to themselves)
if ($class eq __PACKAGE__)
{
my $new_class = __PACKAGE__ . '::' . $data->{'type'};
return $new_class->construct($data);
}
else
{
return $proto->SUPER::construct($data);
}
}
This code goes into your base class, and other classes for this table should sub class this. This means that you can call retrieve on your base class, but end up with an object of derived class.
Additionally, if the field that determines which object to construct is a simple as it is in my example (a type field with the name of the sub-class in it), then you might want to avoid having to set it explicitly with the following code, again in the base class:
__PACKAGE__->add_trigger(before_create => \&set_type);
sub set_type
{
my $self = shift;
my $class = ref $self;
my $type = $class;
my $package = __PACKAGE__;
$type =~ s/$package():://;
$self->type($type);
}
This means that you can call insert on a derived class, and the type field will get set accordingly.
A Caveat
However, there is a problem that scuppers both the object-oriented methods of creating an object factory. When an object is retrieved via a has_a relationship from another Class::DBI class, despite what the CDBI documentation says, inflate (and therefore construct) is not called on the object. Instead, _simple_bless is called on the value in the relationship field, to create a place-holder for the object. This means that construct doesn't get called, and select triggers don't get triggered.
A solution for this is to explicitly specify an inflate method for the relationship:
__PACKAGE__->has_a(service => 'My::Base::Class', inflate => 'retrieve');
There might well be a better method for doing this, and I'd be interested to hear any suggestions.

