Blog-Archiv

Sonntag, 16. Oktober 2016

About Getters and Setters

There has been a lot of discussion about getters and setters, spanning different programming languages.

For example in Java, there are programmers that write classes where all fields always have both public getters and setters. When you ask them for the reason, they say "It can be easily done with my IDE", or "I thought this is required".

In JavaScript, getters and setters are not a programming tradition, because JS is not an object-oriented language. Nevertheless people try to use it as such, and you find getters and setters also in JS code.

I believe the get / set tradition started with C++, which was the first real object-oriented language. There the getter is called "accessor", and the setter "mutator".

What is Getter / Setter?

A getter is a method that returns the value of a "member"-field, and a setter lets change its value. It is a stereotype used in object-oriented languages. Here is an example for a Java Bean.

public class Person
{
    private String name;
        
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Following the Java Beans Specification we call "name" a property, and an instance of Person a bean. Thus a bean is a container for 1..n properties. A bean is meant to be the smallest building-block for composing reusable software.

The "name" property is a read/write property, because it has both getter and setter. Would it have only getter, it was read-only. Would it have neither getter nor setter, it was just a field, not a property.

Why do we need Getter / Setter?

In the bean-example above, getters and setters can be used for implementing read-only access. In other words, when you make a field private in Java, you can neither read nor write it from outside the class. When you make the field protected or public, it would always have both read- and write-access in the same way. You can not implement read-only properties other than by

  • setting the field private,
  • provide a getter,
  • and leave out the setter.

There is no readonly access-modifier in Java which would make it writeable to just the owning class.

Here is the Person bean with a read-only property "name". Mind that "name" is still writeable within the owning class!

public class Person
{
    private String name;
        
    public Person(String name) {
        this.name = name;
    }
        
    public String getName() {
        return name;
    }
}

So whether you need getters and setters seems to depend on the programming-language. Assuming there is a programming language that provides a readonly access modifier, would we still need getters?

Let's say "No". But, we would still like to use setters!

public class Person
{
    private String name;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        fireChanged();
        this.name = name;
    }

    private void fireChanged() {
        .... // TODO
    }
}

This bean has a little "logic" in it. It notifies listeners as soon as one of its properties was changed. How could we implement value-change logic safely when not using a setter?

There is also another common reason for using getters and setters, that is software maintenance. Imagine we made the "name" field public and provided neither getter nor setter. All surrounding code that uses objects of that class now reads and writes to the field directly. (We reduced the lines of code for this class greatly :-)

public class Person
{
    public String name;
}

So this is how a Person looks like now.

Unfortunately a month later it turns out that the name of the person must be provided by another database class PersonData, and Person must delegate to that new class now.

public class PersonData
{
    public String name;
}

public class Person
{
    public PersonData data;

    public String getName() {
        return data.name;
    }
}

Now all the classes that use person.name must be rewritten. When you consider that domain-objects like Person are the most widely used in an application, and that the field-access needs to be refactored to be a method-call, you might agree that using getters and setters makes sense, at least with a programming language like Java (which is still one of the most modern ones).

Of course the same applies to PersonData, so here it is how they should look like:

public class PersonData
{
    private String name;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

public class Person
{
    private final PersonData data;

    public Person(PersonData data) {
        assert data != null;
        this.data = data;
    }

    public String getName() {
        return data.getName();
    }
    public void setName(String name) {
        data.setName(name);
    }
}

There is one last reason why I would like to have a getter. This reason may have expired in environments where debuggers let set watch-points to detect field modifications. But in environments where debuggers do not allow such, I would like to set a breakpoint into the getter to find out what the heck modifies that field so that this bug occurs - !

When Getter exists, use only Getter

In the following example, the getFullName() method accesses firstName and lastName directly, ignoring their getters. The question rises whether you are allowed to directly access a field, within its class, in case a getter exists (same for setter).

public class Person
{
    private String firstName;
    private String lastName;
    
    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String getFullName() {
        return firstName+" "+lastName;
    }
}

The answer is "No". As soon as there is a getter, only the getter should be used. Think of the maintenance example above. This also applies to the owning class itself. And, as soon as a setter exist, value-change logic could have been implemented, which may be ignored when accessing the field directly.

So, as soon as you introduce a getter or setter, refactor the class to not access the field directly any more. Exception is access in constructor, because you should not call overrideable methods from constructor.

public class Person
{
    ....

    public String getFullName() {
        return getFirstName()+" "+getLastName();
    }
}

Advices

If this sounds now like me trying to convince you that setters and getters always make sense: No, I did not say that! Getters and setters sometimes make sense. I just showed some of the situations where you need to encapsulate fields.

  • Getters should be created just when needed
  • When getter exists, use only getter
  • Getters should have as much encapsulation as possible, best private, else package-visible or protected final, worst is public
  • Setters should be created just when needed
  • Setters should have as much encapsulation as possible, ...
  • Getter and setter can have different access modifiers, try to reduce especially the setter's visibility!




Keine Kommentare: