המשך של מבנה ה-Controller.
ראינו שמבנה של controller בו אנחנו משתמשים ב-new על מנת ליצור class אחר, הוא משהו שעובד, אבל לא רצוי.
[HttpGet]
public ActionResult<List<Product>> Get() {
ProductsRepository repo = new ProductsRepository();
var result = repo.GetAllProducts();
return Ok(result);
}
אם כותבים new בקוד, כנראה שעושים משהו לא נכון! מותר לעשות new כאשר מדובר על new של ישויות מידע. לא לשירותים, כלומר classes שמחזיקים פונקציות.
Product הוא ישות, אין בו פונקציונליות, יש לו רק מבנה נתונים. ה-controller ואחרים הם services ואנחנו אף עם לא רוצים לעשות להם new. לא רק ל-classes שלנו, אלא גם לכאלה של צד שלישי, למשל HttpCLient.
מה זה coupling?
כעושים את פעולת ה-new ל-service זה נקרא coupling. צמידות בין שתי מחלקות שגורמת לזה שהן תלויות אחת בשנייה ואין לי אפשרות להחליף יישום של ה-class שכללתי.
למשל, אם יש לי חיבור למדפסת כלשהי למחשב, אני רוצה ליצור פונקציונליות שמאפשרת לי בכל פעם לחבר מדפסת אחרת ולהגדיר אותה, אני לא רוצה להגדיר את יכולות ההדפסה של המחשב למדפסת מסויימת. אם אני אצור class במנהל המדפסות של מדפסת מסויימת, תהיה לי בעיה בחיבור אחר. אני לא רוצה להתייחס ברמת המחשב לסוג המדפסת.
ארכיטקטורה נכונה מאפשרת לנו להחליף את פעולת הרכיב עצמו במינימום מאמץ וסיכון.
יצירת Interface
כשכותבים class עם פונקציונליות הוא חייב לממש interface. הוא יכול להיות אחד מבין כמה מימושים לפעולות שאני רוצה. תמיד יכולים להיות מימושים אחרים וזה לוקח רק כמה דקות להכין את האפשרות לזה.
כשנבנה את ה-class לא נקרא לו HpReadPrinter, אלא ReadPrinter.
public interface IReadPrinter
{
ValueTask<string> GetPrinterNum();
}
בתוך ה-interface תהיה לי פונקציה שמחזירה את הערך שאותו אני צריכה כדי להמשיך ולבצע את הפעולות שלי. כדי לעבוד עם מדפסת מסויימת, עכשיו אני אבנה class עבור זה ואממש את ה-interface.
public class HpReadPrinter: IReadPrinter
{
}
בקוד הראשי, כשאני ארצה להשתמש בפונקציונציות, נגדיר:
private readonly IReadPrinter _printerManager
וביצירת new אני לא אצור ישירות את HpReadPrinter, אני אמנע מכתיבת ה-new והפעלת ה-class תעשה בדרך אחרת.
פעם השיטה היתה שימוש ב-Factory.
_printerManager = PrinterFactory.GetPrinterReader();
זה לא היה טוב, כי עדיין היה coupling עם PrinterFactory ואם היה DLL חיצוני הוא לא הכיר את ה-class הזה ולא יכול לעבוד איתו.
Dependency Injection
מה שנכון לעשות הוא לא להיות אחראים בכלל על ה-dependencies. כמשתמש, אני לא יודעת מראש במקרה שלנו באיזה מדפסת המחשב הולך להשתמש. אני אזריק את ה-Interface ל-constructor.
public PrinterManagerFlow(IReadPrinter printReader)
{
_printerManager = printReader;
}
כמשתמש אני רק צריכה לדעת מה אני רוצה שתהיה הפונקציונליות. אני רוצה שיהיה ניהול דיו במדפסת, אני רוצה שתהיה שליחה של המסמך, אני רוצה שיהיה יישור של המסמך וכו' אני לא יודעת איזה מדפסת יש לי, איזה דפים וכו' זה לא קשור אלי.
כל מי שהוא dependency יגיע ב-constructor וכך לא יהיה לי coupling ולא תהיה אחריות על איזה מימוש.