Loading content...
Humans naturally organize knowledge into hierarchies. A dog is a mammal, which is an animal, which is a living organism. This isn't arbitrary—hierarchical classification captures real relationships: dogs share properties with all mammals (warm-blooded, live birth) while adding dog-specific traits (barking, tail wagging).
Inheritance in object-oriented databases formalizes this natural classification system. It allows classes to be organized into hierarchies where subclasses inherit attributes, methods, and constraints from superclasses, while adding or modifying specialized behavior.
For database systems, inheritance provides:
By the end of this page, you will master inheritance mechanisms in OODM: single and multiple inheritance hierarchies, the IS-A relationship, method overriding, polymorphic collections, and the crucial distinction between type inheritance (interface) and implementation inheritance (code reuse).
Inheritance creates an IS-A relationship between classes. When we declare class Employee extends Person, we're asserting that every Employee IS-A Person. This isn't merely implementation convenience—it's a semantic statement about the domain:
This semantic relationship has profound implications for database operations:
| Operation | Effect of IS-A Relationship |
|---|---|
| Query for all Persons | Returns Persons AND all Employees, Customers, Vendors, etc. |
| Constraint on Person.age | Automatically enforced on Employee, Customer, etc. |
| Method on Person | Callable on any subclass instance |
| Reference typed as Person | Can hold reference to any subclass instance |
| Person extent | Includes instances of all subclasses |
Substitutability Principle:
The Liskov Substitution Principle (LSP) states: if S is a subtype of T, then objects of type T may be replaced with objects of type S without altering the correctness of the program.
In database terms: if Employee IS-A Person, then any operation expecting a Person must work correctly when given an Employee. This constrains how subclasses can modify inherited behavior—they can extend, but should not contradict.
Example of Valid Specialization:
validateAge() that checks age >= 0validateAge() that checks age >= 18Example of Invalid Specialization:
calculateTax() returning a positive numberNot every 'has properties of' relationship should be inheritance. An Employee HAS-A salary, but salary is not a superclass. The IS-A test must pass: is every Employee truly a kind of Person? If the relationship is 'uses', 'contains', or 'has', prefer composition (reference attributes) over inheritance.
In single inheritance, each class has at most one direct superclass. This creates clean, tree-structured hierarchies that are easy to understand and implement.
Characteristics of Single Inheritance:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
// Root class: Personclass Person { attribute string name; attribute Date birthDate; attribute Address address; int getAge() { return currentDate.year - birthDate.year; } string getFullName() { return this.name; }} // Single inheritance: Employee IS-A Personclass Employee extends Person { attribute string employeeId; attribute Date hireDate; attribute float salary; relationship Department department; // Inherits: name, birthDate, address, getAge(), getFullName() // Adds new attributes and methods specific to employees int getYearsOfService() { return currentDate.year - hireDate.year; } float calculateBonus() { return salary * 0.10; }} // Further specialization: Manager IS-A Employee IS-A Personclass Manager extends Employee { attribute float bonusPercentage; relationship Set<Employee> directReports; // Inherits everything from Employee AND Person // Overrides parent method with specialized behavior float calculateBonus() { float baseBonus = super.calculateBonus(); float teamBonus = directReports.size() * 500; return baseBonus + teamBonus; } int getTeamSize() { return directReports.size(); }} // Parallel branch: Customer IS-A Person (not Employee)class Customer extends Person { attribute string customerId; attribute Date registrationDate; attribute string loyaltyTier; relationship Set<Order> orders; // Inherits: name, birthDate, address, getAge(), getFullName() float getTotalSpent() { return orders.sum(o -> o.total); }}The extent of Person includes all Person instances PLUS all Employee instances PLUS all Manager instances PLUS all Customer instances. This polymorphic extent is fundamental to OODM: querying the superclass naturally returns all specialized instances.
Multiple inheritance allows a class to inherit from more than one superclass. While more powerful for modeling, it introduces complexity that single inheritance avoids.
Why Multiple Inheritance Exists:
Real-world entities often simultaneously belong to multiple classification hierarchies:
Forcing single inheritance in these cases requires awkward workarounds—duplicated code, delegation chains, or artificial hierarchy restructuring.
123456789101112131415161718192021222324252627282930313233343536373839
// Multiple inheritance: TeachingAssistant inherits from bothclass Student { attribute string studentId; attribute string major; attribute float gpa; relationship Set<Course> enrolledCourses; boolean isEligibleForHonors() { return gpa >= 3.5; }} class Employee { attribute string employeeId; attribute float salary; attribute Date hireDate; relationship Department department; float calculateTax() { return salary * 0.25; }} // TeachingAssistant IS-A Student AND IS-A Employeeclass TeachingAssistant extends Student, Employee { attribute Course assignedCourse; attribute int weeklyHours; // Inherits from Student: // studentId, major, gpa, enrolledCourses, isEligibleForHonors() // Inherits from Employee: // employeeId, salary, hireDate, department, calculateTax() // TA-specific method boolean canGradePapers() { return isEligibleForHonors() && weeklyHours <= 20; }}The Diamond Problem:
Multiple inheritance introduces the infamous diamond problem when two superclasses share a common ancestor:
Person
/ \
Student Employee
\ /
TeachingAssistant
If both Student and Employee inherit from Person, does TeachingAssistant get TWO copies of Person's attributes? Which version of Person's methods does it inherit?
Resolution strategies:
Many modern systems separate TYPE inheritance (what methods exist) from IMPLEMENTATION inheritance (how methods work). A class can implement multiple interfaces (type inheritance) while extending only one concrete class (implementation inheritance). This captures the benefits of multiple inheritance while avoiding most complexity.
Method overriding allows subclasses to provide specialized implementations of inherited methods. When combined with polymorphism—the ability to treat subclass instances through superclass references—overriding becomes extraordinarily powerful.
How Overriding Works:
super.methodName()123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
class Employee { attribute string name; attribute float baseSalary; // Base implementation float calculateCompensation() { return baseSalary; } string getDescription() { return name + " - Employee"; }} class HourlyEmployee extends Employee { attribute float hourlyRate; attribute int hoursWorked; // Override: completely different calculation float calculateCompensation() { return hourlyRate * hoursWorked; } string getDescription() { return name + " - Hourly ($" + hourlyRate + "/hr)"; }} class SalariedEmployee extends Employee { // No override: uses inherited calculateCompensation() string getDescription() { return name + " - Salaried ($" + baseSalary + "/yr)"; }} class CommissionEmployee extends SalariedEmployee { attribute float commissionRate; attribute float salesTotal; // Override: extends base behavior float calculateCompensation() { float base = super.calculateCompensation(); // Call parent float commission = salesTotal * commissionRate; return base + commission; } string getDescription() { return name + " - Commission (" + commissionRate * 100 + "%)"; }}Polymorphism in Action:
Polymorphism means 'many forms'—the same method call produces different behavior depending on the actual object type:
// All references typed as Employee (superclass)
Set<Employee> employees = getAllEmployees();
float totalPayroll = 0;
for (Employee emp : employees) {
// This calls the ACTUAL type's implementation
// - HourlyEmployee: hourlyRate * hoursWorked
// - SalariedEmployee: baseSalary
// - CommissionEmployee: baseSalary + commission
totalPayroll += emp.calculateCompensation();
}
The calling code doesn't know or care about the specific employee types. It works with the common Employee interface, but each object responds according to its actual class. This is late binding or dynamic dispatch.
| Variable Type | Actual Object Type | Method Called | Result |
|---|---|---|---|
| Employee | HourlyEmployee | calculateCompensation() | hourlyRate × hoursWorked |
| Employee | SalariedEmployee | calculateCompensation() | baseSalary (inherited) |
| Employee | CommissionEmployee | calculateCompensation() | baseSalary + commission |
| Employee | Employee | calculateCompensation() | baseSalary |
In OODBMS, queries on superclass extents are automatically polymorphic. SELECT * FROM Employee returns instances of all Employee subclasses. More importantly, querying WHERE calculateCompensation() > 50000 invokes each object's actual implementation—making the database itself polymorphically aware.
Not all classes are meant to be instantiated directly. Abstract classes define structure and behavior that only makes sense for their subclasses, while interfaces define contracts without any implementation.
Abstract Classes:
An abstract class cannot be instantiated—you cannot create a 'Person' object if Person is abstract. Abstract classes serve as:
Abstract Methods:
An abstract method declares a signature without providing an implementation. Every concrete (non-abstract) subclass must provide an implementation. This enforces that all subclasses support certain operations while allowing each to implement them appropriately.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
// Abstract class: cannot be instantiated directlyabstract class Shape { attribute Color fillColor; attribute Point position; // Regular method: inherited by all subclasses void move(int dx, int dy) { position.x += dx; position.y += dy; } // Abstract methods: MUST be implemented by subclasses abstract float getArea(); abstract float getPerimeter(); abstract void draw(Canvas canvas);} class Circle extends Shape { attribute float radius; // Required implementation of abstract methods float getArea() { return Math.PI * radius * radius; } float getPerimeter() { return 2 * Math.PI * radius; } void draw(Canvas canvas) { canvas.drawCircle(position, radius, fillColor); }} class Rectangle extends Shape { attribute float width; attribute float height; float getArea() { return width * height; } float getPerimeter() { return 2 * (width + height); } void draw(Canvas canvas) { canvas.drawRect(position, width, height, fillColor); }} // Interface: pure contract, no implementationinterface Serializable { byte[] serialize(); void deserialize(byte[] data);} interface Auditable { Date getCreatedAt(); User getCreatedBy(); List<AuditEntry> getAuditLog();} // Class implementing multiple interfacesclass Document extends Shape implements Serializable, Auditable { attribute string content; attribute Date createdAt; attribute User createdBy; attribute List<AuditEntry> auditLog; // Must implement Shape abstract methods float getArea() { /* ... */ } float getPerimeter() { /* ... */ } void draw(Canvas c) { /* ... */ } // Must implement Serializable byte[] serialize() { /* ... */ } void deserialize(byte[] data) { /* ... */ } // Must implement Auditable Date getCreatedAt() { return createdAt; } User getCreatedBy() { return createdBy; } List<AuditEntry> getAuditLog() { return auditLog; }}Use abstract classes when subclasses share structure AND behavior. Use interfaces when unrelated classes need to support common operations. A Shape hierarchy shares drawing logic—use abstract class. An Auditable capability applies to Documents, Users, and Transactions—use interface.
Inheritance profoundly affects how database operations work. Understanding these implications is crucial for designing effective object-oriented schemas.
Querying Inherited Hierarchies:
When you query a class, the result includes instances of all subclasses. This polymorphic query behavior is both powerful and requires careful consideration:
123456789101112131415161718
// Assume hierarchy: Person <- Employee <- Manager// <- Customer // Query all Persons: returns Person, Employee, Manager, Customer instancesSELECT p FROM Person p WHERE p.age > 30 // Query only Employees: returns Employee and Manager instancesSELECT e FROM Employee e WHERE e.salary > 50000 // Query only Managers: returns only Manager instancesSELECT m FROM Manager m WHERE m.directReports.size() > 5 // Filter by type within a querySELECT p FROM Person p WHERE p.age > 30 AND TYPE(p) = Employee // Exclude subclasses (implementation-specific)SELECT p FROM ONLY Person p WHERE p.age > 30Insert Operations:
When you create an instance, you specify the most specific class:
// Creates a Manager, which is also an Employee, which is also a Person
Manager mgr = new Manager();
mgr.name = "Alice"; // Inherited from Person
mgr.salary = 120000; // Inherited from Employee
mgr.bonusPercentage = 15; // Manager-specific
db.persist(mgr);
// mgr is now in: Manager extent, Employee extent, AND Person extent
Update Operations:
Updates to inherited attributes work transparently:
// Can update any attribute, inherited or class-specific
mgr.name = "Alice Smith"; // Person attribute
mgr.salary = 130000; // Employee attribute
mgr.bonusPercentage = 18; // Manager attribute
Delete Operations:
Deleting an object removes it from all class extents in the hierarchy.
| Operation | Behavior | Example |
|---|---|---|
| CREATE | Object appears in all ancestor class extents | new Manager() → in Manager, Employee, Person extents |
| READ (query) | Query on superclass returns all subclass instances | SELECT FROM Person → returns Manager, Customer, etc. |
| UPDATE | All attributes (inherited and own) updatable uniformly | Update Person.name works on Manager instance |
| DELETE | Object removed from all ancestor class extents | delete manager → gone from Manager, Employee, Person |
Polymorphic queries on large hierarchies may need to scan multiple storage areas (one per concrete class). Some OODBMS optimize this with cluster storage or materialized paths. When designing, consider whether deep hierarchies impact query patterns you'll use most frequently.
Under the hood, OODBMS must physically store inheritance hierarchies. Different strategies trade off storage efficiency, query performance, and implementation complexity. Understanding these helps you make informed schema design decisions.
Strategy 1: Single Table Inheritance
All classes in the hierarchy stored in one table with a discriminator column:
Strategy 2: Class Table Inheritance
Each class has its own table containing only its declared attributes:
Strategy 3: Concrete Table Inheritance
Each concrete class has a table with ALL attributes (inherited + own):
123456789101112131415161718192021222324252627
-- Single Table: All Person hierarchy in one tableCREATE TABLE Person ( oid BIGINT PRIMARY KEY, type VARCHAR(20), -- 'Person', 'Employee', 'Manager', 'Customer' -- Person attributes name VARCHAR(100), birth_date DATE, -- Employee attributes (NULL for non-employees) employee_id VARCHAR(20), salary DECIMAL(10,2), hire_date DATE, -- Manager attributes (NULL for non-managers) bonus_percentage DECIMAL(5,2), -- Customer attributes (NULL for non-customers) customer_id VARCHAR(20), loyalty_tier VARCHAR(10)); -- Query all Persons (including subclasses)SELECT * FROM Person WHERE birth_date < '1990-01-01'; -- Query only ManagersSELECT * FROM Person WHERE type = 'Manager';Pure OODBMS systems don't always use relational storage underneath. They may store objects in clustering schemes optimized for inheritance navigation—keeping parent and child data physically adjacent. The strategies above are most relevant to Object-Relational hybrids.
We've explored how inheritance transforms database design. Let's consolidate the key insights:
What's Next:
Now that we understand inheritance, the next page explores encapsulation—how objects hide their internal state and expose controlled interfaces, why this matters for data integrity, and how encapsulation principles translate from programming languages to database systems.
You now understand how inheritance creates powerful, reusable class hierarchies in object-oriented databases. From IS-A relationships through polymorphic queries to storage strategies, inheritance is foundational to OODM design. Next, we'll explore how encapsulation protects and organizes object capabilities.