Loading learning content...
The relationships we've explored so far—association, aggregation, and composition—all describe how objects connect to each other. But there's another fundamental relationship in object-oriented design: the relationship between a general concept and its specific variations.
Consider: A Car is a Vehicle. A SavingsAccount is a BankAccount. A Manager is an Employee. These aren't connections between separate objects—they're statements about what things are. A manager doesn't merely know about an employee; a manager IS a specialized type of employee.
This "is-a" relationship is called inheritance (or generalization in UML terminology), and it's fundamental to object-oriented design. It enables code reuse, polymorphism, and hierarchical classification—but it must be used with care.
By the end of this page, you will understand what inheritance represents in domain modeling, how to draw inheritance using the hollow triangle arrow notation, the difference between single and multiple inheritance, when to use inheritance versus other relationships, and common inheritance hierarchies in software design.
Inheritance is a relationship between a more general class (the superclass, parent, or base class) and a more specific class (the subclass, child, or derived class). The subclass inherits the attributes and methods of the superclass while adding or overriding behavior to specialize it.
The defining characteristics of inheritance:
Inheritance in code terms:
public class Employee {
protected String name;
protected double salary;
public void work() {
System.out.println(name + " is working");
}
}
public class Manager extends Employee { // IS-A Employee
private List<Employee> directReports; // Specialization
public void conductReview(Employee employee) { // New behavior
// Manager-specific functionality
}
@Override
public void work() { // Override inherited behavior
System.out.println(name + " is managing the team");
}
}
A Manager IS an Employee—it inherits name, salary, and work(), while adding directReports and conductReview(), and specializing the work() behavior.
Before using inheritance, apply the IS-A test: 'Is every [Subclass] always a [Superclass]?' If yes, inheritance might be appropriate. 'Is every Manager always an Employee?' Yes. 'Is every Rectangle always a Square?' No—inheritance would be incorrect here.
In UML, inheritance is represented by a solid line with a hollow (unfilled) triangle arrowhead pointing from the subclass to the superclass. The arrow direction is significant: it points toward the more general class.
Basic inheritance visualization:
Key elements of inheritance notation:
| Element | Description |
|---|---|
| Hollow triangle (△) | Arrowhead pointing at the superclass (generalization) |
| Solid line | Connects subclass to superclass |
| Arrow direction | Points from specific (subclass) to general (superclass) |
| No multiplicity | Inheritance doesn't use multiplicity |
Multi-level inheritance hierarchy:
Abstract classes (that cannot be instantiated) are shown with the class name in italics or with the stereotype <<abstract>>. Abstract methods are also italicized or marked with {abstract}. In our Shape example, getArea() is abstract—each subclass must provide its own implementation.
The inheritance relationship can be viewed from two perspectives:
Generalization (bottom-up):
Specialization (top-down):
UML terminology:
UML prefers the term "generalization" over "inheritance" because it captures both directions—the arrow points from specialized to generalized. The relationship itself is about type classification, not just code reuse.
Discriminators and partition:
When multiple subclasses inherit from the same superclass, we can specify what distinguishes them:
| Superclass | Subclasses | Discriminator |
|---|---|---|
| Account | Checking, Savings, Investment | Account type |
| Employee | FullTime, PartTime, Contractor | Employment status |
| Subscription | Free, Basic, Premium | Tier level |
| User | Admin, Moderator, Regular | Permission level |
A generalization is 'complete' if the subclasses cover all possibilities (every User is exactly one of Admin, Moderator, or Regular). It's 'incomplete' if other subclasses might exist. UML allows annotating this explicitly with {complete} or {incomplete}.
Inheritance can take two forms depending on how many superclasses a subclass can have:
Single Inheritance:
Multiple Inheritance:
The Diamond Problem:
Multiple inheritance introduces the diamond problem—when two parent classes inherit from the same grandparent, and the child inherits from both parents. Which version of inherited methods/attributes does the child get?
C++ requires explicit disambiguation. Python uses Method Resolution Order (MRO). Java avoids the problem by allowing only single class inheritance, but permits multiple interface implementation. When drawing UML, be aware of your target language's constraints.
Many languages distinguish between extending a class (inheriting implementation) and implementing an interface (promising to provide behavior). UML represents interface implementation with a dashed line and hollow triangle.
Interface implementation notation:
Notation comparison:
| Relationship | Line Style | Arrowhead | Meaning |
|---|---|---|---|
| Class inheritance | Solid line | Hollow triangle | Extends class, inherits implementation |
| Interface implementation | Dashed line | Hollow triangle | Implements interface, provides behavior |
Alternative notation: Lollipop notation
For simpler diagrams, interfaces can be shown as circles ("lollipops") attached to the implementing class, rather than as separate boxes with dashed arrows. This is compact but hides interface details.
Interfaces are shown with the <<interface>> stereotype above the class name. All interface methods are implicitly abstract (no body). In languages without explicit interfaces (Python), abstract base classes or duck typing serve similar purposes.
Inheritance is powerful but often overused. Modern best practices favor composition over inheritance in many scenarios. Here's guidance on when inheritance is appropriate:
Use inheritance when:
Before using inheritance, ask: 'Can I use the subclass everywhere the superclass is expected without clients knowing the difference?' If the subclass behaves unexpectedly (throws exceptions the parent doesn't, has different preconditions), inheritance may be inappropriate.
Certain inheritance patterns recur across different domains. Recognizing these helps apply inheritance correctly.
Pattern 1: Type Hierarchy
Classifying domain entities into types and subtypes.
| Superclass | Subclasses | Domain |
|---|---|---|
| PaymentMethod | CreditCard, DebitCard, BankTransfer | E-commerce |
| Notification | EmailNotification, SMSNotification, PushNotification | Messaging |
| Account | CheckingAccount, SavingsAccount | Banking |
| Vehicle | Car, Truck, Motorcycle | Transportation |
Pattern 2: Abstract Base Class
Defining common interface and shared implementation, forcing subclasses to implement specific methods.
Pattern 3: Policy/Strategy via Inheritance
Although the Strategy pattern often uses composition with interfaces, inheritance-based strategies are also common when shared state is needed.
Frameworks often use inheritance to define extension points. You extend a base class (AbstractController, BasePage) to plug into the framework. This is the Template Method pattern—the framework defines the skeleton, you provide the specifics.
We've explored inheritance as the IS-A relationship in UML. Let's consolidate the key takeaways:
What's next:
We've now covered the structural relationships: association, aggregation, composition, and inheritance. The final relationship type is dependency—a weaker, transient relationship where one class temporarily uses another. Represented by a dashed arrow, dependency captures usage without ownership.
You now understand inheritance as the IS-A relationship, represented by a hollow triangle arrow pointing toward the superclass. Next, we'll examine dependency—the weakest relationship type indicating transient usage between classes.