JavaRush /בלוג Java /Random-HE /חמישה עקרונות בסיסיים של עיצוב כיתות (SOLID) ב-Java
Ve4niY
רָמָה

חמישה עקרונות בסיסיים של עיצוב כיתות (SOLID) ב-Java

פורסם בקבוצה
מחלקות הן הבלוקים שמהם נבנית אפליקציה. בדיוק כמו הלבנים בבניין. שיעורים כתובים בצורה גרועה עלולים לגרום לבעיות יום אחד. חמישה עקרונות בסיסיים של עיצוב כיתות (SOLID) ב-Java - 1כדי להבין אם כיתה כתובה נכון, אתה יכול לבדוק את "תקני האיכות". בג'אווה, אלו הם מה שנקרא עקרונות SOLID. בואו נדבר עליהם.

עקרונות מוצקים ב-Java

SOLID הוא ראשי תיבות שנוצרו מהאותיות הגדולות של חמשת העקרונות הראשונים של OOP ועיצוב. העקרונות הומצאו על ידי רוברט מרטין בתחילת שנות ה-2000, ואת ראשי התיבות טבע מאוחר יותר מייקל Feathers. הנה מה שעקרונות SOLID כוללים:
  1. עיקרון אחריות יחידה.
  2. עיקרון סגור פתוח.
  3. עקרון ההחלפה של ליסקוב.
  4. עקרון הפרדת ממשק.
  5. עקרון היפוך תלות.

עיקרון אחריות יחידה (SRP)

עיקרון זה קובע שלעולם לא צריכה להיות יותר מסיבה אחת לשנות מעמד. לכל אובייקט אחריות אחת, מובלעת לחלוטין במחלקה. כל שירותי הכיתה מכוונים להבטיח אחריות זו. שיעורים כאלה תמיד יהיה קל לשנות במידת הצורך, כי ברור על מה הכיתה אחראית ועל מה לא. כלומר, ניתן יהיה לבצע שינויים ולא לפחד מההשלכות – ההשפעה על אובייקטים אחרים. וקוד כזה הרבה יותר קל לבדוק, כי אתה מכסה פונקציונליות אחת עם בדיקות במנותק מכל האחרות. דמיינו מודול שמעבד הזמנות. אם ההזמנה נוצרה כהלכה, היא שומרת אותה במסד הנתונים ושולחת מייל לאישור ההזמנה:
public class OrderProcessor {

    public void process(Order order){
        if (order.isValid() && save(order)) {
            sendConfirmationEmail(order);
        }
    }

    private boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }

    private void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}
מודול כזה עשוי להשתנות משלוש סיבות. ראשית, הלוגיקה של עיבוד ההזמנה עשויה להיות שונה, שנית, שיטת השמירה שלה (סוג מסד הנתונים), שלישית, שיטת שליחת מכתב אישור (לדוגמה, במקום דוא"ל אתה צריך לשלוח SMS). עקרון האחריות היחידה מרמז ששלושת ההיבטים של בעיה זו הם למעשה שלוש תחומי אחריות שונים. זה אומר שהם חייבים להיות במחלקות או מודולים שונים. שילוב של מספר גופים שעלולים להשתנות בזמנים שונים ומסיבות שונות נחשב להחלטה עיצובית גרועה. הרבה יותר טוב לחלק את המודול לשלושה נפרדים, שכל אחד מהם יבצע פונקציה אחת בודדת:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}

עיקרון פתוח/סגור (OCP)

עקרון זה מתואר בתמציתיות כדלקמן: ישויות תוכנה (מחלקות, מודולים, פונקציות וכו') חייבות להיות פתוחות להרחבה, אך סגורות לשינוי . המשמעות היא שצריך להיות אפשרי לשנות את ההתנהגות החיצונית של הכיתה מבלי לבצע שינויים פיזיים בכיתה עצמה. בהתאם לעיקרון זה, מחלקות מפותחות כך שכדי להתאים את המחלקה לתנאי יישום ספציפיים, מספיק להרחיב אותה ולהגדיר מחדש כמה פונקציות. לכן, המערכת חייבת להיות גמישה, מסוגלת לעבוד בתנאים משתנים מבלי לשנות את קוד המקור. בהמשך לדוגמה ההזמנה שלנו, נניח שעלינו לבצע כמה פעולות לפני עיבוד ההזמנה ואחרי שליחת דוא"ל האישור. במקום לשנות את המחלקה עצמה OrderProcessor, נרחיב אותה ונשיג פתרון לבעיה שעל הפרק מבלי להפר את עקרון ה-OCP:
public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }

    private void beforeProcessing() {
        // Perform some actions before processing the order
    }

    private void afterProcessing() {
        // Perform some actions after order processing
    }
}

