Chapter 11. The Composite Pattern

Programmers often develop systems in which a component may be an individual object or may represent a collection of objects. The Composite pattern is designed to accommodate both cases. You can use it to build part-whole hierarchies or to construct data representations of trees. In summary, a composite is a collection of objects, any one of which may be either a composite or a primitive object. In tree nomenclature, some objects may be nodes with additional branches, and some may be leaves.

A problem that develops is the dichotomy between having a single, simple interface to access all of the objects in a composite and the ability to distinguish between nodes and leaves. Nodes have children and can have children added to them, while leaves do not at the moment have children and in some implementations may be prevented from having children added to them.

Some authors have suggested creating a separate interface for nodes and leaves, where a leaf could have the following methods:

public String getName();
public String getValue();

A node could have the following methods:

public Enumeration elements();
public Node getChild(String nodeName);
public void add(Object obj);
public void remove(Object obj);

This then leaves us with the programming problem of deciding which elements will be which when we construct the composite. However, Design Patterns suggests that each element should have the same interface, whether it is a composite or a primitive element. This is easier to accomplish, but we are left with the question of what the getChild operation should accomplish when the object is actually a leaf.

Java makes this quite easy for us, since every node or leaf can return an Enumeration of the contents of the Vector where the children are stored. If there are no children, the hasMoreElements method returns false at once. Thus if we simply obtain the Enumeration from each element, we can quickly determine whether it has any children by checking the hasMoreElements method.

Just as difficult is the issue of adding or removing leaves from elements of the composite. A nonleaf node can have child leaves added to it, but a leaf node cannot. However, we would like all of the components in the composite to have the same interface. Attempts to add children to a leaf node must not be allowed, and we can design the leaf node class to throw an exception if the program attempts to add to such a node.

An Implementation of a Composite

Let's consider a small company that was started by one person who got the business going. He was, of course, the CEO, although he might have been too busy to think about it at first. Then he hired a couple of people to handle the marketing and manufacturing. Soon each of them hired some assistants to help with advertising, shopping, and so on, and they became the company's first two vice-presidents. As the company's success continued, thefirm continued to grow until it had the organizational chart we see in Figure 11.1.

A typical organizational chart.

Figure 11.1. A typical organizational chart.

Computing Salaries

Now, if the company is successful, each of these company members receives a salary, and we could at any time ask for the cost of the control span of any employee to the company. We define this control span cost as the salary of that person as well as the combined salaries of all of his or her subordinates. Here is an ideal example for a composite:

  • The cost of an individual employee is that employee's salary (and benefits).

  • The cost of an employee who heads a department is that employee's salary plus the salaries of all employees that the employee controls.

We would like a single interface that will produce the salary totals correctly, whether or not the employee has subordinates.

public float getSalaries();

At this point, we realize that the idea of all Composites having the same standard method names in their interfaces is probably naïve. And, in fact, we'd prefer that the public methods be related to the kind of class that we are actually developing. So rather than have generic methods such as getValue, we'll use getSalaries.

The Employee Classes

We could now imagine representing the company as a Composite made up of nodes: managers and employees. We could use a single class to represent all employees, but since each level might have different properties, defining at least two classes might be more useful: Employees and Bosses. Employees are leaf nodes and cannot have employee nodes under them; Bosses are nodes that may have Employee nodes under them.

We'll start with the AbstractEmployee class and derive our concrete employee classes from it.

public abstract class AbstractEmployee {
    protected String       name;
    protected long         salary;
    protected Employee     parent = null;
    protected boolean      leaf = true;

    public abstract long getSalary();
    public abstract String getName();
    public abstract boolean add(Employee e)
        throws NoSuchElementException;
    public abstract void remove(Employee e)
        throws NoSuchElementException;
    public abstract Enumeration subordinates();
    public abstract Employee getChild(String s);
    public abstract long getSalaries();
    public boolean isLeaf() {
        return leaf;
    }
}

Our concrete Employee class stores the name and salary of each employee and allows us to fetch them as needed.

public Employee(String _name, float _salary) {
    name   =    _name;
    salary =    _salary
    leaf   =    true;
}
//--------------
public Employee(Employee _parent, String _name, float _salary) {
    name   =    _name;
    salary =    _salary;
    parent =    _parent;
    leaf   =    true;
}
//--------------
public float getSalary() {
    return salary;
}
//--------------
public String getName() {
    return name;
}

The Employee class must have concrete implementations of the add, remove, getChild, and subordinates methods classes. Since an Employee is a leaf, all of these will return some sort of error indication. For example, subordinates could return null, but programming will be more consistent if it returns an empty Enumeration.

