javascript - What are the benefits of combined getter and setter methods? -


i've seen apis, in scripting languages (we use perl , js in our team), use combined getter , setter methods. example, in jquery:

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

for example, in perl's cgi.pm module:

# read url parameter request $old_value = $cgi->param('foo'); # change value of parameter in request $cgi->param('foo', $new_value); 

some generic dao class in our perl codebase uses similar pattern. autogenerated accessors call internal getset() method, similar this:

sub foo { # read/write accessor foo() property (autogenerated)     ($self, $new_value) = @_;     return $self->getset('foo', $new_value); }  sub getset {     ($self, $field, $new_value) = @_;     ## snip (omitted magic here) ##      # setter mode     if (defined $new_value) {         ## snip (omitted magic here) ##         $self->{data}{$field} = $new_value;         ## snip (omitted more magic here) ##     }      # getter mode     return $self->{data}{$field} // ''; } 

i see multiple issues design:

  • setter calls go through getter code path, too, inefficient if have relations, getter has resolve stored id object:

    $foo->bar($new_bar); # retrieves bar instance not used 
  • a getter call needs carry $new_value argument around. when call perl method (say, 100000 times, usual number of records in reporting , other cronjobs), can cause measureable overhead.

  • setters cannot take undefined value. (a perl specific issue, work around checking argument count instead of argument defined-ness, make autogenerated accessors more complicated, , break manual accessors.)

  • decreased grepability: if had separate getters , setters (e.g. foo() , set_foo() each foo property), search setter calls using simple grep.

  • even more?

i wonder if there actual benefits combined-setter-and-getter approach, or if it's weird tradition among communities of respective languages/libraries.

synopsis

  • having seperate getters/setters or having combined accessors cultural preference. there nothing prevents choosing path less taken.
  • most of downsides don't exist. not confuse implementation details problems of stylistic decision.

having different set_ , get_ methods looks self-documenting, but

  • are pain write if no autogeneration takes place, and
  • are form of static access control: can have get_ without exposing set_.

the latter point important fine-grained access control , permission systems used, e.g. in languages java, c#, c++ visibility nuances private/protected/public, etc. these languages have method overloading, writing unified getters/setters isn't impossible, instead cultural preference.

from personal perspective, unified accessors have these benefits

  1. the api smaller, can make documentation , maintenance easier.
  2. depending on naming convention, there 3–4 less keystrokes per method call.
  3. objects feel bit more struct-like.
  4. i can think of no real downsides.

it personal opinion foo.bar(foo.bar() + 42) tad easier on eyes foo.setbar(foo.getbar() + 42). however, latter example makes clear each method does. unified accessors overload method different semantics. consider feel natural, complicates understanding code snippet.

now let me analyze alleged downsides:

analysis of alleged issues combined getters/setters

setter calls go through getter code path

this isn't neccessarily case, , rather property of implementation looking at. in perl, can reasonably write

sub accessor {   $self = shift;   if (@_) {     # setter mode     return $self->{foo} = shift;     # if chaining methods,     # return $self;   } else {     # getter mode     return $self->{foo}   } } 

the code paths seperate, except stuff truly common. note accept undef value in setter mode.

[…] retrieves bar instance not used

in perl, free inspect context in method called. can have code path executed in void context, i.e. when return value thrown away:

if (not defined wantarray) { be_lazy() } 
a getter call needs carry $new_value argument around […] measureable overhead.

again, implementation-specific problem. also, overlooked real performance issue here: accessor dispatch method call. , method calls slow. of course method resolution cached, still implies 1 hash lookup. more expensive variable on argument list.

note accessor have been written like

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

which gets rid of half of i can't pass undef problem.

setters cannot take undefined value.

… wrong. covered that.

grepability

i use definition of grepability:

if source file searched name of method/variable/…, site of declaration can found.

this forbids moronic autogeneration like

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

here, set_foo wouldn't bring point of declaration (the above snippet). however, autogeneration like

my @accessors = qw/foo bar/;  $field (@accessors) {   make_accessor($field); } 

does fulfill above definition of grepability.

we can use stricter definition if like:

if source file searched declaration syntax of method/variable/…, site of declaration can found. mean “function foo” js , “sub foo” perl.

this requires @ point of autogeneration, basic declaration syntax put comment. e.g.

# autogenerate accessors # sub set_foo # sub get_foo # sub set_bar # sub get_bar @accessors = qw/foo bar/; ...; # above 

the grepability not impacted using combined or seperate getters , setters, , touches on autogeneration.

on non-moronic autogeneration

i don't know how autogenerate accessors, result produced doesn't have suck. either use form of preprocessor, feels silly in dynamic languages, or use eval, feels dangerous in modern languages.

in perl, i'd rather hack way through symbol table during compile time. package namespaces hashes names %foo:: have so-called globs entries, can contain coderefs. can access glob *foo::foo (note * sigil). instead of doing

package foo; sub foo { ... } 

i also

begin {   *{foo::foo} = sub { ... } } 

now let's consider 2 details:

  1. i can turn off strict 'refs' , can dynamically compose subroutine name.
  2. that sub … closure!

therefore, can loop on array of field names, , assign them same subroutine, difference each closes on different field name:

begin {   @accessors = qw/foo bar/;   # sub foo   # sub bar    $field (@accessors) {     no strict 'refs';      *{ __package__ . '::' . $field } = sub {       # here old `getset`        $self = shift;        ## snip (omitted magic here) ##        if (@_) {         # setter mode         $new_value = shift;         ## snip (omitted magic here) ##         $self->{data}{$field} = $new_value;         ## snip (omitted more magic here) ##         return $new_value; # or something.       }       else {         # getter mode         return $self->{data}{$field};       }     };   } } 

this greppable, more efficient delegating method , can handle undef.

the downside reduced maintainability, if maintaining programmer not know pattern.

also, errors originating inside sub reported coming __anon__:

some error @ script.pl line 12     foo::__anon__(1, 2, 3) called @ foo.pm line 123 

if issue (i.e. accessor contains complex code), mitigated using sub::name, stefan majewsky points out in comment below.


Comments