15.13. Class Interfaces
When it comes to designing the interface of a class, developers are often advised to follow Occam's Razor and avoid multiplying their methods unnecessarily. The result is all too often a class that offers only the absolute minimal set of functionality, as in Example 15-9. Example 15-9. A bit-string class with the smallest possible interfacepackage Bit::String; use Class::Std::Utils; { Readonly my $BIT_PACKING => 'b*'; # i.e. vec( ) compatible binary Readonly my $BIT_DENSITY => 1; # i.e. 1 bit/bit # Attributes... my %bitset_of; # Internally, bits are packed eight-to-the-character... sub new { my ($class, $arg_ref) = @_; my $new_object = bless anon_scalar( ), $class; $bitset_of{ident $new_object} = pack $BIT_PACKING, map {$_ ? 1 : 0} @{$arg_ref->{bits}}; return $new_object; } # Retrieve a specified bit... sub get_bit { my ($self, $bitnum) = @_; return vec($bitset_of{ident $self}, $bitnum, $BIT_DENSITY); } # Update a specified bit... sub set_bit { my ($self, $bitnum, $newbit) = @_; vec($bitset_of{ident $self}, $bitnum, $BIT_DENSITY) = $newbit ? 1 : 0; return 1; } } Rather than enhancing maintainability, classes like that often reduce it, because they force developers who are using the class to invent their own sets of utility subroutines for frequent tasks: # Convenience subroutine to flip individual bits... sub flip_bit_in { my ($bitset_obj, $bitnum) = @_; my $bit_val = $bitset_obj->get_bit($bitnum); $bitset_obj->set_bit( $bitnum, !$bit_val ); return; } # Convenience subroutine to provide a string representation of the bits... sub stringify { my ($bitset_obj) = @_; my $bitstring = $EMPTY_STR; my $next_bitnum = 0; RETRIEVAL : while (1) { my $nextbit = $bitset_obj->get_bit($next_bitnum++); last RETRIEVAL if !defined $nextbit; $bitstring .= $nextbit; } return $bitstring; } And that's definitely "sets" (plural), because it's highly likely that every developeror at least every project teamwill develop a separate set of these utility subroutines. And it's also likelybecause of the strong encapsulation provided by inside-out objectsthat every one of those sets of utility subroutines will be just as inefficient as the ones shown earlier. Don't be afraid to provide optimized methods for the common usages. Implementing frequently used procedures internally, as in Example 15-10, often makes those utilities far more efficient, as well as making the class itself more useful and user-friendly. Example 15-10. A bit-string class with a more useful interface package Bit::String; use Class::Std::Utils; { Readonly my $BIT_PACKING => 'b*'; Convenience methods can also dramatically improve the readability and self-documentation of the resulting client code: $curr_state->flip_bit($VERBOSITY_BIT); print 'The current state is: ', $curr_state->as_string( ), "\n"; Because, if they aren't provided, the developers may not choose to devise their own utility subroutines, preferring instead to cut and paste nasty, incomprehensible fragments like: $curr_state->set_bit($_, !$curr_state->get_bit($_)) for $VERBOSITY_BIT; print 'The current state is: ', do { my @bits; while (defined(my $bit = $curr_state->get_bit(scalar @bits))) { push @bits, $bit; } @bits; }, "\n"; |