Changing the class of a Perl object to a subclass

My question is OO design. I wrote pseudo code below to illustrate my question. (I say "pseudo-pseudo-code" because it is basically correct, just a few bits of bullshit ...)

I use the Factory pattern to create class objects that match the attributes that pass the Factory::new method. However, there are some attributes that I can only get after creating the object, which I want to use to further subclass or "specialize" the type of object. I want to do this, so I can use the same interface with all the objects in main , independent of the class of the object (I think this is polymorphism ).

Factory class first:

 use strict; use warnings; package Vehicle::Factory; sub new { my ( $class, $args ) = @_; if ( $args->{class} =~ /car/i ) { return Vehicle::Car->new($args); } else { # other possible subclasses based on attributes } } 1; 

Now for related classes:

 package Vehicle; sub new { my ( $class, $args ) = @_; bless $self, $class; $self->color( $args->color ); } sub color { $_[1] ? $_[0]->{_color} = $_[1] : return $_[0]->{_color}; } sub wheels { $_[1] ? $_[0]->{_wheels} = $_[1] : return $_[0]->{_wheels}; } 1; 

And subclass:

 package Vehicle::Car; use base qw( Vehicle ); sub get_fueltype { my ( $self, $args ) = @_; $self->fueltype = check_fuel_type; } sub fueltype { $_[1] ? $_[0]->{_fueltype} = $_[1] : return $_[0]->{_fueltype}; } 1; 

Now for subclasses of "stage 2". I can only create them when I know more about an object that has already been created ...

 package Vehicle::Car::Gas; use base qw( Vehicle::Car ); sub fill_her_up { # Make sure it Gas. # ... } 1; package Vehicle::Car::Diesel; use base qw( Vehilce::Car ); sub fill_her_up { # Make sure it Diesel. # ... } 1; package Vehicle::Car::Electric; use base qw( Vehicle::Car ); sub fill_her_up { # Find a socket. # ... } 1; 

And the main part of the code:

 package main; my $thing = Vehicle::Factory->new( color => "red", wheels => 4 ); $thing->get_fueltype; # Somehow convert $thing to be an object of the appropriate subclass based on # the "fueltype" attribute $thing->fill_her_up; 

(I hope my terribly contrived example makes sense!)

Now I'm not sure ... Should I create a new object using the instance data from $thing ? Is there a way to subclass an object without destroying and re-creating it?

Perhaps I should use the following approach and reuse the Car factory?

 package Vehicle::Factory; sub new { my ( $class, $args ) = @_; if ( $args->{class} =~ /car/i ) { return Vehicle::Car->new($args); } if ( $self->fueltype eq "gas" ) { return Vehicle::Car::Gas->new($args); } if ( $self->fueltype eq "diesel" ) { return Vehicle::Car::Diesel->new($args); } if ( $self->fueltype eq "electric" ) { return Vehicle::Car::Electric->new($args); } } 

At this point in my real code - unlike my example - there is a lot of instance data to then move on to a new object. I think this can be a little ugly if I have to explicitly pass all the data between old and new objects.

In my real code, there may be hundreds / thousands of such objects that are loaded from the configuration file, and they all require the same treatment, but with some differences in how to do this. This is the difference between using Expect and SSH to receive data from a remote device or using SNMP. The second “level” of information is based on the information that I receive when I request a remote device and get its device type (among other things) ...

End point: I write software almost completely, but there is a very “late” and important requirement that requires such a change. I really want to post late req as simple and elegant as possible. I don’t want to “crack” it and change the interface in main .

Thanks in advance for any pointers.

+4
source share
3 answers

Changing the type of an object is very simple in Perl, even after it is created (simple enough to get big problems).

 $car = Vehicle::Factory->new( ... ); ... stuff happens to $car ... # Oh! Now I have decided that $car should be a Vehicle::RustBucket::Fiat bless $car, 'Vehicle::RustBucket::Fiat'; 
+9
source

It looks like you want to create a separate inheritance hierarchy and delegate it to the source class. Thus, your car.move method delegates the motor mechanism. The method of burning fuel and the propulsion mechanism may be electric, diesel or gaseous. In principle, prefer polymorphic delegation to a different hierarchy, rather than trying to expand the same hierarchy.

+7
source

Mob is right, but I am making lightweight "interface" classes for such things. For example, I could define a class of receptors as "Reclassified", and all elements which descend from Reclassable support is_complete_candidate check. Or even the cast or as method.

 package Reclassable; sub _cast { Carp::croak ref( $_[1] ) . '::_cast unimplemented!' } sub cast { my ( $self, $inst, $newclass ) = @_; $newclass = $self if $self ne __PACKAGE__; return bless( $inst, $newclass ) if $inst->isa( $newclass ); return $newclass->_cast( $_[1] ) if $newclass->isa( __PACKAGE__ ); return; } package AutoReclass; use parent 'Reclassable'; sub _cast { bless $_[1], $_[0]; } 

You can check in the _cast method. And the host class can decide how reckless it wants to be with the casting.

Then you perform your health checks in the _cast class _cast .

 sub _cast { my ( $cls, $cand ) = @_; return unless ( $cand->{walks_like} eq 'duck' and $cand->{talks_like} eq 'duck' and $cand->{sound} eq 'quack' ); $cand->{covering} = 'down' unless $cand->{covering} eq 'down'; $cand->{initialized} ||= 1; return bless $cand, $cls; } 
+7
source

Source: https://habr.com/ru/post/1381523/


All Articles