Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

how do i implement alternate constructors for an object

by thunders (Priest)
on May 07, 2003 at 16:49 UTC ( [id://256294]=perlquestion: print w/replies, xml ) Need Help??

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

I'm trying to get my head around the way objects work in perl. I'd like to be able to manipulate a "Ticket" object, which will have various methods attached to it. But i'd like to initialize this object as either a new Ticket, or with data fields filled in from a database table. One idea was to create a separate constructor for the different cases.
package MyApp::Ticket; use strict; sub new { my ($class,%args) = @_; my $self = bless { _time => $args{'time'}, _trade_ref => $args{trade_ref}, _adr_buyer_bank => $args{adr_buyer_bank}, _adr_seller_bank => $args{adr_seller_bank}, _seller_pays => $args{seller_pays}, }, $class; return $self; } sub load { my ($class,$dbh,$ticket_number) = @_; my $sql = "SELECT * FROM tickets WHERE id = ?"; my $sth = $dbh->prepare($sql) or die $dbh->errstr; $sth->execute($ticket_number); my $row = $sth->fetchrow_hashref; my $self = bless { _time => $row->{'time'}, _trade_ref => $row->{trade_ref}, _adr_buyer_bank => $row->{adr_buyer_bank}, _adr_seller_bank => $row->{adr_seller_bank}, _seller_pays => $row->{seller_pays}, }, $class; return $self; }
and then initialize tickets like so:
my $new_ticket = new MyApp::Ticket( time=>time(),trad_ref=>34567, adr_buyer_bank => 'blah', adr_seller_bank =>'blah, seller_pays => 99.99 ); my $old_ticket = MyApp::Ticket->load(12345);
I'm not quite sure if that would work, and it seems kind of messy. Another idea I had was to define separate constructors in DBTicket and NewTicket classes, and simply inherit all the common methods from the regular Ticket class. Perhaps that would be cleaner? Any advice would be appreciated.

Replies are listed 'Best First'.
Re: how do i implement alternate constructors for an object
by kilinrax (Deacon) on May 07, 2003 at 17:07 UTC
    You could certainly implement an inheritance heirarchy to make the code cleaner. However, a simpler solution might be to pass the db handle and ticket number as args, check for their existence, and have the 'new' constructor call 'load' with them as arguments if they are:
    package MyApp::Ticket; use strict; my @_args = qw( time trade_ref adr_buyer_bank adr_seller_bank seller_p +ays ); my @_atts = map { '_' . $_ } @_args; sub new { my ($class, %args) = @_; my $self = bless {}, $class; # if constructor has been passed a db handle and a ticket number, # get attributes from database. otherwise, get them from args my ($dbh, $ticket) = @args{ qw( dbh ticket ) }; if( $dbh && $ticket ) { $self->load( $dbh, $ticket ); } else { @{$self}{ @_atts } = @args{ @_args }; } return $self; } sub load { my ($self, $dbh, $ticket_number) = @_; my $sql = "SELECT * FROM tickets WHERE id = ?"; my $sth = $dbh->prepare($sql) or die $dbh->errstr; $sth->execute($ticket_number); my $row = $sth->fetchrow_hashref; @{$self}{ @_atts } = @row{ @_args }; }
Re: how do i implement alternate constructors for an object
by VSarkiss (Monsignor) on May 07, 2003 at 19:09 UTC

    I'd opt for polymorphic constructors. You're initializing the same fields two different ways, from two different sources. Let your constructor examine the object being passed in; if it's a database handle, use that, and if not, see if it has the fields you want.

    Here's a simple sketch:

    package MyApp::Ticket; sub new { my ($class, $from, $extra) = @_; if (UNIVERSAL::isa($from, 'DBI')) # it's a DB handle { my $sth = $from->prepare(...); $sth->execute($extra); # and so on } else { $self->{_time} = $from->{time}; # etc. } bless $self, $class; } # Called like so: my $dbh = DBI->connect(...); my $dbticket = MyApp::Ticket->new($dbh, $ticketno); # or like so: my %args; my $ticket = MyApp::Ticket->new(\%args);
    HTH

    Update
    Better to use isa() instead of can().

Re: how do i implement alternate constructors for an object
by LanceDeeply (Chaplain) on May 07, 2003 at 20:59 UTC
    Here's how I like to do this:

    I prefer keeping one bless in my module. I don't like having to check multiple places in the module for the blessing. I've had some headaches because of that.

    I also like to think that most my modules 'use' or 'contain' a database handle. So my modules don't inherit from DBI. Instead, I opt for a the some sort of singleton factory to get a $dbh instance. There are plenty of ways to do this, here's one that I've been using.

    HTH

    package MyApp::Ticket; use strict; sub new { my ($class,%args) = @_; my $self = bless {}; if ( $args{ticket_number} ) { $self->load( $args{ticket_number} ); } else { $self->init(%args); } return $self; } sub init { my ($self,%args) = @_; $$self{_time} => $args{'time'}; $$self{_trade_ref} => $args{'trade_ref'}; $$self{_adr_buyer_bank} => $args{'adr_buyer_bank'}; $$self{_adr_seller_bank} => $args{'adr_seller_bank'}; $$self{_ti_seller_pays} => $args{'seller_pays'}; return $self; } sub load { my $self = shift; my $ticket_number = shift; # # pull the $dbh from some shared location # like a singleton factory # my $dbh = GetDBH(); my $sql = "SELECT * FROM tickets WHERE id = ?"; my $sth = $dbh->prepare($sql) or die $dbh->errstr; $sth->execute($ticket_number); my $row = $sth->fetchrow_hashref; return $self->init($$row); } # # and then initialize tickets like so: # my $new_ticket = new MyApp::Ticket( time=>time(),trad_ref=>34567, adr_buyer_bank => 'blah', adr_seller_bank =>'blah, seller_pays => 99.99 ); my $new_ticket = new MyApp::Ticket( ticket_number => 12345 );
Re: how do i implement alternate constructors for an object
by nite_man (Deacon) on May 07, 2003 at 21:08 UTC
    You can use your constructor for creation a new object and define its properties by default (include getting dbh, because I think you will store new tickets in the database):
    our $properties = { time => undef, trad_ref => undef, adr_buyer_bank => 'test', adr_seller_bank => undef, seller_pays => undef, }; sub new { my $class = shift; my $self = {}; map { $self->{$_} = $properties->{$_} } keys %$properties; bless $self, $class; $self->_init(@_); return $self; } sub _init { my $self = shift; if(@_) { my %args = @_; map { $self->{$_} = $args{$_} if exists $self->{$_} } keys %ar +gs; } $self->{__dbh} = DBI->connection(...) unless defined $self->{__dbh +}; } sub load { my $self = shift; my $sql = "SELECT * FROM tickets WHERE id = ?"; my $sth = $dbh->prepare($sql) or die $dbh->errstr; $sth->execute($ticket_number); my $row = $sth->fetchrow_hashref; $self->_init(%$row); return 1; }
    Use its in the script or another module:
    my $ticket = MyApp::Ticket->new();
    or using input parameters:
    my $ticket = MyApp::Ticket->new(time => time(), trad_ref => 34567, adr_buyer_bank => 'blah', adr_seller_bank => 'blah, seller_pays => 99.99);
    For load existing ticket use method load, as you wrote:
    my $ticket = MyApp::Ticket->new(); my $res = $ticket->load(12345);
    It's just rough but idea is you need only one constructor and others are methods. Of course, I didn't implement errors catch but it's another story.
          
    --------------------------------
    SV* sv_bless(SV* sv, HV* stash);
    
Re: how do i implement alternate constructors for an object
by shemp (Deacon) on May 07, 2003 at 18:06 UTC
    if the (potential) DBTicket and NewTicket classes will have identical functionality except the initialization, have separate constructors or a multi purpose constructor should be fine. BUT, if ther will be other (non-trivial) functional differences, separate classes would make more sense.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (None)
    As of 2024-04-25 01:05 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      No recent polls found