CodeGym /בלוג Java /Random-HE /עקרונות OOP
John Squirrels
רָמָה
San Francisco

עקרונות OOP

פורסם בקבוצה
Java היא שפה מונחה עצמים. זה אומר שאתה צריך לכתוב תוכניות Java באמצעות פרדיגמה מונחה עצמים. ופרדיגמה זו כרוכה בשימוש באובייקטים ומחלקות בתוכניות שלך. בואו ננסה להשתמש בדוגמאות כדי להבין מה זה מחלקות ואובייקטים, וכיצד ליישם עקרונות OOP בסיסיים (הפשטה, ירושה, פולימורפיזם ואנקפסולציה) בפועל.

מהו חפץ?

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

הַפשָׁטָה

הבה נחשוב כעת כיצד אנו יכולים לעבור מאובייקט בעולם האמיתי לאובייקט בתוכנית. נשתמש בטלפון כדוגמה. לאמצעי תקשורת זה יש היסטוריה שנפרשת על פני יותר מ-100 שנים. הטלפון המודרני הוא מכשיר הרבה יותר מורכב מקודמו מהמאה ה-19. בעת השימוש בטלפון, איננו חושבים על הארגון שלו ועל התהליכים המתרחשים בתוכו. אנחנו פשוט משתמשים בפונקציות שמספקות מפתחי הטלפון: כפתורים או מסך מגע כדי להזין מספר טלפון ולבצע שיחות. אחד ממשקי הטלפון הראשונים היה ארכובה שהיה צריך לסובב כדי לבצע שיחה. כמובן שזה לא היה נוח במיוחד. אבל הוא מילא את תפקידו ללא רבב. אם אתה משווה את הטלפונים המודרניים והראשונים ביותר, אתה יכול לזהות מיד את הפונקציות החשובות ביותר עבור המכשיר של סוף המאה ה-19 ועבור הטלפון החכם המודרני. הם היכולת לבצע שיחות והיכולת לקבל שיחות. למעשה, זה מה שהופך את הטלפון לטלפון, ולא למשהו אחר. עכשיו רק יישם עיקרון של OOP: זהה את המאפיינים והמידע החשובים ביותר של אובייקט. עקרון זה נקרא הפשטה. ב-OOP, ניתן להגדיר הפשטה גם כשיטה לייצוג אלמנטים של משימה בעולם האמיתי כאובייקטים בתוכנית. הפשטה תמיד קשורה להכללה של מאפיינים מסוימים של אובייקט, ולכן העיקר הוא להפריד בין מידע משמעותי לבין חסר משמעות בהקשר של המשימה שעל הפרק. בנוסף, יכולות להיות מספר רמות של הפשטה. בואו ננסה ליישם את עקרון ההפשטה על הטלפונים שלנו. כדי להתחיל, נזהה את סוגי הטלפונים הנפוצים ביותר - מהטלפונים הראשונים ועד לאלו של ימינו. לדוגמה, נוכל לייצג אותם בצורה של הדיאגרמה באיור 1. עקרונות OOP - 2באמצעות הפשטה, נוכל כעת לזהות את המידע הכללי בהיררכיית האובייקטים הזו: האובייקט המופשט הכללי (טלפון), מאפיינים נפוצים של הטלפון (למשל, שנתו יצירה), והממשק המשותף (כל הטלפונים יכולים לקבל ולבצע שיחות). כך זה נראה ב-Java:
public abstract class AbstractPhone {
    private int year;

    public AbstractPhone(int year) {
        this.year = year;
    }
    public abstract void call(int outgoingNumber);
    public abstract void ring(int incomingNumber);
}
בתוכנית, אנו יכולים ליצור סוגים חדשים של טלפונים באמצעות מחלקה מופשטת זו ויישום עקרונות בסיסיים אחרים של OOP, אותם נחקור להלן.

כימוס

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

אנקפסולציה ובקרת גישה

נניח שמידע על טלפון (שנת הייצור שלו או הלוגו של היצרן) נחרט על גבו בעת הכנתו. המידע (מצבו) ספציפי לדגם המסוים הזה. אנו יכולים לומר שהיצרן וידא שהמידע הזה לא ניתן לשינוי - לא סביר שמישהו יחשוב להסיר את החריטה. בעולם Java, מחלקה מתארת ​​את מצבם של אובייקטים עתידיים באמצעות שדות, והתנהגותם מתוארת באמצעות שיטות. הגישה למצב והתנהגות של אובייקט נשלטת באמצעות מתקנים המוחלים על שדות ושיטות: פרטי, מוגן, ציבורי וברירת מחדל. לדוגמה, החלטנו ששנת הייצור, שם היצרן ואחת השיטות הן פרטי יישום פנימיים של המחלקה ואינן ניתנות לשינוי על ידי אובייקטים אחרים בתוכנית. בקוד, ניתן לתאר את המחלקה באופן הבא:
public class SomePhone {

