Recipe 11.7 Using Closures Instead of Objects

11.7.1 Problem

You want records with private state, behavior, and identity, but you don't want to learn object-oriented programming to accomplish this.

11.7.2 Solution

Write a function that returns (by reference) a hash of code references. These code references are closures created in the same scope, so when they execute, they'll share bindings to the same private variables.

11.7.3 Discussion

Because a closure is a binding of code and data, it can implement what might be thought of as an object.

Here's an example that creates and returns a hash of anonymous functions. mkcounter takes an argument of a seed counter and returns a reference to a hash of code references that you can use to manipulate the counter indirectly.

$c1 = mkcounter(20);
$c2 = mkcounter(77);

printf "next c1: %d\n", $c1->{NEXT}->( );  # 21
printf "next c2: %d\n", $c2->{NEXT}->( );  # 78
printf "next c1: %d\n", $c1->{NEXT}->( );  # 22
printf "last c1: %d\n", $c1->{PREV}->( );  # 21
printf "old  c2: %d\n", $c2->{RESET}->( ); # 77

The code values in the hash references in $c1 and $c2 maintain their own separate state. Here's how to set that up:

sub mkcounter {
    my $count  = shift;
    my $start  = $count;
    my $bundle = {
        "NEXT"   => sub { return ++$count  },
        "PREV"   => sub { return --$count  },
        "GET"    => sub { return $count    },
        "SET"    => sub { $count = shift   },
        "BUMP"   => sub { $count += shift  },
        "RESET"  => sub { $count = $start  },
    $bundle->{"LAST"} = $bundle->{"PREV"};
    return $bundle;

Because the lexical variables used by the closures in the $bundle hash reference are returned by the function, they are not deallocated. The next time mkcounter is called, the closures get a different set of variable bindings for the same code. Because no one outside those closures can access these two variables, this assures true privacy.

The assignment right before the return makes both "PREV" and "LAST" values point to the same closure. Depending on your object-oriented background, you might think of these as being two different messages, both implemented using the same method.

The bundle we return is not an object in that it has no obvious inheritance and polymorphism. (Yet.) But it certainly does have state, behavior, and identity, as well as encapsulation.

11.7.4 See Also

The section on "Closures" in Chapter 8 of Programming Perl and the discussion on closures in perlref(1); Recipe 11.4; Recipe 11.9; Chapter 13