Introduction
In our last lesson, we explored polymorphism and the principle of "programming to an interface." You saw how depending on abstractions rather than concrete classes makes your code flexible and loosely coupled, as demonstrated with the Computer interface and the Strategy pattern. Today, we'll build directly on that foundation by examining the two primary ways to establish relationships between classes and reuse code: inheritance and composition.
This lesson addresses the critical skill of choosing the right tool for the job. You will learn to differentiate between the "is-a" relationship of inheritance and the "has-a" relationship of composition. More importantly, we'll delve into why modern object-oriented design strongly advises to "favor composition over inheritance." Understanding this principle is fundamental to creating robust, maintainable, and scalable software—the very goal of learning design patterns.
Defining the Relationships: "Is-A" vs. "Has-A"
At their core, both inheritance and composition are mechanisms for code reuse, but they model fundamentally different relationships.
Inheritance: The "Is-A" Relationship
Inheritance models a parent-child or "is-a" relationship. A subclass extends a superclass, inheriting its public and protected fields and methods. This creates a strong, tightly-coupled hierarchy.
A classic example is a Student which is a Person. A Student has all the properties of a Person (like a name and address) and adds its own specific properties (like courses and grades).
- Subclass: The class that inherits (e.g.,
Student). Also called a child or derived class. - Superclass: The class being inherited from (e.g.,
Person). Also called a parent or base class.
In Java, this is done with the extends keyword: public class Student extends Person { ... }.
Composition: The "Has-A" Relationship
Composition models a "has-a" relationship. Instead of becoming a specialized version of another class, a class is composed of other classes by holding references to them as instance variables. This is a more flexible way to build complex objects from simpler, independent parts.
For instance, a Book has an Author. The Book class contains an instance variable of type Author. The Book is not a type of Author; it simply uses an Author object to fulfill its responsibilities.
To see this in code, let's examine a well-structured example. The following resource provides the Java code for both an Author class and a Book class that is composed with it.
OOP Inheritance & Polymorphism - Java Programming Tutorial
This tutorial from NTU Singapore clearly demonstrates the "has-a" relationship with a practical code example.
Please read the first section on Composition, focusing on the Author and Book classes. Start at the top and review the simple Author class. Then, examine the Book class implementation. Pay close attention to how it declares a private field private Author author; and uses it in its constructor and methods. This is the essence of composition.
As you can see from the code, the Book class contains an Author object. It delegates tasks like getting the author's name to the Author object itself (author.getName()). The Book and Author remain two distinct, independent classes.
A Head-to-Head Comparison
The difference becomes clearest when you try to model the same concept using both approaches. Let's consider a Line. Does it make more sense to say "a line is a point extended by another point" (inheritance) or "a line has two points" (composition)?
The same tutorial you just read provides an excellent side-by-side comparison.
OOP Inheritance & Polymorphism - Java Programming Tutorial
This part of the tutorial directly contrasts the two approaches for the same problem.
Please navigate to the section titled Composition vs. Inheritance. Read the short introductory part, "A line is composed of 2 points" vs. "A line is a point extended by another point". Skim the LineSub.java code, which implements a line using inheritance. Notice how awkward it feels. It inherits one point's coordinates and has to hold a separate Point object for the end. Finally, read the "Rule of Thumb" and the definitions in the next subsection. This is the central heuristic you should take away.
The "is-a" vs. "has-a" test is your primary guide. If you can say class B is a more specific type of class A, inheritance might be appropriate. If it makes more sense to say class A has a class B as part of its state, you should use composition. As the Line example shows, forcing an "is-a" relationship where it doesn't naturally fit leads to confusing and awkward designs.
Why "Favor Composition Over Inheritance"
Knowing the difference is the first step. The next, and more crucial step for building good software, is understanding why composition is generally preferred. The main reasons are flexibility and maintainability.
Inheritance creates a very tight coupling between classes. A change in the superclass can have unintended and cascading effects on all of its subclasses. This is often called the Fragile Base Class Problem.
The following video from the CodeAesthetic channel provides a brilliant, practical demonstration of this problem and how composition solves it.
This video uses a clear, step-by-step refactoring of an image processing library to illustrate the flaws of inheritance and the flexibility of composition combined with interfaces.
Watch the video from the beginning, focusing on these key transformations: The Initial Inheritance Design: The video starts by modeling different image file types (JpgImage, PngImage) using inheritance. Notice how this design breaks down when a new, unrelated requirement (DrawableImage) is introduced, forcing the subclass to implement methods that don't make sense for it. Refactoring to Composition: See how the author dismantles the inheritance hierarchy. The Image class becomes a simple data holder, and functionality like loading/saving and drawing are moved into separate, composable utility classes. Reintroducing Abstraction with Interfaces: This is the key link to our last lesson. To regain polymorphism, the author introduces an ImageFile interface. This allows the code to work with any object that can be loaded and saved, without being tied to a rigid class hierarchy. This is loose coupling in action.
As the video powerfully illustrates, composition allows you to build functionality by combining small, focused, and independent components. This is far more flexible than inheriting from a large, monolithic base class. You can mix and match components as needed at runtime.
Visualizing the Concepts
Let's consolidate these ideas with a couple of diagrams. The first is a mind map that summarizes the decision-making process.

The second diagram shows a design that uses both concepts correctly, reinforcing what you've learned.

This Animal example is a perfect illustration of the Strategy pattern we touched on in the last lesson, built using composition. The Animal class is the "Context," the IAction is the "Strategy," and Barker/Meower are the "Concrete Strategies."
Conclusion
In this lesson, we dissected the two fundamental relationships in object-oriented programming: inheritance and composition. You've learned not only how they differ but, more critically, why one is often a better choice for building flexible and maintainable systems.
Here are your key takeaways:
- Inheritance models an "is-a" relationship. It creates a tight coupling between a superclass and its subclasses, which can make the code rigid and hard to change (the "fragile base class problem").
- Composition models a "has-a" relationship. It promotes loose coupling by building complex objects from smaller, independent parts. This approach is more flexible and scalable.
- The guiding principle is "Favor composition over inheritance." Use inheritance sparingly, and only for clear, stable "is-a" hierarchies where the superclass is explicitly designed for extension.
- Composition, when combined with programming to an interface, allows for powerful and dynamic behavior, as seen in patterns like the Strategy pattern.
In our next lesson, we will explore Java generics. As you've seen, building reusable components is a core tenet of good design. Generics take this one step further, allowing you to write classes and methods that are type-safe yet can operate on a variety of data types. This is essential for creating the kind of flexible, composed objects we've discussed today.