1

I am working on an college problem ( in Perl ). We are working on creating modules and I need to write a simple module that "has get/set methods for four attributes: lastname, firstname, full_name and a list of children who are also person objects".

I think I have it down but it's the list of children who are also person objects that throws me. I guess the module needs to accept a list and then create a list of objects? Python is my core language so this one is throwing me. The get/set methods are working fine. Any ideas?

My module is here...

#!/usr/bin/perl

package Person;

sub new
{
    my $class = shift;
    my $self = {
        _firstName => shift,
        _lastName  => shift,
    };
    bless $self, $class;
    return $self;
}
sub setFirstName {
    my ( $self, $firstName ) = @_;
    $self->{_firstName} = $firstName if defined($firstName);
    return $self->{_firstName};
}

sub getFirstName {
    my( $self ) = @_;
    return $self->{_firstName};
}

sub setLastName {
    my ( $self, $lastName ) = @_;
    $self->{_lastName} = $lastName if defined($lastName);
    return $self->{_lastName};
}

sub getLastName {
    my( $self ) = @_;
    return $self->{_lastName};
}

sub getFullName {
    my( $self ) = @_;
    return $self->{_lastName}.",".$self->{_firstName};
}

1;

My code is here.....

#!/usr/bin/perl

use Person;

$object = new Person("Elvis","Presley");
# Get first name which is set using constructor.
$firstName = $object->getFirstName();
$lastName = $object->getLastName();
$fullname = $object->getFullName();

print "(Getting) First Name is : $firstName\n";
print "(Getting) Last Name is: $lastName\n";
print "(Getting) Full Name is: $fullname\n";
Jane Wilkie
  • 1,703
  • 3
  • 25
  • 49
  • *"I guess the module needs to accept a list and then create a list of objects?"* Well if you pass it a list of `Person` objects then there's nothing to create. Your object needs another field, `_children` which is an array. You can initialise it with an empty array in the constructor `_children => []` and add to it using another setter. You would create a number of children in the same way as you created `$object` and assign them with something like `$object->setChildren($kid1, $kid2, $kid3)` – Borodin Jun 20 '18 at 22:40

3 Answers3

6

Just use a list of objects in the setter:

#! /usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

{   package Person;

    sub new {
        my $class = shift;
        my $self = {
            _firstName => shift,
            _lastName  => shift,
            _children => [],
        };
        return bless $self, $class
    }

    sub setFirstName {
        my ($self, $firstName) = @_;
        $self->{_firstName} = $firstName if defined $firstName;
        return $self->{_firstName}
    }

    sub getFirstName {
        my ($self) = @_;
        return $self->{_firstName}
    }

    sub setLastName {
        my ($self, $lastName) = @_;
        $self->{_lastName} = $lastName if defined $lastName;
        return $self->{_lastName}
    }

    sub getLastName {
        my ($self) = @_;
        return $self->{_lastName}
    }

    sub getFullName {
        my ($self) = @_;
        return $self->{_lastName} . ', ' . $self->{_firstName}
    }

    sub getChildren {
        my ($self) = @_;
        return @{ $self->{_children} }
    }

    sub setChildren {
        my ($self, @children) = @_;
        $self->{_children} = [ @children ];
    }

}

my $object = 'Person'->new('Elvis', 'Presley');

# Get first name which is set using constructor.
my $firstName = $object->getFirstName;
my $lastName = $object->getLastName;
my $fullname = $object->getFullName;

$object->setChildren('Person'->new('Lisa', 'Presley'),
                     'Person'->new('Deborah', 'Presley'));

say "(Getting) First Name is: $firstName";
say "(Getting) Last Name is: $lastName";
say "(Getting) Full Name is: $fullname";

say "Children: ";
say $_->getFullName for $object->getChildren;

Note that there are modules to make building objects easier, e.g. Moo:

#! /usr/bin/perl
use warnings;
use strict;
use feature qw{ say };

{   package Person;
    use Moo;

    has first_name => (is => 'ro');
    has last_name => (is => 'ro');
    has full_name => (is => 'lazy');
    has _children => (is => 'ro',
                      init_arg => undef,
                      default => sub { [] });

    sub _build_full_name {
        my ($self) = @_;
        return $self->last_name . ', ' . $self->first_name
    }

    sub add_child {
        my ($self, $child) = @_;
        push @{ $self->_children }, $child
    }

    sub children {
        my ($self) = @_;
        return @{ $self->_children }
    }

}

my $object = 'Person'->new(first_name => 'Elvis',
                           last_name  => 'Presley');

