Object-Oriented Programming (OOP) is a programming paradigm based on objects containing data and methods. This guide covers all essential OOP concepts.
Four Pillars of OOP #
1. Encapsulation #
Bundle data and methods that operate on that data within a single unit.
class BankAccount {
#balance = 0; // Private field
constructor(initialBalance) {
this.#balance = initialBalance;
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
}
}
withdraw(amount) {
if (amount > 0 && amount <= this.#balance) {
this.#balance -= amount;
return true;
}
return false;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// account.#balance // Error: Private field
2. Abstraction #
Hide complex implementation details, expose simple interface.
class Coffee {
makeCoffee() {
this.boilWater();
this.brewCoffee();
this.pourInCup();
}
boilWater() {
console.log("Boiling water");
}
brewCoffee() {
console.log("Brewing coffee");
}
pourInCup() {
console.log("Pouring in cup");
}
}
const coffee = new Coffee();
coffee.makeCoffee(); // Simple interface
3. Inheritance #
Create new classes based on existing classes.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(`${this.name} barks`);
}
fetch() {
console.log(`${this.name} fetches the ball`);
}
}
const dog = new Dog("Max", "Golden Retriever");
dog.speak(); // "Max barks"
dog.fetch(); // "Max fetches the ball"
4. Polymorphism #
Objects of different types can be accessed through the same interface.
class Shape {
area() {
throw new Error("Method must be implemented");
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
}
const shapes = [
new Circle(5),
new Rectangle(4, 6)
];
shapes.forEach(shape => {
console.log(shape.area());
});Classes and Objects #
Basic Class #
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
get info() {
return `${this.name}, ${this.age} years old`;
}
set birthYear(year) {
this.age = new Date().getFullYear() - year;
}
}
const person = new Person("Alice", 25);
console.log(person.greet());
console.log(person.info);
person.birthYear = 1998;Static Methods and Properties #
class MathUtils {
static PI = 3.14159;
static add(a, b) {
return a + b;
}
static multiply(a, b) {
return a * b;
}
}
console.log(MathUtils.PI);
console.log(MathUtils.add(5, 3));Private Fields and Methods #
class Counter {
#count = 0;
increment() {
this.#count++;
this.#log();
}
#log() {
console.log(`Count: ${this.#count}`);
}
getCount() {
return this.#count;
}
}
const counter = new Counter();
counter.increment();
console.log(counter.getCount());Composition vs Inheritance #
Inheritance (Is-A Relationship) #
class Vehicle {
start() {
console.log("Starting vehicle");
}
}
class Car extends Vehicle {
drive() {
console.log("Driving car");
}
}Composition (Has-A Relationship) #
class Engine {
start() {
console.log("Engine starting");
}
}
class Car {
constructor() {
this.engine = new Engine();
}
start() {
this.engine.start();
console.log("Car started");
}
}Prefer composition - More flexible, avoids deep inheritance hierarchies.
SOLID Principles #
S - Single Responsibility #
Class should have one reason to change.
// Bad
class User {
constructor(name) {
this.name = name;
}
saveToDatabase() {
// Database logic
}
sendEmail() {
// Email logic
}
}
// Good
class User {
constructor(name) {
this.name = name;
}
}
class UserRepository {
save(user) {
// Database logic
}
}
class EmailService {
send(user, message) {
// Email logic
}
}O - Open/Closed #
Open for extension, closed for modification.
class Shape {
area() {
throw new Error("Must implement");
}
}
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
}
// Add new shapes without modifying existing code
class Triangle extends Shape {
constructor(base, height) {
super();
this.base = base;
this.height = height;
}
area() {
return (this.base * this.height) / 2;
}
}L - Liskov Substitution #
Subclasses should be substitutable for base classes.
class Bird {
fly() {
console.log("Flying");
}
}
class Sparrow extends Bird {
fly() {
console.log("Sparrow flying");
}
}
// Violates LSP - Penguin can't fly
class Penguin extends Bird {
fly() {
throw new Error("Penguins can't fly");
}
}
// Better design
class Bird {
move() {
console.log("Moving");
}
}
class FlyingBird extends Bird {
fly() {
console.log("Flying");
}
}
class Penguin extends Bird {
swim() {
console.log("Swimming");
}
}I - Interface Segregation #
Many specific interfaces better than one general.
// Bad - Fat interface
class Worker {
work() {}
eat() {}
sleep() {}
}
// Good - Segregated
class Workable {
work() {}
}
class Eatable {
eat() {}
}
class Human extends Workable {
work() {
console.log("Working");
}
}D - Dependency Inversion #
Depend on abstractions, not concretions.
// Bad
class MySQLDatabase {
save(data) {
console.log("Saving to MySQL");
}
}
class UserService {
constructor() {
this.database = new MySQLDatabase();
}
saveUser(user) {
this.database.save(user);
}
}
// Good
class Database {
save(data) {
throw new Error("Must implement");
}
}
class MySQLDatabase extends Database {
save(data) {
console.log("Saving to MySQL");
}
}
class UserService {
constructor(database) {
this.database = database;
}
saveUser(user) {
this.database.save(user);
}
}
const db = new MySQLDatabase();
const service = new UserService(db);Design Patterns #
Singleton #
Only one instance exists.
class Database {
constructor() {
if (Database.instance) {
return Database.instance;
}
this.connection = null;
Database.instance = this;
}
connect() {
if (!this.connection) {
this.connection = "Connected";
}
return this.connection;
}
}
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true
Factory #
Create objects without specifying exact class.
class CarFactory {
createCar(type) {
switch(type) {
case 'sedan':
return new Sedan();
case 'suv':
return new SUV();
default:
throw new Error("Unknown car type");
}
}
}
class Sedan {
constructor() {
this.type = "Sedan";
}
}
class SUV {
constructor() {
this.type = "SUV";
}
}
const factory = new CarFactory();
const car = factory.createCar('sedan');Observer #
Subscribe to and receive notifications.
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
update(data) {
console.log("Received:", data);
}
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify("Hello observers!");OOP in Different Languages #
Python #
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal):
def speak(self):
return f"{self.name} barks"
dog = Dog("Max")
print(dog.speak())Java #
public class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void speak() {
System.out.println(name + " makes a sound");
}
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void speak() {
System.out.println(getName() + " barks");
}
}Best Practices #
- Keep classes small - Single Responsibility
- Favor composition over inheritance
- Program to interfaces not implementations
- Use meaningful names - Clear and descriptive
- Encapsulate properly - Private by default
- Avoid deep hierarchies - Max 3-4 levels
- Test classes independently - Loose coupling
- Document public APIs - Help other developers
OOP helps organize complex code into manageable, reusable pieces. Master these concepts to write maintainable software.