【问题标题】:How do I define private or internal methods in object oriented Perl?如何在面向对象的 Perl 中定义私有或内部方法?
【发布时间】:2010-12-16 14:29:31
【问题描述】:

我正在使用 Damian Conway 的“由内而外”的对象,正如他的精彩著作 Perl Best Practices 中所描述的那样,为我的客户的安全系统构建面向对象的接口。我遇到了在我的模块中使用内部辅助方法的需要,我通常将其指定为“_some_method”。但是,这似乎破坏了封装,因为它们可以通过包名直接调用。有没有办法让这些方法真正私有化?例如,

use SOD::MyOOInterface;

my $instance1 = SOD::MyOOInterface->new();
$instance1->_some_method;  #this produces an error: 
SOD::MyOOInterface::_some_method;   # this results in a 
                                    # successful method call 

显然我不希望 _some_method 的直接调用成功。有什么方法可以保证吗?

【问题讨论】:

  • @Randal 是的,我知道了,很抱歉我不知道这条规则。

标签: perl oop private-methods


【解决方案1】:

有点。您不能隐藏已安装到符号表中的子例程,但可以使用词法变量来保存对匿名子例程的引用:

package SOD::MyOOInterface;

my $some_method = sub { ... }

$some_method->();

因为$some_method只在实现类的文件中可见,所以子程序不能被外部调用。缺点是不能作为方法调用,必须作为函数调用。如果您想将其用作方法,则必须显式传递对象引用:

$some_method->($obj, @args);

【讨论】:

  • @Micheal 是的,我希望避免标量持有对匿名潜艇的引用的“hackish”性质!
  • 如果您使用的是由内而外的对象,那么您已经在做相当“hackish”的 OO。通过 CODE refs 隐藏私有方法似乎非常适合。
  • @Michael 实际上,一旦你习惯了每个属性作为由内而外对象的哈希特征,它就会变得很自然!
  • 您可以将其作为方法调用。 $self->$some_method() 工作正常。当然,您需要在公共方法中使用$self
【解决方案2】:

我处理这个的方法是在方法的开头添加这样的东西:

my $self = shift;
croak "Instance method called on class" unless ref $self;

这绝不是真正的封装,但这确实意味着通过包调用您的人必须将对象实例作为第一个参数传递。一般来说,对于 Perl,我发现保护我的 API 的恶意用户没有多大意义——这只是帮助我捕捉到我不小心尝试将方法作为类方法调用的情况(这种情况比我想的更频繁)承认)。

就个人而言,我认为下划线约定 + 清楚地将方法记录为私有(或根本不记录它,因此它不会出现在 POD 中)对于实际使用来说已经足够了。这也是它在 Python 中的工作方式。这是language philosophy 不限制用户的一部分。

Perl 模块宁愿您待在客厅之外,因为您没有被邀请,而不是因为它有猎枪...

【讨论】:

    【解决方案3】:

    不要将 PBP 用于对象练习。它很旧。事实上,现在关于 Perl 和对象的最佳实践可以在 Moose 中找到,这几乎是 Perl 的必备品。

    简而言之,Perl 模糊命名空间和类的方式大多数方法都可以在类上静态调用。这不是一件坏事,只是不要记录它。确实没有理由要将方法密封到实例中。没有私有方法有点烦人,但不依赖未记录方法的惯例是如此强大,以至于我们的社区已经足够了。

    特征实际上是一个角色(不允许实例化),可以在运行时编译成一个对象。这将进一步掩盖您的典型用户的方法的起源(因为它们不会在原始类中),但它会产生运行时成本。 有关特征的更多信息,请参阅MooseX::Traits

    前面的下划线是一个很好的约定,进一步说明该方法对凝视的眼睛是私有的。

    最后一点,如果您真的想提出这个问题,您可以使用 Class::MOP::Class->create_anon_class() 使用这些方法创建一个匿名类

    【讨论】:

    • 我同意。为什么让某人无法调用私有方法?如果它没有记录在案,它就不是公共接口的一部分。这不仅破坏了封装,而且显然也不会得到作者的支持。但是作者为什么要关心呢?如果我决定将笔记本电脑用作锤子,这显然不是我的硬件制造商的意图,他们不会支持或维修它。但他们应该不遗余力地阻止我这样做,还是让我继续愚蠢。
    • 这是因为有些人认为他们应该能够通过代码建立接口,阻止人们上吊等等。下划线,未记录的方法是您必须学习的约定,使用它不会'甚至不会触发 warning. 一些语言,如 C# 甚至发明了 sealed 类来推动这一点。
    • 不幸的是,在一些公司中,一些开发人员会忽略文档并利用他们可以利用的任何私有接口。调试 Data::Dumper 之类的“助手”可以很容易地戳入对象的内部——这在一定程度上会被 Inside-Out 对象阻塞。
    • 这将是使用由内向外的完全错误的理由,但为了进一步说明我的观点 MooseX::InsideOut search.cpan.org/~hdp/MooseX-InsideOut-0.104/lib/MooseX/…
    • 将由内而外的对象视为基于 hashref 的对象是微不足道的。 Scrottie 有一个 CPAN 模块来执行此操作。所以基本上,inside-out 技术不会像命名方法“_foo”那样模糊你的界面。
    【解决方案4】:
    package Foo;
    
    ## declare inside-out hashes here:
    
    my %attr_a;
    my %attr_b;
    
    ## declare private methods here
    
    my $private_1 = sub {
      my $self = shift;
      # can use $attr_a{$self} here...
      ...
    };
    
    my $private_2 = sub {
      my $self = shift;
      ... 
    };
    
    ## public methods here
    
    sub new { ... }
    
    sub public_1 {
      my $self = shift;
      # can access attributes here
      # can call private methods too, with slightly odd syntax:
      my $result = $self->$private_1(@args);
      ...
    }
    
    1;
    

    【讨论】:

    • 经过多年的 Perl 编程,我最近在“Camel”一书中发现了这种模式,我很惊讶它没有被更多地使用。但我想所有关于社区规范的回答都回答了这个问题。 ;-)
    猜你喜欢
    • 1970-01-01
    • 2015-12-04
    • 2013-03-11
    • 2012-02-03
    • 1970-01-01
    • 2010-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多