组合getter和setter方法有什么好处?

Ste*_*sky 7 javascript perl jquery api-design properties

我见过一些API,特别是在脚本语言中(我们在团队中使用Perl和JS),它们使用了组合的getter和setter方法.例如,在jQuery中:

//append something to element text
var element = $('div#foo');
element.text(element.text() + 'abc');
Run Code Online (Sandbox Code Playgroud)

例如,在Perl的CGI.pm模块中:

# read URL parameter from request
my $old_value = $cgi->param('foo');
# change value of parameter in request
$cgi->param('foo', $new_value);
Run Code Online (Sandbox Code Playgroud)

Perl代码库中的一些通用DAO类使用类似的模式.自动生成的访问器调用内部getset()方法,类似于:

sub foo { # read/write accessor for the foo() property (autogenerated)
    my ($self, $new_value) = @_;
    return $self->getset('foo', $new_value);
}

sub getset {
    my ($self, $field, $new_value) = @_;
    ## snip (omitted some magic here) ##

    # setter mode
    if (defined $new_value) {
        ## snip (omitted some magic here) ##
        $self->{data}{$field} = $new_value;
        ## snip (omitted more magic here) ##
    }

    # getter mode
    return $self->{data}{$field} // '';
}
Run Code Online (Sandbox Code Playgroud)

我看到这个设计存在多个问题:

  • Setter调用也通过getter代码路径,这是低效的,特别是如果你有关系,所以getter必须将存储的ID解析为一个对象:

    $foo->bar($new_bar); # retrieves a Bar instance which is not used
    
    Run Code Online (Sandbox Code Playgroud)
  • getter调用需要携带$new_value参数.当您经常调用Perl方法时(例如,100000次,报告和其他cronjobs中的常规记录数),这可能已经导致可测量的开销.

  • Setter不能采用未定义的值.(一个Perl特定的问题,我可以通过检查参数计数而不是参数defined-ness来解决这个问题,但这会使自动生成的访问器更加复杂,并且可能会破坏一些手动访问器.)

  • 降低了grepability:如果我有单独的getter和setter(例如foo(),set_foo()对于每个foo属性),我可以使用简单的grep搜索setter调用.

  • 更?

我想知道组合设置和获取方法是否有任何实际好处,或者它是否是各个语言/图书馆社区中的一种奇怪传统.

amo*_*mon 9

概要

  • 拥有单独的getter/setter或具有组合访问器是文化偏好.没有什么可以阻止你选择较少的路径.
  • 你的大多数缺点都不存在.不要将实现细节与风格决策的问题混淆.

具有不同set_get_方法看起来自我记录,但

  • 如果不发生自动生成,那么写起来很痛苦
  • 主要是静态访问控制的一种形式:我可以有一个get_不暴露的set_.

其中,细粒度的访问控制和权限系统的使用,例如,像Java,C#,C语言++能见度细微差别像私有/保护/公众等.由于这些语言具有方法重载,编写统一的干将后一点尤其重要/塞特犬并非不可能,而是一种文化偏好.

从我个人的角度来看,统一访问者具有这些优势

  1. API更小,可以使文档和维护更容易.
  2. 根据命名约定,每次方法调用可减少3-4次击键.
  3. 对象感觉更像结构.
  4. 我认为没有真正的缺点.

我的个人观点认为foo.bar(foo.bar() + 42)眼睛比眼睛更容易foo.setBar(foo.getBar() + 42).但是,后一个例子清楚地说明了每种方法的作用.统一访问器使用不同语义重载方法.我认为这感觉很自然,但它显然使理解代码片段变得复杂.

现在让我分析你所谓的缺点:

分析组合getter/setter的所谓问题

Setter调用遍历getter代码路径

这不是必须的,而是您正在考虑的实现的属性.在Perl中,你可以合理地写

sub accessor {
  my $self = shift;
  if (@_) {
    # setter mode
    return $self->{foo} = shift;
    # If you like chaining methods, you could also
    # return $self;
  } else {
    # getter mode
    return $self->{foo}
  }
}
Run Code Online (Sandbox Code Playgroud)

代码路径是完全独立的,除了真正常见的东西.请注意,这也将接受undefsetter模式中的值.

[...]检索未使用的Bar实例

