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