草庐IT

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

coder 2024-07-23 原文

我见过一些 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 - 组合 getter 和 setter 方法有什么好处?的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby - Facter::Util::Uptime:Module 的未定义方法 get_uptime (NoMethodError) - 2

      我正在尝试设置一个puppet节点,但ruby​​gems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由ruby​​gems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby

    5. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

      我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

    6. Ruby 方法() 方法 - 2

      我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby​​-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco

    7. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

      我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

    8. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    9. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

      为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

    10. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

      我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

    随机推荐