T
Tim S
Hi
I'd be grateful of some feedback or different ways of solving this problem:
Scenario:
To write a suite of libraries for use by system maintenance scripts, eg
common logging, file-write handling (open tmp file, write to that, atomic
rename at end), database access etc. No problem fundamentally - done it
before. Want to improve and make this cut open source (last was at a
previous place of work).
Requirements:
a) Static classes (or modules) - each class is initialised once globally,
first by built in defaults then possibly reconfigured (typically once) at
run time from script arguments.
b) Make it easy for a 3rd party to override or add methods.
c) More complex classes may use the services of simpler classes, eg
everything uses the Log class.
d) Method overrides need to become visible to all users of the original
class as we do not want to edit the "use" line in every utiliser class.
e) No derived class can directly access the data in its SUPER class - public
methods only allowed, so this simplifies things.
eg:
Say we have classes:
Sys::Admin::Log
Sys::Admin:
B
and a script:
useraccounts which regenerates the passwd, group and shadow files on *nix.
But another site wants to override the message format in Sys::Admin::Log, so
derives Sys::Admin::Log::MySite, replacing one method, formatmsg($baremsg)
---------------
My solution so far (which works in test code):
Sys::Admin is a true base class to all others and provides a way for each
derived class to declare itself the "implementor" for its "root class" (my
terminology for the base class provider of that functionality). Sys::Admin
merely contains common helper methods.
When Sys::Admin::Log loads, it notes in a package hash in Sys::Admin that it
is the implementor class for "Sys::Admin::Log" (ie the base of the class
hierarchy that is responsible for logging functionality) and ditto for
Sys::Admin:
B (provider of DB services).
To use the class in another class or script we do:
##########
use Sys::Admin::Log;
sub L { return Sys::Admin::Log->load() }
L->log(L->INFO, 'Initialising ' . __PACKAGE__);
#########
Now our script might do:
#########
use Sys::Admin::Log::MySite;
use Sys::Admin:
B;
sub L { return Sys::Admin::Log::MySite->load() }
sub D { return Sys::Admin:
B->load() }
D->dosql("update table foo='bar' where wibble=1") or
L->log(L->FATAL, 'Bang');
#########
load() initialises the class the first time and everytime it returns the
implementor class name.
Thus L() just returns a class name - eg the literal 'Sys::Admin::Log'
OO style method calls work, eg L->log(..) which is fairly neat.
1) Sys::Admin::Log::MySite loads and notes (via Sys::Admin) that it is the
implementor for the root class "Sys::Admin::Log". It mostly relies on
Sys::Admin::Log's methods but overrides formatmsg()
2) Sys::Admin:
B loads and initialises Sys::Admin::Log. If it needs to log,
it's call to sub L { return Sys::Admin::Log->load() } actually returns the
literal 'Sys::Admin::Log::MySite' (from the hash in Sys::Admin) so it gains
the overridden methods via perl's normal @ISA search.
This is why the "object" is actually a sub - it can potentially change
depending on the order things are initialised - seems safer this way. It
was that or making the "object" a reference which makes the calling
semantics more ugly.
The test code is actually fairly elegant (but weird) in the way that derived
sub classes can be declared, but I'm wondering if I've missed a trick...
It's hard to describe further without getting even more verbose...
It seems rather complicated, and indeed, when we did a similar scheme at my
last workplace, we just made a suite of pure non OO modules. The difference
is, if we needed to change the functionality, we edited the base modules.
I'm looking for a way to give a basic core away, but allow other users to
customise the suite without having to hack around with the base
modules/classes - and thus make integrating updates more difficult.
In effect, I really want neat subroutine overriding on modules, but perl's
OO methodology seemed the only sensible way to achieve this.
Thoughts would be welcome. Sorry if this is not clear - it's not very easy
to describe in a couple of screen of text!
Cheers
Tim
I'd be grateful of some feedback or different ways of solving this problem:
Scenario:
To write a suite of libraries for use by system maintenance scripts, eg
common logging, file-write handling (open tmp file, write to that, atomic
rename at end), database access etc. No problem fundamentally - done it
before. Want to improve and make this cut open source (last was at a
previous place of work).
Requirements:
a) Static classes (or modules) - each class is initialised once globally,
first by built in defaults then possibly reconfigured (typically once) at
run time from script arguments.
b) Make it easy for a 3rd party to override or add methods.
c) More complex classes may use the services of simpler classes, eg
everything uses the Log class.
d) Method overrides need to become visible to all users of the original
class as we do not want to edit the "use" line in every utiliser class.
e) No derived class can directly access the data in its SUPER class - public
methods only allowed, so this simplifies things.
eg:
Say we have classes:
Sys::Admin::Log
Sys::Admin:
and a script:
useraccounts which regenerates the passwd, group and shadow files on *nix.
But another site wants to override the message format in Sys::Admin::Log, so
derives Sys::Admin::Log::MySite, replacing one method, formatmsg($baremsg)
---------------
My solution so far (which works in test code):
Sys::Admin is a true base class to all others and provides a way for each
derived class to declare itself the "implementor" for its "root class" (my
terminology for the base class provider of that functionality). Sys::Admin
merely contains common helper methods.
When Sys::Admin::Log loads, it notes in a package hash in Sys::Admin that it
is the implementor class for "Sys::Admin::Log" (ie the base of the class
hierarchy that is responsible for logging functionality) and ditto for
Sys::Admin:
To use the class in another class or script we do:
##########
use Sys::Admin::Log;
sub L { return Sys::Admin::Log->load() }
L->log(L->INFO, 'Initialising ' . __PACKAGE__);
#########
Now our script might do:
#########
use Sys::Admin::Log::MySite;
use Sys::Admin:
sub L { return Sys::Admin::Log::MySite->load() }
sub D { return Sys::Admin:
D->dosql("update table foo='bar' where wibble=1") or
L->log(L->FATAL, 'Bang');
#########
load() initialises the class the first time and everytime it returns the
implementor class name.
Thus L() just returns a class name - eg the literal 'Sys::Admin::Log'
OO style method calls work, eg L->log(..) which is fairly neat.
1) Sys::Admin::Log::MySite loads and notes (via Sys::Admin) that it is the
implementor for the root class "Sys::Admin::Log". It mostly relies on
Sys::Admin::Log's methods but overrides formatmsg()
2) Sys::Admin:
it's call to sub L { return Sys::Admin::Log->load() } actually returns the
literal 'Sys::Admin::Log::MySite' (from the hash in Sys::Admin) so it gains
the overridden methods via perl's normal @ISA search.
This is why the "object" is actually a sub - it can potentially change
depending on the order things are initialised - seems safer this way. It
was that or making the "object" a reference which makes the calling
semantics more ugly.
The test code is actually fairly elegant (but weird) in the way that derived
sub classes can be declared, but I'm wondering if I've missed a trick...
It's hard to describe further without getting even more verbose...
It seems rather complicated, and indeed, when we did a similar scheme at my
last workplace, we just made a suite of pure non OO modules. The difference
is, if we needed to change the functionality, we edited the base modules.
I'm looking for a way to give a basic core away, but allow other users to
customise the suite without having to hack around with the base
modules/classes - and thus make integrating updates more difficult.
In effect, I really want neat subroutine overriding on modules, but perl's
OO methodology seemed the only sensible way to achieve this.
Thoughts would be welcome. Sorry if this is not clear - it's not very easy
to describe in a couple of screen of text!
Cheers
Tim