Summary


Composition-Based Design As A Force Multiplier

Good compositional design has its foundations in the thorough understanding of inheritance, interfaces, and polymorphism. Compositional design acts as a force multiplier in that it combines the power of all these design techniques. In this regard you should never be forced to chose composition over inheritance, but rather, you should apply composition in a way that compliments inheritance, considers the use of interfaces, and keeps the goal of polymorphic behavior in mind from the very beginning.

Two Types Of Aggregation

An object is an aggregate if it contains and uses the services of other objects. An aggregate object consists of itself (the whole) and the objects it contains (its parts).

Recall from chapter 10 that there are two types of aggregation: 1) simple aggregation, and 2) composite aggregation. Simple aggregation occurs when the whole object does not control the lifetime of its part objects. Conversely, a composite aggregate has complete control over the lifetime of its part objects.

Polymorphic Containment

An aggregate object is dependent upon the behavior of its part objects. Complex aggregates may comprise many different types of part objects, each providing specialized behavior. A careful consideration of polymorphic behavior can offer a uniform treatment of these differing part types. This can be achieved via polymorphic containment where the whole class targets the interface(s) of its part class(es), treats its part objects as a collection of parts, and obtains its part objects from a part object factory.

In some cases polymorphic containment may not be strictly necessary. An example of this would be when the concrete part class is considered fairly stable design-wise, meaning there is a low probability that its interface will change during the application’s maintenance lifetime. This is an example of an engineering trade-off.

An Extended Example

Considering all that’s been said about inheritance, interfaces, polymorphic behavior, and compositional design, I’d like to present a different approach to the Person-Employee example. Figure 22-3 gives the class diagram for the complete application.

image from book
Figure 22-3: Revised Person - Employee Example

Referring to figure 22-3 — the IEmployee interface serves as the interface specification for employee objects. The Employee class no longer inherits from Person. Instead, the Employee class implements the IEmployee interface and contains a Person object by value. The rationale for this design decision might be as follows: a company has a number of employee positions available. These positions are filled by people. In most corporations, there is a permanent association between an employee position, signified by an employee number, and the person who fills the position. (If you leave the company and return you are usually assigned the same employee number.)

Having the Employee class contain the Person class breaks the inheritance relationship between Person and Employee, and, as you learned earlier, based on Coad’s Criteria, this inheritance relationship was invalid from the start. The revised Employee - HourlyEmployee and Employee - SalariedEmployee inheritance hierarchy models dominant roles within the company (and within the app) that are unlikely to change. For instance, there will always be a clear distinction between hourly employees and salaried employees.

The MainTestApp class has dependencies upon the IEmployee interface, the IEmployeeFactory interface, the EmployeeFactory class, and the PayInfo class. The EmployeeFactory has a dependency upon the HourlyEmployee and SalariedEmployee classes, but limits this dependency to their constructors. The HourlyEmployee and SalariedEmployee classes each depend upon the PayInfo class. However, this dependency is not necessarily bad in that it is unlikely that the PayInfo class will undergo future change.

There is a simple association between the IEmployeeFactory interface and the IEmployee interface as well as between the PayInfo class, IEmployee, and the classes in the Employee inheritance hierarchy, but they have been omitted from the diagram for clarity. Examples 22.1 through 22.9 provide the code for this application.

Example 22.1: IEmployee.java

image from book
 1     public interface IEmployee { 2           int getAge(); 3        String getFullName(); 4        String getNameAndAge(); 5        String getFirstName(); 6        String getMiddleName(); 7        String getLastName(); 8        String getGender(); 9        String getEmployeeNumber(); 10         void setBirthday(int year, int month, int day); 11         void setFirstName(String f_name); 12         void setMiddleName(String m_name); 13         void setLastName(String l_name); 14         void setGender(String gender); 15         void setEmployeeNumber(String emp_no); 16         void setPayInfo(PayInfo pi); 17       double getPay(); 18       String toString(); 19    }
image from book

Example 22.2: Employee.java

