skazat has asked for the wisdom of the Perl Monks concerning the following question:

Hello!

Here's a stumper for me, but perhaps something easy to someone else,

I have a program that intereacts with a database, which, at the moment is a plain text file. I want to allow people to also use an SQL table for saving information, BUT I want to allow people to still be able to use that plain text format.

I thought I would solve this having a base Class, let's call it TheDb and then have subclasses (i hope this is the correct lingo), called, say TheDb::PlainText and TheDb::SQL TheDb.pm will just have enough code to say 'hey , inherent the Plain Text methods for your own' or 'hey, SQL methds are the way to go' preferably by passing a variable in the new() statement for TheDb

my $h = TheDb->new('PlainText'); $h->fetch($this); $h->save($that); $h->get_me('a_beer');

Seems like this would be pretty cut and dry, but the actual way you set the Base class up is stumping me, can anyone help?

-justin simoni
!skazat!

Replies are listed 'Best First'.
Re (tilly) 1: Loading a different Module depending on the Configuration
by tilly (Archbishop) on Sep 26, 2001 at 22:10 UTC
    If you are going to use OO, then use the OO dispatch mechanism to do what you want. You don't need to do a lot of work inventing your own.

    That means that you have people call your constructor like:

    my $h = TheDb::PlainText->new();
    and in TheDB/PlainText.pm have:
    package TheDB::PlainText; @ISA = 'TheDb'; use TheDb; use strict; # do stuff sub new { my $class = shift; my $self = $class->SUPER::new(@_); # whatever is specific to a text file here return $self; } # more stuff 1;
    and in TheDb.pm have:
    package TheDb; # usual stuff sub new { my $class = shift; my $self = {}; # Whatever initializations return bless($self, $class); }
    It is now the user's responsibility to use the right child module for what they are doing.
      It is now the user's responsibility to use the right child module for what they are doing.

      Unless, of course, the right module needs to be determined at run time :-)

      Update: I guess sometimes its just preference between doing this:

      my $package = "Parent::Package::".$subpackage; $package->method; # And this: Parent::Package->method($subpackage);
      I see the reasoning of doing it the first way, but sometimes I just prefer to do it the second way. I started allowing it with Parse::FixedLength, and I'm not the only one, as AnyData also does that. One of the wonders (or drawbacks, depending on who you ask) of Perl is that its only as OO as you want it to be :)
        No unless. Method calling is fully dynamic, you don't have to hardcode the package anywhere. If you want to get abusive about it you can even do something like this:
        my $pack = "Foo"; my $meth = "new"; my $obj = $pack->$meth(); # Calls method new in class Foo
        So you can pass the buck to the client code, knowing that if they need to be dynamic, they still can, but it will still be simpler in the common case, and the base class will not need maintainance as you add child classes.

        UPDATE
        I think there is more than just a preference here. If you choose to pass the subpackage in as a string, the parent class has to do a lot more. For one thing I would now demand that it loads the child class. Which means that either the parent has to hardcode all of its children (maintainance issues anyone?), or you have to write dynamic loading logic. You have also just arbitrarily limited the naming and location of your child classes. In many cases this may be innocuous. But I don't like putting any restrictions on how things must be done unless I see very specific benefits to doing it.

        Therefore I don't see the different structure as being a benefit in any way, shape, or form.

        However it bears costs. You have to write more code for the feature. That is more code to write, debug, test, and be aware of. You now have objects that are not blessed into the class which people would expect them to be blessed into. It is now harder to subclass an arbitrary part of your hierarchy. Someone who wants to understand your code now has to do extra work to figure out your data structure.

        This is not to say that you might not from time to time want to have dynamic loading of specific subclasses. DBI's proxying off of appropriate DBD::* classes is an excellent case in point. However such cases are somewhat exceptional. And furthermore if ever I have the choice between a minor aesthetic issue and managing to write the most naturally straightforward code possible which achieves the goal, I retrain my aesthetics! Unnecessary complications are a bad idea in my books.

