We often hear of the importance of getters and setters. These code constructs are increasingly supported by frameworks such as dotNet, Java, and others. While getters and setters may have the potential to provide improvements to code quality, they can also reduce code readability. In this posting, I will attempt to illustrate a minimalistic use of getters and setters as opposed to the opinion that these constructs should always be used everywhere.
I am going to address the use of getters and setters from a number of perspectives. It is common to see opinions on the web regarding getters and setters, but those opinions are often from one perspective. This article attempts to comprehensively describe the use of getters and setters from a number of perspectives. First, the philosophy of encapsulation will be discussed. Then we address the human factors of readability and typing ability. Maintenance is analyzed next, followed by considering the effects of tool usage.
There is a common opinion that getters and setters provide better encapsulation. However, when we look more closely, it does not appear that this is a true statement. First, the precursor to encapsulation [1] was information hiding [2] which is concerned with "the criteria to be used in dividing a system into modules" [2]. The critical point of information hiding is that it applies at a module level, not at a lower level such as getters and setters. Encapsulation refers to the boundary between the internals and the externals of a class in Object Oriented Design (OOD) [3]. Generally terms such as coupling and cohesion are used to analyze things at a finer level of detail.
Now, for a strict analysis, an Object Oriented (OO) class exposes some functions and data to the outside world and keeps others hidden inside of it. In this case a getter or setter can be used to limit a data member to be read-only, thereby enabling read-only encapsulation. For example, in Figure 1 we encapsulate age to be a read-only data member. If you are using a getter and a setter for a data member, then you are not hiding anything and therefore that getter and setter is not contributing to the encapsulation of your module. Figure 2 illustrates a getter and setter for the name data member.
There is a professional field that studies the interaction of humans and technology and a couple books on the topic of software code and readability exist [4, 5]. I don't claim to know what is contained in those books, but it seems quite obvious in this small example that having 6 lines of code (Figure 2) instead of 1 (Figure 1) to express the name property results in less readability.
1 class Person { 2 3 public String name; 4 private Datetime date_of_birth; 5 6 public int getAge () { 7 Datetime age = Datetime.Now - date_of_birth; 8 return age.years; 9 } 10 }Figure 1: Encapsulating use of a getter
1 class Person { 2 3 private String name; 4 private Datetime date_of_birth; 5 6 public String getName () { 7 return name; 8 } 9 10 public String setName () { 11 this.name = name; 12 } 13 14 public int getAge () { 15 Datetime age = Datetime.Now - date_of_birth; 16 return age.years; 17 } 18 }Figure 2: Non-encapsulating use of name as a getter and setter
1 class Person { 2 3 public String name; 4 private Datetime date_of_birth; 5 6 public int age { 7 get { 8 Datetime age = Datetime.Now - date_of_birth; 9 return age.years; 10 } 11 } 12 }Figure 3: A read only property in C#
With certain languages like dotNet or Java, getters and setters have been recast into their own language structures. Take the example shown in Figure 3, which shows the specialized format for a getter. The problem here is that it does not reduce the number of lines of code and in fact in increases the lines from Figure 1 by 2 lines. Now, there is another type of way to specifically state a read only class member and that is with the readonly keyword as in readonly int age. This terse expression of a read only data member increases the readability of the interface of the class.
Some languages allow getters and setters to be accessed as if they were just plain data members (i.e. person.name = "John Doe" rather than person.setName( "John Doe")). Having the more terse reference to a getter and setter also increases readability.
People differ in their typing speeds. Obviously getters and setters require more typing to produce. Slow typists have more difficulty with creating getters and setters. Since much of the analysis regarding readability referenced the number of lines, the topic of typing ability closely parallels the readability analysis.
Many people propose that getters and setters allow someone to make small changes to one object without affecting the external interface and thereby save themselves from having to change a lot of uses of that object's data member. As an example, lets say we have person.weight = 150. Of course the assumption is that this weight is in pounds. Now, lets say that we have to change this to kilograms instead. This change reverberates throughout the program because every time we assigned a weight, we must convert that number from pounds to kilograms. However, if we had used a getter / setter for weight, we would have been able to keep all external references the same by adding a conversion function to our setter routine. So, in this case you have saved some time.
The problem with using getters and setters to solve problems when changing from pounds to kilograms is in destroying encapsulation. The first encapsulation says that person.weight is in pounds, but our second encapsulation says that person.setWeight(in pounds) and weight in kg = person.getWeight(). This change in encapsulation introduces a lot of confusion with pounds and kilograms. Therefore, using getters and setters in this manner is not as beneficial as it may seem at first. The most optimal solution is to redesign the class Person around the new requirement and edit all references accordingly. A popular redesign method for such a situation is refactoring [6].
In regards to readability, modern software tools have mitigated many of the factors affecting the use of getters and setters. This allows you to use getters and setters more easily or to avoid them just as easily.
The readability of getters and setters can be enhanced with code folding. Tools that provide assistance in the writing of getters and setters are autocompletion and special key combinations to automate the typing of getters and setters. Maintenance concerns regarding getters and setters can be relieved by using a refactoring tool which allows you to rename a data member and automatically changing all of the references to that data member. This effectively allows you to change your data members very easily and thereby reduce the need for getters and setters.
Modern Integrated Development Environment (IDE)s such as Eclipse or Visual Studio come with all of the tools mentioned previously. If you are writing code in a console screen, you may be more inclined to use the getter / setter construct to solve some of your problems in changing the design. There are some tools available for editing in a console, but they require more work to install and to learn how to use whereas a modern IDE just has many tools included.
Getters and setters are useful, but they can also complicate your code. The use of these elements are affected by philosophy, readability, typing ability, maintenance concerns, and the coding tools that are used. Any specific perspective regarding getters and setters can be rationalized in a number of ways, but you will often find a better solution if you take a holistic viewpoint.