[Long] Static classes, inheritance and base class funkery - or overriding module subroutines

Discussion in 'Perl Misc' started by Tim S, May 31, 2007.

  1. Tim S

    Tim S Guest

    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::DB

    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::DB (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::DB;

    sub L { return Sys::Admin::Log::MySite->load() }
    sub D { return Sys::Admin::DB->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::DB 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
     
    Tim S, May 31, 2007
    #1
    1. Advertising

  2. Tim S

    -berlin.de Guest

    Tim S <> wrote in comp.lang.perl.misc:

    [...]

    Sorry for the big snip, I only want to comment on one thing.

    > In effect, I really want neat subroutine overriding on modules, but perl's
    > OO methodology seemed the only sensible way to achieve this.


    Have you tried the direct approach? If you want subroutine overriding
    in modules, use subroutine overriding in modules, it may be powerful
    enough. What I mean is that you *can* redefine a sub that is called
    elsewhere, *after* "elsewhere" has been compiled and have "elsewhere"
    use the new definition.

    Suppose you have routine logger(), based on a central_logging_routine()
    that defaults to an English format. Then, in some environment, someone
    wants to use logger() in a German format, but continue to call logger()
    for the purpose. This can be realized as follows:

    sub logger {
    central_logging_routine( shift);
    }

    sub central_logging_routine {
    my $msg = shift;
    print "Log Message: $msg\n";
    }

    logger( "Message one");

    {
    no warnings 'redefine';
    *central_logging_routine = sub {
    my $msg = shift;
    print "Log-Meldung: $msg\n";
    };
    }

    logger( "Meldung zwei");

    That prints

    Log Message: Message one
    Log-Meldung: Meldung zwei

    The effect is program-wide. After the change, everyone using logger()
    will use the new format.

    The example shows everything in one file, with the switch in behavior
    at run-time. The switch might as well be performed in a loadable
    file through "use" or "require" without first exercising the default
    version.

    I'm not sure if that is the behavior you want, but you may want to
    consider it.

    Anno
     
    -berlin.de, Jun 1, 2007
    #2
    1. Advertising

  3. Tim S

    Tim S Guest

    -berlin.de wrote:

    > Tim S <> wrote in comp.lang.perl.misc:
    >
    > [...]
    >
    > Sorry for the big snip, I only want to comment on one thing.
    >
    >> In effect, I really want neat subroutine overriding on modules, but
    >> perl's OO methodology seemed the only sensible way to achieve this.

    >
    > Have you tried the direct approach? If you want subroutine overriding
    > in modules, use subroutine overriding in modules, it may be powerful
    > enough. What I mean is that you *can* redefine a sub that is called
    > elsewhere, *after* "elsewhere" has been compiled and have "elsewhere"
    > use the new definition.
    >
    > Suppose you have routine logger(), based on a central_logging_routine()
    > that defaults to an English format. Then, in some environment, someone
    > wants to use logger() in a German format, but continue to call logger()
    > for the purpose. This can be realized as follows:
    >
    > sub logger {
    > central_logging_routine( shift);
    > }
    >
    > sub central_logging_routine {
    > my $msg = shift;
    > print "Log Message: $msg\n";
    > }
    >
    > logger( "Message one");
    >
    > {
    > no warnings 'redefine';
    > *central_logging_routine = sub {
    > my $msg = shift;
    > print "Log-Meldung: $msg\n";
    > };
    > }
    >
    > logger( "Meldung zwei");
    >
    > That prints
    >
    > Log Message: Message one
    > Log-Meldung: Meldung zwei
    >
    > The effect is program-wide. After the change, everyone using logger()
    > will use the new format.
    >
    > The example shows everything in one file, with the switch in behavior
    > at run-time. The switch might as well be performed in a loadable
    > file through "use" or "require" without first exercising the default
    > version.
    >
    > I'm not sure if that is the behavior you want, but you may want to
    > consider it.
    >
    > Anno


    Ah yes - that is a pretty simple way to achieve overrides. I'd initially
    discounted direct redefines as I'd erroneously considered
    redefining "logger()" which I worried wouldn't affect the already exported
    verions (I tend to EXPORT_OK if doing modules). But what you are doing is,
    in effect, using logger() as a dispatcher to central_logging_routine()
    which is never exported, so there is no problem.

    Neat and simple and will work with OO and non OO modules.

    Does mean that potential hooks need to be pre-declared (perhaps every public
    method simply wraps an internal sub??), but may be beneficial because it is
    clearer.

    May have a problem with sub-classing a sub-class - but I'm not sure that is
    ever going to be a practical problem...

    Thanks for your ideas Anno. I going to think further on this.

    Cheers

    Tim
     
    Tim S, Jun 1, 2007
    #3
  4. Re: Static classes, inheritance and base class funkery - or overriding module subroutines

    On Jun 1, 12:21 am, Tim S <> wrote:
    > -berlin.de wrote:
    >
    > > sub logger {
    > > central_logging_routine( shift);
    > > }

    >
    > > sub central_logging_routine {
    > > my $msg = shift;
    > > print "Log Message: $msg\n";
    > > }

    >
    > > logger( "Message one");

    >
    > > {
    > > no warnings 'redefine';
    > > *central_logging_routine = sub {
    > > my $msg = shift;
    > > print "Log-Meldung: $msg\n";
    > > };
    > > }

    >
    > > logger( "Meldung zwei");


    > Ah yes - that is a pretty simple way to achieve overrides. I'd initially
    > discounted direct redefines as I'd erroneously considered
    > redefining "logger()" which I worried wouldn't affect the already exported
    > verions (I tend to EXPORT_OK if doing modules). But what you are doing is,
    > in effect, using logger() as a dispatcher to central_logging_routine()
    > which is never exported, so there is no problem.


    Hey, that's neat. I'd never thought of that in those terms. I've used
    this technique in the past but I've never thought to describe it in
    such succinct terms.
     
    Brian McCauley, Jun 1, 2007
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Corey Lubin
    Replies:
    8
    Views:
    375
    Hung Jung Lu
    Nov 18, 2003
  2. Mathieu Dutour

    long long and long

    Mathieu Dutour, Jul 17, 2007, in forum: C Programming
    Replies:
    4
    Views:
    482
    santosh
    Jul 24, 2007
  3. Rick
    Replies:
    4
    Views:
    479
    Kira Yamato
    Nov 30, 2007
  4. Bart C

    Use of Long and Long Long

    Bart C, Jan 9, 2008, in forum: C Programming
    Replies:
    27
    Views:
    810
    Peter Nilsson
    Jan 15, 2008
  5. ankur
    Replies:
    3
    Views:
    345
    ankur
    Aug 10, 2008
Loading...

Share This Page