This pattern was previously described in Grand98.
In general, classes in an application are designed to carry data and have behavior. Sometimes a class may be designed in such a way that its instances can be used just as carriers of related data without any specific behavior. Such classes can be called data model classes and instances of such classes ar e referred to as data objects. For example, consider the Employee class in Figure 8.1 and Listing 8.1.
Employee |
firstName:String |
lastName:String |
SSN:String |
address:String |
car:Car |
________________________________ |
getFirstName():String |
getLastName():String |
getSSN():String |
getAddress():String |
getCar():Car |
setFirstName(fname:String) |
setLastName(lname:String) |
setSSN(ssn:String) |
setAddress(addr:String) |
setCar(c:Car) |
save():boolean |
delete():boolean |
isValid():boolean |
update():boolean |
Figure 8.1 Employee Representation
Listing 8.1 Employee Class
public class Employee {
//State
private String firstName;
private String lastName;
private String SSN;
private String address;
private Car car;
//Constructor
public Employee(String fn, String ln, String ssn,
String addr, Car c) {
firstName = fn;
lastName = ln;
SSN = ssn;
address = addr;
car = c;
}
//Behavior
public boolean save() {
//…
return true;
}
public boolean isValid() {
//…
return true;
}
public boolean update() {
//…
return true;
}
//Setters
public void setFirstName(String fname) {
firstName = fname;
}
public void setLastName(String lname) {
lastName = lname;
}
public void setSSN(String ssn) {
SSN = ssn;
}
public void setCar(Car c) {
car = c;
}
public void setAddress(String addr) {
address = addr;
}
//Getters
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getSSN() {
return SSN;
}
public Car getCar() {
return car;
}
public String getAddress() {
return address;
}
}
Instances of the Employee class above have both the data and the behavior. The corresponding data model class can be designed as in Figure 8.2 and Listing 8.2 without any behavior.
In a typical application scenario, several client objects may simultaneously access instances of such data model classes. This could lead to problems if changes to the state of a data object are not coordinated properly. The Immutable Object pattern can be used to ensure that the concurrent access to a data object by several client objects does not result in any problem. The Immutable Object pattern accomplishes this without involving the overhead of synchronizing the methods to access the object data.
EmployeeModel |
firstName:String |
lastName:String |
SSN:String |
address:String |
car:Car |
________________________ |
getFirstName():String |
getLastName():String |
getSSN():String |
getAddress():String |
getCar():Car |
setFirstName(fname:String) |
setLastName(lname:String) |
setSSN(ssn:String) |
setAddress(addr:String) |
setCar(c:Car) |
Figure 8.2 EmployeeModel Class
Listing 8.2 EmployeeModel Class
public class EmployeeModel {
//State
private String firstName;
private String lastName;
private String SSN;
private String address;
private Car car;
//Constructor
public EmployeeModel(String fn, String ln, String ssn,
String addr, Car c) {
firstName = fn;
lastName = ln;
SSN = ssn;
address = addr;
car = c;
}
//Setters
public void setFirstName(String fname) {
firstName = fname;
}
public void setLastName(String lname) {
lastName = lname;
}
public void setSSN(String ssn) {
SSN = ssn;
}
public void setCar(Car c) {
car = c;
}
public void setAddress(String addr) {
address = addr;
}
//Getters
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getSSN() {
return SSN;
}
public Car getCar() {
return car;
}
public String getAddress() {
return address;
}
}
Applying the Immutable Object pattern, the data model class can be designed in such a way that the data carried by an instance of the data model class remains unchanged over its entire lifetime. That means the instances of the data model class become immutable.
In general, concurrent access to an object creates problems when one thread can change data while a different thread is reading the same data. The fact that the data of an immutable object cannot be modified makes it automatically thread-safe and eliminates any concurrent access related problems.
Though using the Immutable Object pattern opens up an application for all kinds of performance tuning tricks, it must be noted that designing an object as immutable is an important decision. Every now and then it turns out that objects that were once thought of as immutables are in fact mutable, which could result in difficult implementation changes.
As an example, let us redesign the EmployeeModel class to make it immutable by applying the following changes.
Figure 8.3 and Listing 8.3 show the resulting immutable version of the EmployeeModel class.
The immutable version of the EmployeeModel objects can safely be used in a multithreaded environment.
1. Design an immutable class that contains the line styles and colors used in a given image.
2. Design an immutable class to carry the data related to a company such as the company address, phone, fax, company name and other details.
Figure 8.3 EmployeeModel Class: Immutable Version
Listing 8.3 EmployeeModel Class: Immutable Version
public final class EmployeeModel {
//State
private final String firstName;
private final String lastName;
private final String SSN;
private final String address;
private final Car car;
//Constructor
public EmployeeModel(String fn, String ln, String ssn,
String addr, Car c) {
firstName = fn;
lastName = ln;
SSN = ssn;
address = addr;
car = c;
}
//Getters
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getSSN() {
return SSN;
}
public Car getCar() {
//return a copy of the car object
return (Car) car.clone();
}
public String getAddress() {
return address;
}
}
3.145.105.105