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

标签 javascript jquery perl properties api-design

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

//append something to element text
var element = $('div#foo');
element.text(element.text() + 'abc');

例如,在 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);

我们 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} // '';
}

我看到此设计存在多个问题:
  • Setter 调用也通过 getter 代码路径,这是低效的,尤其是当您有关系时,因此 getter 必须将存储的 ID 解析为一个对象:
    $foo->bar($new_bar); # retrieves a Bar instance which is not used
    
  • getter 调用需要携带 $new_value周围的争论。当您非常频繁地调用 Perl 方法时(例如,100000 次,报告和其他 cronjob 中通常的记录数),这已经可能导致可衡量的开销。
  • Setter 不能采用未定义的值。 (Perl 特定问题,我可以通过检查参数计数而不是参数定义来解决这个问题,但这会使自动生成的访问器更加复杂,并且可能会破坏一些手动访问器。)
  • 减少 grepability :如果我有单独的 getter 和 setter(例如每个 foo() 属性的 set_foo()foo),我可以使用简单的 grep 搜索 setter 调用。
  • 更?

  • 我想知道组合 setter-and-getter 方法是否有任何实际好处,或者它是否只是各个语言/库社区中的一个奇怪的传统。

    最佳答案

    概要

  • 拥有单独的 getter/setter 或组合访问器是一种文化偏好。没有什么能阻止你选择少走的路。
  • 你的大部分缺点都不存在。不要将实现细节与文体决定问题混淆。


  • 有不同 set_get_方法看起来是自我记录的,但是
  • 如果没有自动生成,写起来会很痛苦,而且
  • 主要是静态访问控制的一种形式:我可以有一个 get_不暴露 set_ .

  • 后一点在使用细粒度访问控制和许可系统的情况下尤其重要,例如在 Java、C#、C++ 等具有私有(private)/ protected /公共(public)等可见性细微差别的语言中。由于这些语言具有方法重载,因此编写统一的 getter/setter 并非不可能,而是一种文化偏好。

    从我个人的 Angular 来看,统一访问器有这些好处
  • API 更小,这可以使文档和维护更容易。
  • 根据命名约定,每个方法调用的击键次数减少 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}
      }
    }
    

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

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

    在 Perl 中,您可以自由地检查调用您的方法的上下文。您可以拥有在 void 上下文中执行的代码路径,即当返回值被丢弃时:
    if (not defined wantarray) { be_lazy() }
    

    getter 调用需要携带 $new_value围绕 [...] 可测量开销的争论。

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

    请注意,访问器也可以写成
    sub foo {
       my $self = shift;
       return $self->getset('foo', @_);
    }
    

    去掉了一半的 I can't pass undef问题。

    Setter 不能采用未定义的值。

    ……这是错误的。我已经涵盖了。

    可抓取性

    我将使用 grepability 的这个定义:

    If a source file is searched for the name of a method/variable/…, then the site of declaration can be found.



    这禁止愚蠢的自动生成,如
    my @accessors = qw/foo bar/;
    for my $field (@accessors) {
      make_getter("get_$field");
      make_setter("set_$field");
    }
    

    在这里,set_foo不会把我们带到声明点(上面的片段)。然而,自动生成就像
    my @accessors = qw/foo bar/;
    
    for my $field (@accessors) {
      make_accessor($field);
    }
    

    确实满足上述 grepability 的定义。

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

    If a source file is searched for the declaration syntax of a method/variable/…, then the site of declaration can be found. This would mean “function foo” for JS and “sub foo” for Perl.



    这只需要在自动生成时,将基本声明语法放入注释中。例如。
    # AUTOGENERATE accessors for
    # sub set_foo
    # sub get_foo
    # sub set_bar
    # sub get_bar
    my @accessors = qw/foo bar/;
    ...; # as above
    

    grepability 不受使用组合或单独的 getter 和 setter 的影响,只涉及自动生成。

    关于非愚蠢的自动生成

    我不知道你如何自动生成你的访问器,但产生的结果并不一定很糟糕。要么你使用某种形式的预处理器,这在动态语言中感觉很傻,要么你使用 eval ,这在现代语言中感觉很危险。

    在 Perl 中,我宁愿在编译时修改符号表。包命名空间只是散列名称,如 %Foo::具有所谓的 globs 作为条目的条目,其中可以包含 coderefs。我们可以访问像 *Foo::foo 这样的 glob (注意 * 印记)。所以而不是做
    package Foo;
    sub foo { ... }
    

    我也可以
    BEGIN {
      *{Foo::foo} = sub { ... }
    }
    

    现在让我们考虑两个细节:
  • 我可以关掉 strict 'refs'然后可以动态组合子程序名称。
  • 那个子……这是一个关闭!

  • 因此,我可以遍历一个字段名称数组,并为它们分配相同的子例程,不同之处在于每个子例程关闭不同的字段名称:
    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};
          }
        };
      }
    }
    

    这是 grepable,比仅仅委托(delegate)给另一种方法更有效,并且可以处理 undef .

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

    此外,来自该子程序内部的错误报告为来自 __ANON__ :
    Some error at script.pl line 12
        Foo::__ANON__(1, 2, 3) called at Foo.pm line 123
    

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

    关于javascript - 组合 getter 和 setter 方法有什么好处?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17651297/

    相关文章:

    javascript - 如何在 Canvas 中绘制读取 json?

    jquery - 更改输入值 [type=time]?

    javascript - Rails3-jquery-autocomplete 没有响应

    java - 无法从服务器计算机运行 perl 脚本

    javascript - 如何在javascript中输入框中的文本发生变化时立即调用该函数

    javascript - 对象比较 : check if an object contains the whole other object

    javascript - 如何将字符串格式的图像传递给JavaScript并将其写入文档

    javascript - 如何计算元素相对于屏幕的位置

    linux - 运行 Matlab 代码失败时如何使 Perl 脚本停止?

    perl - 如何在 Perl 中比较两个字符串?