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]