在Perl中,您可以自由地检查调用方法的上下文.您可以拥有在void上下文中执行的代码路径,即抛弃返回值时:

if (not defined wantarray) { be_lazy() }
Run Code Online (Sandbox Code Playgroud) getter调用需要$new_value围绕可测量的开销进行争论.

同样,这是一个特定于实现的问题.此外,您忽略了这里的实际性能问题:您访问者所做的就是调度方法调用.方法调用很慢.当然,方法解析是缓存的,但这仍然意味着一个哈希查找.这比参数列表中的额外变量贵得多.

请注意,访问器也可以像

sub foo {
   my $self = shift;
   return $self->getset('foo', @_);
}
Run Code Online (Sandbox Code Playgroud)

摆脱了一半我无法通过的undef问题.

Setter不能采用未定义的值.

......这是错的.我已经介绍过了.

Grepability

我将使用grepability的这个定义:

如果在源文件中搜索方法/变量/的名称,则可以找到声明的站点.

这禁止像愚蠢的自动生成

my @accessors = qw/foo bar/;
for my $field (@accessors) {
  make_getter("get_$field");
  make_setter("set_$field");
}
Run Code Online (Sandbox Code Playgroud)

在这里,set_foo不会把我们带到声明点(上面的代码片段).但是,自动生成就好

my @accessors = qw/foo bar/;

for my $field (@accessors) {
  make_accessor($field);
}
Run Code Online (Sandbox Code Playgroud)

确实满足上述grepability的定义.

如果我们喜欢,我们可以使用更严格的定义:

如果在源文件中搜索方法/变量/ ...的声明语法,则可以找到声明的站点.这function foo对于JS 来说意味着" ",sub foo对于Perl 意味着" " .

这只需要在自动生成时,将基本声明语法放入注释中.例如

# AUTOGENERATE accessors for
# sub set_foo
# sub get_foo
# sub set_bar
# sub get_bar
my @accessors = qw/foo bar/;
...; # as above
Run Code Online (Sandbox Code Playgroud)

使用组合或单独的getter和setter不会影响grepability,只触及自动生成.

关于非愚蠢的自我生成

我不知道你如何自动生成你的访问器,但产生的结果不必吮吸.要么使用某种形式的预处理器,在动态语言中感觉很愚蠢,要么使用eval,这在现代语言中会感觉很危险.

在Perl中,我宁愿在编译期间通过符号表破解我的方式.包名称空间只是具有名称的哈希值,%Foo::所谓的名称为globs作为条目,可以包含coderefs.我们可以访问类似的*Foo::foo(注意*sigil).所以不要这样做

package Foo;
sub foo { ... }
Run Code Online (Sandbox Code Playgroud)

我也可以

BEGIN {
  *{Foo::foo} = sub { ... }
}
Run Code Online (Sandbox Code Playgroud)

现在让我们考虑两个细节:

  1. 我可以关闭strict 'refs'然后可以动态组合子程序名称.
  2. 那个子......这是一个封闭!

因此,我可以遍历一个字段名称数组,并为它们分配相同的子例程,区别在于每个字段名称都关闭不同的字段名称:

BEGIN {
  my @accessors = qw/foo bar/;
  # sub foo
  # sub bar

  for my $field (@accessors) {
    no strict 'refs';

    *{ __PACKAGE__ . '::' . $field } = sub {
      # this here is essentially the old `getset`

      my $self = shift;

      ## snip (omitted some magic here) ##

      if (@_) {
        # setter mode
        my $new_value = shift;
        ## snip (omitted some magic here) ##
        $self->{data}{$field} = $new_value;
        ## snip (omitted more magic here) ##
        return $new_value; # or something.
      }
      else {
        # getter mode
        return $self->{data}{$field};
      }
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

这比仅委托给另一个方法并且可以处理更容易,更有效undef.

如果维护程序员不知道这种模式,那么缺点是可维护性降低.

此外,源于该子内部的错误报告来自__ANON__:

Some error at script.pl line 12
    Foo::__ANON__(1, 2, 3) called at Foo.pm line 123
Run Code Online (Sandbox Code Playgroud)

如果这是一个问题(即访问者包含复杂的代码),可以通过使用来减轻这种情况Sub::Name,正如Stefan Majewsky在下面的评论中指出的那样.