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

I'm trying to implement a DBI-like module that handles input and output in a generic manner. I want to be able to take input from a hash, a database, freezethaw, xml, or whatever, then output it using html, xml, text, etc. The method calls would ideally take the following form:
$myObj = Obj->connect( $source, @additionalArgs ); $myObj->some_operations; $myObj->set_output( 'html' ); $myObj->output;
or something very similar (the 'connect' method could be replaced with anything, but it's familiar since DBI's pretty well known).

The way I have the object model physically structured is as follows:

Obj 
| 
+-- Input 
|    | 
|    +-- DB 
|    +-- XML
|    +-- Hash 
|    +-- etc. 
|
+-- Output 
     |
     +-- HTML 
     +-- XML 
     +-- etc. 
Now, the crux of the problem is that I don't want the programmer using this module to have to worry about creating a new Obj::Input::XML object. I want that to be part of the calling syntax, ala Tim.
I have the output portion running fine, using the $myObj->set_output( $type ) method and a symbol table / autoload approach. Would it be Kosher to use a $myObj->set_input( $type ) method to hack the symbol table so that $myObj->input points to the appropriate place, or is something like DBI's install_driver() relatively easily implementable WITHOUT using XS or anything too fancy? I can understand parts of the DBI code, but simply don't have enough time / experience to learn how the whole darn thing works internally *grin* This module is relatively lightweight (outside of its extensive use of object orientation), and I don't want to make it any bigger than necessary.

Also, I'm having some trouble deciding when to use inheritance, since it's rather a strange situation.

Should MyObj be in MyObj::Input::*'s @ISA, or should the Input::* classes inherit from a generic MyObj::Input class that doesn't inherit from MyObj? I'm rather confused, since MyObj::Input::* is used by MyObj, rather than a subclass.

I would provide an example snippet, but I'm trying to figure out the starting point, so snippets are hard to come by ;-p

Any help to smooth my very confused line of thought would be MUCH appreciated.

Replies are listed 'Best First'.
RE: Object Heirarchy / Design
by btrott (Parson) on Nov 15, 2000 at 06:28 UTC
    Here's my take on it.

    The idea behind your system, it seems to me, is that you want objects that are basically input-output neutral: they can be read from anywhere, and they can be written to anywhere. So what you want (and it sounds like what you have) is a series of polymorphic input and output drivers, such that you can provide a transparent transparent interface to the various input and output sources.

    With that said, then, I would have one main object that doesn't inherit from anything. That object, in my system, would be of type "Obj". That's it. It would maintain *state* about its input and output drivers, so that when it came time to read and write this object, you could dispatch to the appropriate driver. So I might say:

    my $obj = Obj->get('DB', @params);
    and this would, under the hood, do the following:
    • load the Obj::Input::DB class (easy; just use require of eval "use")
    • do something like
      Obj::Input::DB->get(@params)
    • give you back an object blessed into Obj
    You could then set the output for your object, and do something much similar: load the proper class; call that class's "put" (or whatever) method.

    The reason I would bless my object into Obj, rather than into a driver-specific input class, is that I find that confusing: your object is generic, and it's not tied to a particular input class. So why add restrictions that aren't already there?

    With respect to inheritance, I'd have a main Obj class that inherits from nothing. That's your main object class. Then either one of two things:

    • Obj would provide a generic interface to input and output: "get" and "put" or whatever you want. Then the input drivers and output drivers would inherit from Obj.
    • You'd provide some abstract classes Obj::Input and Obj::Output--abstract drivers--that don't do anything, but which provide the interface to your drivers that *do* do something. Your input and output drivers would then inherit from these classes.
    I don't know which I'd choose. The second option feels cleaner to me. But then what do I know.
      That sounds reasonable; my only question would be in regards to the actual calling syntax. It seems to me that Obj::Input::DB->get( @params ) is a bit clumsy, and I'd prefer to use something a bit more neutral, like a simple string passed as one of the arguments, eg:
      $myObj = Obj->get( "db", @args );
      I suppose i'm answering my own question here, since the above is almost identical in terms of implementation to
      $myObj = Obj::Input::DB->get( @params )
        That's easy enough. Just make Obj a wrapper around a more specific instance, like the DBI module you mention.
        package main; # example construction my $obj = Obj->new('Database', @parameters); my @data = $obj->get(@foo); package Obj; my %sources = { Database => Source::DB, XML => Source::XML, HTML => Source::HTML, }; sub new { my $class = shift; my $source = shift; my $source_obj = init($sources{$source}, @_); my $self = { source => $source_obj, }; bless($self, $class); } sub get { my $self = shift; return $self->{source}->get(@_); }
        Magic happens in the constructor :).
