Is-a vs Has-a¶
Relationship Types¶
1. Fundamental Concepts¶
In object-oriented design, relationships between classes fall into two primary categories:
- Is-a: Inheritance relationship (subclass/superclass)
- Has-a: Composition/Aggregation relationship (container/component)
Understanding these relationships is crucial for choosing the right design pattern.
2. Identifying Relationships¶
Ask these questions:
- Is-a: "Is the subclass a specialized type of the superclass?"
- Has-a: "Does the class contain or use instances of another class?"
The answer guides your design choice.
3. Language Test¶
Use natural language to test relationships:
- Dog is a Animal ✅ (inheritance)
- Car has a Engine ✅ (composition)
- Student has a Course ✅ (aggregation)
Is-a Relationship¶
1. Inheritance Model¶
The is-a relationship represents a hierarchical connection where the subclass is a specialized version of the superclass:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
2. Key Characteristics¶
- Subclass inherits all attributes and methods
- Subclass can override inherited behavior
- Represents generalization/specialization
- Creates a tight coupling between classes
3. When to Use¶
Use inheritance when:
# Clear specialization hierarchy
class Vehicle:
pass
class Car(Vehicle): # Car is-a Vehicle ✅
pass
class Truck(Vehicle): # Truck is-a Vehicle ✅
pass
# Polymorphic behavior needed
def process_vehicle(vehicle: Vehicle):
vehicle.start() # Works for any Vehicle subclass
Has-a Relationship¶
1. Composition Model¶
The has-a relationship represents containment where one class holds references to instances of other classes:
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self):
self.engine = Engine() # Car has-a Engine
def start(self):
return self.engine.start()
2. Key Characteristics¶
- Container class contains component objects
- Component lifetime depends on container (composition)
- Component lifetime independent of container (aggregation)
- Creates loose coupling between classes
3. When to Use¶
Use composition/aggregation when:
# Building from components
class Computer:
def __init__(self):
self.cpu = CPU()
self.ram = RAM()
self.storage = Storage()
# Flexible assembly
class Team:
def __init__(self, players):
self.players = players # Team has-a Players
Comparison¶
1. Coupling Differences¶
| Aspect | Is-a (Inheritance) | Has-a (Composition) |
|---|---|---|
| Coupling | Tight | Loose |
| Flexibility | Less | More |
| Reusability | Limited | High |
| Change impact | High | Low |
2. Code Examples¶
Is-a (Inheritance):
class Shape:
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
Has-a (Composition):
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Circle:
def __init__(self, center, radius):
self.center = center # Has-a Point
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
3. When Each Fails¶
Inheritance fails when:
# ❌ BAD: Square is-a Rectangle (violates LSP)
class Rectangle:
def set_width(self, w):
self.width = w
def set_height(self, h):
self.height = h
class Square(Rectangle):
# Breaks when width != height
pass
Composition works:
# ✅ GOOD: Square has-a Shape interface
class Square:
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
Design Guidelines¶
1. Favor Composition¶
Modern OOP design favors composition over inheritance:
# Instead of deep inheritance
class Animal:
pass
class Mammal(Animal):
pass
class Dog(Mammal):
pass
# Use composition
class Animal:
def __init__(self, behavior):
self.behavior = behavior # Has-a Behavior
def act(self):
self.behavior.perform()
2. Mix Both Approaches¶
Combine inheritance and composition:
class Drawable:
"""Interface through inheritance"""
def draw(self):
pass
class Circle(Drawable):
def __init__(self, center, radius):
self.center = center # Has-a Point
self.radius = radius
def draw(self):
# Implementation
pass
3. Decision Tree¶
Need to model relationship?
↓
Is it specialization? → Use Inheritance (is-a)
↓
Is it containment? → Use Composition (has-a)
↓
Strong ownership? → Composition
↓
Weak ownership? → Aggregation
Common Patterns¶
1. Interface Inheritance¶
Use inheritance for interfaces, composition for implementation:
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process(self, amount):
pass
class CreditCardProcessor(PaymentProcessor):
def __init__(self, gateway):
self.gateway = gateway # Has-a Gateway
def process(self, amount):
return self.gateway.charge(amount)
2. Strategy Pattern¶
Combine both:
class SortStrategy(ABC):
@abstractmethod
def sort(self, data):
pass
class QuickSort(SortStrategy):
def sort(self, data):
# Quick sort implementation
pass
class DataProcessor:
def __init__(self, strategy):
self.strategy = strategy # Has-a Strategy
def process(self, data):
return self.strategy.sort(data)
3. Decorator Pattern¶
Composition for extending behavior:
class Coffee:
def cost(self):
return 5
class MilkDecorator:
def __init__(self, coffee):
self.coffee = coffee # Has-a Coffee
def cost(self):
return self.coffee.cost() + 2
coffee = MilkDecorator(Coffee())
print(coffee.cost()) # 7
Real-World Examples¶
1. UI Components¶
# Inheritance for type hierarchy
class Widget:
pass
class Button(Widget):
pass
class TextField(Widget):
pass
# Composition for assembly
class Form:
def __init__(self):
self.buttons = [] # Has-a Buttons
self.textfields = [] # Has-a TextFields
2. Game Entities¶
# Inheritance for base entity
class GameObject:
pass
class Character(GameObject):
def __init__(self):
self.position = Vector2D() # Has-a Position
self.health = HealthBar() # Has-a HealthBar
self.inventory = [] # Has-a Items
3. Business Systems¶
# Inheritance for business entities
class Person:
pass
class Employee(Person):
def __init__(self, department):
self.department = department # Has-a Department
class Customer(Person):
def __init__(self, orders):
self.orders = orders # Has-a Orders
Testing Validity¶
1. Liskov Substitution¶
Test inheritance with LSP:
def process_animal(animal: Animal):
animal.speak()
# Should work for any subclass
process_animal(Dog())
process_animal(Cat())
2. Composition Validity¶
Test composition by swapping components:
class Car:
def __init__(self, engine):
self.engine = engine
# Should work with any Engine
car1 = Car(ElectricEngine())
car2 = Car(GasEngine())
3. Relationship Questions¶
Ask yourself:
- Can I say "B is-a A" naturally? → Inheritance
- Can I say "B has-a A" naturally? → Composition
- Does B exist without A? → Aggregation
- Is B meaningless without A? → Composition