עקרונות SOLID בתכנות מונחה עצמים
רוברט מרטין (המוכר גם כ- Uncle Bob), האיש שהביא לנו את Clean Code, הגדיר במאמריו ובספריו חמישה עקרונות שנועדו לשפר את התכנון של מערכות תוכנה מונחות עצמים – OOP. חמשת העקרונות האלו עוזרים ליצור תוכנה קלה להבנה, לתחזוקה ולהרחבה.
עקרונות אלו נודעו בראשי התיבות: SOLID והם:
- Single Responsibility Principle (SRP)
- Open/Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
- Dependency Inversion Principle (DIP)
________________________________________________________________________________________________________________
Single Responsibility Principle (SRP)
יש אומרים שגברים לא יכולים לטפל ביותר מדבר אחד בכל פעם. לא יודע עד כמה זה נכון, אבל בכתיבת קוד, אכן מומלץ שלכל מחלקה תהיה אחריות אחת בלבד. כלומר, כל מחלקה צריכה לטפל בהיבט אחד בלבד של התוכנה ולא יותר מכך.
לדוגמא:
public class Invoice { public void CalculateTotal() { // Logic for calculating total amount } } public class InvoicePrinter { public void Print(Invoice invoice) { // Logic for printing the invoice } }
במקרה זה, מחלקת `Invoice` מטפלת רק בחישוב הסכום הכולל, בעוד ש-`InvoicePrinter` מטפלת בהדפסת החשבונית.
טיפ: אם המילה And מופיעה בשם המחלקה שלכם, זו כנראה אינדיקציה לכך שהמחלקה שלכם מפרה את SRP.
וגם – כאשר קשה להבין מה בדיוק מחלקה מסוימת עושה, או שקשה לשנות את הקוד בלי לפגוע בפונקציות אחרות של המחלקה, יש סיכוי לא קטן שפגשתם הפרה של SRP.
Open/Closed Principle (OCP)
כידוע, כל תוכנית היא בסיס לשינויים. זה נכון בצבא וזה נכון בתוכנה. בתכנון טוב נוכל לדאוג לכך שניתן יהיה להרחיב את התוכנה על ידי הוספת פונקציונליות חדשה על פי הצרכים העתידיים, אך לעשות זאת מבלי שנצטרך לשנות את הקוד הקיים.
public abstract class Shape { public abstract double Area(); } public class Rectangle : Shape { public double Width { get; set; } public double Height { get; set; } public override double Area() { return Width * Height; } } public class Circle : Shape { public double Radius { get; set; } public override double Area() { return Math.PI * Radius * Radius; } }
במקרה זה, ניתן להוסיף צורות חדשות על ידי יצירת מחלקות חדשות היורשות מ-`Shape` ומיישמות את השיטה `Area`.
טיפ: אם בכל פעם שאתה מוסיף תכונה חדשה אתה צריך לשנות את הקוד הקיים באופן משמעותי, סביר להניח שאתה מפר את OCP.
וגם – אם אתה רואה תלות ישירה בין מחלקות כך ששינוי במחלקה אחת מחייב שינוי במחלקות אחרות בהחלט יתכן שיש פה הפרה של OCP.
Liskov Substitution Principle (LSP)
ברברה ליסקוב (Barbara Liskov) היא מדענית מחשב אמריקאית, אחת הדמויות המשפיעות ביותר בתחום מדעי המחשב והנדסת התוכנה וזוכת פרס טיורינג בשנת 2008 .
לציבור המתכנתים היא ידועה בעיקר מעיקרון ההחלפה שנקרא על שמה. עיקרון זה קובע שמחלקות בסיס צריכות להיות בעלות יכולת להיות מוחלפות במחלקות הנגזרות מהן, וזאת מבלי לשבור את הפונקציונליות של התוכנה.
דוגמא:
public class Bird { public virtual void Fly() { // Birds can fly } } public class Ostrich : Bird { public override void Fly() { throw new InvalidOperationException("Ostriches can't fly"); } }
בדוגמא הזו אנחנו “כופים” על יענים לעוף… גם אם נטמון את הראש בחול, זה לא יעבוד. תהיה שגיאה. ולכן, כדי להימנע משבירת LSP, יש לשנות את התכנון כך שמחלקת `Ostrich` לא תירש מ-`Bird`, אם פעולה זו אינה הגיונית.
כדי לתקן את ההיררכיה כך שנשמור על עקרון ההחלפה של ליסקוב (LSP), נבצע את השינויים הבאים:
במקום שהמחלקה Ostrich תירש מ-Bird ניצור היררכיה חדשה שבה לכל הציפורים יש פעולה כללית של Move במקום פעולה ספציפית של Fly. כל ציפור תיישם את הפעולה הזו בדרכה שלה.
הנה דוגמא לקוד מתוקן:
public abstract class Bird { public abstract void Move(); } public class FlyingBird : Bird { public override void Move() { Console.WriteLine("Flying"); } } public class Ostrich : Bird { public override void Move() { Console.WriteLine("Running"); } }
טיפ: אם הקוד שלכם מכיל תנאים לבדיקת סוג האובייקט על מנת להחליט לאיזה מתודה לקרוא, קרוב לוודאי שהפרתם את LSP.
Interface Segregation Principle (ISP)
עיקרון זה קובע שיש לפצל אינטרפייסים גדולים למספר אינטרפייסים קטנים וספציפיים, כך שה-client לא יהיה חייב ליישם פונקציות שאינן נדרשות לו.
public interface IPrinter { void Print(); } public interface IScanner { void Scan(); } public class MultiFunctionPrinter : IPrinter, IScanner { public void Print() { // Print logic } public void Scan() { // Scan logic } } public class SimplePrinter : IPrinter { public void Print() { // Print logic } }
בדוגמא הזו `SimplePrinter` צריך ליישם רק את `IPrinter`.
טיפ: אם האינטרפייס שלכם מכיל יותר מדי מתודות כך שקלאסים שמיישמים אותו צריך ליישם בין השאר גם מתודות שנותרות ללא שימוש – כנראה שהפרתם את ISP.
Dependency Inversion Principle (DIP)
הרבה מדברים על הפרדת הרשויות במדינה, אבל גם בקוד זה ככה. כדי שקוד יהיה קל לתחזוקה ולהרחבה צריך להישמר עיקרון הפרדת הרשויות. חלקי התוכנה צריכים להיות כמה שפחות צמודים (coupled) אחד לשני. אחד העקרונות שעוזרים לנו בזה הוא Dependecy Inversion Principle, עיקרון היפוך התלות.
עקרונות ה-DIP
- מחלקות ברמה גבוהה לא צריכות להיות תלויות במחלקות ברמה נמוכה: מחלקות שמכילות לוגיקה עסקית מרכזית לא צריכות להיות תלויות בפרטים של מחלקות שמממשות פונקציונליות ספציפית.
- שתיהן צריכות להיות תלויות באינטרפייסים: הן המחלקות ברמה הגבוהה והן המחלקות ברמה הנמוכה צריכות להיות תלויות באינטרפייסים (או ב abstract class) במקום במימושים קונקרטיים.
אם למשל יהיה לנו אינטרפייס של לוגים וכמה מימושים שונים שלו באופן הזה:
public interface ILogger { void Log(string message); } public class FileLogger : ILogger { public void Log(string message) { // Log to file } } public class DatabaseLogger : ILogger { public void Log(string message) { // Log to database } }
נוכל בקליינט להשתמש באיזה מימוש שנרצה מכיוון שהוא מקבל בקונסטרקטור שלו את האינטרפייס
public class UserService { private readonly ILogger _logger; public UserService(ILogger logger) { _logger = logger; } public void CreateUser(string username) { // Create user logic _logger.Log("User created"); } }
במקרה זה, `UserService` לא תלוי ביישום ספציפי של `ILogger` אלא בממשק, כך שניתן להחליף את הלוגרים ללא שינוי בקוד של `UserService`.
טיפ: אם קשה לכם לכתוב טסטים לקלאס או לפונקציה בגלל שהיא תלויה במימושים קונקרטיים של קלאסים אחרים, בהחלט יתכן שהפרתם את DIP.
_______________________________________________________________________________________________________________
זוהי טעימה על קצה המזלג של עקרונות SOLID. מי שיקפיד עליהם בזמן כתיבת הקוד, ירוויח קוד קריא יותר, קל לתחזוק ולבדיקה ומהיר יותר להוספות ושינויים בטווח הרחוק.
4 תגובות
Yohai Israel Ohana
מאמר מצויין ומובן מאוד תודה רבה
רחל
מאמר מעולה,
smorter giremal
Appreciate it for this post, I am a big big fan of this website would like to go along updated.
uv led bulb
I like this post, enjoyed this one appreciate it for putting up.