עיקרון החלפה של ברברה ליסקוב (LSP)

זוהי וריאציה של העיקרון הפתוח/סגור שנדון קודם לכן. ניתן לתאר זאת כך: אובייקטים בתוכנית יכולים להיות מוחלפים על ידי יורשיהם מבלי לשנות את מאפייני התוכנית. המשמעות היא שמחלקה שפותחה על ידי הרחבת מחלקה בסיס חייבת לעקוף את השיטות שלה באופן שאינו שובר את הפונקציונליות מנקודת מבטו של הלקוח. כלומר, אם מפתח מרחיב את המחלקה שלך ומשתמש בה באפליקציה, הוא לא צריך לשנות את ההתנהגות הצפויה של השיטות הנעקבות. תת-מחלקות חייבות לעקוף את שיטות מחלקות הבסיס באופן שאינו שובר את הפונקציונליות מנקודת המבט של הלקוח. ניתן לבחון זאת בפירוט באמצעות הדוגמה הבאה. נניח שיש לנו מחלקה שאחראית על אימות ההזמנה ובודקת האם כל פריטי ההזמנה במלאי. למחלקה הזו יש שיטה isValidשמחזירה true או false :
public class OrderStockValidator {

    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if (! item.isInStock()) {
                return false;
            }
        }

        return true;
    }
}
נניח גם שחלק מההזמנות צריכות לעבור אימות שונה: בדוק האם כל הסחורה בהזמנה נמצאת במלאי והאם כל הסחורה ארוזה. לשם כך, הרחבנו את הכיתה OrderStockValidatorעם הכיתה OrderStockAndPackValidator:
public class OrderStockAndPackValidator extends OrderStockValidator {

    @Override
    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if ( !item.isInStock() || !item.isPacked() ){
                throw new IllegalStateException(
                     String.format("Order %d is not valid!", order.getId())
                );
            }
        }

        return true;
    }
}
עם זאת, במחלקה זו הפרנו את עקרון ה-LSP, מכיוון שבמקום להחזיר false אם ההזמנה לא עברה אימות, השיטה שלנו זורקת חריגה IllegalStateException. לקוחות של קוד זה אינם מצפים לכך: הם מצפים ש-true או false יוחזרו . זה עלול להוביל לשגיאות בתוכנית.

עיקרון פיצול ממשק (ISP)

מאופיין בהצהרה הבאה: אין להכריח לקוחות ליישם שיטות שהם לא ישתמשו בהן . עקרון הפרדת הממשקים מציע שממשקים "עבים" מדי צריכים להתחלק לממשקים קטנים וספציפיים יותר, כך שלקוחות ממשקים קטנים יודעים רק על השיטות הנחוצות לעבודתם. כתוצאה מכך, בעת שינוי שיטת ממשק, לקוחות שאינם משתמשים בשיטה זו לא צריכים להשתנות. בואו נסתכל על דוגמה. המפתח אלכס יצר את ממשק ה"דוח" והוסיף שתי שיטות: generateExcel()ו generatedPdf(). כעת לקוח א' רוצה להשתמש בממשק הזה, אבל הוא מתכוון להשתמש רק בדוחות PDF ולא באקסל. האם הוא יהיה מרוצה מהפונקציונליות הזו? לא. הוא יצטרך ליישם שתי שיטות, שאחת מהן מיותרת במידה רבה וקיימת רק בזכות אלכס, מעצב התוכנה. הלקוח ישתמש בממשק אחר או ישאיר את שדה ה-Excel ריק. אז מה הפתרון? זה מורכב מחלוקת הממשק הקיים לשניים קטנים יותר. האחד הוא דוח בפורמט PDF, השני הוא דוח בפורמט אקסל. זה ייתן למשתמש את האפשרות להשתמש רק בפונקציונליות הדרושה לו.

