gpt4 book ai didi

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

转载 作者:数据小太阳 更新时间:2023-10-29 04:33:47 24 4
gpt4 key购买 nike

我见过一些 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/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com