public Enumeration subordinates () {
    return v.elements ();
}

The add and remove methods must generate errors, since members of the basic Employee class cannot have subordinates.

public boolean add(Employee e) throws NoSuchElementException {
    throw new NoSuchElementException("No subordinates");
    }
    //
public void remove(Employee e) throws NoSuchElementException {
    throw new NoSuchElementException("No subordinates");
}

The Boss Class

Our Boss class is a subclass of Employee and allows us to store subordinate employees as well. We'll store them in a Vector called subordinates, which we return through an Enumeration. Thus if a particular Boss has temporarily run out of Employees, the Enumeration will be empty.

public class Boss extends Employee {
    Vector employees;

    public Boss(String _name, long _salary) {
        super(_name, _salary);
        leaf = false;
        employees = new Vector();
    }
    //-------------------
    public Boss(Employee _parent, String _name, long _salary) {
        super(_parent, _name, _salary);
        leaf = false;
        employees = new Vector();
    }
    //-------------------
    public Boss(Employee emp) {
        //promotes an employee position to a Boss
        //and thus allows it to have employees
        super(emp.getName (), emp.getSalary());
        employees = new Vector();
        leaf = false;
    }
    //-------------------
    public boolean add(Employee e) throws NoSuchElementException {
        employee.add(e);
        return true;
    }
    //-------------------
    public void remove(Employee e) throws NoSuchElementException {
        employees.removeElement(e);
    }
    //-------------------
    public Enumeration subordinates () {
        return employees.elements ();
    }

If you want to get a list of employees of a given supervisor, you can obtain an Enumeration of them directly from the subordinates Vector. Similarly, you canuse this same Vector to return a sum of salaries for any employee and his or her subordinates.

public long getSalaries() {
        long sum = salary;
        for (int i = 0; i < employees.size(); i++) {
        sum +=
            ((Employee)employees.elementAt(i)).getSalaries();
    }
    return sum;
}

Note that this method starts with the salary of the current Employee and then calls the getSalaries method on each subordinate. This is, of course, recursive, and any employees who themselves have subordinates will be included. A diagram of these classes in shown in Figure 11.2.

The AbstractEmployee class and how Employee and Boss are derived from it.

Figure 11.2. The AbstractEmployee class and how Employee and Boss are derived from it.

Building the Employee Tree

We start by creating a CEO Employee and then add that employee's subordinates and their subordinates as follows:

private void makeEmployees() {
    prez = new Boss("CEO", 200000);
    prez.add(marketVP = new Boss("Marketing VP", 100000));
    prez.add(prodVP = new Boss("Production VP", 100000));

    marketVP.add(salesMgr = new Boss("Sales Mgr", 50000));
    marketVP.add(advMgr = new Boss("Advt Mgr", 50000));
    //add salesmen reporting to sales manager
    for (int i = 0; i < 5; i++)
        salesMgr .add(new Employee("Sales "+ i,
                rand_sal(30000)));
    advMgr.add(new Employee("Secy", 20000));

    prodVP.add(prodMgr = new Box("Prod Mgr", 40000));
    prodVP.add(shipMgr = new Boss("Ship Mgr", 35000));
    //add manufacturing staff
    for (int i = 0; i < 4; i++)
        prodMgr.add( new Employee("Manuf "+i,
                rand_sal(25000)));
    //add shipping clerks
    for (int i = 0; i < 3; i++)
        shipMgr.add( new Employee("ShipClrk "+i,
                rand_sal(20000)));
    }

Once we have constructed this Composite structure, we can load a visual JTree list by starting at the top node and calling the addNode method recursively until all of the leaves in each node are accessed.

private void addNodes(DefaultMutableTreeNode pnode, Employee emp)
{
        DefaultMutableTreeNode node;

        Enumeration e = emp.subordinates();
        if (e != null) {
            while (e.hasMoreElements()) {
                Employee newEmp = (Employee)e.nextElement();
                node = new
                    DefaultMutableTreeNode(newEmp.getName());
                pnode.add(node);
                addNodes(node, newEmp);
            }
        }
   }

The final program display is shown in Figure 11.3.

The corporate organization shown in a TreeList control.

Figure 11.3. The corporate organization shown in a TreeList control.

In this implementation, the cost (sum of salaries) is shown in the bottom bar for any employee you click on. This simple computation calls the getChild method recursively to obtain all of the subordinates of that employee.

public void valueChanged(TreeSelectionEvent evt) {
        TreePath path = evt.getPath();
        String selectedTerm =
            path.getLastPathComponent().toString();
        Employee emp = prez.getChild(selectedTerm);
        if (emp != null)
            cost.setText(new Float(emp.getSalaries()).toString());
    }

Self-Promotion

Sometimes, an Employee will stay in a current job but acquire new subordinates. For example, a Salesman might be asked to supervise sales trainees. For such a case, we can conveniently provide a Boss constructor that creates a Boss from an Employee.

public Boss(Employee emp) {
    //promotes an employee position to a Boss
    //and thus allows it to have employees
    super(emp.getName (), emp.getSalary());
    employees = new Vector();
    leaf = false;
}

The problem of replacing the Employee instance with the Boss instance means replacing a Vector element with a new one using the setElementAt method.

Doubly Linked List

In the previous implementation, we keep a reference to each subordinate in the Vector in each Boss class. This means that you can move down the chain from the CEO to any employee, but that there is no way to move back up to identify an employee's supervisor. This is easily remedied by providing a constructor for each AbstractEmployee subclass that includes a reference to the parent node.

public Employee(Employee _parent, String _name,
        float _salary) {
    name   = _name;
    salary = _salary;
    parent = _parent; //save parent node
    leaf   = true;
}

Then you can quickly walk up the tree to produce a reporting chain.

list.add (emp.getName ()); //employee name
while(emp.getParent () != null) {
    emp = emp.getParent (); //get boss
    list.add (emp.getName ());
}

This is shown in Figure 11.4.

The tree list display of the composite, with a display of the parent nodes on the right.

Figure 11.4. The tree list display of the composite, with a display of the parent nodes on the right.

Consequences of the Composite Pattern

The Composite pattern allows you to define a class of hierarchy of simple objects and more-complex composite objects so that they appear to be the same to the client program. Because of this simplicity, the client can be that much simpler, since nodes and leaves are handled in the same way.

The Composite pattern also makes it easy for you to add new kinds of components to your collection, as long as they support a similar programming interface. On the other hand, this has the disadvantage of making your system overly general. You might find it harder to restrict certain classes, where this would normally be desirable.

A Simple Composite

The intent of the Composite pattern is to allow you to construct a tree of various related classes, even though some have different properties than others and some are leaves that do not have children. However, for very simple cases you can sometimes use a single class that exhibits both parent and leaf behavior. In the SimpleComposite example, we create an Employee class that always contains the Vector employees. This Vector of employees will either be empty or be populated, and this determines the nature of the values that are returned from the getChild and remove methods. In this simple case, you do not throw exceptions, and you always allow leaf nodes to be promoted to have child nodes. In other words, you always allow the execution of the add method.

While you might not regard this automatic promotion as a disadvantage, in a system that has a very large number of leaves, keeping a Vector initialized and unused in each leaf node is wasteful. When there are relatively few leaf nodes, this is not a serious problem.

Composites in Java

In Java, you will note that the DefaultMutableTreeNode class we use to populate the JList pane is, in fact, just such a simple composite pattern. You will also find that the Composite describes the hierarchy of JFrame, JPanel, and JComponents in any user interface program. JFrames, Jtoolbars, and JPanels are all containers, and each may contain any number of other containers.

Any container may then contain components such as JButtons, JCheckboxes, and JTextPanels, each of which is a leaf node that cannot have further children. They may also contain JLists and JTables that may be treated as leaf nodes or that may contain further graphical components. You can walk down the Composite tree using the getComponent method and up the Composite tree using the getParent method.

Other Implementation Issues

Other composite implementation issues you might consider are

  • Ordering of Components. . In some programs, the order of the components may be important. If that order differs somehow from the order in which they were added to the parent, then the parent must doadditional work to return them in the correct order. For example, you might sort the original Vector alphabetically and return the Enumerator to a new sorted Vector.

  • Caching results. . If you often ask for data that must be computed froma series of child components, as was done earlier with salaries, itmight be advantageous to cache these computed results in the parent.However, unless the computation is relatively intensive and youare quite certain that the underlying data have not changed, doingthis might not be worth the effort.

Thought Questions

  1. A baseball team can be considered an aggregate of its individual players. How could you use a composite to represent individual and team performance?

  2. The produce department of a supermarket needs to track its sales performance by food item. Suggest how a composite might be helpful in accomplishing this.

Programs on the CD-ROM

Programs Description

CompositeStdCompositeempTree.java

The Composite pattern using the Boss and Employee classes.

CompositeparentCompositepempTree.java

The Composite pattern using a doubly linked list to show the reporting chain.

CompositeSimpleCompositeempTree.java

The SimpleComposite using just the Employee class.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset