So now we have a Perl that doesn't give you compile time checking and doesn't give you run time checking either!
That's why you have a test suite.
- Cumbersome - more code means more bugs
See above. Also, consider adding this attribute to many employees:
my @employees; # Defined elsewhere as array of Employee objects
my $effect = Effect->new;
$effect->set( salary => 1.02, '*' );
$_->apply_effect( $effect ) for @employees;
Compared to:
my @employees; # Defined elsewhere as array of Employee objects
$_->salary( $_->salary() * 1.02 ) for @employees;
Now we have to cram more into the for statement, while the Effect implementation lifted some of that complexity out. And we could even get rid of a line by having our Effect constructor take what we were passing to the set() method:
my $effect = Effect->new( [ salary => 1.02, '*' ] );
Expanding this further, what about setting multiple attributes?
my @employess; # Defined elsewhere
my $effect = Effect->new(
[ salary => 1.02, '*' ],
[ frobnitz => 1.5, '+' ],
);
$_->apply_effect( $effect ) for @employees;
Compared to:
my @employees; # Defined elsewhere
for (@employees) {
$_->salary( $_->salary() * 1.02 );
$_->frobnitz( $_->frobnitz + 1.5 );
}
# Or if you want to keep closer to the old code and
# live with O(2n) . . .
$_->salary( $_->salary() * 1.02 ) for @employees;
$_->frobnitz( $_->frobnitz + 1.5 ) for @employees;
Now the line-count complexity isn't as clear.
Did I also mention that it's much easier for effects to dynamically change what operation they do? What if the user wants to add to a salary instead of applying a multiplier?
my $do_operation; # Defined elsewhere, possibly from user input
my $by_ammount; # Also from user
my @employees; # Also defined elsewhere
my $op =
$do_operation eq 'add' ? '+' :
$do_operation eq 'mult' ? '*' :
# And so on
;
my $effect = Effect->new( [ salary => $by_ammount, $op ] );
$_->apply_effect( $effect ) for @employees;
Other than getting what operation we want to perform (which we have to do in any case), this code is barely more complex than the first case.
Key note: OOP is not about decreasing complexity. It's about keeping it manageable.
Then why are you using Perl objects at all? And your proposed solution is an AUTOLOAD everywhere, which is one of the last places checked when a method is called. That'll be even slower.
- Ignoring errors should not be the default
I don't consider it an error. Classes that don't have the given attribute ignore it. Those that do will use it. It's very polymorphic.
If you want it to be an error, there's nothing stopping you from defining an exception to be thrown when your object encounters an Effect attribute it doesn't use.
I'm working on a framework for building games in Perl that uses just such a design. The framework provides a domain-specific language that is used to build a series of Perl objects (so you can use this framework, at least at a basic level, without any Perl knowledge). Effects can be created in this language just like any other item in your game.
In an RTS game, buildings could be considered units that don't move. You can shoot them, repair them, and add upgrades using whatever methods the game designer provides, just like any other unit in the game.
In our RTS example, a building wouldn't have a "speed" attribute like a normal unit (or it would have a speed of 0). You could also have a weapon that makes a unit travel at 50% of normal speed. Fire such a weapon on a building would, at the code level, apply the effect of the weapon to the building. The building would then simply ignore the effect. The player is free to keep on shooting if they want. Or the interface designer could make it difficult (need to use the force-fire feature or some such) to shoot such a weapon at a building, but that's a matter for the interface, not for the level I'm currently working at.
"There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.
|