Re: Loading a different Module depending on the Configuration
by maverick (Curate) on Sep 26, 2001 at 21:34 UTC
    What you're describing is (if I recall correctly) a 'factory' pattern. Try something like this (untested code)
    package TheDb; use strict; use TheDb::PlainText; use TheDb::SQL; sub new { my $class = shift; my $type = shift; my $full_name = "TheDB::" . $type; return $full_name->new; } 1;
    Basically you write one package that uses in all the others. The new method for that package just creates an object of the type you ask for and returns it. Each of your 'TheDB::' object inherits from, say, 'TheDB::Base'.

    /\/\averick
    perl -l -e "eval pack('h*','072796e6470272f2c5f2c5166756279636b672');"

      It's also called a 'polymorphic constructor'. I wouldn't reccomend this as good OO practice, but it's certainly ok.

      ------
      We are the carpenters and bricklayers of the Information Age.

      Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      hmm,

      the idea is to have the actual methods and whatever way you store and fetch stuff plugable, so, down the line I can say,

      my $scheme = 'FooSQL'; my $h = TheDb->new($scheme);

      And, perhaps someone else, with a different setup and wants will go:

      my $scheme = 'DB_File'; my $h = TheDb->new($scheme);

      having all the subclasses loaded at the same time would be incredibly wasteful and useless, since I may not know exactly what DB (or subclass module) is there.

      This may seem like more work, why would you have n ways of doing the same thing? Well, you may not have a Spiffy SQL and are OK with using a plaintext solution, knowing some performance woes.

      This is for a very mature, stable app, not a quick 15 minute script.

      -justin simoni
      !skazat!

        This is for a very mature, stable app, not a quick 15 minute script.

        <sarcasm>Heh, he he, ha HA! HAHAHAAHA!</sarcasm>

        Seriously, what school of Software Engineering did you graduate from - do you really think that using a Factory Pattern is something that a script kiddie would come up with? It's a very common Design Pattern.

        i do appreciate your desire to write your app 'The Right Way' - i really do, but sounds to me like you are too worried about 'false optimization' - imagine a interstate with a lovely 8 lane bridge that has been carefully constructed to maximize car flow ... only to be followed 2 miles down the road by a one lane bridge that you have no control over - that's false optimization.

        You have a lot of good clues here on this thread on how to solve your problem - take these ideas and run with 'em! Sounds to me like a factory pattern, or as dragonchild called it, a 'polymorphic constructor' - is a pretty valid idea for this problem. If you don't like that - then you might want to rack your brain against an Abstract Factory.

        But as for being concerned about loading subclasses that won't be used for a particular run of the app - do you _really_ need to concern yourself with that? And even if you do - doesn't that sound like a job for the _next_ version? (nudge, nudge) ;)

        jeffa

Re: Loading a different Module depending on the Configuration
by tachyon (Chancellor) on Sep 26, 2001 at 22:16 UTC

    Here is a different way to do it

    my $DB; $DB = new TheDB('SQL'); $DB->fetch(); $DB = new TheDB('Text'); $DB->fetch(); $DB->{'DB'} = 'SQL'; $DB->fetch(); $DB = $DB->new('Text'); $DB->fetch(); $DB = new TheDB('Oracle'); $DB->fetch(); package TheDB; use strict; sub new { my ($proto, $DB) = @_; my $class = ref($proto) || $proto; my $self = {'DB' => $DB}; bless $self, $class; return $self; } sub fetch { if ($_[0]->{'DB'} eq 'SQL') { &_SQL_fetch(@_); } elsif ($_[0]->{'DB'} eq 'Text') { &_Text_fetch(@_); } else { die "Unknown class ".$_[0]->{'DB'}; } } sub _SQL_fetch { print "Using SQL method\n"; } sub _Text_fetch { print "Using Text method\n"; }

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

      I don't like this approach since it puts all the different saving and fetching and what have you functions in the same module, which is what I'm trying to get out of, I don't want alot of if/else statement hanging round. I want the entire system pluggable.

      -justin simoni
      !skazat!

Re: Loading a different Module depending on the Configuration
by ducky (Scribe) on Sep 27, 2001 at 02:49 UTC

    Well, if you were using DBI you could use DBD::CSV for the plaintext part. That way, changing the code to use a full database requires one change in the DBI->connect statement as it's already SQL.

    -Ducky