    private int year;
    private String company;
    public SomePhone(int year, String company) {
        this.year = year;
        this.company = company;
    }
private void openConnection(){
    // findSwitch
    // openNewConnection...
}
public void call() {
    openConnection();
    System.out.println("Calling");
}

public void ring() {
    System.out.println("Ring-ring");
}

 }
השינוי הפרטי מאפשר גישה לשדות ולשיטות של המחלקה רק בתוך מחלקה זו. זה אומר שאי אפשר לגשת לשדות פרטיים מבחוץ, כי לא ניתן לקרוא לשיטות הפרטיות. הגבלת הגישה לשיטת openConnection מותירה לנו גם את היכולת לשנות באופן חופשי את היישום הפנימי של השיטה, שכן מובטח שהשיטה לא תשמש או תפריע לעבודתם של אובייקטים אחרים. כדי לעבוד עם האובייקט שלנו, אנו משאירים את שיטות השיחה והצלצול זמינות באמצעות השינוי הציבורי. אספקת שיטות ציבוריות לעבודה עם אובייקטים היא גם חלק מהאנקפסולציה, שכן אם הגישה הייתה נמנעת לחלוטין, היא הייתה הופכת לחסרת תועלת.

יְרוּשָׁה

בואו נסתכל שוב על דיאגרמה של טלפונים. ניתן לראות שמדובר בהיררכיה שבה למודל יש את כל המאפיינים של הדגמים הממוקמים גבוה יותר לאורך הענף שלו, ומוסיף כמה משלו. לדוגמה, סמארטפון משתמש ברשת סלולרית לתקשורת (בעל תכונות של טלפון סלולרי), הוא אלחוטי ונייד (בעל תכונות של טלפון אלחוטי), ויכול לקבל ולבצע שיחות (בעל תכונות של טלפון). מה שיש לנו כאן הוא ירושה של מאפייני אובייקט. בתכנות, ירושה פירושה שימוש במחלקות קיימות כדי להגדיר מחלקות חדשות. הבה נשקול דוגמה לשימוש בירושה ליצירת מחלקה לסמארטפון. כל הטלפונים האלחוטיים מופעלים על ידי סוללות נטענות, בעלות חיי סוללה מסוימים. בהתאם לכך, אנו מוסיפים את המאפיין הזה למחלקת הטלפונים האלחוטיים:
public abstract class CordlessPhone extends AbstractPhone {

    private int hour;

    public CordlessPhone (int year, int hour) {
        super(year);
        this.hour = hour;
    }
    }
טלפונים סלולריים יורשים את המאפיינים של טלפון אלחוטי, ואנו מיישמים את שיטות השיחה והצלצול במחלקה זו:
public class CellPhone extends CordlessPhone {
    public CellPhone(int year, int hour) {
        super(year, hour);
    }

    @Override
    public void call(int outgoingNumber) {
        System.out.println("Calling " + outgoingNumber);
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("Incoming call from " + incomingNumber);
    }
}
ולבסוף, יש לנו את מחלקת הסמארטפונים, שבניגוד לטלפונים סלולריים קלאסיים, יש לה מערכת הפעלה מלאה. אתה יכול להרחיב את הפונקציונליות של הטלפון החכם שלך על ידי הוספת תוכניות חדשות שיכולות לפעול על מערכת ההפעלה שלו. בקוד, ניתן לתאר את המחלקה באופן הבא:
public class Smartphone extends CellPhone {

    private String operationSystem;

    public Smartphone(int year, int hour, String operationSystem) {
        super(year, hour);
        this.operationSystem = operationSystem;
    }
public void install(String program) {
    System.out.println("Installing " + program + " for " + operationSystem);
}

}
כפי שאתה יכול לראות, יצרנו לא מעט קוד חדש כדי לתאר את מחלקת הסמארטפונים , אבל קיבלנו מחלקה חדשה עם פונקציונליות חדשה. עיקרון זה של OOP מאפשר להפחית משמעותית את כמות קוד ה-Java הנדרשת, ובכך להקל על החיים למתכנת.

רב צורתיות

