Strong encapsulation requires that classes hide attributes (i.e., member variables) from client programs; classes hide their member variables s by making them private. However, there are times when a client has a legitimate need to know some things about an object. For example, if the client program is using a Person object, at some time, it may need to know the name of the represented person. Similarly, objects often must change over time to reflect the problem domain's ever-changing conditions. For example, a person may move, and the program must update the person's address. We could satisfy that legitimate need by making the data public but doing so would expose the data to both legitimate and illegitimate use. Fortunately, there is a way to satisfy the legitimate need to access private data while maintaining encapsulation and, more importantly, maintaining the security offered by the private specification.
The preferred way of allowing one object (the client) to access the private data of another object (the server or supplier) is to define access functions in the server object. Access functions, which become a part of the object's public interface, provide controlled access to some of an object's private data. Two kinds of access functions exist: accessor or getter and mutator or setter.
Accessor / Getter Functions
Accessor or getter functions allow clients to get the value saved in one of a supplier object's member variables. Getter functions are typically relatively small and simple, but the following example illustrates that they can be more complex when necessary. By default, C++ functions perform a return by value (i.e., return by copy), so a client can get the value saved in an object but cannot change the value. However, if a getter function returns a pointer or a reference, the client can change a saved value. To prevent clients from changing an object, getter functions can return const pointers and references (covered in detail later in the chapter). C++ follows a similar but less strict naming convention than Java for naming getter functions. Like Java programmers, C++ programmers often use camel notation (aka camel case) to name class features. However, the C++ naming style is less strict, so another standard naming style adds the prefix "get" to the variable name. For example, suppose that a class has a string member variable, string name;, then common getter function names are:
string getName() C++ and Java
string get_name() C++
Mutator / Setter Functions
Mutator/setter functions allow clients to change the value stored in one of an object's member variables. Setter functions may include validity checks or reformat data to match a standard format. The naming conventions are similar to those for accessor functions. Assuming the same string name; member variable as before, then common setter function names are:
void setName(string n) C++ and Java
void set_name(string n) C++
Access Function Examples
Advantages of Accessors And Mutators
At first, it may seem like accessors and mutators (or getters and setters) violate encapsulation, but they do offer many advantages over simply setting the attribute visibility to public:
Class designers are not obligated to provide assessors and mutators for all member variables. They may choose which variables to expose by providing access functions and which to conceal.
Class designers also maintain full control of the direction of the data flow with the client. They may allow the client to get the value saved in a member variable by providing a getter but prevent them from changing the value by not providing a setter. Although less common, designers may allow the client to set the value in a variable but not get it.
Accessors can maintain strong encapsulation or allow indirect member variable modification depending on how they return the variable. If the accessor or getter returns a copy of the data (the typical behavior), the client will be unable to alter the variable, fully preserving encapsulation. But if the getter returns a pointer or a reference, the client can modify the variable, deliberately weakening encapsulation.
Simple mutators or getters may assign a value to an attribute, but more complex functions may also perform appropriate data validation, formatting, and conversion. For example, a setter whose parameter is a string representing a birthdate can verify that the date is valid - rejecting "February 31." If the object stores the birthrate in a standard format like "1960/12/25," but the user enters a date as "Dec 25, 1960," the setter function can reformat the date to match the standard before saving it.
(a)
(b)
Setters and getters: Creating a stable public interface. Imagine a stack class with member functions:
push
pop
size
Two implementations of the stack class illustrate two problems caused by exposing a class's implementation.
We can implement a stack as a zero-indexed array with an index called the stack pointer, sp. The stack pointer indicates the top of the stack - the location of the next push or pop operation - 3 in this example. The stack pointer can also indicate the stack's size - the number of elements on the stack - which the client may legitimately need to "know." But if the class makes the stack pointer public, the client can change the value saved in sp, invalidating the next push or pop. We can solve the problem with a getter function:
int size() { return sp; }
We can also implement the stack as a linked list. Each square represents a node divided into two member variables: the top rectangle is a pointer linking the nodes together, and the bottom is the data pushed on the stack. Significantly, this implementation does not have a stack pointer. Although we have changed the stack's implementation, a client may still need to know the stack's size. The linked list implementation will "walk" the list and count the nodes to satisfy this need. If we had exposed sp in the array implementation (i.e., if we had made it public), the client could depend on the stack class having a member named sp and the new implementation would break the client. But if the function signature, int size(), remains constant, we are free to replace return sp; with the counting code. Setters and getters help class designers create a stable public interface even when the class's underlying implementation changes.