[英]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調用。
更?
我想知道組合設置和獲取方法是否有任何實際好處,或者它是否是各個語言/圖書館社區中的一種奇怪傳統。
使用不同的set_
和get_
方法看起來是自我記錄的,但是
get_
不暴露set_
。 后一點在使用細粒度訪問控制和權限系統時尤其重要,例如在Java,C#,C ++等語言中具有可見的細微差別,如私有/受保護/公共等。由於這些語言有方法重載,編寫統一的getter /塞特犬並非不可能,而是一種文化偏好 。
從我個人的角度來看,統一訪問者具有這些優勢
我個人認為foo.bar(foo.bar() + 42)
在眼睛上比foo.setBar(foo.getBar() + 42)
更容易。 但是,后一個例子清楚地說明了每種方法的作用。 統一訪問器使用不同語義重載方法。 我認為這感覺很自然,但它顯然使理解代碼片段變得復雜。
現在讓我分析你所謂的缺點:
這不是必須的,而是您正在考慮的實現的屬性。 在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
值。
在Perl中,您可以自由地檢查調用方法的上下文。 您可以擁有在void上下文中執行的代碼路徑,即拋棄返回值時:
if (not defined wantarray) { be_lazy() }
getter調用需要在可測量的開銷周圍攜帶$new_value
參數。
同樣,這是一個特定於實現的問題。 此外,您忽略了這里的實際性能問題:您訪問者所做的就是調度方法調用 。 方法調用很慢。 當然,方法解析是緩存的,但這仍然意味着一個哈希查找。 這比參數列表中的額外變量貴得多。
請注意,訪問器也可以像
sub foo { my $self = shift; return $self->getset('foo', @_); }
擺脫了一半我無法通過undef
問題。
......這是錯的。 我已經介紹過了。
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 { ... } }
現在讓我們考慮兩個細節:
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}; } }; } }
這比僅委托給另一個方法更容易,更有效,並且可以處理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.