Understanding Composition
Introduction to Association
Association describes the relationship between classes. Two main types of relationships exist in object-oriented programming: inheritance and association. You can further divide association into composition and aggregation. While inheritance creates an “is-a” relationship, association creates a “has-a” relationship. You use a straight line with an optional arrow to draw the UML representation for an association. Below is an example:
Understanding Composition
Composition refers to the items and constituents that make up those items. For example, a house is composed of rooms; and a person is made up of a heart, lungs, and a lot more. While some constituents can exist without their host in certain instances, such as a single room built outside a house, it’s not the case for a person’s heart, as the person needs it to survive.
In OOP, composition defines a “part-of” relationship between the parent object and its constituent objects. This implies that the constituent objects can’t exist without the host object. One object “owns” the other, and the composed object’s lifetime depends on the containing object’s lifetime. Composed objects cease to exist when the containing object is destroyed.
In UML terms, you use a filled diamond to identify the composite class at the end of the line that establishes the relationship.
Using Composition
Composition has many uses in programming. Some data structures, like trees and nodes, are implemented via composition. When the root of a tree is deleted, its leaves or branches go along with it. The diagram below shows an example where you can remove node 4 without affecting any other node. However, if you remove node 2, node 4 will be orphaned and, therefore, must also be removed.
In a desktop app, for instance, you may have some buttons for minimizing, maximizing, and closing a window. You may also have a section of the window that displays an image. When the window is closed, all these elements will have to close along with it. It’s not a good design to have the minimize button and image display panel objects live in memory when the main window has been closed.
Consider the example of a ViewGroup in Android. A ViewGroup is a special View that can contain multiple child views like TextView, Button, etc. Layout containers like LinearLayout and RelativeLayout all extend the ViewGroup. The lifecycle of child views is strongly related to the ViewGroup. If the ViewGroup is destroyed, the children inside it will also cease to exist.
Benefiting From the Use of Association
Composition provides a way to create complex objects from simple ones. However, in terms of relationships, it’s a stricter form of association. While it may be less flexible, it provides the guarantee that orphaned objects can’t exist. If your program has no use for a room without a house, composition will ensure that getting rid of your house gets rid of all the rooms in the house as well.
Association is more flexible than other forms of relationships like inheritance. Composite classes can evolve independently of their owners without any side effects. This isn’t true for inheritance. With inheritance, child classes are modified implicitly when the parent class is updated. This can lead to bugs and unexpected behavior for other objects that interact with the child’s class.
By composing an object into another, you enforce separation of concerns, which is one of the SOLID principles. Loose coupling is good for building scalable programs with minimal fuss. This independence also increases maintainability. The separation of concerns promotes encapsulation, where functionalities are provided in separate classes. By so doing, the single responsibility principle is adhered to as well.
Having composite objects isolated from where they’re used also promotes code reuse. Any class that uses composite objects automatically gets the same behavior.
You could use association to provide dynamic capabilities in your app in a clean, object-oriented way. Since the sub-classes are external to the host class, you can use different types of these sub-classes at different times during runtime to provide more capabilities for your app.
Using association can simplify the testing process by letting you isolate and test specific parts of a complex class without having to involve its composite objects. Additionally, you can use association to support multiple inheritance by enabling the use of features from multiple classes without directly inheriting from them.
Understanding the Disadvantages of Association
When building complex classes, having many separate classes from which to build them may increase complexity. This is because the complex classes may have to know how to create the sub-classes and manage any other complexities that come with initializing them.
However, including all the required classes in the host class — or even as a child class — can reduce complexity. This means fewer files, less code and reduced overhead in processing them. Having more files could also impact code organization and hinder readability.
In the following demos, you’ll see how composition works using an example of an e-commerce app.