# Get first name which is set using constructor.
my $firstName = $object->first_name;
my $lastName = $object->last_name;
my $fullname = $object->full_name;

$object->add_child($_) for 'Person'->new(first_name => 'Lisa',
                                         last_name => 'Presley'),
                           'Person'->new(first_name => 'Deborah',
                                         last_name => 'Presley');

say "(Getting) First Name is: $firstName";
say "(Getting) Last Name is: $lastName";
say "(Getting) Full Name is: $fullname";

say "Children: ";
say $_->full_name for $object->children;
choroba
  • 231,213
  • 25
  • 204
  • 289
5

The requirement means that there should be an attribute which can accommodate a collection of objects, so a reference to an array. This is defined in the constructor

sub new
{
    my $class = shift;
    my $self = {
        _firstName => shift,
        _lastName  => shift,
        _children  => [ @_ ],
    };
    bless $self, $class;
    return $self;
}

where [ ] creates an anonymous array and returns its reference, which is a scalar so it can be used for a hash value. The @_ in it contains the optional rest of the arguments (Person objects) after the class and names have been shift-ed.

Arguments need be checked but this gets hard with a plain list, when they are used positionally. Instead, consider using named parameters, ie. passing a hash(ref) to the constructor, with which it's easy to check which arguments have or have not been supplied.

Next, you need a method to add children to this attribute, for example

sub add_children {
    my ($self, @children) = @_;             # and check what's passed
    push @{$self->{_children}}, @children;
    return $self;                           # for chaining if desired
}

Finally, when you invoke this method you pass objects of the class Person to it

use warnings;
use strict;

use Person;

my $object = Person->new('Elvis', 'Presley');

my $child = Person->new('First', 'Last');

$object->add_children( $child );

or, if there is no use of a $child variable (object) in the rest of the code

$object->add_children( Person->new(...) );

You can add a list of children, add_children($c1, $c2, ...), for example to initially populate the data structure, or can add them individually as they appear.

A list of Person children can be used in the constructor as well

my $obj = Person->new('First', 'Last', $c1, $c2,...);

This gets clearer and far more flexible with mentioned named parameters, which are unpacked and sorted out in the constructor. But more to the point, once you learn the Perl's native OO system look at modules for this, best Moose and its light-weight counterpart Moo.

Comments

  • Always have use warnings; and use strict; at the beginning

  • Don't use the indirect object notation

     my $obj = new ClassName(...);   # DO NOT USE
    

See this post and this great example. The fact that it can be used to call a constructor is really an abuse of its other legitimate uses. Use a normal method call

    my $obj = ClassName->new(...);
zdim
  • 64,580
  • 5
  • 52
  • 81
3

It's great that your college is teaching you Perl, but slightly disappointing that they're teaching you the "classic" version of Perl OO, when in the real world most OO work in Perl uses a framework like Moo or Moose.

For interest, I've included a Moo version of the Person object below:

package Person;

use Moo;
use Types::Standard qw[Str ArrayRef Object];

has first_name => (
  is => 'rw',
  isa => Str,
  required => 1,
);

has last_name => (
  is => 'rw',
  isa => Str,
  required => 1,
);

has children => (
  is => 'rw',
  isa => ArrayRef[Object],
);

sub full_name {
  my $self = shift;

  return $self->first_name . ' ' . $self->last_name;
}

1;

And here's a simple test program:

#!/usr/bin/perl

use strict;
use warnings;
use feature 'say';

use Person;

my $elvis = Person->new(
  first_name => "Elvis",
  last_name  => "Presley",
  children   => [Person->new(
    first_name => 'Lisa Marie',
    last_name  => 'Presley',
  )],
);

my $first_name = $elvis->first_name;
my $last_name  = $elvis->last_name;
my $full_name  = $elvis->full_name;

say "(Getting) First Name is : $first_name";
say "(Getting) Last Name is: $last_name";
say "(Getting) Full Name is: $full_name";

say "Elvis's first child is ", $elvis->children->[0]->full_name;

A few things to note:

  • Always include use strict and use warnings in your code
  • Always use Class->new in preference to new Class
  • Perl programmers prefer snake_case to camelCase
  • Moo likes you to use named parameters to an object constructor
  • Declarative attributes (using has) are far less repetitive than writing all your own getter and setter methods
  • People programmers tend to prefer a single method (foo() which can be used as both a getter and a setter over separate get_foo() and set_foo() methods.
Dave Cross
  • 68,119
  • 3
  • 51
  • 97