Приглашаем посетить
CSS (css.find-info.ru)

Section 15.13.  Class Interfaces

Previous
Table of Contents
Next

15.13. Class Interfaces

Provide an optimal interface, rather than a minimal one.

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 interface
package 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*';    
# i.e. vec( ) compatible binary
Readonly my $BIT_DENSITY => 1;
# i.e. 1 bit/bit

    # Attributes...
my %bitset_of; sub new {
# [As in
Example 15-9
]
} sub get_bit {
# [As in
Example 15-9
]
} sub set_bit {
# [As in
Example 15-9
]
}
# Convenience method to flip individual bits...
sub flip_bit { my ($self, $bitnum) = @_; vec($bitset_of{ident $self}, $bitnum, $BIT_DENSITY) = !vec($bitset_of{ident $self}, $bitnum, $BIT_DENSITY); return; }
# Convenience method to provide a string representation of the bits...
sub as_string { my ($self) = @_; return join $EMPTY_STR, unpack $BIT_PACKING, $bitset_of{ident $self}; } }

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";

    Previous
    Table of Contents
    Next