לדלג לתוכן

5.7 עקרונות סוליד הרצאה

הבעיה בOOP

  • שימוש לא נכון בOOP יכול להיות רע מאוד, הקוד שלכם מהר מאוד יכול להיות לא קריא, לא ברור ובאגי.

עקרונות סוליד - SOLID

  • עקרונות סוליד (SOLID) יכולים לעזור לנו לפתח עם OOP בצורה טובה יותר.
  • הראשי תיבות של SOLID הן:
    • S - Single-Responsibility Principle
    • O - Open-Closed Principle
    • L - Liskov substitution principle
    • I - Interface segregation principle
    • D - Dependency inversion principle
  • בהרצאה הזו נעבור על כל אחת העקרונות הללו.

עקרון: Single-Responsibility Principle - משמעות אחת

  • למחלקה צריכה להיות רק אחריות אחת או תפקיד אחד

  • אחריות אחת:

    • מחלקה צריכה להיות מתוכננת לטפל רק באחריות או דאגה ספציפית אחת. זה הופך את המחלקה לממוקדת ומבטיחה שהיא עושה דבר אחד ועושה אותו טוב.
  • יתרונות:
    • קריאות קוד קלה יותר: כאשר לכל מחלקה יש אחריות אחת, קל יותר להבין מה כל מחלקה עושה.
    • תחזוקה קלה יותר: כאשר לכל מחלקה יש אחריות אחת, יהיה קל יותר לשנות דברים בקוד שלה ולתקן באגים. למה? בגלל שלמחלקה יהיה פחות קוד, כי לכל מחלקה יש תחום אחריות אחד.
# Without SRP
class Report:
    def generate_report(self, data):
        # Code to generate the report
        pass

    def save_to_database(self, report_data):
        # Code to save the report to the database
        pass

# With SRP
class ReportGenerator:
    def generate_report(self, data):
        # Code to generate the report
        pass

class ReportSaver:
    def save_to_database(self, report_data):
        # Code to save the report to the database
        pass

עקרון: Open-Closed Principle - פתוח-סגור

  • מחלקה צריכה להיות פתוחה להרחבות אבל סגורה לשינויים.

  • פתוח להרחבה:

    • מחלקה צריכה להיות בנוייה בצורה שיהיה קל לנו להוסיף לה קוד, מבלי לשנות את הקוד שכבר קיים במחלקה.
  • סגור לשינויים:
    • אין לשנות את קוד של מחלקה שכבר קיים כדי להוסיף פונקציונליות חדשה למחלקה. במקום זאת, המחלקה מריכה להיות מתוכננת באופן המאפשר הרחבות בלי שינויים.
  • יתרונות:
    • יכולת תחזוקה: מכיוון שהקוד הקיים אינו משתנה, יש פחות סיכון לבאגים או תופעות לוואי לא מכוונות בעת הוספת תכונות חדשות למחלקה.
  • שימוש באבסטרקציה:
    • כדי להשיג את עקרון הפתוח-סגור, לעתים קרובות משתמשים באבסטרקציה. זה כולל הגדרת מחלקות אבסטרקציות או ממשקים.

עקרון: Liskov substitution principle - חילוף

  • מחלקות בת לא יכולות להחליף את המחלקות אבא שלהן עם לגרום לשגיאות.

  • בלי הפתעות:

    • תת-מחלקות אמורות לעבוד כרגיל כשמחליפים את המחלקת אב שלהם. כמו שראינו בפרק הקודם יכולים להיות מצבים שהקוד תלוי בכך שמחלקת בת מסויימת מימשה מתודה מסויימת מהמחלקת אב שלה, ואם יום אחד המחלקת אב תתחלף, והמתודה כבר לא תהיה קיימת במחלקת אב, המחלקת בן עדיין צריכה לממש אותה כדי שהקוד עדיין יעבוד.
  • שימוש בפולימורפיזם:
    • השתמשו בפולימורפיזם! הדרך שלנו לוודא שמתודת אב מסויימת לא תעלם יום אחד היא לממש אותה בעצמנו.

עקרון: Interface segregation principle - הפרדת ממשק

  • מחלקות לא צריכות ליישם ממשקים שהן לא צריכות.

  • הימנעות מיישומים כפויים:

    • על ידי יצירת ממשקים ספציפיים, מחלקות אינן נאלצות ליישם מתודות שאינן רלוונטיות לפונקציונליות שלהן. זה עוזר בהפחתת תלות מיותרת ובעיות פוטנציאליות הקשורות ליישום שיטות שאין להן משמעות בהקשר של מחלקה מסוימת.
  • מניעת ממשקים גדולים:
    • ממשקים גדולים, המכילים מתודות רבות, עלולים להיות בעייתיים. מחלקות שמיישמים ממשקים כאלה עלולים להיות כבדים באחריות מיותרת. העקרון ממליץ לפרק ממשקים כאלה לממשקים קטנים יותר וממוקדים יותר כדי למנוע בעיה זו.
# Without ISP
class Worker:
    def work(self):
        pass

    def eat(self):
        pass

# With ISP
class Workable:
    def work(self):
        pass

class Eatable:
    def eat(self):
        pass

class Worker(Workable, Eatable):
    pass

עקרון: Dependency inversion principle - היפוך תלות

  • מחלקות לא יכולות להיות תלויות במחלקות אחרות כל כך בקלות, כאשר מחלקות צריכות להיות תלויות באבסטרקציות
    הסתכלו על הדוגמה הבאה:
    # Without DIP
    class LightBulb:
        def turn_on(self):
            # Code to turn on the light bulb
            pass
    
    class Switch:
        def control_light(self, bulb):
            bulb.turn_on()
    

    בדוגמה הראשונה, מחלקת Switch תלויה ישירות במחלקה LightBulb, ויוצרת חיבור הדוק ביניהם.

# With DIP
from abc import ABC, abstractmethod

class Switchable(ABC):
    @abstractmethod
    def turn_on(self):
        pass

class LightBulb(Switchable):
    def turn_on(self):
        # Code to turn on the light bulb
        pass

class Switch:
    def control_light(self, device: Switchable):
        device.turn_on()

בדוגמה השנייה, גם Switch וגם LightBulb תלויים באבסטרקציה (Switchable), מה שמאפשר יותר גמישות והחלפה של סוגי "מכשירים" יותר בקלות.