לדלג לתוכן

4.3 פרוטוטייפים ומחלקות תרגול

תרגול - פרוטוטייפים ומחלקות


תרגיל 1 - מחלקת Stack

צרו מחלקה Stack שמממשת מחסנית (LIFO - last in, first out):

  • push(item) - מוסיף פריט לראש המחסנית
  • pop() - מוציא ומחזיר את הפריט העליון (זורק שגיאה אם ריקה)
  • peek() - מחזיר את הפריט העליון בלי להוציא אותו
  • isEmpty() - מחזיר true אם המחסנית ריקה
  • size - getter שמחזיר את מספר הפריטים
  • clear() - מרוקן את המחסנית
  • toArray() - מחזיר עותק של המחסנית כמערך

המערך הפנימי צריך להיות פרטי (#).

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]

תרגיל 2 - ירושה - צורות גיאומטריות

צרו היררכיית מחלקות:

Shape (מחלקת בסיס):
- constructor(color) - צבע
- area() - זורקת שגיאה ("must be implemented")
- describe() - מחזירה מחרוזת: "<ClassName> with area <area>"

Rectangle (יורש מ-Shape):
- constructor(width, height, color)
- area() - שטח מלבן
- perimeter - getter להיקף

Circle (יורש מ-Shape):
- constructor(radius, color)
- area() - שטח עיגול
- perimeter - getter להיקף

Square (יורש מ-Rectangle):
- constructor(side, color)
- רק שדה אחד - side

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 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

צרו מחלקה Temperature שמנהלת טמפרטורה:

  • constructor(celsius) - מקבלת ערך בצלזיוס
  • #celsius - שדה פרטי
  • get celsius / set celsius - getter ו-setter (סטר מוודא שהערך מעל -273.15)
  • get fahrenheit / set fahrenheit - getter ו-setter שממירים אוטומטית
  • get kelvin / set kelvin - getter ו-setter שממירים אוטומטית
  • static fromFahrenheit(f) - factory method
  • static fromKelvin(k) - factory method
  • toString() - מחזירה "<celsius>C / <fahrenheit>F / <kelvin>K"

נוסחאות:
- F = C * 9/5 + 32
- K = C + 273.15

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

תרגיל 4 - מחלקה LinkedList

צרו מחלקת LinkedList (רשימה מקושרת):

צרו קודם מחלקת עזר Node:
- constructor(value) - ערך ו-next שמצביע על הצומת הבא (ברירת מחדל null)

מחלקת LinkedList:
- append(value) - מוסיף צומת בסוף
- prepend(value) - מוסיף צומת בתחילה
- find(value) - מחזיר את הצומת הראשון עם הערך, או null
- remove(value) - מסיר את הצומת הראשון עם הערך
- toArray() - מחזיר מערך של כל הערכים
- size - getter שמחזיר את מספר הצמתים
- toString() - מחזיר מחרוזת כמו "1 -> 2 -> 3 -> null"

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"

תרגיל 5 - מערכת הרשאות עם ירושה

צרו מערכת משתמשים:

User (בסיס):
- constructor(name, email)
- login() - מדפיסה הודעת כניסה
- getPermissions() - מחזירה ["read"]
- describe() - מחזירה "User: <name> (<email>)"

Moderator (יורש מ-User):
- constructor(name, email, department)
- getPermissions() - מחזירה ["read", "edit", "delete"]
- ban(user) - מדפיסה הודעה שהמשתמש הורחק
- describe() - מוסיפה את ה-department

Admin (יורש מ-Moderator):
- constructor(name, email)
- getPermissions() - מחזירה ["read", "edit", "delete", "admin"]
- createUser(name, email) - יוצרת User חדש ומחזירה אותו
- static superAdmin(email) - factory שיוצרת admin עם השם "Super Admin"

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(admin instanceof User); // true

תרגיל 6 - מחלקה Iterable

צרו מחלקה Range שמייצגת טווח מספרים ותומכת ב-for...of:

  • constructor(start, end, step = 1)
  • צריכה לעבוד עם for...of (צריך לממש [Symbol.iterator]())
  • includes(n) - בודקת אם מספר בטווח
  • toArray() - מחזירה מערך של כל המספרים בטווח
  • get length - כמה מספרים בטווח

רמז: כדי לתמוך ב-for...of, צריך לממש מתודה [Symbol.iterator]() שמחזירה אובייקט עם מתודת next().

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]

תרגיל 7 - תבנית Observer

ממשו את תבנית העיצוב Observer עם מחלקות:

EventEmitter:
- on(event, listener) - רושם listener
- off(event, listener) - מסיר listener
- emit(event, ...data) - מפעיל את כל ה-listeners לאירוע

Store (יורש מ-EventEmitter):
- constructor(initialState) - מקבל state התחלתי (אובייקט)
- getState() - מחזיר עותק של ה-state
- setState(updates) - מעדכן את ה-state ומפעיל אירוע "change" עם ה-state החדש
- subscribe(listener) - קיצור ל-on("change", listener), מחזירה פונקציית unsubscribe

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 log - unsubscribed