במאמר הקודם למדנו על גנריות בטייפסקריפט – דרך לאפשר למי שצורך את הפונקציות שלנו לשדר לנו איזה פלט הוא שולח או מקווה לקבל. במאמר הזה נלמד על קלאסים.
למי שלא יודע קלאסים זה Syntactic Sugar – הם לא קיימים "באמת". גם מתכנתי ריאקט שהצטרפו בשנים האחרונות פחות מכירים את הקלאס ודרך ארגון הקוד שלו. באופן עקרוני, ג'אווהסקריפט הוא לא מונחית עצמים אלא מונחית פרוטוטייפ וזה הבדל מאוד גדול. אבל טייפסקריפט היא גם, במובן מסוים, חוטאת לג'אווהסקריפט ונועדה במקור שמתכנתים בשפות אחרות ירגישו איתה יותר בנוח. אז קלאסים איט איז! אולי יש מצב שתרצו לחזור על מה זה קלאסים במדריך שלי על ES6 Class.
בגדול קלאסים בטייפסקריפט עובדים כמעט באופן זהה למה שראינו בעבר עם פונקציות ומשתנים כאשר הגדרות הסוגים עובדים בדיוק אותו דבר בואו ונדגים עם קלאס די קלאסי (הא! אני מקווה שראיתם מה עשיתי פה!) של משתמש:
class User {
userId: number;
userName: string;
constructor(userId: number, userName: string) {
this.userName = userName;
this.userId = userId;
}
getUserStatus() : string {
// Some Call or Business logic
return 'admin';
}
}
אז אפשר לראות שהגדרתי בקונסטרקטור איך אני מצפה שהמשתנים שהוא דורש יגיעו וכן במתודה שיש (שאינה פרטית) איך אני מצפה שהפלט שלה יהיה. עד כה שום דבר חדש ובאמת אין כאן שום דבר חדש. ואם אני אקרא לקונסרקטור או למתודה באופן לא הולם, אני אקבל שגיאות.
let userA = new User(1, 'Ran');
let userB = new User('1', 'Ran'); // Argument of type 'string' is not assignable to parameter of type 'number'.
let userAStatus = userA.getUserStatus();
if (userAStatus * 5 > 10) { // The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.(2362)
}
כלומר התנהגות זהה לחלוטין למשתנה או לפונקציה. גם באינטרפייס לא תמצאו הפתעות גדולות. הנה דוגמה:
interface User {
userId: number;
userName: string;
getUserStatus():string;
}
class BasicUser implements User {
userId: number;
userName: string;
constructor(userId: number, userName: string) {
this.userName = userName;
this.userId = userId;
}
getUserStatus() : string {
// Some Call or Business loginc
return 'admin';
}
}
שימו לב שהאינטרפייס פה רק בודק התאמה – כמו כל טייפסקריפט. הוא לא יוצר דברים בקלאס (אומר את זה כי יש לאינטרפייס קשר לקלאס ב-OOP).
או קיי, אז אין הפתעות גדולות, כן חשוב להתעדכן על ענייני private, protected ו-public.
סימון Private, Public או Protected
בטייפסקריפט יש לנו דרך לסמן תכונות או מתודות כ-private (כלומר אי אפשר להשתמש בהן מחוץ לקלאס) או protected (רק קלאס שיורש מהקלאס שלנו יכול להשתמש בהם) ואז הטרנספיילר יצעק על שימוש לא תקין. הנה דוגמה:
class BasicUser {
userId: number;
userName: string;
constructor(userId: number, userName: string) {
this.userName = userName;
this.userId = userId;
}
private getUserStatus() : string {
// Some Call or Business loginc
return 'admin';
}
}
let userA = new BasicUser(1, 'Ran');
let userAStatus = userA.getUserStatus(); // Property 'getUserStatus' is private and only accessible within class 'BasicUser'.
מאז שטייפסקריפט הכניס את זה אז גם בג'אווהסקריפט זה נכנס בתקן ES2021 – אז באמת לשיקולכם אם להשתמש בזה בטייפסקריפט או בג'אוהסקריפט טהור. אני אומר שאם כבר משתמשים בטייפסקריפט אז להשתמש בזה ולא ב-ES2021.
כמובן שיש לנו גם קלאס גנרי, שעובד בדיוק כרגיל. אני יכול להגדיר טייפ דינמי שמשתנה בהתאם לקלט שאני מעביר לו או ההגדרות שלי. בדיוק כמו שראינו בעבר. כאן למשל בקלאס שהגדרתי כגנרי, אני יכול ליצור את הקלאס כשה-id הוא מספרי ושם המשתמש הוא מחרוזת טקסט או ליצור את הקלאס כשה-id הוא מחרוזת טקסט וגם השם הוא מחרוזת טקסט. לפי בחירתי.
class BasicUser<T, U> {
userId: T;
userName: U;
constructor(userId: T, userName: U) {
this.userName = userName;
this.userId = userId;
}
private getUserStatus() : string {
// Some Call or Business loginc
return 'admin';
}
}
let userA = new BasicUser<number, string>(1, 'Ran');
let userB = new BasicUser<string, string>('abcdef-24564', 'Ran');
קלאס אבסטרקטי
מה שטייפסקריפט כן מאפשרת לנו זה ליצור קלאס אבסטרקטי. דבר שעדיין לא קיים בג'אווהסקריפט (למרות שאם איך שהשפה הולכת לכיוון ה-OOP נראה לי שזו רק שאלה של זמן). בגדול קלאס אבסרקטי זה קלאס שמגדיר מבנה של קלאס (לא יכול להיות קלאס בעצמו ואי אפשר לעשות לו new) ומי שמממש אותו מקבל את כל תכונותיו בחינם. אני לא רוצה להכנס לתוך הסבר יותר מדי מפורט מה זה, כי זה כבר יותר OOP והרבה פחות ג'אווהסקריפט – אבל בגדול – בדוגמת הקוד הזו יש לי קלאס אבסטרקטי בשם User, אני לא יכול לעשות לו new ואם אני אעשה טייפסקריפט יצעק עלי. אבל אני כן יכול לקחת אותו ולרשת ממנו מקלאס אחר ואז לקבל את כל התכונות שלו במתנה.
abstract class User {
userId: number;
userName: string;
constructor(userId: number, userName: string) {
this.userName = userName;
this.userId = userId;
}
getUserStatus() : string {
// Some Call or Business loginc
return 'admin';
}
}
class BasicUser extends User {
getUserPoints() :number {
// some logic
return 100;
}
}
let userA = new User(1, 'Ran'); // Cannot create an instance of an abstract class.
let userB = new BasicUser(1, 'Ran');
console.log(userB.getUserStatus()); // admin
console.log(userB.getUserPoints()); // 100
קלאס אבסטרקטי שימושי במיוחד בכל מיני קלאסי בסיס כאלו ואם המערכת שלכם בנויה לפי OOP זה ממש תפור עליה להשתמש בזה. כדאי מאוד לשים לב שמבחינת ג'אווהסקריפט טהור אין את זה. מי שיעצור אתכם מלהשתמש בזה זה הטרנספיילר של טייפסקריפט.
Readonly
לבסוף, תכונה משובבת נפש שקיימת בג'אווהסקריפט באופן מסוים היא readonly בקלאס. בגדול, כאשר אני מוסיף לתכונה בקלאס את המילה readonly, אי אפשר לשנות אותה. בעוד שזו התנהגות טבעית בשפות אחרות, בג'אווהסקריפט אני יכול לשנות תכונות באובייקט שנוצר מהקלאס גם בלי set\get. אם אני מוסיף readonly, טייפסקריפט לא תיתן לעשות את התעלול הזה:
class User {
readonly userId: number;
userName: string;
constructor(userId: number, userName: string) {
this.userName = userName;
this.userId = userId;
}
getUserStatus() : string {
// Some Call or Business logic
return 'admin';
}
}
let userA = new User(1, 'Ran');
userA.userId = 5; // Cannot assign to 'userId' because it is a read-only property.
ובזה נסכם – אפשר לראות שעם טייפסקריפט שפת ג'אווהסקריפט הופכת להיות הרבה יותר OOP ממה שהיא (אפילו שבשנים האחרונות היא ממש זזה לכיוון הזה למרות שבבסיסה היא פרוטוטייפ בייס). אם אתם מתכנתי פרונט אנד ובדגש על ריאקט מודרני, סביר להניח שהתייחסתם למאמר הזה בשיעמום קל, אם בכלל הגעתם לפה. מתכנתי ג'אווהסקריפט אחרים דווקא יהנו ואני בטוח שאם יש לכם רקע בשפות אחרות של OOP, יותר סטנדרטיות, זה מאוד מאוד ייראה לכם מוכר.
במאמר הבא אנו נדון על Declaration Files. שזו הדרך לתאר את הקוד שלנו לטייפסקריפט ולהשתמש בספריות. אותם d.ts שבטח יצא לכם לראות פה ושם.
3 תגובות
אבל מה בעצם קורה שם בפנים במחלקה האבסטרקטית? זו בעצם מחלקה רגילה של es6 רק שטייפסקריפט צועק עליך אם אתה מנסה לעשות לה new ?
בכלל בג'אווהסקריפט, כשמחלקה עושה extend למחלקה אחרת ואז אני יוצר instance של המחלקה השניה אז איפה המחלקה הראשונה נמצאת? היא מקבלת instance משל עצמה בתור הפרוטוטייפ של הפרוטוטייפ? או שהכל מתערבב לפרוטוטייפ אחד?
לכאורה Class A נמצא ב Prototype של ה Instance של Class B (איזה היבריש).
אז מה בעצם ההבדל בין מחלקה (קלאס) אבסטרקטי, לבין אינטרפייס?
אינטרפייס הוא אובייקט עם טייפים בלבד, ללא מתודות עם קוד, בעוד שקלאס אבסטרקטי יכול לכלול מתודות של ממש?
אם כן, כדאי שיודגש בגוף המאמר.
תודה…