Apero has asked for the wisdom of the Perl Monks concerning the following question:
Monks, I seek some feedback on a design to simplify the common-case caller usage of an OO class (called Widget in my prototype) where an inner object factory is loosely hidden behind the outer class methods. My goal is to keep the consumer access as simple as possible; realistically, advanced inner objects methods may be present eventually, but unlikely to be needed in the common case.
To get a bit more specific, my prototype layout assumes callers want to manipulate Widget objects, which may contain Parts (tracked as Widget::Part objects), where Parts are each truly identified as subclasses. This is to say that Widget::Part is a factory-class that abstracts away the types of parts, which may not be known in advance. This prototype allows for a Cog type, defined as the class Widget::Part::Cog.
I've included a full prototype layout for the Widget, Widget::Part, and Widget::Part::Cog classes below, in addition to a test.pl high-level consumer.
The piece I am particularly interested in feedback in is the following code segment within the Widget::Part class:
# Build and require the subclass for this type: my $class = __PACKAGE__ . "::" . $opts{type}; eval "require $class"; if ($@) { Carp::carp("Part type '$opts{type}' unknown"); return undef; } # Build the Part object template: my $self = { # name provided directly, or implicitly by type. name => $opts{name} // $opts{type}, }; # Bless into the determined subclass for this type: bless $self, $class;
Primary concern: is the use of $class combined with the quoted eval the best approach? I don't think I can avoid a quoted eval and get the dynamic class loading I'm looking for, so this seems to allow a consumer to define arbitrary subclasses that need not be defined in any parent class (exactly the result I'm looking for.) I'm curious is this part of the design is reasonably sound.
Full class layout for the prototype below:
The Widget and all subclasses are self-contained in the ./lib directory out of convenience, each listed below with the relative pathing expected from the test.pl caller.
Also, please note that while my test.pl example stores and reports on details of the $part added to the Widget, many simple implementations won't ever need to reference components of the top-level object once added. This is included here mostly for demonstration purposes.
File: test.pl (the high-level consumer)
use strict; use warnings; require Carp; use lib 'lib'; require Widget; # Create a new widget. my $w = Widget->new or Carp::confess("new widget failed"); # Add a part of type 'Cog' to this widget, named 'cog-62' my $part = $w->addPart(type=>'Cog', name=>'cog-62') or Carp::confess("addPart failed"); # Show some info about the part just added: printf( "part name is: %s, of class: %s\n", $part->name(), ref($part) );
File: lib/Widget.pm (the main object class)
package Widget; use strict; use warnings; require Carp; require Widget::Part; sub new { my $class = shift; $class = ref($class) || $class; my $self = { parts => { }, }; bless $self, $class; return $self; } sub addPart { my $self = shift; my %opts = (@_); # Create the new part: my $part; unless ( $part = Widget::Part->new(%opts) ) { Carp::carp("unable to add Widget part"); return undef; } $self->{parts}{$opts{type}} = $part; return $part; # use as bool or ref to part object. } 1;
File: lib/Widget/Part.pm (the Part factory for Widget)
package Widget::Part; use strict; use warnings; require Carp; sub new { shift; # throw away this class. We won't use it. my %opts = (@_); # Must have a part 'type' for success. unless (defined $opts{type}) { Carp::carp("no part type provided"); return undef; } # Build and require the subclass for this type: my $class = __PACKAGE__ . "::" . $opts{type}; eval "require $class"; if ($@) { Carp::carp("Part type '$opts{type}' unknown"); return undef; } # Build the Part object template: my $self = { # name provided directly, or implicitly by type. name => $opts{name} // $opts{type}, }; # Bless into the determined subclass for this type: bless $self, $class; # Call subclass initilization code: $self->init(); return $self; } # Basic example accessor: return this part's name. sub name { my $self = shift; return $self->{name}; } 1;
File: lib/Widget/Part/Cog.pm (a minimal Part implementation called Cog)
package Widget::Part::Cog; use strict; use warnings; use parent 'Widget::Part'; sub init { my $self = shift; # Additional 'Cog' attributes would be set up here. # Omitted for example brevity. } 1;
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: OO factory behind simple methods
by Anonymous Monk on Dec 18, 2015 at 01:59 UTC | |
by Apero (Scribe) on Dec 18, 2015 at 03:21 UTC | |
by Anonymous Monk on Dec 18, 2015 at 03:54 UTC |