Module 2: Inheritance and Polymorphism I
Module Overview
Explore advanced object-oriented programming concepts and best practices in Java.
Learning Objectives
- Understand advanced OOP principles like SOLID and DRY
- Implement polymorphism effectively in Java applications
- Apply composition over inheritance when appropriate
- Create and utilize abstract classes and interfaces for flexibility
- Implement encapsulation strategies for better data protection
- Use dependency injection to improve testability
Advanced OOP Principles
Object-oriented programming is a powerful paradigm that, when used correctly, can lead to maintainable, flexible, and robust code. This module focuses on advanced OOP principles and practices that will elevate your Java programming skills.
SOLID Principles:
- Single Responsibility Principle: A class should have only one reason to change
- Open/Closed Principle: Software entities should be open for extension but closed for modification
- Liskov Substitution Principle: Derived classes must be substitutable for their base classes
- Interface Segregation Principle: Clients should not be forced to depend on methods they do not use
- Dependency Inversion Principle: High-level modules should not depend on low-level modules; both should depend on abstractions
Polymorphism in Depth:
Polymorphism is a fundamental concept in OOP that allows objects of different classes to be treated as objects of a common superclass.
- Runtime Polymorphism: Method overriding and dynamic method dispatch
- Compile-time Polymorphism: Method overloading
- Advantages: Flexibility, extensibility, and code reuse
Sample Code:
// Runtime polymorphism example
abstract class Shape {
abstract double calculateArea();
}
class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
double calculateArea() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
double calculateArea() {
return width * height;
}
}
// Usage
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
System.out.println("Circle area: " + circle.calculateArea());
System.out.println("Rectangle area: " + rectangle.calculateArea());
Composition vs. Inheritance:
While inheritance is a powerful feature, composition often provides more flexibility and reduces coupling between classes.
- Inheritance: "is-a" relationship (e.g., Car is a Vehicle)
- Composition: "has-a" relationship (e.g., Car has an Engine)
- When to use composition: When behavior needs to be shared across unrelated classes or when behavior might change at runtime