A comprehensive learning repository for mastering Object-Oriented Programming (OOP) concepts, Class Relationships, and Design Principles through hands-on TypeScript implementations.
This repository serves as a structured learning path for Low Level Design (LLD) concepts. Each module contains practical implementations, real-world examples, and comprehensive documentation to help you understand and apply OOP principles effectively.
OOP Fundamentals β Class Relationships β Design Principles β Design Patterns
β
β
π π
- Node.js (v16 or higher)
- TypeScript (v5.x)
- Basic understanding of JavaScript/TypeScript
- Familiarity with Object-Oriented Programming concepts
# Clone the repository
git clone <repository-url>
cd "Low Level Design"
# Install dependencies
npm install
# Run the project
npm run devnpm run dev # Start development server with hot reload
npm run build # Compile TypeScript to JavaScript
npm start # Run the compiled JavaScriptLocation: src/encapsulation/
Learn how to properly encapsulate data and behavior within classes.
- Bad Example:
BadBankAccount.ts- Demonstrates problems with public fields - Good Example:
GoodBankAccount.ts- Shows proper encapsulation with private fields and controlled access
Key Concepts:
- Private fields and methods
- Public getters/setters
- Data validation
- Business rule enforcement
Example:
// β Bad: Public field allows invalid states
class BadBankAccount {
balance: number = 0; // Anyone can set this to -1000!
}
// β
Good: Encapsulated with validation
class GoodBankAccount {
private balance: number = 0;
public deposit(amount: number): void {
if (amount <= 0) throw new Error("Amount must be positive");
this.balance += amount;
}
}Location: src/Abstraction/
Understand how to hide implementation details and expose only what's necessary.
- Notification System:
NotificationSystem.ts- Interface defining notification contract - Implementations:
EmailNotifier.ts,SMSNotifier.ts- Concrete implementations - Logger:
Logger.ts- Abstraction example for logging
Key Concepts:
- Interface-based design
- Implementation hiding
- Contract definition
- Polymorphism through interfaces
Example:
// Abstraction: What can be done, not how
interface Notifier {
send(message: string): void;
}
// Implementation: How it's done
class EmailNotifier implements Notifier {
send(message: string): void {
console.log(`Sending email: ${message}`);
}
}This module covers the five fundamental relationships in OOP, demonstrated through practical examples.
Location: src/Class Relationships/BuildingRiderSystem/entities/RideStrict.ts
Definition: A relationship where two classes are connected but can exist independently.
Real-world Example: In the Ride Booking System, a Ride is associated with a Driver and Rider. They know about each other, but can exist independently.
// Ride knows about Driver (association)
class Ride {
private driverId?: string; // Associated, not owned
public assignDriver(driverId: string): void {
this.driverId = driverId;
}
}Key Points:
- Objects can exist independently
- Relationship is temporary or optional
- No ownership implied
Location: src/Class Relationships/Aggregation.ts
Definition: A "has-a" relationship where the whole contains parts, but parts can exist without the whole.
Examples:
OrderaggregatesItem[]- Items can exist without an OrderTeamaggregatesDeveloper[]- Developers can exist without a Team
// Order aggregates Items (items can exist independently)
class Order {
private items: Item[] = [];
public addItem(item: Item): void {
this.items.push(item); // Item exists independently
}
}
// Team aggregates Developers
class Team {
private developers: Developer[] = [];
public addDeveloper(developer: Developer): void {
this.developers.push(developer);
}
}Key Points:
- "Has-a" relationship
- Parts can exist independently
- Whole doesn't create parts
- Parts can be shared
Location: src/Class Relationships/Composition.ts
Definition: A strong "owns-a" relationship where parts cannot exist without the whole.
Example: House composes Room[] - Rooms are created by and owned by the House.
// House composes Rooms (rooms cannot exist without house)
class House {
private rooms: Room[] = [];
constructor() {
// Composition: House CREATES its rooms
this.rooms = [
new Room("Living Room"),
new Room("Kitchen"),
new Room("Bedroom"),
];
}
}Key Points:
- "Owns-a" relationship
- Parts are created by the whole
- Parts cannot exist independently
- Lifecycle is tied together
Location: src/Class Relationships/BuildingRiderSystem/services/
Definition: A relationship where one class uses another temporarily, often through method parameters.
Example: RideService depends on MatchingService to find drivers.
// RideService depends on MatchingService
class RideService {
constructor(
private matchingService: MatchingService // Dependency injection
) {}
requestRide(input: RideInput): Ride {
// Uses MatchingService temporarily
const driver = this.matchingService.findNearestDriver(input.pickup);
// ...
}
}Key Points:
- Temporary usage relationship
- Often through method parameters or constructor injection
- No permanent ownership
- Loose coupling
Location: src/Class Relationships/Realization.ts
Definition: A relationship where a class implements an interface, defining a contract.
Example: Multiple payment methods realize the Payment interface.
// Interface defines the contract
interface Payment {
pay(amount: number): void;
}
// Classes realize the interface
class CreditCardPayment implements Payment {
pay(amount: number): void {
console.log(`Paying ${amount} with credit card`);
}
}
class PayPalPayment implements Payment {
pay(amount: number): void {
console.log(`Paying ${amount} with PayPal`);
}
}Key Points:
- "Implements" relationship
- Contract definition through interfaces
- Multiple classes can realize the same interface
- Enables polymorphism
Location: src/SOLID/
Location: src/SOLID/SingleResPrinciple/
What we learned:
- One reason to change: keep each class focused on a single responsibility (persistence vs calculation vs formatting vs delivery).
- Better testing & change isolation: you can change email logic without touching salary calculation (and vice-versa).
- Clean entry point: keep demos/orchestration in a single
run...()function and call it fromsrc/index.ts.
Files:
SRP.tsβEmployeeentity (data + getters)EmpRepo.tsβEmployeeRepository(persistence)PaySlipCalc.tsβ net pay calculationPayslipGen.tsβ payslip formattingEmailPayslip.tsβ email sendingSRPDemo.tsβ orchestration (runSRPDemo())
Location: src/SOLID/OpenClosed/
What we learned:
- Open for extension, closed for modification:
PaymentProcessordepends on thePaymentMethodinterface, so new payment types donβt require editing the processor. - Add new behavior by adding a class: to support UPI, we add
UPIPayment implements PaymentMethod(noif/else, noswitchin the processor). - Important TypeScript pitfall: a
.tsfile with no imports/exports is treated as a script, which can leak declarations into the global scope.- We hit this because
src/Class Relationships/Realization.tsdefines globalCreditCardPayment/PayPalPaymentwithpay(), while OCP expectsprocessPayment(). - Fix: in OCP usage files, import concrete implementations from the correct module (
./OnlinePayment) so they correctly satisfyPaymentMethod.
- We hit this because
Files:
PaymentMethod.tsβ interface/contractPaymentProcessor.tsβ usesPaymentMethod(stable code)OnlinePayment.tsβ concrete implementations (CreditCardPayment,PayPalPayment,UPIPayment)CheckoutService.tsβ demo usageWithoutOpenClosed.tsβ anti-pattern example (needs modification for new methods)
Location: src/SOLID/LiskovSubstitution/
What we learned:
- Subtypes must keep promises of supertypes: if code expects a
Birdthat can fly, every subtype used in that place must really behave like a flying bird. - Donβt lie with inheritance: avoid child classes that override methods in a way that breaks expectations (e.g. a
Penguinthat βfliesβ but actually canβt). - Model abilities explicitly: split capabilities into more precise types (e.g.
BirdvsFlyingBird) so you only use each subtype where it is truly valid.
Files:
LSP.tsβ correct design withBird,FlyingBird,Sparrow,PenguinLSPDemo.tsβ story-style demo (runLSPDemo())WithoutLSP.tsβ anti-pattern example with a non-flyingPenguinthat still extendsBird
Location: src/SOLID/InterfaceSegregation/
What we learned:
- Small, focused interfaces: give each client only the methods it really needs instead of one giant interface.
- Avoid βfatβ interfaces: big interfaces force classes to implement unused methods, leading to fake/empty implementations and confusion.
- Design by role: split abilities into tiny interfaces (
CanWalk,CanTalk,CanTakePhoto) and compose them per need (robot dog vs camera toy).
Files:
ISP.tsβ good design with small capability interfaces andRobotDog/CameraToyISPDemo.tsβ story-style demo (runISPDemo())WithoutISP.tsβ anti-pattern with a singleSuperToyinterface and a bloated implementation
Location: src/Class Relationships/BuildingRiderSystem/
A complete, production-like implementation demonstrating all class relationships in a real-world scenario.
BuildingRiderSystem/
βββ entities/ # Domain entities
β βββ Driver.ts # Driver entity
β βββ Rider.ts # Rider entity
β βββ RideStrict.ts # Ride entity (shows Association)
β βββ RiderEvent.ts # Event entity (shows Composition)
β βββ PaymentReceipt.ts # Payment receipt
βββ services/ # Business logic services
β βββ RideService.ts # Main ride orchestration
β βββ MatchingService.ts # Driver matching logic
β βββ PricingService.ts # Fare calculation
β βββ PaymentService.ts # Payment processing
β βββ payments/ # Payment implementations (Realization)
β βββ CashPayment.ts
β βββ UPIPayment.ts
β βββ CardPayment.ts
βββ repository/ # Data access layer
β βββ DriverRepository.ts
β βββ RiderRepository.ts
β βββ RideRepository.ts
βββ interfaces/ # Contracts
β βββ PaymentMethod.ts # Payment interface (Realization)
βββ UML/ # Design documentation
β βββ 01-UseCaseDiagram.md
β βββ 02-ClassDiagram.md
β βββ 03-StateDiagram.md
βββ ProjectContext.md # Project requirements and design decisions
| Relationship | Example | File |
|---|---|---|
| Association | Ride β Driver |
entities/RideStrict.ts |
| Aggregation | MatchingService has Driver[] |
services/MatchingService.ts |
| Composition | Ride owns RideEvent[] |
entities/RideStrict.ts |
| Dependency | RideService uses MatchingService |
services/RideService.ts |
| Realization | UPIPayment implements PaymentMethod |
services/payments/ |
- β Complete ride lifecycle (REQUESTED β ACCEPTED β IN_PROGRESS β COMPLETED)
- β Driver matching algorithm
- β Fare calculation
- β Multiple payment methods (polymorphism)
- β State management with validation
- β Repository pattern
- β Service layer architecture
- β UML diagrams
// See src/index.ts for complete example
const ride = rideService.requestRide({
rideId: "ride-101",
riderId: rider.getId(),
pickup: new Location(19.08, 72.88),
drop: new Location(19.1, 72.9),
});
rideService.startRide(ride.getId());
rideService.completeRide(ride.getId());
const receipt = paymentService.payForRide({
rideId: ride.getId(),
durationMin: 18,
method: new UPIPayment(), // π₯ Polymorphism in action
});Low Level Design/
βββ src/
β βββ Abstraction/ # Abstraction examples
β β βββ NotificationSystem.ts
β β βββ EmailNotifier.ts
β β βββ SMSNotifier.ts
β β βββ Logger.ts
β β
β βββ Class Relationships/ # Class relationship examples
β β βββ Aggregation.ts # Aggregation examples
β β βββ Composition.ts # Composition examples
β β βββ Realization.ts # Realization examples
β β βββ BuildingRiderSystem/ # Comprehensive example
β β βββ entities/
β β βββ services/
β β βββ repository/
β β βββ interfaces/
β β βββ UML/
β β
β βββ encapsulation/ # Encapsulation examples
β β βββ BadBankAccount.ts
β β
β βββ index.ts # Main entry point
β
βββ package.json
βββ tsconfig.json
βββ README.md
The next phase will cover SOLID principles and other fundamental design principles:
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
- DRY (Don't Repeat Yourself)
- KISS (Keep It Simple, Stupid)
- YAGNI (You Aren't Gonna Need It)
- Creational Patterns (Factory, Builder, Singleton)
- Structural Patterns (Adapter, Decorator, Facade)
- Behavioral Patterns (Observer, Strategy, Command)
By completing this repository, you will:
β
Understand core OOP concepts (Encapsulation, Abstraction)
β
Master all five class relationships (Association, Aggregation, Composition, Dependency, Realization)
β
Apply design principles in real-world scenarios
β
Build maintainable, scalable, and testable code
β
Understand when to use each relationship type
β
Design systems with proper separation of concerns
- All examples are written in TypeScript for type safety and better OOP support
- Code follows production-grade practices with proper error handling
- Each module includes comments explaining the concepts
- The Ride Booking System serves as a comprehensive example combining all concepts
This is a personal learning repository. If you find any issues or have suggestions:
- Create an issue describing the problem or suggestion
- Fork the repository (if applicable)
- Make your changes
- Submit a pull request
This project is for educational purposes.
- Inspired by real-world system design challenges
- Based on industry best practices and design patterns
- Built to understand Low Level Design concepts deeply
Happy Learning! π