簡體   English   中英

組合getter和setter方法有什么好處?

[英]What are the benefits of combined getter and setter methods?

我見過一些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次,報告和其他cronjobs中的常規記錄數),這可能已經導致可測量的開銷。

  • Setter不能采用未定義的值。 (一個Perl特定的問題,我可以通過檢查參數計數而不是參數defined-ness來解決這個問題,但這會使自動生成的訪問器更加復雜,並且可能會破壞一些手動訪問器。)

  • 降低grepability :如果我有單獨的getter和setter(例如每個foo屬性的foo()set_foo() ),我可以使用簡單的grep搜索setter調用。

  • 更?

我想知道組合設置和獲取方法是否有任何實際好處,或者它是否是各個語言/圖書館社區中的一種奇怪傳統。

概要

  • 擁有單獨的getter / setter或具有組合訪問器是文化偏好。 沒有什么可以阻止你選擇較少的路徑。
  • 你的大多數缺點都不存在。 不要將實現細節與風格決策的問題混淆。

使用不同的set_get_方法看起來是自我記錄的,但是

  • 如果不發生自動生成,那么寫起來很痛苦
  • 主要是靜態訪問控制的一種形式:我可以有一個get_不暴露set_

后一點在使用細粒度訪問控制和權限系統時尤其重要,例如在Java,C#,C ++等語言中具有可見的細微差別,如私有/受保護/公共等。由於這些語言有方法重載,編寫統一的getter /塞特犬並非不可能,而是一種文化偏好

從我個人的角度來看,統一訪問者具有這些優勢

  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} } } 

代碼路徑是完全獨立的,除了真正常見的東西。 請注意,這也將在setter模式下接受undef值。

[...]檢索未使用的Bar實例

在Perl中,您可以自由地檢查調用方法的上下文。 您可以擁有在void上下文中執行的代碼路徑,即拋棄返回值時:

 if (not defined wantarray) { be_lazy() } 
getter調用需要在可測量的開銷周圍攜帶$new_value參數。

同樣,這是一個特定於實現的問題。 此外,您忽略了這里的實際性能問題:您訪問者所做的就是調度方法調用 方法調用很慢。 當然,方法解析是緩存的,但這仍然意味着一個哈希查找。 這比參數列表中的額外變量貴得多。

請注意,訪問器也可以像

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

擺脫了一半我無法通過undef問題。

Setter不能采用未定義的值。

......這是錯的。 我已經介紹過了。

Grepability

我將使用grepability的這個定義:

如果在源文件中搜索方法/變量/的名稱,則可以找到聲明的站點。

這禁止像愚蠢的自動生成

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

在這里, set_foo不會將我們帶到聲明點(上面的代碼片段)。 但是,自動生成就好

# AUTOGENERATE accessors for
# sub set_foo
# sub get_foo
# sub set_bar
# sub get_bar
my @accessors = qw/foo bar/;
...; # as above

確實滿足上述grepability的定義。

如果我們喜歡,我們可以使用更嚴格的定義:

如果在源文件中搜索方法/變量/ ...的聲明語法,則可以找到聲明的站點。 這意味着JS的“ function foo ”和Perl的“ sub foo ”。

這只需要在自動生成時,將基本聲明語法放入注釋中。 例如

package Foo;
sub foo { ... }

使用組合或單獨的getter和setter不會影響grepability,只觸及自動生成。

關於非愚蠢的自我生成

我不知道你如何自動生成你的訪問器,但產生的結果不必吮吸。 要么使用某種形式的預處理器,在動態語言中感覺很傻,要么使用eval ,這在現代語言中感覺很危險。

在Perl中,我寧願在編譯期間通過符號表破解我的方式。 包命名空間只是名稱為%Foo::哈希,它具有所謂的globs作為條目,可以包含coderefs。 我們可以訪問類似*Foo::foo的glob(請注意* sigil)。 所以不要這樣做

 package Foo; sub foo { ... } 

我也可以

 BEGIN { *{Foo::foo} = sub { ... } } 

現在讓我們考慮兩個細節:

  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}; } }; } } 

這比僅委托給另一個方法更容易,更有效,並且可以處理undef

如果維護程序員不知道這種模式,那么缺點是可維護性降低。

此外,源自該子組的錯誤報告為來自__ANON__

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

如果這是一個問題(即訪問者包含復雜的代碼),可以通過使用Sub::Name來減輕這種情況,正如Stefan Majewsky在下面的評論中指出的那樣。

有好處,但它們被缺點所抵消。

在典型情況下,可讀性或易於理解是代碼最重要的考慮因素。

我認為這種模式的開發者試圖簡潔,這很重要。 API中的方法越多,理解起來就越困難。

但是,精心設計的功能應該只做一件事。 這簡化了任何好處。

另一個需要考慮的方面是語言是否支持左值訪問器。 例如,在Perl中,您可以將變異運算符應用於此類訪問器,從而實現類似的功能

$obj->name =~ s/foo/bar/;
$obj->value += 10;

沒有左值訪問器+ mutator方法,需要更少的整潔

$obj->set_name( $obj->get_name =~ s/foo/bar/r );  # perl 5.14+
$obj->set_name( do { $_ = $obj->get_name; s/foo/bar/; $_ } );  # previously

$obj->set_value( $obj->get_value + 10 );

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM