How to hide method from base class in a way `can` do not see it?

Viewed 66

I try to hide method from base class by just forward declare subroutine like:

sub xid;

If I call this method I get expected error:

DBG> $tuple->xid
ERROR: Undefined subroutine &HyperMouse::Schema::Result::SaldoAnal::xid called at (eval 2346)[/home/kes/perl5/perlbrew/perls/perl-5.24.1/lib/site_perl/5.24.1/DB/Hooks.pm:422] line 8, <STDIN> line 13.

but $tuple->can( 'xid' ) still return true value:

&HyperMouse::Schema::Result::SaldoAnal::xid

How can I hide xid method, so $tuple->can( 'xid' ) return false instead of:

&HyperMouse::Schema::Result::SaldoAnal::xid
2 Answers

You can override can.

Hide all existing-but-undefined subs:

sub can {
   my $self = shift;
   my $cv = $self->SUPER::can(@_)
      or return undef;
   defined(&$cv)
      or return undef;
   return $cv;
}

Hide a specific sub:

sub can {
   my $self = shift;
   my $cv = $self->SUPER::can(@_)
      or return undef;
   $cv == \&xid
      and return undef;
   return $cv;
}

Your whole attempt to hide a method of the base class is a very strange thing to do. Causing the call to fail would be less surprising.

sub xid {
   croak("Not supported.");
}

But that doesn't really hide the stink of poor class design.

Whichever hack you use, it's entirely bypassable using

$tuple->Base::Class::xid()

can works because the method name is still present in your class's symbol table, but it points to undef. You have to write your own override for can.

sub can {
  my($self) = shift;
  my($method) = @_;

  return undef if $method == "xid";
  return $self->SUPER::can($method);
}

However, this means your subclass now violates its base class's interface, it does not behave like the base object. This indicates poor design. Consider a different approach, subclassing may be inappropriate. Try composition instead.

Related