Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How important is it to indicate if a class implements an interface in Perl?

I've been discussing a code style issue with a friend. We have a series of packages that implement an interface by returning a specific type of value via a named subroutine. For example:

package Foo::Type::Bar;
sub generate_foo {
    # about 5-100 lines of code
    return stuff here;
}

So you can go:

my $bar_foo = Foo::Type::Bar->generate_foo;
my $baz_foo = Foo::Type::Baz->generate_foo;

We have many of these, all under the same Foo::Type::* hierarchy.

I think the packages should clearly indicate that they implement the foo_generate interface, e.g.:

package Foo::Type::Bar;
use base 'Foo::Type';
sub generate_foo {
    ...
    return stuff here;
}

I think this is good code style, much more clear and clean for other coders exploring the code. It also lets you check Foo::Type::Bar->isa('Foo::Type') to see if it implements the interface (other parts of the subsystem are entirely OO).

My friend disagrees. Some arguments he makes are:

  • Foo::Type::* packages are clearly named, and used only in an internal project, and therefore there's no question of wondering whether or not a given package implements an interface
  • the packages are often small and part of a standalone subsystem, and they feel to him like batch files or conf files, not heavy Perl OO code
  • Perl expresses implementation via inheritance, which may be complex or problematic, particularly when one gets to multiple inheritance
  • adding a Foo::Type superclass doesn't add any value, as it would literally be an empty package, used only to enable ->isa lookups
  • programmatically indicating interface implementation is a matter of personal code style

Is one or the other of us "right"? What would you do?

Edit: in examples, renamed Foo::Generator to Foo::Type

like image 672
Anirvan Avatar asked Nov 21 '08 20:11

Anirvan


People also ask

Should every class implement an interface?

No, it's not necessary for every class to implement an interface. Use interfaces only if they make your code cleaner and easier to write. If your program has no current need for to have more than 1 implementation for a given class, then you don't need an interface.

Why do we need interface instead of class?

An interface defines common functionality across unrelated classes. For example, all sorts of classes that look nothing like each other may have the need to safely get rid of the resources they use.

Are interfaces always necessary?

No, every class should not have an interface. It's overkill squared. You use an interface when you need to abstract what's done from how it's done, and you're certain that the implementation can change.

When would you use an interface instead of a class?

Abstract classes should be used primarily for objects that are closely related, whereas interfaces are best suited for providing a common functionality to unrelated classes.


1 Answers

Well, there's "duck-typing" that other dynamic languages use. UNIVERSAL::can accomplishes this to the degree that other languages tend to use it.

$unknown->doodle() if $unknown->can( 'doodle' );

The name "duck" typing comes from the idea that "if it walks like a duck, and talks like a duck... it's a duck". However, we're not seeing if it walks "like a duck", we're just seeing if it does something somebody called "walk" because that's what we call what a duck does too. A Tree Walker object, for example, does not "walk" like a duck!

You wouldn't want to pass the wrong object certain things.

$path_or_OS_I_dont_know_which->format( "c:", 'y' );

So how well duck-typing works depends on your expectations on the other side. Plus it tends to get messy if the contract is more complex. You just multiply uncertainty--though narrowing out objects by sheer unlikely hood that two classes would accidentally have 23 like-named methods.

if ( $duck->can( 'talk' ) && $duck->can( 'walk' )) { 
    $duck->walk();
    $duck->talk();
}

that's fine, but

if ( $cand->can( 'walk' ) && $cand->can( 'talk' ) && ... && $cand->can( 'gargle' )) {
    ...
}

gets a little silly. Plus, some of these methods might really be optional, and so you would have to intersperse it with:

$cand->snicker() if $cand->can( 'snicker' );

(Of course junctions would clean it up a little bit.)

And it can get really complex with intermediate products of methods you have to pass to some other method which also might be there.

Compatible

I have a lightweight pattern I call 'Compatible'. There is no actual package called this, but if a class implements what Date does then it puts it in @ISA;

our @ISA = qw<... Date::Compatible ...>;

Thus there are only two isas that need to be called--and I encapsulate it in an is_one sub, anyway:

sub UNIVERSAL::is_one { 
    my ( $self, @args ) = @_;
    foreach my $pkg ( @args ) { 
        return 1 if $self->isa( $pkg );
    }
    return 0;
}

sub UNIVERSAL::is_compatible_with {
    my ( $self, $class ) = @_;
    return $self->is_one( $class, "${class}::Compatible" );
}

It at least gives things a contract to fulfill, and for inquiring code to test against. The implementation can still decide how important it is to fulfill a particular method of the contract, if it's not picky.

like image 175
Axeman Avatar answered Oct 22 '22 10:10

Axeman