Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Using closures to achieve data hiding in OO Perl

by saurabh.hirani (Beadle)
on Jun 12, 2009 at 14:02 UTC ( [id://770977]=perlmeditation: print w/replies, xml ) Need Help??

Hi guys,

I was just reading about closures on perl.com and read that Tom Christiansen said that closures can be used to achieve data hiding in OO perl. I thought about it and came up with an example to do that:

A module written without closures:

package Dinner; use strict; use warnings; my $self = {}; sub new { my ($class) = @_; bless $self, $class; return $self; } sub washhands { my ($obj) = @_; $obj->{'handsclean'} = 1; return 1; } sub eatfood { my ($obj) = @_; if (exists $obj->{'handsclean'}) { print "\nYou washed your hands -eat all you want\n"; } else { print "\nYou filthy animal!! Go wash your hands\n"; } return 1; } 1;

Its corresponding use:

#!/usr/bin/perl -w use strict; use Dinner; my $dirtyeater = Dinner->new(); # I don't want to wash hands, I will just set the flag directly # $dirtyeater->washhands; $dirtyeater->{'handsclean'} = 1; $dirtyeater->eatfood;

A Data::Dumper dump of $dirtyeater gives

$VAR1 = bless( { 'handsclean' => 1 }, 'Dinner' );

$dirtyeater can eat food if he knows what to change in the objects data structure. I know that if you are playing with the object's ds you do it at your own risk but I want to prevent the end user from changing any object ds specific variables. So I write another module called Dinnerclosure.pm:

package Dinnerclosure; use strict; use warnings; sub new { my ($class) = @_; my $self = {}; my $selfmirage = {}; $selfmirage->{'washhands'} = sub { washhands($self); }; $selfmirage->{'eatfood'} = sub { eatfood($self); }; bless $selfmirage, $class; return $selfmirage; } sub washhands { my ($obj) = @_; $obj->{'handsclean'} = 1; return 1; } sub eatfood { my ($obj) = @_; if (exists $obj->{'handsclean'}) { print "\nYou washed your hands -eat all you want\n"; } else { print "\nYou filthy animal!! Go wash your hands\n"; } return 1; } 1;

The testcode:

#!/usr/bin/perl -w use strict; use Dinnerclosure; my $cleaneater = Dinnerclosure->new(); # comment next line and uncomment the one after it, but it won't wash +hands $cleaneater->{'washhands'}->(); #$cleaneater->{'handsclean'} = 1; $cleaneater->{'eatfood'}->(); print Dumper $cleaneater;

$dirtyeater cannot change the 'cleanhands' flag. If he does, he will be changing the $selfmirage hashref which does not decide cleanliness, $self does. And $dirtyeater cannot access $self because the object is:

$VAR1 = bless( { 'eatfood' => sub { "DUMMY" }, 'washhands' => sub { "DUMMY" } }, 'Dinnerclosure' );

Advantages:

  1. You have to wash hands before eating. :) - to generalize - you can only call subs to manipulate data.There is no other choice.
  2. $self and $selfmirage are lexically scoped within the new sub. They don't even have to be declared at top of package before any subs in case of Dinner.pm - better lexical scoping
  3. The end user does not see any object innards in the ds

WYDSIWYDG - What you don't see is what you don't get :)

-- Saurabh

