Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Beginner OOP style question

by Tardis (Pilgrim)
on Mar 02, 2002 at 06:42 UTC ( [id://148783]=perlquestion: print w/replies, xml ) Need Help??

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

I've finally got around to sitting down and working through perltoot and developing a few simple OO modules of my own. So far I'm having lots of fun, but I've run into a quandry over a style issue.

If we use the example from perltoot which some of you may be familiar with, or at least have available, a class is developed for managing 'Person' objects. What I'm developing is similar enough to the Person example.

In my code, I have some objects for storing data, but I'd also like a search function. My objects are identified by an integer id - because they are going into an SQL DB at this stage.

However I'd like to be able to do searches. The natural inclination is for a search method which returns a list of integers, each of I could pass to my 'fetch' method on a new object on to get the data in.

However this doesn't quite 'feel' right. For example, I need to do a $thing->new to initialize the object so I can use the $thing->search method. So I've allocated storage, created an object that didn't need to be made.

Of course this is all semantic - but it's not obvious to me in what way I've got it 'wrong', nor how to implement it 'right'.

Can provide more detail if needed, but it's basically very similar to the 'Person' class from the manpage, with some added store and fetch methods to do SQL stuff.

Would appreciate some insights from more experienced OOP programmers.

Replies are listed 'Best First'.
Re: Beginner OOP style question
by rjray (Chaplain) on Mar 02, 2002 at 08:12 UTC

    First of all, welcome to Perl OO. Don't let the whiny Java-apologists or Python-huggers convince you that Perl's OO model is broken. It may not suit their aesthetic tastes, but I happen to really enjoy the flexibility it offers.

    To answer the question itself first, can you implement the search method without having an actual object reference? It sounds like you have already drawn that conclusion, since you perceive the creation of an object for the sole purpose of calling the method to be wasteful. So let me introduce you to the static method, also known as the class method.

    A static (or class) method is one that does not use an actual instance of an object in its operation. You will often see them in code or in examples within books, being called something like:

    $req = Apache->request();

    Note that what you see is a method call-- it is calling the request() method of the Apache class. But there is no need for an object in order to call this. So instead, the class name itself is used.

    Another common example is the constructor. You're probably already using it as a class method. You are probably coding something akin to:

    my $new_human = new Person (name => 'Bob'); # Always 'Bob'...

    If you do, be aware that TheDamian may set aside a dunce cap especially for you. He doesn't like that syntax, and I personally have come to appreciate the more-clear:

    my $new_human = Person->new(name => 'Bob'); # Still 'Bob'...

    Your method code must still shift a first argument off the list-- the argument is still there, it's just that it's a string literal ('Person' in this case) rather than a reference. I've written a lot of OO Perl, and every class I've written has had at least 3 or more class-methods. They're as much a part of a package as constants and the inclusion of other libraries.

    On the topic of your search() method, I would recommend a change, however. There is no point in giving a list of integers back to your caller. What is he or she supposed to do with them? Return a list (or better, a list reference) of objects. Returning a list reference keeps the method in a scalar context, and it's easier to discern a successful return versus an error (which would be either undef or an error string, both singular values).

    Lastly, I cannot recommend Damian's book, Object Oriented Perl, highly enough. Everyone I know who has read it, regardless of their level of Perl expertise, has taken away something useful from it. It is as valuable an investment in your library as the Camel book.

    --rjray

      Thankyou! I hardly expected such an insightful reply so quickly!

      What you've said makes loads of sense. Firstly, regarding the class method, I give myself a big forehead slap, and say 'Doh' for that. I should have made that intuitive leap.

      Secondly the search. Again, this makes lots of sense. I did think of this but initially discarded it, since my backend is SQL and I thought this would be costly in time and memory (each object could be several K worth of text).

      But after reading your words and some reflection, I think this is mitigated by two factors:

      1. Searches should not return that many results. A hard limit can be imposed if necessary.

      2. Better, if I change my implementation of the 'fetch' method, which pulls from the SQL, and my AUTOLOAD proxy methods for the data, I can initialize a bunch of objects from a search, but not actual load a single byte of data from the database backend until a data method actually requests it.

      This is some real work, but I think it's doable, and would be massively elegant.

      BTW, just some background on my little project, may be interesting to you or others, and comments are as always welcome. I'm designing my modules to support on online journal, the 'dear diary' kind.

      I have implemented this several times over the last few years, the earliest being a .BAT file and a bunch of editor macros. The most recent and still in use is a PHP version.

      So to bring it into the OOP context, each 'Entry' object holds some meta information like author (probably will be another object), date written and so forth, plus the actual text.

      The part that made me think it would be an ideal bench test for my OOP learning experience was that I wanted to implement a DBM backend later, to replace the SQL backend. If done right, I simply copy Backend.pm-DBM over Backend.pm and suddenly my appliction no longer needs an SQL database. I also don't need to touch Entry.pm, or any of the other modules.

      This comes from a pet peeve of mine, the amount of free software out there which requires a MySQL server, yet doesn't really need one. I'd like to release something that gives the user a choice.

      Anyway, that's enough raving about my project, I'm off to go and think about how to implement my load on demand Journal Entry objects.

      Thanks again!

      Update 2002/3/3

      Both the class search method, and the on-demand fetching are both working a treat.

      For the latter, basically I just renamed my fetch method to real_fetch, and made a new fetch method, which simply sets an fetch_ID field in the object.

      When my AUTOLOAD proxy method gets called when something tries to access the object data, the method sees that it is necessary to fetch it, and passes the fetch_id to the real_fetch method, which goes out to the backend to do the grunt work.

      Great! With the above working, the search class method can return umpteen objects back to the program, without incurring any database access for each one. As soon as the program tries to get data out of them, it is fetched automatically.

        "...the amount of free software out there which requires a MySQL server, yet doesn't really need one"

        Have you seen DBD::SQLite yet?

        Also, instead of simply 'copying Backend.pm-DBM over Backend.pm', why not instead subclass? Maybe something like Backend::SQL and Backend::DBD.

        Good luck!

        jeffa

        L-LL-L--L-LL-L--L-LL-L--
        -R--R-RR-R--R-RR-R--R-RR
        B--B--B--B--B--B--B--B--
        H---H---H---H---H---H---
        (the triplet paradiddle with high-hat)
        
        Please forgive me for being mildly off-topic, but I thought I might suggest an alternative for the way you're creating your search() query (using an array). I'm assuming that you're going to be opening a db connect(), running a query for matches, and returning the positives. A lot of people will tell you to let the db perform the heavy work for you. Depending on the size of the table, they may or may not be right.

        I recently completed a script to store logfile statistics into a MySQL db (who hasn't), and found that it's actually quicker for me to open the dbh, pull all of my data out (assuming I only need to search one field), and insert them as keys into a hash (null values). Hashes are incredible for handling massive amounts of similar data. Anyhoo, then you can perform a simple defined() or exists() on the hash keys for your search criteria (or some regex derivative thereof). My particular application dropped from a runtime of minutes, to just under 7 seconds (comparison/injection of approximately 12000 complex entries).

        That said, it's just my $.02. I'm not an expert, it just happened to work well for me... it may not be applicable to your needs, but maybe it will in the future. :-)

        Hashes are your friend

        -fuzzyping
Re: Beginner OOP style question
by Zaxo (Archbishop) on Mar 02, 2002 at 09:12 UTC

    Your database is a persistent collection of Persons. It is not a Person, but it needs knowlege of a Person's data. One stylish way to do that is to name its columns after a Person's accessor methods. A Person constructor can be designed to initialize directly from a single record in the database. Example:

    my $gnsth = $dbi->prepare $sqlthing; my @bettys = map {Person->new($_)} @{$gnsth->fetchall_arrayref('Elizabeth')};
    which obtains an array of potential Betty Persons from an SQL statement with knowlege of what data array can construct a Person, and a single placeholder for given neames. The OOP model is that the database is a persistent store of a collection of Person objects.

    After Compline,
    Zaxo

      ...and Class::DBI may just end up doing all your work for you if you take to the poop approach.

      wonderful, wonderful module. Well, modules: you do have to install a lot of the Schwern back catalogue. It has a few quirks, naturally, but an increasingly devoted following.

      All you have to do is write a six or eight line subclass for each of your categories of object - in which you mention the names of the table, the columns and the key, and describe any relationships with other tables - and you have your application. Or a big part of it, at least.

      It works particularly well sat between template toolkit and mysql, by the way, leaving a site designer able to hook objects directly out of the database at will.

      sorry about the evangelical outburst.

Re: Beginner OOP style question
by steves (Curate) on Mar 02, 2002 at 10:00 UTC

    I'd second rjray's comment on flexibility. One key to productive Perl OO programming, IMO, is not to become bound by constraints other OO languages impose on you.

    In the case of your search method, consider the fact that Perl lets you build hybrid methods that work as either instance (object) or class (static) methods. I know this is another one that drives OO purists wild, but I often find it useful. In the case of a search method, the possible application might be to either search within a row or across the entire set. Such a dual purpose method would look something like this:

    sub search { my $invocant = shift; if (ref($invocant)) { $invocant->_set_search(); } else { $invocant->_instance_search(); } }

    As you advance down the Perl OO path, take a look at ties and closures -- 2 Perl constructs that give you OO possibilities many other OO languages don't offer.

      While I have been one of the many saints to rail against my $class = ref $proto || $proto;, this is a perfect example of what a hybrid method should be.
      1. You are more perfectly emulating DWIM
      2. You can explain WHY you're doing it
      3. You actually have code that supports both invocation types
      ++ for an excellent example!

      ------
      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.

Re: Beginner OOP style question
by ixo111 (Acolyte) on Mar 02, 2002 at 16:35 UTC
    You might try to export a generic 'search' method, assuming you don't have to create a new database handle for each object .. possibly have the search method retrieve a list which can then be passed to a 'load' method which would instantiate SomeObject for each item so loaded? When your code tries to do two distinct things (dealing with your objects and dealing with moving those objects in/out of secondary storage), that is the place to design a dividing line, or use/author another module altogether.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://148783]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others studying the Monastery: (5)
As of 2024-04-23 09:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found