RE (tilly) 1: Object Heirarchy / Design
by tilly (Archbishop) on Nov 15, 2000 at 06:38 UTC
    I would follow the second thing that btrott suggested. (EDIT I had misread what he wrote slightly and thought I was differing when I wasn't.)

    I would have your object contain two other objects, one for input and one for output. The connect method could then be implemented something like this (untested):

    sub connect_read { my $self = shift; my $class = shift; $class = "Obj::Driver::$class"; # Load the class if need be then instantiate require $class; $self->{input} = $class->connect_read(@_); }
    Similarly for your output method.

    For both types of class (input and output) I would define a base class that is worthwhile to inherit from. For instance many of your drivers might need a number of rather low-level functions, but in your object code you may want to talk to a rather high-level functions. Well in your base class put the layer of obvious functions between the simple functions your drivers need to provide and the complex functions you would like to call. Then a driver only need inherit from the base and write a set of low-level functions, and the high-level functions will just work.

      This brings up some other questions that I forgot to bring up.

      1. How should logging be handled from these input classes? Obviously, they should all log through a standard interface, but due to the fact that they're not direct subclasses, I can't simply call $self->log( $msg ) or anything like that.
      2. If there is an object hierachy such as follows:
        package Obj; package Obj::Input; @ISA = 'Obj'; package Obj::Input::Type; @ISA = 'Obj::Input';
        then Obj::Input::Type is inheriting from Obj, which doesn't make sense, since Obj::Input::Type isn't an Obj. But it should have access to some of the 'utility' functions provided by Obj. Those utility functions should somehow be provided by Obj::Input, which should also definitely by in Obj::Input::Type's @ISA.
        phew.
        As tilly wrote: Obj::Input::Type doesn't need to inherit from Obj. If those "utility" functions are needed by both Obj::Input::Type and Obj, then they should be broken out of Obj and moved into their own utility class.

        I don't know what your "utility" methods are (perhaps you mean logging, etc.), but my personal preference is this: if from a logical perspective it doesn't make sense to have them methods of Obj, then they shouldn't be declared as methods of Obj. In my opinion, it doesn't make sense for an Obj to have a "log" method. Why should it? How is "log" acting on, or acting with, or have anything to do with an Obj object? I don't think it does. If you come at it from that perspective, I think it may be a bit clearer what methods belong in what classes.

        Why would Obj::Input bother inheriting from Obj? What does either Obj or Obj::Input get from this?

        Think about your classic diagrams with Dog and Cat inheriting from Animal. Well Animals generally contain four instances of Leg, that doesn't mean that a Leg is an Animal though!

        Instead Obj::Input exists for the purpose of containing useful utility methods for drivers to use. Your Obj class just assumes the interface it needs will work.

        As for logging (if you want it), provide a logging method in your base class, then you do just call the method.

RE: Object Heirarchy / Design
by metaperl (Curate) on Nov 15, 2000 at 19:30 UTC
    There are two projects underway for the type of functionality you desire, Boulder and Alzabo.

    Also, be sure to take a good look at the DBIx hierarchy before beginning your design and coding.