4.3 פרוטוטייפים ומחלקות פתרון
פתרון - פרוטוטייפים ומחלקות¶
תרגיל 1 - מחלקת Stack¶
class Stack {
#items = [];
push(item) {
this.#items.push(item);
}
pop() {
if (this.isEmpty()) {
throw new Error("Stack is empty");
}
return this.#items.pop();
}
peek() {
if (this.isEmpty()) {
throw new Error("Stack is empty");
}
return this.#items[this.#items.length - 1];
}
isEmpty() {
return this.#items.length === 0;
}
get size() {
return this.#items.length;
}
clear() {
this.#items = [];
}
toArray() {
return [...this.#items];
}
}
// test
const stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.peek()); // 3
console.log(stack.pop()); // 3
console.log(stack.size); // 2
console.log(stack.toArray()); // [1, 2]
stack.clear();
console.log(stack.isEmpty()); // true
תרגיל 2 - ירושה - צורות גיאומטריות¶
class Shape {
constructor(color) {
this.color = color;
}
area() {
throw new Error("area() must be implemented by subclass");
}
describe() {
return `${this.constructor.name} with area ${this.area()}`;
}
}
class Rectangle extends Shape {
constructor(width, height, color) {
super(color);
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
get perimeter() {
return 2 * (this.width + this.height);
}
}
class Circle extends Shape {
constructor(radius, color) {
super(color);
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
get perimeter() {
return 2 * Math.PI * this.radius;
}
}
class Square extends Rectangle {
constructor(side, color) {
super(side, side, color);
}
}
// test
const rect = new Rectangle(10, 5, "red");
console.log(rect.area()); // 50
console.log(rect.perimeter); // 30
console.log(rect.describe()); // "Rectangle with area 50"
const circle = new Circle(7, "green");
console.log(circle.area().toFixed(2)); // "153.94"
console.log(circle.perimeter.toFixed(2)); // "43.98"
const square = new Square(4, "blue");
console.log(square.area()); // 16
console.log(square.perimeter); // 16
console.log(square instanceof Rectangle); // true
console.log(square instanceof Shape); // true
תרגיל 3 - מחלקה עם שדות פרטיים ו-validation¶
class Temperature {
#celsius;
constructor(celsius) {
this.celsius = celsius; // use the setter for validation
}
get celsius() {
return this.#celsius;
}
set celsius(value) {
if (value < -273.15) {
throw new Error("Temperature cannot be below absolute zero (-273.15C)");
}
this.#celsius = value;
}
get fahrenheit() {
return this.#celsius * 9 / 5 + 32;
}
set fahrenheit(f) {
this.celsius = (f - 32) * 5 / 9;
}
get kelvin() {
return this.#celsius + 273.15;
}
set kelvin(k) {
this.celsius = k - 273.15;
}
static fromFahrenheit(f) {
return new Temperature((f - 32) * 5 / 9);
}
static fromKelvin(k) {
return new Temperature(k - 273.15);
}
toString() {
return `${this.#celsius}C / ${this.fahrenheit}F / ${this.kelvin}K`;
}
}
// test
const t = new Temperature(100);
console.log(t.fahrenheit); // 212
console.log(t.kelvin); // 373.15
console.log(t.toString()); // "100C / 212F / 373.15K"
t.fahrenheit = 32;
console.log(t.celsius); // 0
const t2 = Temperature.fromKelvin(0);
console.log(t2.celsius); // -273.15
// validation
// new Temperature(-300); // Error: Temperature cannot be below absolute zero
תרגיל 4 - מחלקה LinkedList¶
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
class LinkedList {
#head = null;
#size = 0;
append(value) {
const newNode = new Node(value);
if (!this.#head) {
this.#head = newNode;
} else {
let current = this.#head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
this.#size++;
}
prepend(value) {
const newNode = new Node(value);
newNode.next = this.#head;
this.#head = newNode;
this.#size++;
}
find(value) {
let current = this.#head;
while (current) {
if (current.value === value) {
return current;
}
current = current.next;
}
return null;
}
remove(value) {
if (!this.#head) return false;
if (this.#head.value === value) {
this.#head = this.#head.next;
this.#size--;
return true;
}
let current = this.#head;
while (current.next) {
if (current.next.value === value) {
current.next = current.next.next;
this.#size--;
return true;
}
current = current.next;
}
return false;
}
toArray() {
const result = [];
let current = this.#head;
while (current) {
result.push(current.value);
current = current.next;
}
return result;
}
get size() {
return this.#size;
}
toString() {
const parts = this.toArray().map(String);
parts.push("null");
return parts.join(" -> ");
}
}
// test
const list = new LinkedList();
list.append(1);
list.append(2);
list.append(3);
list.prepend(0);
console.log(list.toString()); // "0 -> 1 -> 2 -> 3 -> null"
console.log(list.size); // 4
list.remove(2);
console.log(list.toString()); // "0 -> 1 -> 3 -> null"
console.log(list.find(3).value); // 3
console.log(list.find(99)); // null
תרגיל 5 - מערכת הרשאות עם ירושה¶
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
login() {
console.log(`${this.name} logged in`);
}
getPermissions() {
return ["read"];
}
describe() {
return `User: ${this.name} (${this.email})`;
}
}
class Moderator extends User {
constructor(name, email, department) {
super(name, email);
this.department = department;
}
getPermissions() {
return ["read", "edit", "delete"];
}
ban(user) {
console.log(`${this.name} banned ${user.name}`);
}
describe() {
return `${super.describe()} - Moderator [${this.department}]`;
}
}
class Admin extends Moderator {
constructor(name, email) {
super(name, email, "All");
}
getPermissions() {
return ["read", "edit", "delete", "admin"];
}
createUser(name, email) {
console.log(`${this.name} created user ${name}`);
return new User(name, email);
}
static superAdmin(email) {
return new Admin("Super Admin", email);
}
}
// test
const user = new User("Alice", "alice@mail.com");
const mod = new Moderator("Bob", "bob@mail.com", "Support");
const admin = Admin.superAdmin("admin@mail.com");
console.log(user.getPermissions()); // ["read"]
console.log(mod.getPermissions()); // ["read", "edit", "delete"]
console.log(admin.getPermissions()); // ["read", "edit", "delete", "admin"]
console.log(mod.describe());
// "User: Bob (bob@mail.com) - Moderator [Support]"
mod.ban(user); // "Bob banned Alice"
admin.createUser("Charlie", "charlie@mail.com");
// "Super Admin created user Charlie"
console.log(admin instanceof User); // true
console.log(admin instanceof Moderator); // true
console.log(admin instanceof Admin); // true
תרגיל 6 - מחלקה Iterable¶
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const end = this.end;
const step = this.step;
return {
next() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
}
return { done: true };
}
};
}
includes(n) {
if (n < this.start || n > this.end) return false;
return (n - this.start) % this.step === 0;
}
toArray() {
return [...this];
}
get length() {
return Math.max(0, Math.floor((this.end - this.start) / this.step) + 1);
}
}
// test
const range = new Range(1, 10, 2);
for (const n of range) {
console.log(n); // 1, 3, 5, 7, 9
}
console.log(range.includes(5)); // true
console.log(range.includes(4)); // false
console.log(range.toArray()); // [1, 3, 5, 7, 9]
console.log(range.length); // 5
console.log([...range]); // [1, 3, 5, 7, 9]
[Symbol.iterator]() מחזיר iterator - אובייקט עם מתודת next() שמחזירה { value, done }. זה מה שמאפשר ל-for...of ולספרד (...) לעבוד.
תרגיל 7 - תבנית Observer¶
class EventEmitter {
#events = {};
on(event, listener) {
if (!this.#events[event]) {
this.#events[event] = [];
}
this.#events[event].push(listener);
}
off(event, listener) {
if (!this.#events[event]) return;
this.#events[event] = this.#events[event].filter(l => l !== listener);
}
emit(event, ...data) {
if (!this.#events[event]) return;
this.#events[event].forEach(listener => listener(...data));
}
}
class Store extends EventEmitter {
#state;
constructor(initialState = {}) {
super();
this.#state = { ...initialState };
}
getState() {
return { ...this.#state };
}
setState(updates) {
this.#state = { ...this.#state, ...updates };
this.emit("change", this.getState());
}
subscribe(listener) {
this.on("change", listener);
// return unsubscribe function
return () => {
this.off("change", listener);
};
}
}
// test
const store = new Store({ count: 0, name: "App" });
const unsubscribe = store.subscribe((state) => {
console.log("State changed:", state);
});
store.setState({ count: 1 });
// "State changed: { count: 1, name: "App" }"
store.setState({ count: 2, name: "Updated" });
// "State changed: { count: 2, name: "Updated" }"
unsubscribe();
store.setState({ count: 3 }); // no output - unsubscribed
console.log(store.getState()); // { count: 3, name: "Updated" }
הפונקציה subscribe מחזירה closure שזוכר את ה-listener וקורא ל-off כדי להסיר אותו. זו תבנית נפוצה מאוד ב-React ובספריות state management.