javascript
exercises
exercises.js⚡javascript
/**
* ============================================
* 8.2 CLASS INHERITANCE - EXERCISES
* ============================================
* Practice inheritance concepts
*/
/**
* EXERCISE 1: Basic Inheritance
* Difficulty: ⭐
*
* Create a class hierarchy:
* - Animal class with name property and speak() method
* - Dog class that extends Animal with bark() method
* - Cat class that extends Animal with meow() method
*/
function exercise1() {
// YOUR CODE HERE
// TEST CASES (uncomment to test)
// const dog = new Dog("Rex");
// console.log(dog.name); // "Rex"
// dog.speak(); // "[name] makes a sound"
// dog.bark(); // "[name] says: Woof!"
//
// const cat = new Cat("Whiskers");
// cat.speak(); // "[name] makes a sound"
// cat.meow(); // "[name] says: Meow!"
}
/*
* SOLUTION 1:
*
* class Animal {
* constructor(name) {
* this.name = name;
* }
*
* speak() {
* console.log(`${this.name} makes a sound`);
* }
* }
*
* class Dog extends Animal {
* bark() {
* console.log(`${this.name} says: Woof!`);
* }
* }
*
* class Cat extends Animal {
* meow() {
* console.log(`${this.name} says: Meow!`);
* }
* }
*/
/**
* EXERCISE 2: Constructor with super()
* Difficulty: ⭐
*
* Create:
* - Person class with name and age
* - Student class extending Person with grade property
* - Student constructor must use super() and add grade
*/
function exercise2() {
// YOUR CODE HERE
// TEST CASES
// const student = new Student("Alice", 20, "A");
// console.log(student.name); // "Alice"
// console.log(student.age); // 20
// console.log(student.grade); // "A"
// console.log(student instanceof Person); // true
}
/*
* SOLUTION 2:
*
* class Person {
* constructor(name, age) {
* this.name = name;
* this.age = age;
* }
* }
*
* class Student extends Person {
* constructor(name, age, grade) {
* super(name, age);
* this.grade = grade;
* }
* }
*/
/**
* EXERCISE 3: Method Overriding
* Difficulty: ⭐
*
* Create Shape class with area() method that returns 0.
* Create Circle and Rectangle that override area() with proper calculations.
*/
function exercise3() {
// YOUR CODE HERE
// TEST CASES
// const circle = new Circle(5);
// console.log(circle.area()); // ~78.54 (Math.PI * 25)
//
// const rect = new Rectangle(4, 6);
// console.log(rect.area()); // 24
}
/*
* SOLUTION 3:
*
* class Shape {
* area() {
* return 0;
* }
* }
*
* 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;
* }
* }
*/
/**
* EXERCISE 4: Using super.method()
* Difficulty: ⭐⭐
*
* Create:
* - Vehicle class with describe() returning "Vehicle: [make] [model]"
* - Car class with doors, describe() should include parent info + doors
* - Use super.describe() in Car
*/
function exercise4() {
// YOUR CODE HERE
// TEST CASES
// const car = new Car("Toyota", "Camry", 4);
// console.log(car.describe());
// // "Vehicle: Toyota Camry, Doors: 4"
}
/*
* SOLUTION 4:
*
* class Vehicle {
* constructor(make, model) {
* this.make = make;
* this.model = model;
* }
*
* describe() {
* return `Vehicle: ${this.make} ${this.model}`;
* }
* }
*
* class Car extends Vehicle {
* constructor(make, model, doors) {
* super(make, model);
* this.doors = doors;
* }
*
* describe() {
* return `${super.describe()}, Doors: ${this.doors}`;
* }
* }
*/
/**
* EXERCISE 5: Custom Error Class
* Difficulty: ⭐⭐
*
* Create a ValidationError class that:
* - Extends Error
* - Has field property (which field failed)
* - Has value property (the invalid value)
* - Sets appropriate error name
*/
function exercise5() {
// YOUR CODE HERE
// TEST CASES
// const error = new ValidationError("email", "invalid", "Invalid email format");
// console.log(error.name); // "ValidationError"
// console.log(error.message); // "Invalid email format"
// console.log(error.field); // "email"
// console.log(error.value); // "invalid"
// console.log(error instanceof Error); // true
}
/*
* SOLUTION 5:
*
* class ValidationError extends Error {
* constructor(field, value, message) {
* super(message);
* this.name = "ValidationError";
* this.field = field;
* this.value = value;
* }
* }
*/
/**
* EXERCISE 6: Extending Array
* Difficulty: ⭐⭐
*
* Create a Queue class that extends Array:
* - enqueue(item) - adds to end
* - dequeue() - removes and returns from front
* - peek() - returns front without removing
* - isEmpty() - check if empty
*/
function exercise6() {
// YOUR CODE HERE
// TEST CASES
// const queue = new Queue();
// queue.enqueue(1);
// queue.enqueue(2);
// queue.enqueue(3);
// console.log(queue.peek()); // 1
// console.log(queue.dequeue()); // 1
// console.log(queue.dequeue()); // 2
// console.log(queue.isEmpty()); // false
// console.log(queue.dequeue()); // 3
// console.log(queue.isEmpty()); // true
}
/*
* SOLUTION 6:
*
* class Queue extends Array {
* enqueue(item) {
* this.push(item);
* return this;
* }
*
* dequeue() {
* return this.shift();
* }
*
* peek() {
* return this[0];
* }
*
* isEmpty() {
* return this.length === 0;
* }
* }
*/
/**
* EXERCISE 7: Three-Level Inheritance
* Difficulty: ⭐⭐
*
* Create:
* - Entity: id, createdAt
* - User: extends Entity, adds name, email
* - Admin: extends User, adds permissions array and hasPermission(perm)
*/
function exercise7() {
// YOUR CODE HERE
// TEST CASES
// const admin = new Admin(1, "Alice", "alice@example.com", ["read", "write", "delete"]);
// console.log(admin.id); // 1
// console.log(admin.name); // "Alice"
// console.log(admin.createdAt); // Date object
// console.log(admin.hasPermission("write")); // true
// console.log(admin.hasPermission("admin")); // false
// console.log(admin instanceof Entity); // true
// console.log(admin instanceof User); // true
// console.log(admin instanceof Admin); // true
}
/*
* SOLUTION 7:
*
* class Entity {
* constructor(id) {
* this.id = id;
* this.createdAt = new Date();
* }
* }
*
* class User extends Entity {
* constructor(id, name, email) {
* super(id);
* this.name = name;
* this.email = email;
* }
* }
*
* class Admin extends User {
* constructor(id, name, email, permissions) {
* super(id, name, email);
* this.permissions = permissions;
* }
*
* hasPermission(perm) {
* return this.permissions.includes(perm);
* }
* }
*/
/**
* EXERCISE 8: Abstract Class Pattern
* Difficulty: ⭐⭐⭐
*
* Create an abstract Formatter class:
* - Cannot be instantiated directly (throw error if new.target === Formatter)
* - Has abstract format(data) method
* Create JSONFormatter and XMLFormatter implementations
*/
function exercise8() {
// YOUR CODE HERE
// TEST CASES
// try {
// const formatter = new Formatter(); // Should throw
// } catch (e) {
// console.log("Correctly throws:", e.message);
// }
//
// const json = new JSONFormatter();
// console.log(json.format({ name: "Alice", age: 30 }));
// // '{"name":"Alice","age":30}'
//
// const xml = new XMLFormatter("user");
// console.log(xml.format({ name: "Alice", age: 30 }));
// // '<user><name>Alice</name><age>30</age></user>'
}
/*
* SOLUTION 8:
*
* class Formatter {
* constructor() {
* if (new.target === Formatter) {
* throw new Error("Formatter is abstract and cannot be instantiated directly");
* }
* }
*
* format(data) {
* throw new Error("format() must be implemented by subclass");
* }
* }
*
* class JSONFormatter extends Formatter {
* format(data) {
* return JSON.stringify(data);
* }
* }
*
* class XMLFormatter extends Formatter {
* constructor(rootElement = "root") {
* super();
* this.rootElement = rootElement;
* }
*
* format(data) {
* const inner = Object.entries(data)
* .map(([key, value]) => `<${key}>${value}</${key}>`)
* .join('');
* return `<${this.rootElement}>${inner}</${this.rootElement}>`;
* }
* }
*/
/**
* EXERCISE 9: Polymorphic Processing
* Difficulty: ⭐⭐⭐
*
* Create a payment system:
* - PaymentMethod base class with process(amount) and getName()
* - CreditCard, PayPal, BankTransfer implementations
* - Create processPayment(method, amount) function that works with any payment type
*/
function exercise9() {
// YOUR CODE HERE
// TEST CASES
// const methods = [
// new CreditCard("1234-5678-9012-3456"),
// new PayPal("user@example.com"),
// new BankTransfer("123456789")
// ];
//
// for (const method of methods) {
// processPayment(method, 100);
// // Credit Card (*3456): Processing $100
// // PayPal (user@example.com): Processing $100
// // Bank Transfer (******789): Processing $100
// }
}
/*
* SOLUTION 9:
*
* class PaymentMethod {
* process(amount) {
* throw new Error("process() must be implemented");
* }
*
* getName() {
* throw new Error("getName() must be implemented");
* }
* }
*
* class CreditCard extends PaymentMethod {
* constructor(cardNumber) {
* super();
* this.cardNumber = cardNumber;
* }
*
* process(amount) {
* return `Processing $${amount} with Credit Card`;
* }
*
* getName() {
* return `Credit Card (*${this.cardNumber.slice(-4)})`;
* }
* }
*
* class PayPal extends PaymentMethod {
* constructor(email) {
* super();
* this.email = email;
* }
*
* process(amount) {
* return `Processing $${amount} with PayPal`;
* }
*
* getName() {
* return `PayPal (${this.email})`;
* }
* }
*
* class BankTransfer extends PaymentMethod {
* constructor(accountNumber) {
* super();
* this.accountNumber = accountNumber;
* }
*
* process(amount) {
* return `Processing $${amount} with Bank Transfer`;
* }
*
* getName() {
* return `Bank Transfer (******${this.accountNumber.slice(-3)})`;
* }
* }
*
* function processPayment(method, amount) {
* console.log(`${method.getName()}: ${method.process(amount)}`);
* }
*/
/**
* EXERCISE 10: Template Method Pattern
* Difficulty: ⭐⭐⭐
*
* Create a Report base class with generate() template method:
* - fetchData() - abstract, must be implemented
* - processData(data) - can be overridden, default returns data as-is
* - formatOutput(data) - abstract, must be implemented
* - generate() calls these in order and returns result
*
* Create SalesReport and InventoryReport implementations
*/
function exercise10() {
// YOUR CODE HERE
// TEST CASES
// const salesReport = new SalesReport();
// console.log(salesReport.generate());
// // Should fetch sales data, optionally process, and format
//
// const inventoryReport = new InventoryReport();
// console.log(inventoryReport.generate());
}
/*
* SOLUTION 10:
*
* class Report {
* fetchData() {
* throw new Error("fetchData() must be implemented");
* }
*
* processData(data) {
* return data; // Default: no processing
* }
*
* formatOutput(data) {
* throw new Error("formatOutput() must be implemented");
* }
*
* generate() {
* const data = this.fetchData();
* const processed = this.processData(data);
* return this.formatOutput(processed);
* }
* }
*
* class SalesReport extends Report {
* fetchData() {
* return [
* { product: "Widget", sales: 100 },
* { product: "Gadget", sales: 50 }
* ];
* }
*
* processData(data) {
* const total = data.reduce((sum, item) => sum + item.sales, 0);
* return { items: data, total };
* }
*
* formatOutput(data) {
* return `Sales Report\n${data.items.map(i =>
* `${i.product}: ${i.sales}`).join('\n')}\nTotal: ${data.total}`;
* }
* }
*
* class InventoryReport extends Report {
* fetchData() {
* return [
* { item: "Widget", quantity: 200 },
* { item: "Gadget", quantity: 75 }
* ];
* }
*
* formatOutput(data) {
* return `Inventory Report\n${data.map(i =>
* `${i.item}: ${i.quantity} in stock`).join('\n')}`;
* }
* }
*/
/**
* EXERCISE 11: Extending Map with Expiry
* Difficulty: ⭐⭐⭐
*
* Create ExpiringMap that extends Map:
* - set(key, value, ttl) - ttl in milliseconds
* - get(key) - returns undefined if expired
* - cleanup() - removes all expired entries
*/
function exercise11() {
// YOUR CODE HERE
// TEST CASES (use setTimeout to test expiry)
// const cache = new ExpiringMap();
// cache.set("key1", "value1", 100); // expires in 100ms
// cache.set("key2", "value2", 1000); // expires in 1000ms
//
// console.log(cache.get("key1")); // "value1"
// console.log(cache.get("key2")); // "value2"
//
// setTimeout(() => {
// console.log(cache.get("key1")); // undefined (expired)
// console.log(cache.get("key2")); // "value2" (still valid)
// }, 200);
}
/*
* SOLUTION 11:
*
* class ExpiringMap extends Map {
* constructor() {
* super();
* this._expiries = new Map();
* }
*
* set(key, value, ttl) {
* super.set(key, value);
* if (ttl) {
* this._expiries.set(key, Date.now() + ttl);
* }
* return this;
* }
*
* get(key) {
* if (this._expiries.has(key)) {
* if (Date.now() > this._expiries.get(key)) {
* this.delete(key);
* return undefined;
* }
* }
* return super.get(key);
* }
*
* delete(key) {
* this._expiries.delete(key);
* return super.delete(key);
* }
*
* cleanup() {
* const now = Date.now();
* for (const [key, expiry] of this._expiries) {
* if (now > expiry) {
* this.delete(key);
* }
* }
* }
* }
*/
/**
* EXERCISE 12: Event-Driven Inheritance
* Difficulty: ⭐⭐⭐⭐
*
* Create:
* - EventEmitter base class with on(), off(), emit()
* - ObservableCollection extending EventEmitter
* - Emits 'add', 'remove', 'clear' events
* - Has add(), remove(), clear(), items getter
*/
function exercise12() {
// YOUR CODE HERE
// TEST CASES
// const collection = new ObservableCollection();
//
// collection.on('add', (item) => console.log(`Added: ${item}`));
// collection.on('remove', (item) => console.log(`Removed: ${item}`));
// collection.on('clear', () => console.log('Collection cleared'));
//
// collection.add('A'); // Logs: "Added: A"
// collection.add('B'); // Logs: "Added: B"
// collection.remove('A'); // Logs: "Removed: A"
// console.log(collection.items); // ['B']
// collection.clear(); // Logs: "Collection cleared"
}
/*
* SOLUTION 12:
*
* class EventEmitter {
* constructor() {
* this._events = new Map();
* }
*
* on(event, callback) {
* if (!this._events.has(event)) {
* this._events.set(event, []);
* }
* this._events.get(event).push(callback);
* return this;
* }
*
* off(event, callback) {
* if (this._events.has(event)) {
* const callbacks = this._events.get(event);
* const index = callbacks.indexOf(callback);
* if (index > -1) {
* callbacks.splice(index, 1);
* }
* }
* return this;
* }
*
* emit(event, ...args) {
* if (this._events.has(event)) {
* for (const callback of this._events.get(event)) {
* callback(...args);
* }
* }
* return this;
* }
* }
*
* class ObservableCollection extends EventEmitter {
* constructor() {
* super();
* this._items = [];
* }
*
* add(item) {
* this._items.push(item);
* this.emit('add', item);
* return this;
* }
*
* remove(item) {
* const index = this._items.indexOf(item);
* if (index > -1) {
* this._items.splice(index, 1);
* this.emit('remove', item);
* }
* return this;
* }
*
* clear() {
* this._items = [];
* this.emit('clear');
* return this;
* }
*
* get items() {
* return [...this._items];
* }
* }
*/
/**
* EXERCISE 13: Mixin Factory
* Difficulty: ⭐⭐⭐⭐
*
* Create mixin factories:
* - Timestamped(Base) - adds createdAt, updatedAt, touch()
* - Identifiable(Base) - adds auto-generated id
* - Comparable(Base) - adds compareTo(other), requires implementing compareKey()
*
* Create a Task class using all three mixins
*/
function exercise13() {
// YOUR CODE HERE
// TEST CASES
// const task1 = new Task("Learn JavaScript", 1);
// const task2 = new Task("Build Project", 2);
//
// console.log(task1.id); // Auto-generated unique ID
// console.log(task1.createdAt); // Date object
//
// task1.touch();
// console.log(task1.updatedAt); // Updated date
//
// console.log(task1.compareTo(task2)); // -1 (priority comparison)
}
/*
* SOLUTION 13:
*
* const Timestamped = (Base) => class extends Base {
* constructor(...args) {
* super(...args);
* this.createdAt = new Date();
* this.updatedAt = new Date();
* }
*
* touch() {
* this.updatedAt = new Date();
* return this;
* }
* };
*
* const Identifiable = (Base) => class extends Base {
* static _nextId = 1;
*
* constructor(...args) {
* super(...args);
* this.id = this.constructor._nextId++;
* }
* };
*
* const Comparable = (Base) => class extends Base {
* compareKey() {
* throw new Error("compareKey() must be implemented");
* }
*
* compareTo(other) {
* const a = this.compareKey();
* const b = other.compareKey();
* if (a < b) return -1;
* if (a > b) return 1;
* return 0;
* }
* };
*
* class BaseTask {
* constructor(title, priority) {
* this.title = title;
* this.priority = priority;
* }
* }
*
* class Task extends Comparable(Identifiable(Timestamped(BaseTask))) {
* compareKey() {
* return this.priority;
* }
* }
*/
/**
* EXERCISE 14: Repository Pattern with Inheritance
* Difficulty: ⭐⭐⭐⭐
*
* Create:
* - Repository<T> abstract class with:
* - Abstract: findById(id), findAll(), save(entity), delete(id)
* - Concrete: exists(id), count()
* - InMemoryRepository implementation storing data in Map
* - UserRepository extending InMemoryRepository with findByEmail()
*/
function exercise14() {
// YOUR CODE HERE
// TEST CASES
// const userRepo = new UserRepository();
//
// userRepo.save({ id: 1, name: "Alice", email: "alice@example.com" });
// userRepo.save({ id: 2, name: "Bob", email: "bob@example.com" });
//
// console.log(userRepo.count()); // 2
// console.log(userRepo.exists(1)); // true
// console.log(userRepo.findById(1)); // { id: 1, name: "Alice", ... }
// console.log(userRepo.findByEmail("bob@example.com")); // { id: 2, name: "Bob", ... }
// console.log(userRepo.findAll().length); // 2
//
// userRepo.delete(1);
// console.log(userRepo.count()); // 1
}
/*
* SOLUTION 14:
*
* class Repository {
* constructor() {
* if (new.target === Repository) {
* throw new Error("Repository is abstract");
* }
* }
*
* findById(id) { throw new Error("Not implemented"); }
* findAll() { throw new Error("Not implemented"); }
* save(entity) { throw new Error("Not implemented"); }
* delete(id) { throw new Error("Not implemented"); }
*
* exists(id) {
* return this.findById(id) !== null;
* }
*
* count() {
* return this.findAll().length;
* }
* }
*
* class InMemoryRepository extends Repository {
* constructor() {
* super();
* this._store = new Map();
* }
*
* findById(id) {
* return this._store.get(id) || null;
* }
*
* findAll() {
* return [...this._store.values()];
* }
*
* save(entity) {
* this._store.set(entity.id, entity);
* return entity;
* }
*
* delete(id) {
* return this._store.delete(id);
* }
* }
*
* class UserRepository extends InMemoryRepository {
* findByEmail(email) {
* return this.findAll().find(user => user.email === email) || null;
* }
* }
*/
/**
* EXERCISE 15: Complete Application Example
* Difficulty: ⭐⭐⭐⭐⭐
*
* Build a game entity system:
* - GameObject: id, position {x, y}, update(), render()
* - MovableObject extends GameObject: velocity, move()
* - CollidableObject extends MovableObject: hitbox, collidesWith(other)
* - Player extends CollidableObject: health, takeDamage(), isAlive()
* - Enemy extends CollidableObject: damage, attack(target)
*
* Create a simple game loop simulation
*/
function exercise15() {
// YOUR CODE HERE
// TEST CASES
// const player = new Player(1, { x: 0, y: 0 }, 100);
// const enemy = new Enemy(2, { x: 5, y: 0 }, 10);
//
// // Move player
// player.velocity = { x: 1, y: 0 };
// player.move();
// console.log(player.position); // { x: 1, y: 0 }
//
// // Check collision (simplified)
// enemy.position = { x: 1, y: 0 };
// if (player.collidesWith(enemy)) {
// enemy.attack(player);
// console.log(player.health); // 90
// }
//
// // Game loop simulation
// console.log(player.isAlive()); // true
}
/*
* SOLUTION 15:
*
* class GameObject {
* static nextId = 1;
*
* constructor(position = { x: 0, y: 0 }) {
* this.id = GameObject.nextId++;
* this.position = { ...position };
* }
*
* update() {
* // Override in subclasses
* }
*
* render() {
* console.log(`Object ${this.id} at (${this.position.x}, ${this.position.y})`);
* }
* }
*
* class MovableObject extends GameObject {
* constructor(position) {
* super(position);
* this.velocity = { x: 0, y: 0 };
* }
*
* move() {
* this.position.x += this.velocity.x;
* this.position.y += this.velocity.y;
* return this;
* }
*
* update() {
* super.update();
* this.move();
* }
* }
*
* class CollidableObject extends MovableObject {
* constructor(position, hitboxSize = 1) {
* super(position);
* this.hitboxSize = hitboxSize;
* }
*
* collidesWith(other) {
* const dx = Math.abs(this.position.x - other.position.x);
* const dy = Math.abs(this.position.y - other.position.y);
* const minDist = (this.hitboxSize + other.hitboxSize) / 2;
* return dx < minDist && dy < minDist;
* }
* }
*
* class Player extends CollidableObject {
* constructor(position, health = 100) {
* super(position, 1);
* this.health = health;
* this.maxHealth = health;
* }
*
* takeDamage(amount) {
* this.health = Math.max(0, this.health - amount);
* console.log(`Player took ${amount} damage! Health: ${this.health}`);
* }
*
* isAlive() {
* return this.health > 0;
* }
*
* render() {
* console.log(`Player [HP: ${this.health}/${this.maxHealth}] at (${this.position.x}, ${this.position.y})`);
* }
* }
*
* class Enemy extends CollidableObject {
* constructor(position, damage = 10) {
* super(position, 1);
* this.damage = damage;
* }
*
* attack(target) {
* if (target.takeDamage) {
* target.takeDamage(this.damage);
* }
* }
*
* render() {
* console.log(`Enemy [DMG: ${this.damage}] at (${this.position.x}, ${this.position.y})`);
* }
* }
*
* // Game loop simulation
* function gameLoop(entities, frames = 5) {
* for (let frame = 0; frame < frames; frame++) {
* console.log(`\n--- Frame ${frame + 1} ---`);
*
* // Update all entities
* for (const entity of entities) {
* entity.update();
* }
*
* // Check collisions
* for (let i = 0; i < entities.length; i++) {
* for (let j = i + 1; j < entities.length; j++) {
* if (entities[i].collidesWith(entities[j])) {
* console.log(`Collision between ${entities[i].constructor.name} and ${entities[j].constructor.name}`);
* }
* }
* }
*
* // Render all entities
* for (const entity of entities) {
* entity.render();
* }
* }
* }
*/
// Run exercises
console.log('=== Class Inheritance Exercises ===\n');
// Uncomment to run individual exercises
// exercise1();
// exercise2();
// exercise3();
// exercise4();
// exercise5();
// exercise6();
// exercise7();
// exercise8();
// exercise9();
// exercise10();
// exercise11();
// exercise12();
// exercise13();
// exercise14();
// exercise15();
console.log('\nUncomment exercises to practice!');