File Coverage

blib/lib/MooseX/Role/Flyweight.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package MooseX::Role::Flyweight;
2             # ABSTRACT: Automatically memoize your Moose objects for reuse
3             $MooseX::Role::Flyweight::VERSION = '1.03';
4              
5 2     2   69922 use 5.006;
  2         10  
  2         102  
6 2     2   893 use JSON 2.00 (); # works with JSON::XS
  0            
  0            
7             use Moose::Role;
8             use namespace::autoclean;
9             use Scalar::Util ();
10              
11             my $JSON = JSON->new->utf8->canonical;
12              
13             our %INSTANCES;
14              
15             sub instance {
16             my ( $class, @args ) = @_;
17             my $args = $class->BUILDARGS(@args);
18             my $key = $class->normalizer($args);
19              
20             # return the existing instance
21             return $INSTANCES{$class}{$key}
22             if defined $INSTANCES{$class}{$key};
23              
24             # create a new instance
25             my $instance = $class->new(@args);
26             $INSTANCES{$class}{$key} = $instance;
27             Scalar::Util::weaken $INSTANCES{$class}{$key};
28              
29             return $instance;
30             }
31              
32             sub normalizer {
33             my ( $class, @args ) = @_;
34             my $args =
35             ( @args > 1 || ref( $args[0] ) ne 'HASH' )
36             ? $class->BUILDARGS(@args)
37             : $args[0];
38              
39             return $JSON->encode($args);
40             }
41              
42             1;
43              
44             __END__
45              
46             =pod
47              
48             =head1 NAME
49              
50             MooseX::Role::Flyweight - Automatically memoize your Moose objects for reuse
51              
52             =head1 VERSION
53              
54             version 1.03
55              
56             =head1 SYNOPSIS
57              
58             Compose MooseX::Role::Flyweight into your Moose class.
59              
60             package Glyph::Character;
61             use Moose;
62             with 'MooseX::Role::Flyweight';
63              
64             has 'c' => (is => 'ro', required => 1);
65              
66             sub draw {
67             my ($self, $context) = @_;
68             ...
69             }
70              
71             # Optional: override normalizer()
72             sub normalizer {
73             my ($class, $init_args) = @_;
74             return $init_args->{c};
75             }
76              
77             Get cached object instances by calling C<instance()> instead of C<new()>.
78              
79             # the same initialisation arguments produces the same object
80             $shared_object = Glyph::Character->instance( %init_args );
81             $same_object = Glyph::Character->instance( %init_args );
82              
83             # different initialisation arguments produces a different object
84             $another_object = Glyph::Character->instance( %diff_args );
85              
86             # new() still works but its objects are not shared
87             $unshared_object = Glyph::Character->new( %init_args );
88              
89             =head1 DESCRIPTION
90              
91             I<A million tiny objects can weigh a ton.>
92              
93             Instead of creating a multitude of identical copies of objects, a flyweight
94             is a memoized instance that may be reused in multiple contexts simultaneously
95             to minimize memory usage. And due to the cost of constructing objects the
96             reuse of flyweights has the potential to speed up your code.
97              
98             MooseX::Role::Flyweight is a Moose role that enables your Moose class to
99             automatically manage a cache of reusable instances. In other words, the class
100             becomes its own flyweight factory.
101              
102             =head2 Flyweight v. Singleton
103              
104             MooseX::Role::Flyweight provides an C<instance()> method which looks similar
105             to L<MooseX::Singleton>. This is in part because MooseX::Role::Flyweight
106             departs from the original "Gang of Four" design pattern in that the role of
107             the Flyweight Factory has been merged into the Flyweight class itself. But the
108             choice of the method name was based on MooseX::Singleton.
109              
110             While MooseX::Role::Flyweight and MooseX::Singleton look similar, understanding
111             their intentions will highlight their differences:
112              
113             =over 4
114              
115             =item Singleton
116              
117             MooseX::Singleton limits the number of instances allowed for that class to
118             ONE. For this reason, its C<instance()> method does not accept construction
119             arguments and will always return the same instance. If arguments are required
120             for construction, then you will need to call its C<initialize()> method.
121              
122             =item Flyweight
123              
124             MooseX::Role::Flyweight is used to facilitate the reuse of objects to reduce
125             the cost of having many instances. The number of instances created will be
126             reduced, but it does not set a limit on how many instances are allowed. Its
127             C<instance()> method does accept construction arguments because it is
128             responsible for managing the construction of new instances when it finds
129             that it cannot reuse an existing one.
130              
131             =back
132              
133             =head1 METHODS
134              
135             =head2 instance
136              
137             $instance = My::Flyweight->instance( %init_args );
138              
139             This class method returns an instance that has been constructed from the given
140             arguments. The first time it is called with a given set of arguments it will
141             construct the object and cache it. On subsequent calls with the equivalent set
142             of arguments it will reuse the existing object by retrieving it from the cache.
143              
144             The arguments may be in any form that C<new()> will accept. This is normally a
145             hash or hash reference of named parameters. Non-hash(ref) arguments are also
146             possible if you have defined your own C<BUILDARGS> class method to handle them
147             (see L<Moose::Manual::Construction>).
148              
149             Note that instances that are constructed by calling C<new()> directly do not
150             get cached and therefore will never be returned by this method.
151              
152             =head2 normalizer
153              
154             $instance_identifier_string = My::Flyweight->normalizer( $init_args_hashref );
155              
156             This class method generates the keys used by C<instance()> to identify objects
157             for storage and retrieval in the cache. It is passed the arguments used for
158             construction as a hashref (after they have passed through C<BUILDARGS>). It
159             returns a string representation of those arguments as the key. Equivalent
160             arguments should result in the same string.
161              
162             Note that this does not handle blessed references as arguments.
163              
164             Generally you should not need to access this method directly. The only reason
165             you would want to know about this method is if you want to change the way it
166             generates the cache keys, in which case you should wrap or override this
167             method in your class that consumes this role.
168              
169             =head1 NOTES ON USAGE
170              
171             =head2 Flyweights should be immutable
172              
173             Your flyweight object attributes should be read-only. It is dangerous to have
174             mutable flyweight objects because it means you may get something you don't
175             expect when you retrieve it from the cache the next time.
176              
177             my $flight = Flight->instance( destination => 'Australia' );
178             $flight->set_destination('Antarctica');
179              
180             # ... later, in another context
181             my $flight = Flight->instance( destination => 'Australia' );
182             die 'hypothermia' if $flight->destination eq 'Antarctica';
183              
184             Value objects are the type of objects that are suited as flyweights.
185              
186             =head2 Argument normalization
187              
188             Instances are identified for reuse based on the equivalency of the named
189             parameters used for construction as interpreted by C<normalizer()>.
190              
191             Factors to consider when determining equivalency:
192              
193             =over 4
194              
195             =item *
196              
197             There is no distinction between hash and hashref (and non-hash) arguments.
198              
199             # same object is returned
200             $obj1 = My::Flyweight->instance( attr => 'value' );
201             $obj2 = My::Flyweight->instance({attr => 'value'});
202              
203             =item *
204              
205             The order of named parameters does not affect equivalency.
206              
207             The keys in the hash(ref) are sorted, which means that the same string will
208             always be produced for the same named parameters regardless of the order
209             they are given.
210              
211             # same object is returned
212             $obj1 = My::Flyweight->instance( attr1 => 1, attr2 => 2 );
213             $obj2 = My::Flyweight->instance( attr2 => 2, attr1 => 1 );
214              
215             =back
216              
217             On the other hand, C<normalizer()> does not handle:
218              
219             =over 4
220              
221             =item *
222              
223             Unused construction parameters.
224              
225             You can use L<MooseX::StrictConstructor> to prevent this.
226              
227             # different objects with same values returned
228             $obj1 = My::Flyweight->instance( attr => 'value' );
229             $obj2 = My::Flyweight->instance( attr => 'value', unused_attr => 'value' );
230              
231             =item *
232              
233             Default attribute values.
234              
235             You can extend/override C<normalizer()> to handle this if you wish.
236              
237             # different objects with same values returned
238             $obj1 = My::Flyweight->instance( attr1 => 'value' );
239             $obj2 = My::Flyweight->instance( attr1 => 'value', attr2 => 'default' );
240              
241             =back
242              
243             =head2 Garbage collection of cached objects
244              
245             The cache uses weak references to the objects so that the cache references
246             do not prevent the objects from being garbage collected. This means that an
247             object in the cache will be destroyed when all other references to it go out
248             of scope.
249              
250             my $obj = My::Flyweight->instance(%args);
251             # $obj is in the cache
252             undef $obj;
253             # $obj is garbage collected and disappears from the cache
254              
255             =head1 AUTHOR
256              
257             Steven Lee <stevenwh.lee@gmail.com>
258              
259             =head1 ACKNOWLEDGEMENTS
260              
261             Thanks to Mark Stosberg (MARKSTOS) for suggesting to explain the difference
262             between MooseX::Role::Flyweight and MooseX::Singleton.
263              
264             =head1 SEE ALSO
265              
266             L<Perl Design Patterns|http://www.perl.com/pub/2003/06/13/design1.html>
267              
268             L<Memoize>
269              
270             =head1 COPYRIGHT AND LICENSE
271              
272             This software is copyright (c) 2014 by Steven Lee.
273              
274             This is free software; you can redistribute it and/or modify it under
275             the same terms as the Perl 5 programming language system itself.
276              
277             =cut