File Coverage

blib/lib/Class/Virtual.pm
Criterion Covered Total %
statement 59 63 93.6
branch 8 10 80.0
condition 1 3 33.3
subroutine 12 12 100.0
pod 2 2 100.0
total 82 90 91.1


line stmt bran cond sub pod time code
1             package Class::Virtual;
2              
3 3     3   15234 use strict;
  3         4  
  3         111  
4 3     3   11 use vars qw($VERSION @ISA);
  3         6  
  3         196  
5             $VERSION = '0.07';
6              
7 3     3   1369 use Carp::Assert qw(DEBUG); # import only the tiny bit we need so it doesn't
  3         2767  
  3         15  
8             # get inherited.
9 3     3   1462 use Class::ISA;
  3         5553  
  3         75  
10              
11 3     3   1415 use Class::Data::Inheritable;
  3         600  
  3         302  
12             @ISA = qw(Class::Data::Inheritable);
13             __PACKAGE__->mk_classdata('__Virtual_Methods');
14              
15              
16             =pod
17              
18             =head1 NAME
19              
20             Class::Virtual - Base class for virtual base classes.
21              
22              
23             =head1 SYNOPSIS
24              
25             package My::Virtual::Idaho;
26             use base qw(Class::Virtual);
27              
28             __PACKAGE__->virtual_methods(qw(new foo bar this that));
29              
30              
31             package My::Private::Idaho;
32             use base qw(My::Virtual::Idaho);
33              
34             # Check to make sure My::Private::Idaho implemented everything
35             my @missing = __PACKAGE__->missing_methods;
36             die __PACKAGE__ . ' forgot to implement ' . join ', ', @missing
37             if @missing;
38              
39             # If My::Private::Idaho forgot to implement new(), the program will
40             # halt and yell about that.
41             my $idaho = My::Private::Idaho->new;
42              
43             # See what methods we're obligated to implement.
44             my @must_implement = __PACKAGE__->virtual_methods;
45              
46              
47             =head1 DESCRIPTION
48              
49             B Avoid using it for new code. There's
50             nothing wrong with it, but there are better ways to accomplish the
51             same thing. Look into the L ecosystem.
52              
53             This is a base class for implementing virtual base classes (what some
54             people call an abstract class). Kinda kooky. It allows you to
55             explicitly declare what methods are virtual and that must be
56             implemented by subclasses. This might seem silly, since your program
57             will halt and catch fire when an unimplemented virtual method is hit
58             anyway, but there's some benefits.
59              
60             The error message is more informative. Instead of the usual
61             "Can't locate object method" error, you'll get one explaining that a
62             virtual method was left unimplemented.
63              
64             Subclass authors can explicitly check to make sure they've implemented
65             all the necessary virtual methods. When used as part of a regression
66             test, it will shield against the virtual method requirements changing
67             out from under the subclass.
68              
69             Finally, subclass authors can get an explicit list of everything
70             they're expected to implement.
71              
72             Doesn't hurt and it doesn't slow you down.
73              
74              
75             =head2 Methods
76              
77             =over 4
78              
79             =item B
80              
81             Virtual::Class->virtual_methods(@virtual_methods);
82             my @must_implement = Sub::Class->virtual_methods;
83              
84             This is an accessor to the list of virtual_methods. Virtual base
85             classes will declare their list of virtual methods. Subclasses will
86             look at them. Once the virtual methods are set they cannot be undone.
87              
88             =for notes
89             I'm tempted to make it possible for the subclass to override the
90             virtual methods, perhaps add to them. Too hairy to think about for
91             0.01.
92              
93             =cut
94              
95             #"#
96             sub virtual_methods {
97 29     29 1 1305 my($class) = shift;
98              
99 29 100       57 if( @_ ) {
100 10 100       48 if( defined $class->__Virtual_Methods ) {
101 4         45 require Carp;
102 4         1745 Carp::croak("Attempt to reset virtual methods.");
103             }
104 6         111 $class->_mk_virtual_methods(@_);
105             }
106             else {
107 19         16 return @{$class->__Virtual_Methods};
  19         58  
108             }
109             }
110              
111              
112             sub _mk_virtual_methods {
113 3     3   13 no strict 'refs'; # symbol table mucking! Getcher goloshes on.
  3         4  
  3         772  
114              
115 6     6   444 my($this_class, @methods) = @_;
116              
117 6         19 $this_class->__Virtual_Methods(\@methods);
118              
119             # private method to return the virtual base class
120 6         26 *{$this_class.'::__virtual_base_class'} = sub {
121 11     11   17 return $this_class;
122 6         231 };
123              
124 6         15 foreach my $meth (@methods) {
125             # Make sure the method doesn't already exist.
126 15 50       539 if( $this_class->can($meth) ) {
127 0         0 require Carp;
128 0         0 Carp::croak("$this_class attempted to declare $meth() virtual ".
129             "but it appears to already be implemented!");
130             }
131              
132             # Create a virtual method.
133 15         61 *{$this_class.'::'.$meth} = sub {
134 2     2   5 my($self) = shift;
135 2   33     11 my($class) = ref $self || $self;
136              
137 2         9 require Carp;
138              
139 2 50       6 if( $class eq $this_class) {
140 0         0 my $caller = caller;
141 0         0 Carp::croak("$caller called the virtual base class ".
142             "$this_class directly! Use a subclass instead");
143             }
144             else {
145 2         394 Carp::croak("$class forgot to implement $meth()");
146             }
147 15         50 };
148             }
149             }
150              
151              
152             =pod
153              
154             =item B
155              
156             my @missing_methods = Sub::Class->missing_methods;
157              
158             Returns a list of methods Sub::Class has not yet implemented.
159              
160             =cut
161              
162             sub missing_methods {
163 11     11 1 3042 my($class) = shift;
164              
165 11         29 my @vmeths = $class->virtual_methods;
166 11         87 my @super_classes = Class::ISA::self_and_super_path($class);
167 11         493 my $vclass = $class->__virtual_base_class;
168              
169             # Remove everything in the hierarchy beyond, and including,
170             # the virtual base class. They don't concern us.
171 11         24 my $sclass;
172 11         6 do {
173 39         107 $sclass = pop @super_classes;
174 39         62 Carp::Assert::assert( defined $sclass ) if DEBUG;
175             } until $sclass eq $vclass;
176              
177 11         36 my @missing = ();
178              
179             {
180 3     3   17 no strict 'refs';
  3         4  
  3         276  
  11         11  
181 11         15 METHOD: foreach my $meth (@vmeths) {
182 39         438 CLASS: foreach my $class (@super_classes) {
183 39 100       28 next METHOD if defined &{$class.'::'.$meth};
  39         140  
184             }
185              
186 30         36 push @missing, $meth;
187             }
188             }
189              
190 11         110 return @missing;
191             }
192              
193             =pod
194              
195             =back
196              
197             =head1 CAVEATS and BUGS
198              
199             Autoloaded methods are currently not recognized. I have no idea
200             how to solve this.
201              
202              
203             =head1 AUTHOR
204              
205             Michael G Schwern Eschwern@pobox.comE
206              
207              
208             =head1 LEGAL
209              
210             Copyright 2000-2015 Michael G Schwern
211              
212             This program is free software; you can redistribute it and/or
213             modify it under the same terms as Perl itself.
214              
215             See L
216              
217              
218             =head1 SEE ALSO
219              
220             L
221              
222             =cut
223              
224             return "Club sandwich";