File Coverage

blib/lib/Homer.pm
Criterion Covered Total %
statement 51 51 100.0
branch 8 10 80.0
condition 5 12 41.6
subroutine 12 12 100.0
pod 1 1 100.0
total 77 86 89.5


line stmt bran cond sub pod time code
1             package Homer;
2              
3             # ABSTRACT: Simple prototype-based object system
4              
5 2     2   27486 use warnings;
  2         3  
  2         66  
6 2     2   6 use strict;
  2         3  
  2         32  
7              
8 2     2   5 use Carp;
  2         6  
  2         348  
9              
10             our $VERSION = "1.000001";
11             $VERSION = eval $VERSION;
12              
13             =head1 NAME
14              
15             Homer - Simple prototype-based object system
16              
17             =head1 SYNOPSIS
18              
19             use Homer;
20              
21             # create a prototype object
22             my $person = Homer->new(
23             first_name => 'Generic',
24             last_name => 'Person',
25             say_hi => sub {
26             my $self = shift;
27             print "Hi, my name is ", $self->first_name, ' ', $self->last_name, "\n";
28             }
29             );
30              
31             # create a new object based on it
32             my $homer = $person->extend(
33             first_name => 'Homer',
34             last_name => 'Simpson'
35             );
36              
37             $homer->say_hi; # prints 'Hi, my name is Homer Simpson'
38              
39             # let's extend even more
40             my $bart = $homer->extend(
41             first_name => 'Bart',
42             father => sub { print "My father's name is ", $_[0]->prot->first_name, "\n" }
43             );
44              
45             $bart->say_hi; # prints 'Hi, my name is Bart Simpson'
46             $bart->father; # prints "My father's name is Homer"
47              
48             =head1 DESCRIPTION
49              
50             C is a very simple B, similar to JavaScript.
51             In a prototype based object system there are no classes. Objects are either directly created
52             with some attributes and methods, or cloned from existing objects, in which case the object
53             being cloned becomes the prototype of the new object. The new object inherits all attributes
54             and methods from the prototype. Attributes and methods can be overridden, and new ones can be
55             added. The new object can be cloned as well, becoming the prototype of yet another new object,
56             thus creating a possibly endless chain of prototypes.
57              
58             Prototype-based objects can be very powerful and useful in certain cases. They can provide a
59             quick way of solving problems. Plus, sometimes you just really need an object, but don't need
60             a class. I like to think of prototype-based OO versus class-based OO as being similar to
61             schema-less database systems versus relational database systems.
62              
63             C is a quick and dirty implementation of such a system in Perl. As Perl is a class-based
64             language, this is merely a hack. When an object is created, C creates a specific class just
65             for it behind the scenes. When an object is cloned, a new class is created for the clone, with the
66             parent object's class pushed to the new one's C<@ISA> variable, thus providing inheritance.
67              
68             I can't say this implementation is particularly smart or efficient, but it gives me what I need
69             and is very lightweight (C has no non-core dependencies). If you need a more robust
70             solution, L might fit your need.
71              
72             =head1 HOMER AT A GLANCE
73              
74             =over
75              
76             =item * Prototypes are created by calling C on the C class with a hash, holding
77             attributes and methods:
78              
79             my $prototype = Homer->new(
80             attr1 => 'value1',
81             attr2 => 'value2',
82             meth1 => sub { print "meth1" }
83             );
84              
85             $prototype->attr1; # value1
86             $prototype->attr2; # value2
87             $prototype->meth1; # prints "meth1"
88              
89             =item * A list of all pure-attributes of an object (i.e. not methods) can be received by
90             calling C on the object.
91              
92             $prototype->attributes; # ('attr1', 'attr2')
93              
94             =item * Every object created by Homer can be cloned using C. The hash can
95             contain new attributes and methods, and can override existing ones.
96              
97             my $clone = $prototype->extend(
98             attr2 => 'value3',
99             meth2 => sub { print "meth2" }
100             );
101              
102             $clone->attr1; # value1
103             $clone->attr2; # value3
104             $clone->meth1; # prints "meth1"
105             $clone->meth2; # prints "meth2"
106              
107             =item * Objects based on a prototype can refer to their prototype using the C method:
108              
109             $clone->prot->attr2; # value2
110              
111             =item * All attributes are read-write:
112              
113             $clone->attr1('value4');
114             $clone->attr1; # value4
115             $clone->prot->attr1; # still value1
116              
117             =item * New methods can be added to an object after its construction. If the object is a
118             prototype of other objects, they will immediately receive the new methods too.
119              
120             $prototype->add_method('meth3' => sub { print "meth3" });
121             $clone->can('meth3'); # true
122              
123             =item * New attributes can't be added after construction (for now).
124              
125             =item * Cloned objects can be cloned too, creating a chain of prototypes:
126              
127             my $clone2 = $clone->extend;
128             my $clone3 = $clone2->extend;
129             $clone3->prot->prot->prot; # the original $prototype
130              
131             =back
132              
133             =head1 CONSTRUCTOR
134              
135             =head2 new( [ %attrs ] )
136              
137             Creates a new prototype object with the provided attributes and methods (if any).
138              
139             =cut
140              
141             sub new {
142 1     1 1 71026 my ($this_class, %attrs) = @_;
143              
144 1         3 my $new_class = $this_class->_generate_class;
145              
146 1         5 return $this_class->_generate_object($new_class, %attrs);
147             }
148              
149             sub _generate_class {
150 3     3   4 my $this_class = shift;
151              
152 3         18 my @caller = caller(1);
153              
154 3         12 return join('::', $this_class, @caller[3,2]);
155             }
156              
157             sub _generate_object {
158 3     3   6 my ($this_class, $new_class, %attrs) = @_;
159              
160 2     2   8 no strict 'refs';
  2         2  
  2         633  
161 3         7 foreach my $a (keys %attrs) {
162 10 100 66     30 if (ref $attrs{$a} && ref $attrs{$a} eq 'CODE') {
163             # method
164 4         6 *{"${new_class}::$a"} = delete($attrs{$a});
  4         22  
165             } else {
166 6         18 *{"${new_class}::$a"} = sub {
167 13     13   716 my ($self, $newval) = @_;
168              
169 13 100       22 $self->{$a} = $newval
170             if $newval;
171              
172 13         31 return $self->{$a};
173 6         22 };
174             }
175             }
176              
177 3     2   21 *{"${new_class}::attributes"} = sub { keys %attrs };
  3         11  
  2         7  
178              
179 3         8 *{"${new_class}::extend"} = sub {
180 2     2   499 my ($prot, %attrs) = @_;
181              
182 2         4 foreach ($prot->attributes) {
183             $attrs{$_} = $prot->$_
184 3 100       10 unless exists $attrs{$_};
185             }
186              
187 2         6 my $new_class = $this_class->_generate_class;
188 2         3 @{"${new_class}::ISA"} = (ref($prot));
  2         22  
189              
190 2     2   5 *{"${new_class}::prot"} = sub { $prot };
  2         7  
  2         4  
191              
192 2         7 return $this_class->_generate_object($new_class, %attrs);
193 3         11 };
194              
195 3         10 *{"${new_class}::add_method"} = sub {
196 1     1   271 my ($self, $name, $code) = @_;
197              
198 1 50 33     7 croak "You must provide the name of the method"
199             unless $name && !ref $name;
200 1 50 33     9 croak "You must provide an anonymous subroutine"
      33        
201             unless $code && ref $code && ref $code eq 'CODE';
202              
203 1         1 *{"${new_class}::$name"} = $code;
  1         8  
204 3         12 };
205              
206 3         13 return bless \%attrs, $new_class;
207             }
208              
209             =head1 CONFIGURATION AND ENVIRONMENT
210              
211             C requires no configuration files or environment variables.
212              
213             =head1 DEPENDENCIES
214              
215             None other than L.
216              
217             =head1 BUGS AND LIMITATIONS
218              
219             Please report any bugs or feature requests to
220             C, or through the web interface at
221             L.
222              
223             =head1 SUPPORT
224              
225             You can find documentation for this module with the perldoc command.
226              
227             perldoc Homer
228              
229             You can also look for information at:
230              
231             =over 4
232              
233             =item * RT: CPAN's request tracker
234              
235             L
236              
237             =item * AnnoCPAN: Annotated CPAN documentation
238              
239             L
240              
241             =item * CPAN Ratings
242              
243             L
244              
245             =item * Search CPAN
246              
247             L
248              
249             =back
250              
251             =head1 AUTHOR
252              
253             Ido Perlmuter
254              
255             =head1 LICENSE AND COPYRIGHT
256              
257             Copyright 2017 Ido Perlmuter
258              
259             Licensed under the Apache License, Version 2.0 (the "License");
260             you may not use this file except in compliance with the License.
261             You may obtain a copy of the License at
262              
263             http://www.apache.org/licenses/LICENSE-2.0
264              
265             Unless required by applicable law or agreed to in writing, software
266             distributed under the License is distributed on an "AS IS" BASIS,
267             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
268             See the License for the specific language governing permissions and
269             limitations under the License.
270              
271             =cut
272              
273             1;
274             __END__