Design Pattern: Whole-Part
Introduction
Sometimes called Composite
Helps with the aggregation of components
(parts) that together form a semantic unit (whole).
Direct access to the Parts is not
possible
- Compose objects into tree structures to represent
part-whole hierarchies.
- Whole-Part lets clients treat individual objects and
compositions of object uniformly
- Supported in OO Programming Language
- Whole-Part is part of why OOP design the way it is
- Remember to read the text book
Example
- Graphics applications, like drawing editors, let users
build complex diagrams out of simple components. For
example, a graph might contain lines, rectangles, texts,
pictures
- The user can group components to form larger components,
which in turn can be grouped to form still larger
components
- A simple implementation could define classes for
graphical primitives such as Text and Lines plus other
classes that act as containers for these primitives
- Computer-Aided Design (CAD)
Context
Problem
- A complex object should either be decomposed into smaller
objects or composed of existing objects, to support
reusability, changeability and the recombination of the
constituent objects in other types of aggregate
- Clients should see the aggregate object as an atomic
object that does not allow any direct access to its
constituent parts
- Code that uses these classes must treat primitive (Text,
Lines, etc.) and container (Graphic) objects differently
- Having to distinguish these objects makes the application
more complex.
Solution
- Use a component that encapsulates smaller objects, and
prevents clients from accessing these constituent parts
directly
- Use an interface as the only means of access to the
functionality of the encapsulated objects (appear as a
semantic unit)
- An assembly-parts relationship differentiates between a
product and its parts or subassemblies
- A container-contents relationship
- A collection-members relationship helps to group similar
objects - such as an organization and its members
Structure
Class: Whole Responsibility:
- Aggregates several smaller objects
- Provides services built on top of part objects
- Acts as a wrapper around its constituent parts
Collaborators: Part
|
Class: Part Responsibility:
- Pepresents a particular object and its services
Collaborators: -
|
- Whole object: An aggregation of smaller objects
- The smaller objects are called Parts
- Whole object forms a semantic grouping of its Parts in
that it coordinates and organizes their collaboration
- The Whole uses the functionality of Parts objects for
implementing services
- Some of the Whole methods are just placeholders for
specific Part services. When that kind of method is
invoked, the Whole calls the relevant Parts service, and
returns the result to the client
- Use different relationships and further
"divide" Whole-Part, the following
"components" can be inferred
Component
Declares the interface for objects in the Whole.
Implements default behavior for the interface common
to all classes, as appropriate.
Declares an interface for accessing and managing its
child components
Defines an interface for accessing a components
parent in the recursive structure, and implements it if
that is appropriate.
Leaf
Represents leaf objects in the Whole. A leaf has no
children.
Defines behavior for primitive objects in the Whole.
Composite
Defines behavior for components having children.
Stores child components.
Implements child-related operations in the Component
interface.
Client
Manipulates objects in the Whole through the Whole
interface.
Implementation
Using Graphic as example
- An abstract class that represents both primitives and
their containers. Here, this class is Graphic
- It declares operations, like Draw() here, that are
specific to the object family
- It also declares operations that all composite object
share, such operations for accessing and managing its
children, like Add(Graphic) here
- The subclasses define primitive objects, like Line,
Rectangle, and Text here.
- Each primitive object implements operations, for example,
class Line implements Draw() to draw lines.
- Primitive objects have no child objects, none of these
subclasses implements child-related operations, for
example, class Line does not implement child-related
operation Add(Graphic)
- The Picture class defines an aggregate of Graphic objects
- Picture implements Draw() to call Draw() on its children,
and it implements child-related operations accordingly
- Because the Picture interface conforms to the Graphic
interface, Picture objects can compose other Pictures
recursively.
In General
- Design the public Interface of the Whole
- Separate the Whole into Parts, or make it from existing
ones (Use either bottom-up, top-down or both mixed)
- Use existing Parts from component libraries or class
libraries or package, specify their collaboration if you
use bottom-up approach
- Partition the Whole's services into smaller collaborating
services and map these collaborating services to separate
Parts if you follow a top-down approach
- Specific the services of the Whole in terms of services
of the Parts
- Implement the Parts (Recursively if Parts are not leaf)
- Implement the Whole by putting all the Parts together
Cleaning Up
Explicit parent references
Simplify the traversal and management of a composite
structure
Simplify moving up the structure and deleting a
component
Maximizing the Component interface
The Component class should define as many common
operations for Composite and Leaf classes as possible
Do we conflict the principle of class hierarchy design
Principle: a class should only define operations
that are meaningful to its subclass.
There are many operations that Component supports
that do not seem to make sense for Leaf classes
Trade off between principle and pattern
Using abstract class ... but do not overuse it
Use interface for "multiple parents"
Declaring the Part management operations
Should we declare these operations in the Whole and
make them meaningful for Part/Leaf classes?
Should we declare and define them only in Composite
and its subclasses?
What is the trade-off between safety and transparency?
- Defining the child management interface at the root of
the class hierarchy gives you transparency, because you
can treat all components uniformly. It costs you safety,
however, because clients may try to do meaningless things
like add and remove objects from leaves
- Defining child management in the Composite class gives
you safety, because any attempt to add or remove objects
from leaves will be caught at compile-time in a
statically typed language. But you lost transparency,
because leaves and composites have different interface.
Sample Code
class Equipment
{
public Equipment(String);
public String name() { return name; }
public abstract Watt Power();
public abstract Currency NetPrice();
public abstract Currency DiscountPrice();
public abstract void Add(Equipment*);
public abstract void Remove(Equipment*);
public abstract Equipment* CreateIterator();
private String name;
};
- Equipment such as computer and stereo components are
often organized into part-whole or containment hierarchies
- Equipment class defines an interface fro all equipment in
the part-whole hierarchy.
class FlppyDisk extends Equipment
{
public FloppyDisk(String);
public abstract Watt Power();
public abstract Currency NetPrice();
public abstract Currency DiscountPrice();
};
- Subclasses of Equipment might include Leaf classes that
represent disk drives, integrated circuits, and switches.
class CompositeEquipment extends Equipment
{
CompositeEquipment(String);
public abstract Watt Power();
public abstract Currency NetPrice();
public abstract Currency DiscountPrice();
public abstract void Add(Equipment*);
public abstract void Remove(Equipment*);
public abstract Equipment* CreateIterator();
};
- CompositeEquipment is the base class for equipment that
contains other equipment
- It is a subclass of Equipment
class Chassis : public CompositeEquipment
{
public:
Chassis(const char*);
virtual ~Chassis();
virtual Watt Power();
virtual Currency NetPrice();
virtual Currency DiscountPrice();
};
Applicability
- Use the Whole-Part pattern when:
- You want to represent part-whole hierarchies of objects
- You want clients to be able to ignore the difference
between compositions of objects and individual objects.
Clients will treat all objects in the composite structure
uniformly.
Consequences
Advantages
Changeability of Parts
Whole encapsulates the Parts and thus conceals them from
its client
Separation of concerns
Each concern is implemented by a separate Part
Reusability
Parts of Whole can resued in other aggregate objects
Defines class hierarchies consisting of primitive objects and
composite objects.
Primitive objects can be composed into more complex
objects, which in turn can be composed, and so on
recursively.
Makes the client simple.
Clients can treat composite structures and individual
objects uniformly.
Makes it easier to add new kinds of components.
Newly defined Composite or Leaf subclasses work
automatically with existing structures and client code.
Disadvantages
Makes your design overly general
The disadvantage of making it easy to add new components
is that it makes it harder to restrict the components of a
composite. For example, if operations are provided in Parts
... how can we prevent the make of some non-sense object
Lower efficiency through indirection
Wrappers are wrapper. You call somthing, something calls
another thing and so on ... slow in some case
Complexity of decomposition into Parts might be an art
Depends on bottom-up, top-down, domain experts, technology
used, etc