in reply to Re^9: Assignable Subroutines
in thread Assignable Subroutines

Maybe it is in the DB but I'm not changing it in the database because the part of my program that wants to mutate the Employee doesn't (and shouldn't) need to know about databases. Also, storing it in a DB does not always mean that the mutator handles updating in the DB. If persistence is handled by a framework outside the object then the mutator remains trivial and storage is handled outside of your classes.

Basically it comes down to the fact that many objects have chunks of them that behave exactly like a record/struct in that they are nothing more than passive fields that get twiddled by outsiders. These plain old fields should still not be part of your public interface because if they need to be something more than just plain old fields in the future you're shafted (unless you're in a language that makes it possible for an object to intercept direct twiddling of it's fields and do fancy stuff - Python's properties for example).

If you don't provide mutators, even for trivial fields, then you are just storing up pain for the future.

Replies are listed 'Best First'.
Re^11: Assignable Subroutines
by hardburn (Abbot) on Jan 27, 2005 at 14:31 UTC

    If you don't provide mutators, even for trivial fields, then you are just storing up pain for the future.

    No, you fix your design so you don't need them in the first place.

    In this case, I would make an "effect" object (I'm sure there is a Pattern for this). An effect stores values that can mutate other values when applied to another object.

    my $employee = Employee->new( salary => 50_000 ); my $effect = Effect->new; $effect->set( salary => 1.02, '*' ); # A 2% raise $effect->set( frobnitz => 1.5, '+' ); # The frobnitz is up $employee->apply_effect( $effect );

    The key here is you don't need to know for certain that a given field is implememented in Employee. The apply_effect method ignores any fields it doesn't recognize (like frobnitz). I'll give you that it is more lines of code than:

    $employee->salary( $employee->salary * 1.02 );

    But it is also a superior form of encapsulation.

    "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.

      What does the following code do?

      my $effect = Effect->new; $effect->set( salary => 1.02, '*' ); # A 2% raise $employee->apply_effect( $effect );
      All one can say it that it might raise the salary by 2% or it might do nothing at all, not even report an error. So now we have a Perl that doesn't give you compile time checking and doesn't give you run time checking either!

      The exact same effect can be achieved by having all your objects inherit from a base class with

      sub AUTOLOAD { # do nothing }
      then go back to using
      $employee->salary( $employee->salary * 1.02 );
      Yes this would be an incredibly bad idea but it's exactly equivalent to using effect objects and it's a hell of a lot easier.

      There are some times when you want missing fields to be ignored but you should make that explicit in your code by doing something like

      $employee->salary( $employee->salary * 1.02 ); # we don't mind if they don't have a frobnitz eval { $employee->frobnitz( $employee->frobnitz + 1.5 ) };

      Problems with effect objects

      • Cumbersome - more code means more bugs
      • Slower
      • Ignoring errors should not be the default

      The only real use I can think of for an effect object is to use it as a callback mechanism. Various people can add effects (or even remove or reorder effects) as the object is passed around, but I still don't see a reason to make it ignore errors by default.

        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.

        • Slower

        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.

      While I'm at it, a couple of questions. Where is this "no mutators" idea coming from? What exactly is the damage that results from having mutators?

      I've read the Java world article and all it's saying is don't create public mutuators for fields that are for internal use only. Blindingly obvious advice but I can understand why it may have been needed. I imagine the sequence of events in Javaland to be

      • direct field access is bad (ok so far)
      • make accessor methods and change public fields to private (still ok)
      • get in the habit of making all fields private (fine, just remember that some are private because you don't want direct access and some are private because you don't want any access)
      • now that everything is called private, get confused and start adding accessors for all private fields (doh!)

      That last step would probably be performed by newer programmers, copying without understanding, seeing that public fields are not being used at all and that private + accessor seems to be "correct".

        Why do you think you need to poke a value into a memory location like you're an ASM Programmer? Because, with a mutator, that's really what you're doing. You're not telling the object to do something and trusting it to accomplish the task you've set for it. You're pushing values into its head, then executing a series of statements that run based on the values you've shoved into its head. Which means you've broken encapsulation.

        No, not because you shoved values into the object's head. Because you had to know what values to shove into its head into order to make sure the series of statements you ran did what you wanted it to do.

        There is always a better way to write an object than to use mutators. Except, it takes more time in the design phase and Lord&Lady know that no-one ever has time to design.

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        The "no trivial mutators" idea comes from the fact that if you wanted a struct, then you should have used a struct and left the OO baggage behind.

        Java gets stuck on the fact that everything has to be an object, which means that when a struct really is the best way to do something (as it is in many simple programs), you still have to define it in terms of an object. That's an inheirent difficulty in single-paradigm languages.

        We don't have that problem in Perl, but it's still good advice that if you're going to trouble yourself with OO, then you should really do OO. I'm not going to demand that you apply OO everywhere, only that if you use it, you use it correctly. Bad OO is often worse than not using it in the first place.

        "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.

Re^11: Assignable Subroutines
by hardburn (Abbot) on Jan 27, 2005 at 20:30 UTC

    Ahh, I was right about there being a Pattern for this. It's basically a Visitor Pattern.

    "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.

      The visitor pattern is actually just a symptom of only having single dispatch. Have a look at Visitor pattern considered useless to see what you can do in an OO language that can dispatch methods on more than one arguemnt.