Replies are listed 'Best First'.
Re: Using closures to achieve data hiding in OO Perl
by ikegami (Patriarch) on Jun 12, 2009 at 16:52 UTC
    You have it backwards. If you want to use closures cleanly and effectively, it's the inner layer of the methods that need to be a closure.
    package UsesClosures; sub new { my $attr; return bless({ get_attr => sub { $attr }, set_attr => sub { $attr = $_[1] }, some_method => sub { print "$attr\n"; } }, shift); } sub get_attr { &{ shift->{get_attr } } }; sub set_attr { &{ shift->{set_attr } } }; sub some_method { &{ shift->{some_method} } };
    my $o = UsesClosures->new(); $o->set_attr(123); print $o->get_attr(); $o->some_method();

    Your Dinner module would be:

    package Dinner; sub new { my $hands_clean = 1; return bless({ wash_hands => sub { $hands_clean = 1; }, eat_food => sub { die "Filthy\n if !$hands_clean; print "Om nom nom\n"; }, }, shift); } sub wash_hands { &{ shift->{wash_hands} } }; sub eat_food { &{ shift->{eat_food } } };
    my $dirtyeater = Dinner->new(); $dirtyeater->wash_hands(); $dirtyeater->eat_food();

    Like inside-out objects, such objects are hard to debug.

      That is a much better way to use closures. Rather than accessing the method through, $obj->{'key'}->(), it makes more sense to do it the way you've posted. Thanks. Interesting comment that one, about closures being hard to debug. In a big application, data hiding can come at a cost.

      How often do you guys use closures? I mean, there are certain obvious places where closures can help like callbacks. I've never used closures. Never felt the need to. But citing from your experience, are there any other places where using closures is a good idea?

        I use closures all the time. But never explicitly for data hiding (although it can come as a byproduct). Closures especially when coupled with eval, makes for an extremely powerful approach to solving problems, optimizing, simplifying design, and providing encapsulation. Using closures you can basically avoid OO, and do things that are extremely inefficient to do via OO techniques (especially in perl where method calls are slow). In fact I'd argue that closures are generally speaking more useful than OO.

        Go and get yourself a copy of Higher Order Perl by dominus. Its one of the better Perl books out there, and its almost all about using closures.

        Note: I updated this node by changing the previous link to mjd to the correct dominus.

        ---
        $world=~s/war/peace/g

        I didn't say closures were hard to debug, and I don't think that's true in most cases.

        What's difficult to debug are objects that hide their attributes. You can't dump them (fixable) or inspect them in a debugger (not fixable).

        In C, private attributes are hidden by denying code access to them. The debugger isn't subject to those access restrictions, so it can access private attributes just as easily as public ones. In Perl, the attributes aren't simply hidden. They are fundamentally different than public attributes. In a sense, they're not even in the object. (In inside-out objects, they truly aren't in the object at all.)

        So what has no cost in C has a great cost in Perl.

        How often do you guys use closures?

        Often (but never for the purpose you propose). I worked on a VB project recently, and closures were what I missed the most.

Re: Using closures to achieve data hiding in OO Perl
by JavaFan (Canon) on Jun 12, 2009 at 16:20 UTC
    There are big holes in your implementation. Sure, $selfmirage->{'washhands'} contains a closure, but all the closure does is call a global subroutine with the private data as argument.

    All $dirtyeater needs to do is to replace Dinnerclosure::washhands with a sub of his own, and $dirtyeater can modify handsclean all he wants. See the following test code:

    #!/usr/bin/perl use strict; use warnings; use Dinnerclosure; my $dirtyeater = Dinnerclosure->new(); { no strict 'refs'; no warnings 'redefine'; local *{"Dinnerclosure::washhands"} = sub { my $obj = shift; $obj->{'handsclean'} = 1; print "I didn't really wash my hands\n"; return 1; }; $dirtyeater->{'washhands'}->(); $dirtyeater->{'eatfood'}->(); } __END__ I didn't really wash my hands You washed your hands -eat all you want
    $dirtyeater didn't wash his hands - he just pretended to. But he still was allowed to eat.
      Thanks for pointing that out. I hadn't thought of that. But using closures at least thwarts out the easier way to manipulate the object.
Re: Using closures to achieve data hiding in OO Perl
by demerphq (Chancellor) on Jun 13, 2009 at 11:09 UTC

    IMO people that obsess over data hiding in perl have missed the point of perl and should probably be using a different language entirely.

    All code is visible in perl. If I encountered your code I'd rip the closure crap out, convert it to a standard hash based object and use Perls strengths (code analysis and data introspection) to deal with the very minimal level of problems that come from Perl's relatively weak data hiding facilities.

    Data hiding makes sense when you can not see or inspect the code that you are interoperating with, and is particularly important when the language lacks decent data introspection tools. However Perl does not satisfy either of these points, you ALWAYS have access to the code, and you ALWAYS have access to data introspection tools. Thus hiding data is actually counterproductive, as it means that the standard debugging techniques are useless. This in turn means that your data hiding code is a liability, not an advantage.

    ---
    $world=~s/war/peace/g

Re: Using closures to achieve data hiding in OO Perl
by diotalevi (Canon) on Jun 13, 2009 at 07:47 UTC
Re: Using closures to achieve data hiding in OO Perl
by tantarbobus (Hermit) on Jun 12, 2009 at 16:37 UTC
    sub steal_ref { my $cleaneater = shift; my $gotself; local *{Dinnerclosure::washhands} = sub { $gotself = shift; }; $cleaneater->{'washhands'}->(); return $gotself; } my $gotself = steal_ref($cleaneater);

    -r

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://770977]
Approved by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (5)
As of 2024-03-29 12:36 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found