image from book
 1     public abstract class Employee implements IEmployee { 2        private Person _person = null; 3        private String    _employee_number = null; 4 5        protected Employee(String f_name, String m_name, String l_name, int dob_year, int dob_month, 6                        int dob_day, String gender, String employee_number){ 7           _person = new Person(f_name, m_name, l_name, dob_year, dob_month, dob_day, gender); 8           _employee_number = employee_number; 9         } // end constructor 10 11        public int getAge() { return _person.getAge(); } 12        public String getFullName() { return _person.getFullName(); } 13        public String getNameAndAge() { return _person.getNameAndAge(); } 14        public String getFirstName() { return _person.getFirstName(); } 15        public String getMiddleName() { return _person.getMiddleName(); } 16        public String getLastName() { return _person.getLastName(); } 17        public String getGender() { return _person.getGender(); } 18        public String getEmployeeNumber() { return _employee_number; } 19        public void setBirthday(int year, int month, int day) { _person.setBirthday(year, month, day); } 20        public void setFirstName(String f_name) { _person.setFirstName(f_name); } 21        public void setMiddleName(String m_name) { _person.setMiddleName(m_name); } 22        public void setLastName(String l_name) { _person.setLastName(l_name); } 23        public void setGender(String gender) { _person.setGender(gender); } 24        public void setEmployeeNumber(String emp_no) { _employee_number = emp_no; } 25        public String toString(){ return _person.getNameAndAge() + " " + _employee_number; } 26        public abstract void setPayInfo(PayInfo pi); // defer implementation 27        public abstract double getPay(); // defer implementation 28 29    }  // end Employee class definition
image from book

Example 22.3: Person.java

image from book
 1     import java.util.*; 2 3     public class Person { 4       private String first_name = null; 5       private String middle_name = null; 6       private String last_name = null; 7       private Calendar birthday = null; 8       private String gender = null; 9 10      public static final String MALE = "Male"; 11      public static final String FEMALE = "Female"; 12 13      public Person(String f_name, String m_name, String l_name, int dob_year, 14                    int dob_month, int dob_day, String gender){ 15        first_name = f_name; 16        middle_name = m_name; 17        last_name = l_name; 18        this.gender = gender; 19 20        birthday = Calendar.getInstance(); 21        birthday.set(dob_year, dob_month, dob_day); 22      } 23 24      public int getAge(){ 25        Calendar today = Calendar.getInstance(); 26        int now = today.get(Calendar.YEAR); 27        int then = birthday.get(Calendar.YEAR); 28        return (now - then); 29      } 30 31      public String getFullName(){ return (first_name + " " + middle_name + " " + last_name); } 32 33      public String getFirstName(){ return first_name; } 34      public void setFirstName(String f_name) { first_name = f_name; } 35 36      public String getMiddleName(){ return middle_name; } 37      public void setMiddleName(String m_name){ middle_name = m_name; } 38 39      public String getLastName(){ return last_name; } 40      public void setLastName(String l_name){ last_name = l_name; } 41 42      public String getNameAndAge(){ return (getFullName() + " " + getAge()); } 43 44      public String getGender(){ return gender; } 45      public void setGender(String gender){ this.gender = gender; } 46 47      public void setBirthday(int year, int month, int day){ birthday.set(year, month, day); } 48 49     } //end Person class definition
image from book

Example 22.4: HourlyEmployee.java

image from book
 1     public class HourlyEmployee extends Employee { 2       private double _hours_worked = 0; 3       private double _hourly_rate = 0.0; 4 5       public HourlyEmployee(String f_name, String m_name, String l_name, int dob_year, int dob_month, 6                             int dob_day, String gender, String employee_number){ 7         super(f_name, m_name, l_name, dob_year, dob_month, dob_day, gender, employee_number); 8       } 9 10       public void setPayInfo(PayInfo pi){ 11           _hours_worked = pi.getHoursWorked(); 12           _hourly_rate = pi.getHourlyRate(); 13       } 14 15       public double getPay() { return _hours_worked * _hourly_rate; } 16 17       public String toString() { return super.toString() + "   $" + getPay(); } 18 19    } // end HourlyEmployee class definition
image from book

Example 22.5: SalariedEmployee.java

image from book
 1     public class SalariedEmployee extends Employee { 2        private double _salary = 0; 3 4        public SalariedEmployee(String f_name, String m_name, String l_name, int dob_year, int dob_month, 5                              int dob_day, String gender, String employee_number){ 6         super(f_name, m_name, l_name, dob_year, dob_month, dob_day, gender, employee_number); 7       } 8 9        public void setPayInfo(PayInfo pi){ 10           _salary = pi.getSalary(); 11       } 12 13       public double getPay() { return (_salary/12.0)/2.0; } 14 15       public String toString() { return super.toString() + " $" + getPay(); } 16    } // end SalariedEmployee class definition
image from book

Example 22.6: PayInfo.java

image from book
 1     public class PayInfo { 2       private double _salary = 0; 3       private double _hours_worked = 0; 4       private double _hourly_rate = 0; 5 6       public PayInfo(double salary){ 7          _salary = salary; 8       } 9 10      public PayInfo(double hours_worked, double hourly_rate){ 11         _hours_worked = hours_worked; 12         _hourly_rate = hourly_rate; 13      } 14 15      public PayInfo(){ } 16 17      public double getHoursWorked(){ return _hours_worked; } 18      public double getHourlyRate(){ return _hourly_rate; } 19      public double getSalary(){ return _salary; } 20 21    } // end PayInfo class definition
image from book

Example 22.7: IEmployeeFactory.java

image from book
 1     public interface IEmployeeFactory { 2       IEmployee getNewSalariedEmployee(String f_name, String m_name, String l_name, int dob_year, 3                              int dob_month, int dob_day, String gender, String employee_number); 4       IEmployee getNewHourlyEmployee(String f_name, String m_name, String l_name, int dob_year, 5                              int dob_month, int dob_day, String gender, String employee_number); 6     } // end IEmployeeFactory interface definition
image from book

Example 22.8: EmployeeFactory.java

image from book
 1     public class EmployeeFactory implements IEmployeeFactory { 2 3 4       public IEmployee getNewSalariedEmployee(String f_name, String m_name, String l_name, int dob_year, 5                              int dob_month, int dob_day, String gender, 6                              String employee_number){ 7            return new SalariedEmployee(f_name, m_name, l_name, dob_year, dob_month, dob_day, gender, 8                              employee_number); 9        } 10 11      public IEmployee getNewHourlyEmployee(String f_name, String m_name, String l_name, int dob_year, 12                              int dob_month, int dob_day, String gender, 13                              String employee_number){ 14           return new HourlyEmployee(f_name, m_name, l_name, dob_year, dob_month,   dob_day, gender, 15                              employee_number); 16 17       } 18 19 20    } // end EmployeeFactory class definition
image from book

Example 22.9: MainTestApp.java

image from book
 1     public class MainTestApp { 2 3       private IEmployeeFactory _employee_factory = null; 4       private IEmployee[] _employee_array = null; 5 6 7       public MainTestApp(){ 8         _employee_factory = new EmployeeFactory(); 9         _employee_array = new IEmployee[2]; 10 11      } 12 13      public void createEmployees(){ 14        _employee_array[0] = _employee_factory.getNewSalariedEmployee("Rick", "W", "Miller", 1977, 03, 15                                               04, "Male", "12345"); 16        _employee_array[0].setPayInfo(new PayInfo(123000.00)); 17        _employee_array[1] = _employee_factory.getNewHourlyEmployee("Laura", "Jean", "Richter", 1980, 05, 18                                             06, "Female", "16273"); 19        _employee_array[1].setPayInfo(new PayInfo(40, 45.00)); 20      } 21 22      public void showEmployeeInformation(){ 23         for(int i = 0; i < _employee_array.length; i++){ 24           System.out.println(_employee_array[i]); 25         } 26      } 27 28      public static void main(String[] args){ 29        MainTestApp mta = new MainTestApp(); 30        mta.createEmployees(); 31        mta.showEmployeeInformation(); 32      } 33 34    } // end MainTestApp class definition
image from book

Referring to example 22.9 — the MainTestApp has two private fields: one of type IEmployeeFactory and the other an array of type IEmployee. The constructor method on line 7 initializes the _employee_factory reference to point to an EmployeeFactory instance. The constructor also initializes the _employee_array reference, giving it a length of two.

The createEmployees() method beginning on line 13 uses the _employee_factory reference to create an HourlyEmployee and a SalariedEmployee, assigning the reference to each IEmployee object to an element of the _employee_array. (Remember, the EmployeeFactory returns references to objects that implement the IEmployee inter face.) The createEmployees() method then sets each employee’s pay information by calling the setPayInfo() method with a new PayInfo object as an argument.

The showEmployeeInformation() method beginning on line 22 simply steps through the _employee_array and prints information about each employee to the console.

The main() method starts on line 28 and creates an instance of the MainTestApp class followed by a call to the createEmployees() and showEmployeeInformation() methods respectively. Figure 22-4 shows the results of running this program.

image from book
Figure 22-4: Results of Running Example 22.9

Quick Review

Good compositional design has its foundations in the thorough understanding of inheritance, interfaces, and polymorphism. Compositional design acts as a force multiplier in that it combines the power of all these design techniques. In this regard you should never be forced to chose composition over inheritance, but rather, you should apply composition in a way that compliments inheritance, considers the use of interfaces, and keeps the goal of polymorphic behavior in mind from the very beginning.

An object is an aggregate if it contains and uses the services of other objects. An aggregate object consists of itself (the whole) and the objects it contains (its parts).

There are two types of aggregation: 1) simple aggregation, and 2) composite aggregation. Simple aggregation occurs when the whole object does not control the lifetime of its part objects. Conversely, a composite aggregate has complete control over the lifetime of its part objects.

Complex aggregates may comprise many different types of part objects, each providing specialized behavior. A careful consideration of polymorphic behavior can offer a uniform treatment of these differing part types. This can be achieved via polymorphic containment where the whole class targets the interface(s) of its part class(es), treats its part objects as a collection of parts, and obtains its part objects from a part object factory.




Java For Artists(c) The Art, Philosophy, and Science of Object-Oriented Programming
Java For Artists: The Art, Philosophy, And Science Of Object-Oriented Programming
ISBN: 1932504052
EAN: 2147483647
Year: 2007
Pages: 452

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net