File Coverage

blib/lib/Patterns/UndefObject.pm
Criterion Covered Total %
statement 24 24 100.0
branch 3 4 75.0
condition n/a
subroutine 10 10 100.0
pod 1 1 100.0
total 38 39 97.4


line stmt bran cond sub pod time code
1             package Patterns::UndefObject;
2              
3             our $VERSION = '0.004';
4              
5 2     2   69047 use strict;
  2         4  
  2         75  
6 2     2   10 use warnings;
  2         3  
  2         96  
7 2     2   12 use Scalar::Util 'blessed';
  2         8  
  2         303  
8             use Sub::Exporter -setup => {
9             exports => [ Maybe => sub {
10 1         129 my $c = shift;
11 1     5   5 return sub { $c->maybe(@_) };
  5         15  
12 2         25 } ],
13 2     2   1269 };
  2         19214  
14              
15             use overload
16 2     2   12 'bool' => sub { 0 },
17 2     2   7 '!' => sub { 1 },
18 2     2   724 'fallback' => 0;
  2         5  
  2         23  
19              
20 12     12   36 sub AUTOLOAD { shift }
21              
22             sub maybe {
23 9 50   9 1 53 blessed $_[0] ? $_[0] : do {
24 9         11 my ($class, $obj) = @_;
25 9 100       80 defined $obj ? $obj :
26             bless {}, $class };
27             }
28              
29             1;
30              
31             =head1 NAME
32              
33             Patterns::UndefObject - A version of the undefined object (null object) pattern
34              
35             =head1 SYNOPSIS
36              
37             use Patterns::UndefObject 'Maybe';
38              
39             my $name = Maybe($user_rs->find(100))->name
40             || 'Unknown Username';
41              
42              
43             =head1 DESCRIPTION
44              
45             Sometimes when you are calling methods on a object you can't be sure that a
46             particular call chain is going to be valid. For example, if you are using
47             something like L you might start by finding out if a given user
48             exists in a database and then following that user's relationships for a given
49             purpose:
50              
51             my $primary = $schema
52             ->resultset('User')
53             ->find(100)
54             ->telephone_numbers
55             ->primary;
56              
57             However this call chain will die hard during dynamic invocation should the
58             method call C fail to find a user. This failure would return a
59             value of C and then a subsequent "Can't call method 'telephone_numbers'
60             on an undefined value.
61              
62             This often leads to writing a lot of defensive code:
63              
64             my $primary;
65             if(my $user = $schema->resultset('User')) {
66             $primary = $user
67             ->telephone_numbers
68             ->primary;
69             } else {
70             $primary = "Unknown Number";
71             }
72              
73             Of course, to be truly safe, you'll need to write defensive code all the way
74             down the chain should the relationships not be required ones.
75              
76             I believe this kind of boilerplate defensive code is time consuming and
77             distracting to the reader. Its verbosity draws one's attention away from the
78             prime purpose of the code. Additionally, it feels like a bit of a code smell
79             for good object oriented design. L offers one possible
80             approach to addressing this issue. This class defined a factory method called
81             L which accepts one argument and returns that argument if it is defined.
82             Otherwise, it returns an instance of L, which defines
83             C such that no matter what method is called, it always returns itself.
84             This allows you to call any arbitrary length of method chains of that initial
85             object without causing an exception to stop your code.
86              
87             This object overloads boolean context such that when evaluated as a bool, it
88             always returns false. If you try to evaluate it in any other way, you will
89             get an exception. This allows you to replace the above code sample with the
90             following:
91              
92             use Patterns::UndefObject;
93             my $primary = Patterns::UndefObject
94             ->maybe($schema->resultset('User')->find(100))
95             ->telephone_numbers
96             ->primary || 'Unknown Number';
97              
98             You can use the available export C to make this a bit more concise (
99             particularly if you need to use it several times).
100              
101             use Patterns::UndefObject 'Maybe';
102             my $primary = Maybe($schema->resultset('User')->find(100))
103             ->telephone_numbers
104             ->primary || 'Unknown Number';
105              
106             Personally I find this pattern leads to more concise and readable code and it
107             also provokes deeper though about ways one can use similar techniques to better
108             encapsulate certain types of presentation logic.
109              
110             =head1 AUTHOR NOTE
111              
112             Should you actually use this class? Personally I have no problem with people
113             using it and asking for me to support it, however I tend to think this module
114             is probably more about inspiring thoughts related to object oriented code,
115             polymorphism, and clean separation of ideas.
116              
117             B Please be aware that the undefined object pattern is not a cure-all
118             and in fact can have some significant issues, among the being the fact that it
119             can lead to difficult to debug typos and similar bugs. Think of its downsides
120             as being similar to how Perl autovivifies Hashs, expect possibly worse! In
121             particular this problem can manifest when deeply chaining methods (something
122             you might wish to avoid in most cases anyway).
123              
124             =head1 METHODS
125              
126             This class exposes the following public methods
127              
128             =head2 maybe
129              
130             my $user = Patterns::UndefObject->maybe( $user->find(100)) || "Unknown";
131              
132             Accepts a single argument which should be an object or an undefined value. If
133             it is a defined object, return that object, otherwise return an instance of
134             L.
135              
136             This is considered a class method.
137              
138             =head1 EXPORTS
139              
140             This class defines the following exports functions.
141              
142             =head2 Maybe
143              
144             use Patterns::UndefObject 'Maybe';
145             my $user = Maybe($user->find(100)) || "Unknown";
146              
147             Is a function that wraps the class method L such as to provide a
148             more concise helper.
149              
150             =head1 SEE ALSO
151              
152             The following modules or resources may be of interest.
153              
154             L, L
155              
156             =head1 AUTHOR
157              
158             John Napiorkowski C<< >>
159              
160             =head1 COPYRIGHT & LICENSE
161              
162             Copyright 2015, John Napiorkowski C<< >>
163              
164             This program is free software; you can redistribute it and/or modify
165             it under the same terms as Perl itself.
166              
167             =cut