למרות הבדלים במראה ובעיצוב של סוגים שונים של טלפונים, אנו יכולים לזהות כמה התנהגות נפוצה: כולם יכולים לקבל ולבצע שיחות ולכולם יש מערכת בקרה ברורה ופשוטה למדי. מבחינת תכנות, עקרון ההפשטה (שאנחנו כבר מכירים) מאפשר לנו לומר שלאובייקטים בטלפון יש ממשק משותף. זו הסיבה שאנשים יכולים להשתמש בקלות בדגמים שונים של טלפונים בעלי אותם פקדים (לחצנים מכניים או מסך מגע), מבלי להתעמק בפרטים הטכניים של המכשיר. כך, אתה משתמש בטלפון סלולרי כל הזמן ואתה יכול בקלות לבצע שיחה מהטלפון הקווי של חברך. העיקרון של OOP שאומר שהתוכנה יכולה להשתמש באובייקטים בעלי ממשק משותף ללא כל מידע על המבנה הפנימי של האובייקט נקרא פולימורפיזם. בואו נדמיין שאנחנו צריכים את התוכנית שלנו כדי לתאר משתמש שיכול להשתמש בכל טלפון כדי להתקשר למשתמש אחר. הנה איך אנחנו יכולים לעשות את זה:
public class User {
    private String name;

    public User(String name) {
        this.name = name;
            }

    public void callAnotherUser(int number, AbstractPhone phone){
// And here's polymorphism: using the AbstractPhone type in the code!
        phone.call(number);
    }
}
 }
כעת נתאר מספר סוגים של טלפונים. אחד הטלפונים הראשונים:
public class ThomasEdisonPhone extends AbstractPhone {

public ThomasEdisonPhone(int year) {
    super(year);
}
    @Override
    public void call(int outgoingNumber) {
        System.out.println("Crank the handle");
        System.out.println("What number would you like to connect to?");
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("The phone is ringing");
    }
}
טלפון קווי רגיל:
public class Phone extends AbstractPhone {

    public Phone(int year) {
        super(year);
    }

    @Override
    public void call(int outgoingNumber) {
        System.out.println("Calling " + outgoingNumber);
    }

    @Override
    public void ring(int incomingNumber) {
        System.out.println("The phone is ringing");
    }
}
ולבסוף, טלפון וידאו מגניב:
public class VideoPhone extends AbstractPhone {

    public VideoPhone(int year) {
        super(year);
    }
    @Override
    public void call(int outgoingNumber) {
        System.out.println("Connecting video call to " + outgoingNumber);
    }
    @Override
    public void ring(int incomingNumber) {
        System.out.println("Incoming video call from " + incomingNumber);
    }
  }
ניצור אובייקטים בשיטת main() ונבדוק את שיטת callAnotherUser() :
AbstractPhone firstPhone = new ThomasEdisonPhone(1879);
AbstractPhone phone = new Phone(1984);
AbstractPhone videoPhone=new VideoPhone(2018);
User user = new User("Jason");
user.callAnotherUser(224466, firstPhone);
// Crank the handle
// What number would you like to connect to?
user.callAnotherUser(224466, phone);
// Calling 224466
user.callAnotherUser(224466, videoPhone);
// Connecting video call to 224466
קריאה לאותה שיטה באובייקט המשתמש מייצרת תוצאות שונות. יישום ספציפי של שיטת ה-call נבחר באופן דינמי בתוך שיטת callAnotherUser() בהתבסס על סוג האובייקט הספציפי שהועבר כאשר התוכנית פועלת. זהו היתרון העיקרי של הפולימורפיזם - היכולת לבחור יישום בזמן ריצה. בדוגמאות של מחלקות טלפון שניתנו לעיל, השתמשנו בדריסת שיטה - טריק שבו אנו משנים יישום של שיטה שהוגדרה במחלקה הבסיסית מבלי לשנות את חתימת השיטה. זה בעצם מחליף את השיטה: המתודה החדשה שהוגדרה בתת-המחלקה נקראת כאשר התוכנית מבוצעת. בדרך כלל, כאשר אנו עוקפים שיטה, נעשה שימוש בהערת @Override . זה אומר למהדר לבדוק את החתימות של השיטות הנעקבות והדרסות. לבסוף, כדי להבטיח שתוכניות ה-Java שלך תואמות את העקרונות של OOP, עקוב אחר העצות הבאות:
  • לזהות את המאפיינים העיקריים של אובייקט;
  • לזהות מאפיינים והתנהגות נפוצים ולהשתמש בירושה בעת יצירת מחלקות;
  • להשתמש בסוגים מופשטים לתיאור אובייקטים;
  • נסה תמיד להסתיר שיטות ושדות הקשורים ליישום הפנימי של מחלקה.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION