Module 4: Advanced OOP Concepts
Understanding the Three Pillars of Object-Oriented Programming.
Building a Solid Foundation
Welcome to Module 4! You've learned how to create blueprints (Classes) and build from them (Objects). Now it's time to explore the three core principles that give OOP its power and flexibility: **Inheritance**, **Polymorphism**, and **Encapsulation**. Mastering these concepts is what separates a beginner from an intermediate programmer and is essential for building robust, scalable applications.
1Inheritance: Reusing and Extending Code
Inheritance allows a new class to absorb the properties (fields) and behaviors (methods) of an existing class. This promotes code reuse. The existing class is called the **superclass** (or parent), and the new class is the **subclass** (or child).
Imagine we have our `Dog` class. Now we want to create a `Cat` class. Dogs and cats share common traits; they are both animals. Let's create a general `Animal` superclass.
Creating a Superclass and Subclass
The `Animal` class will contain the shared logic. The `Dog` class can then **extend** `Animal` to inherit that logic, while also adding its own specific behaviors.
// Superclass
public class Animal {
String name;
public void eat() {
System.out.println(name + " is eating.");
}
}
// Subclass
public class Dog extends Animal {
// The 'name' field and 'eat()' method are automatically inherited!
// Dog-specific method
public void bark() {
System.out.println("Woof Woof!");
}
}
2Polymorphism: One Action, Many Forms
Polymorphism** (from Greek, meaning "many forms") is the ability of an object to take on many forms. In practice, this often means a subclass can provide its own specific implementation of a method that is already defined in its superclass. This is called **Method Overriding**.
For example, all animals make a sound, but they do it differently. Let's add a `makeSound()` method to our `Animal` class and have `Dog` and `Cat` subclasses override it.
public class Animal {
public void makeSound() {
System.out.println("The animal makes a sound.");
}
}
public class Dog extends Animal {
@Override // Annotation to indicate we are overriding
public void makeSound() {
System.out.println("Woof Woof!");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow!");
}
}
3Encapsulation: Protecting Your Data
Encapsulation** is the practice of bundling the data (fields) and the methods that operate on that data within a single unit (the class) and restricting access to some of the object's components. This is a protective barrier that prevents the data from being accidentally or maliciously modified.
We achieve this by making our fields **`private`**. Private fields can only be accessed by methods within the same class.
Getters and Setters
To allow controlled access to these private fields, we create public methods known as **getters** (to retrieve the value) and **setters** (to change the value). Setters can include validation logic to ensure the new value is valid.
public class BankAccount {
// This field is private and cannot be accessed directly from outside
private double balance;
public BankAccount(double initialBalance) {
if (initialBalance > 0) {
this.balance = initialBalance;
}
}
// A "getter" method to safely read the balance
public double getBalance() {
return this.balance;
}
// A "setter" method with validation
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount;
}
}
}