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

I'm trying to simplify a terribly complicated network inventory program by using objects, but I can't figure out how to do what I want to do.

Given a list of pingable IP addresses, I use async SNMP to query sysName, sysDesc, and sysObjectID. This part is easy.

But here's where I get stuck. Given the IP addresses that reply to the SNMP query, I want to create an SNMPDevice object. However, in the constructor of SMNPDevice, I want to do some additional queries to see if the device requires special handling (like a cisco switch stack where one IP address actually consists of multiple hardware units). If a special device type is detected, I want the SNMPDevice object to create an object of the appropriate derived class (like SNMPDevice::CiscoStack) and returning it instead of its own blessed self.

Something like this:

package SMNPDevice; sub new { my $class = shift; my $ip = shift; my $sysName = shift; my $sysDesc = shift; my $sysObjectID = shift; my @members = GetStackMembers($ip); return SNMPObject::CiscoStack::new($ip, $sysName, $sysDesc, $sysObj +ectID) if scalar @members > 1; return SNMPDevice::CiscoWLC::new($ip, $sysName, $sysDesc, $sysObjec +tID) if $sysDesc =~ /Cisco WLC/; #etc }
Or, would SNMPDevice bless itself as a SNMPDevice::CiscoStack ?
  • Comment on Can a class constructor create an instance of a derived class and return it? Or, can objects transmute into other objects?
  • Download Code

Replies are listed 'Best First'.
Re: Can a class constructor create an instance of a derived class and return it? Or, can objects transmute into other objects?
by Corion (Patriarch) on Sep 28, 2017 at 15:55 UTC

    Why have the package SNMPDevice be a class at all?

    package SNMPDevice; use strict; use SNMPDevice::CiscoWLC; use SNMPDevice::Juniper; use SNMPDevice::Generic; sub new { my( $class, %args ) = @_; my $instance_class; if( $args{ cisco } ) { $instance_class = 'SNMPDevice::CiscoWLC'; } elsif( $args{ juniper }) { $instance_class = 'SNMPDevice::Juniper'; } else { $instance_class = 'SNMPDevice::Generic'; }; return $instance_class->new( %args ); } 1;
      Because it holds some global class data.

        Why not turn this "global class data" into simply global variables?

        What makes that "global class data" different from global variables in that package?

Re: Can a class constructor create an instance of a derived class and return it? Or, can objects transmute into other objects?
by Arunbear (Prior) on Sep 28, 2017 at 18:18 UTC
    You might have heard of the Single Responsibility Principle which says that a class should only have one job to do (or only one reason to change).

    Your SMNPDevice is holding down two jobs i.e. being a base class and being a factory (a factory decides what type of objects to create). A cleaner design would be to just let SMNPDevice be a base class, and introduce a separate factory (essentially what Corion's example does).

Re: Can a class constructor create an instance of a derived class and return it? Or, can objects transmute into other objects?
by alexander_lunev (Pilgrim) on Sep 28, 2017 at 21:37 UTC

    If i understand you correctly, it's the "Factory method" pattern case. I'm using it for storage abstraction.

    package Storage::Factory; { use Storage::SQLite; use Storage::MySQL; sub new { my $class = shift; my $opt = {@_}; return Storage::SQLite->new(file => $opt->{dsn}) if $opt->{typ +e} eq 'sqlite'; return Storage::MySQL->new(dsn => $opt->{dsn}) if $opt->{type} + eq 'mysql'; } } 1; use Storage::Factory; my $storage = Storage::Factory->new(%{ $self->plugin('Config')->{db} } +)
Re: Can a class constructor create an instance of a derived class and return it? Or, can objects transmute into other objects?
by Anonymous Monk on Sep 28, 2017 at 15:43 UTC
    Can a class constructor create an instance of a derived class and return it? Or, can objects transmute into other objects?
    Yes, and yes. As long as you document it properly, it's fine. Re-blessing already-blessed objects is a little bit sketchy, but if you know what you're doing, go ahead (that's actually how subclass constructors work in C++). I would tend to write SNMPObject::CiscoStack->new(...) instead of SNMPObject::CiscoStack::new(...) in the superclass constructor, though.

      When I'm doing things like this, I prefer to use a second method aside from new in the parent class to generate the new objects:

      sub new { return bless {}, shift; } sub build { my $self = shift; my $sub_obj = Other::Class->new; push @{ $self->{sub_objects} }, $sub_obj; return $sub_obj; }

      That way, the other objects can be injected into a parent object for tracking purposes (ie. it provides a list of all other in-use objects of the sub types. Here's a very basic example.

      my $parent = Obj->new; my $sub_obj_1 = $parent->build; my $sub_obj_2 = $parent->build; for (@{ $parent->{sub_objects} }){ print $_->name . "\n"; }
A reply falls below the community's threshold of quality. You may see it by logging in.