עקרון היפוך תלות (DIP)

עיקרון מוצק זה ב-Java מתואר באופן הבא: תלות בתוך המערכת נבנות על בסיס הפשטות . מודולים ברמה העליונה אינם תלויים במודולים ברמה נמוכה יותר. הפשטות לא אמורות להיות תלויות בפרטים. הפרטים חייבים להיות תלויים בהפשטות. תוכנה צריכה להיות מתוכננת כך שהמודולים השונים יהיו אוטונומיים ויתחברו זה לזה באמצעות הפשטה. יישום קלאסי של עיקרון זה הוא מסגרת האביב. במסגרת Spring, כל המודולים מיושמים כרכיבים נפרדים שיכולים לעבוד יחד. הם כל כך עצמאיים שניתן להשתמש בהם באותה קלות במודולי תוכנה אחרים מלבד ה-Spring framework. זה מושג באמצעות תלות של עקרונות סגורים ופתוחים. כל המודולים מספקים גישה רק להפשטה שניתן להשתמש בה במודול אחר. בואו ננסה להדגים זאת באמצעות דוגמה. אם כבר מדברים על עקרון האחריות הבלעדית, שקלנו כמה OrderProcessor. בואו נסתכל שוב על הקוד של המחלקה הזו:
public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}
בדוגמה זו, שלנו OrderProcessorתלוי בשתי מחלקות ספציפיות MySQLOrderRepositoryו- ConfirmationEmailSender. אנו מציגים גם את הקוד לשיעורים אלה:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }
}
כיתות אלו רחוקות מלהיקרא הפשטות. ומנקודת המבט של עקרון ה-DIP, נכון יותר יהיה להתחיל ביצירת כמה הפשטות שיאפשרו לנו לפעול איתם בעתיד, ולא עם מימושים ספציפיים. בואו ניצור שני ממשקים MailSenderו- OrderRepository, שיהפכו להפשטות שלנו:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
עכשיו בואו ליישם את הממשקים האלה בשיעורים שכבר מוכנים לכך:
public class ConfirmationEmailSender implements MailSender {

    @Override
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Sending a letter to the client
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // save the order to the database

        return true;
    }
}
עשינו את עבודת ההכנה כך שהכיתה שלנו OrderProcessorתלויה לא בפרטים קונקרטיים, אלא בהפשטות. בואו נעשה בו שינויים על ידי הצגת התלות שלנו בבנאי המחלקה:
public class OrderProcessor {

    private MailSender mailSender;
    private OrderRepository repository;

    public OrderProcessor(MailSender mailSender, OrderRepository repository) {
        this.mailSender = mailSender;
        this.repository = repository;
    }

    public void process(Order order){
        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }
}
הכיתה שלנו תלויה כעת בהפשטות ולא ביישום קונקרטי. אתה יכול לשנות בקלות את ההתנהגות שלו על ידי הזרקת התלות הרצויה בזמן יצירת המופע OrderProcessor. הסתכלנו על SOLID - עקרונות עיצוב בג'אווה. עוד על OOP באופן כללי, היסודות של שפת התכנות הזו - לא משעמם ועם מאות שעות תרגול - בקורס JavaRush. הגיע הזמן לפתור כמה בעיות :) חמישה עקרונות בסיסיים של עיצוב כיתות (SOLID) ב-Java - 2
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION