Hamish Whittal <[EMAIL PROTECTED]> writes:

> Robin,
> 
> Thanks so much for the effort you put into this reply....I just have a
> few more questions...see below.

<snip>

> > What it does do is show how I solve the problem of the parent's
> > classes 'new' not knowing about data members needed for the children.
> OK. This is the problem I was battling to understand. See, a cs2600 has
> a whole host attributes (characteristics) that I need to set. Since a
> device is-a (or may-be-a - to be more correct) cs2600, I additionally
> need to set attributes that pertain to a specific device.
> Example:
> A Cisco2600 has the following attributes (i.e. when I query it, I can
> obtain the following information from it):
>       - nvRam
>       - swVersion
>       - hwVersion
>       - systemObjectID
>       - sysUpTime
> 
> But, I cannot just send the queries into the ether(net). I need to
> direct the query to a device (of type Cisco 2600). The specific device,
> should have all the properties (attributes) of a Cisco 2600 (above), but
> additionally have an IP address and a Community string.
> So, a device (if it's a Cisco 2600) should inherit the characteristics
> of cs2600, but be able to initialise itself with an IPAddress and
> Community string additionally. Clear?

Ok.  Some properties will be specific to a Cisco 2600, others will be
generic for everything of the 'Device' type.  There will be yet other
properties specific to other kinds of devices.  Just put the
cs2600-specific properties in cs2600.pm, the generic properties in
NetDevice.pm, and other type-specific properties wherever they go.

Then, using the example code below, you can:

my $cisco = new NetDevice::Cisco::cs2600(ipaddress => "200.12.1.50", object_id => 57);

cs2600.pm does not refer to an 'ipaddress' field anywhere, yet this
constructor works, and I can then call:

$cisco->ipaddress; # returns "200.12.1.50"

This is inheritance at work - my $cisco object has all the properties
and methods of a NetDevice object.

You might also have a set of properties that are common for all
'Cisco' type devices.  This might indicate that you need a 'Cisco'
object with the properties common to cs2600, cs5200, cs1100, etc.

<snip>

> >   foreach (keys %{$fields}) {
> >     if (exists $attr{$_}) {
> >       $self->$_($attr{$_});
> >     }
> >   }
> OK. I get this bit. Basically, call the sub's to initialise the IP
> address and the Community. V. clever.

The important bit here is that keys %{$fields} not only has the fields
from %valid_fields in the NetDevice object, but *also* includes any
fields from child objects returned by their 'valid_fields' method.

> > 
> >   $self->_init; # class-specific init code
> 
> But, why do you init after you have set the attributes?

I don't have much use for it here, but maybe some subclass of
NetDevice needs to make sure objects have *either* a ipaddress *or* a
systemid.  Or maybe a subclass of NetDevice initializes an SNMP
connection to the device in question so it can be polled for more data.

<snip>

> > NetDevice/Cisco/cs2600.pm:
> > 
> > package NetDevice::Cisco::cs2600;
> > 
> > use strict;
> > use warnings;
> > 
> > use NetDevice;
> > 
> > our @ISA = qw/NetDevice/;
> > 
> > my %valid_fields = (object_id => undef,
> >                 );
> Create the attributes for the Cisco2600....check.
> > 
> > sub valid_fields { return (shift->SUPER::valid_fields(), %valid_fields) }
> Now, I am not sure what is happening here. You seem to be running the
> subroutine valid_fields from the SUPERCLASS, and returning the hash
> returned by that, as well as a valid_fields hash created above with
> object_id being undefined. This therefore returns a double hash - one
> from the superclass and one from this class.

I'll expand the above subroutine from a one liner to make things more
clear.  Here's what's happening:

sub valid_fields { # this overrides the valid_fields sub found in NetDevice.pm
  my $class = shift; 

  return ($class->SUPER::valid_fields(), # SUPER calls NetDevice,
                                         # and returns it's %valid_fields
          %valid_fields) # valid_fields from this package
}

The part I think you're missing is that hashes in list context return
a list of the form (key1, value1, key2, value2) - so in our example
the above is expanded to something like this:

(ipaddress => undef, community => undef, object_id => undef)

The order of the pairs can vary, but a key is always followed by it's
value.  So when NetDevice::new() calls 'valid_fields', it gets the
above list, and assigns it to a hash - which gives you all the fields
appropriate to your NetDevice::Cisco::cs2600.  Magic.

> > sub object_id {
> >   my $self = shift;
> >   my $id = shift;
> > 
> >   if (defined $id) {
> >     $self->{object_id} = $id;
> >   }
> > 
> >   return $self->{object_id};
> > }
> I know you are doing the same thing here as in NetDevice above, but I
> never see this sub called. How does it populate the object_id then?

object_id is called from NetDevice::new - the keys of the %fields hash
at that point in the execution are (ipaddress, community, object_id).
Since the 'object_id' in the below call to new is passed into the
%attr hash, new calls $self->object_id, finds it in
NetDevice::Cisco::cs2600, and runs it.

> > my $cisco = new NetDevice::Cisco::cs2600(ipaddress => "200.12.1.50", object_id => 
> > 57);
> 
> Here you call the new from NetDevice, as there is not new in cs2600.pm.
> Would it be possible to explain this a little more?

If it helps, this line could also be written:

my $cisco = NetDevice::Cisco::cs2600->new(ipaddress => "200.12.1.50", object_id => 57);

Since the NetDevice::Cisco::cs2600 package ISA NetDevice, here's what
happens when you call new NetDevice::Cisco::cs2600 in the code above- 

perl looks for new() in cs2600.pm, and doesn't find it.  Next, it
looks in NetDevice.pm, finds new(), and calls it with the following
parameters:

('NetDevice::Cisco::cs2600', 'ipaddress', "200.12.1.50", 'object_id',
57);

Now to go through 'new' again:

sub new { # from NetDevice.pm
  my $class = shift; # $class = 'NetDevice::Cisco::cs2600'
  my %attr = @_;     # becomes the hash described above

  my $fields = {$class->valid_fields}; # becomes the hash(ref)
                                       # described above

  my $self = bless { %{$fields} }, $class;

  # $self is now a 'NetDevice::Cisco::cs2600' object, which is a
  # hashref-type object, with the keys described above, and 'undef'
  # for all the values.

  foreach (keys %{$fields}) { # qw/ipaddress community object_id/

    if (exists $attr{$_}) {   # only ipaddress and object_id exist in
                              #  %attr this time we called new()

      $self->$_($attr{$_});   # calls ipaddress() from this file
                              #  (NetDevice.pm), and find object_id
                              #  in cs2600.pm
    }
  }

# One of the important bits in the above is that while ipaddress() is
# found in NetDevice.pm, in this case it is a method of the
# NetDevice::Cisco::cs2600 *object* ($self) we just created.

  $self->_init; # _init() from cs2600 if one exists, or _init() 
                # from NetDevice.pm

  return $self; # a NetDevice::Cisco::cs2600 object.
}

One last comment - while I've emphasized that we are creating a
NetDevice::Cisco::cs2600 object, it is *also* a NetDevice - you can
check that with 'isa()', which is available to all objects:

print "cisco ISA NetDevice::Cisco::cs2600\n"
  if $cisco->isa("NetDevice::Cisco::cs2600");

print "cisco also ISA NetDevice\n"
  if $cisco->isa("NetDevice");

print "cisco also ISA Monkey\n"
  if $cisco->isa("Monkey");


> Thankyou again so much for taking time out to answer this long-winded
> question.

No problem.  OOP is hard.

-RN

-- 
Robin Norwood
Red Hat, Inc.

"The Sage does nothing, yet nothing remains undone."
-Lao Tzu, Te Tao Ching

-- 
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to