File Coverage

blib/lib/Class/Virtual.pm
Criterion Covered Total %
statement 62 66 93.9
branch 8 10 80.0
condition 1 3 33.3
subroutine 13 13 100.0
pod 2 2 100.0
total 86 94 91.4


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