תכנות מונחה עצמים (OOP) - דוגמה מסכמת לפולימורפיזם

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

  דוגמה מסכמת לפולימורפיזם - שלב ב
מאת: ארז קלר


דוגמת קודהורדת דוגמאות קוד 

בשלב הראשון של כתיבת הדוגמה המסכמת תכננו והגדרנו את ההיררכיה, בשלב השני נממש אותה.

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

 1  : abstract class Person 
 2  : { 
 3  :     private string firstName; 
 4  :     private string lastName; 
 5  :     private string id; 
 6  :     public Person(string first, string last, string id) 
 7  :     { 
 8  :         this.firstName = first; 
 9  :         this.lastName = last; 
 10 :         this.id = id; 
 11 :     } 
 12 :     public void Print() 
 13 :     { 
 14 :         Console.WriteLine("Name : {0} {1}", lastName, firstName); 
 15 :         Console.WriteLine("ID : {0}", id); 
 16 :         PrintDetails(); 
 17 :     } 
 18 :     protected abstract void PrintDetails(); 
 19 :     public string FirstName 
 20 :     { 
 21 :         get { return firstName; } 
 22 :         set { firstName = value; } 
 23 :     } 
 24 :     public string LastName 
 25 :     { 
 26 :         get { return lastName; } 
 27 :         set { lastName = value; } 
 28 :     } 
 29 :     public string ID 
 30 :     { 
 31 :         get { return id; } 
 32 :     } 
 33 : } 
שורה 1 - הגדרת המחלקה האבסטרקטית Person
שורה 12 - המתודה ()Print משקפת התנהגות כללית של "הדפסת פרטים", לא משנה באיזה סוג אדם מדובר (כרגע מדובר בכל תפקיד שמייצד "אדם", לאו דווקא עובד) נרצה להדפיס את השם הפרטי, את שם המשפחה ואת מספר הזהות. לאחר מכן המידע אשר יודפס תלוי בתפקיד הייחודי במערכת וזה כבר התפקיד של המחלקות הנגזרת.
ואיך נותנים למחלקות הנגזרות להביא את ההתמחות שלהן לידי ביטוי? באמצעות מתודה וירטואלית או מתודה אבסטרקטית.
זו תפקידה של המתודה ()PrintDetails (שורה 18)
 
העקרונות המודגמים במחלקה Person:
המחלקה Person מוגדרת כמחלקה אבסטרקטית, דהיינו ,ההתייחסות אליה היא כאל מושג מופשט ולא כאל יישות ממשית.
המחלקה היא כללית מדי, ולכן אין שום צורך להגדיר ממנה אובייקטים (לעיתים זה אף יכול להזיק).
המתודות Person.Print ו- Person.PrintDetails מאוד מעניינות, לא בגלל מה שהן מבצעות, זה ברור, אלא בשל הקשר ביניהן:
מייצגת תהליך גנרי המשותף לכלל האובייקטים בהיררכיה:
הדפסת שם + הדפסת פרטי שכר.
הדפסת השם מתבצעת במחלקת הבסיס משום שזה פורמט אחיד לכל המחלקות הנגזרות, הדפסת פרטי העובד (הפרמטרים לשכר ומחלקה) משתנים מסוג עובד אחד למשנהו ולכן המתודה Person.Print מפעילה את המתודה האבסטרקטית Print.PrintDetails אשר תמומש בנפרד בכל אחת מהמחלקות הנגזרות באופן שונה ממחלקה אחת לרעותה והמתאים למחלקה.
במילים אחרות ישנה התנהגות כללית המשותפת לכל האובייקטים הנוצרים מהיררכיית המחלקות (Person.Print) המפעילה, היכן שצריך, התנהגות מתמחית וספציפית אשר תממומש במחלקות הנגזרות (Person.PrintDetails).
קוד גנרי (Person.Print) משותף לכל המחלקות המפעיל קוד ספציפי-מתמחה היכן שיש צורך (Person.PrintDetails)
 


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

 1  : abstract class Employee : Person 
 2  : { 
 3  :     private int empNum; 
 4  :     private string department; 
 5  :     public Employee(string first, string last, string id, int num, string dpt) 
 6  :         : base(first, last, id) 
 7  :     { 
 8  :         this.empNum = num; 
 9  :         this.department = dpt; 
 10 :     } 
 11 :     protected override void PrintDetails() 
 12 :     { 
 13 :         Console.WriteLine("Employee num : {0}\nDepatment : {1}", empNum, department); 
 14 :     } 
 15 :     public int EmpNum 
 16 :     { 
 17 :         get 
 18 :         { 
 19 :             return empNum; 
 20 :         } 
 21 :         set 
 22 :         { 
 23 :             if (value < 0) 
 24 :                 empNum = value; 
 25 :         } 
 26 :     } 
 27 :     public string Department 
 28 :     { 
 29 :         get 
 30 :         { 
 31 :             return department; 
 32 :         } 
 33 :         set 
 34 :         { 
 35 :             department = value; 
 36 :         } 
 37 :     } 
 38 :     public float CalcSalary() 
 39 :     { 
 40 :         float bruto = CalcBruto(); 
 41 :         return bruto - CalcIncomeTax(bruto) - CalcSocialInsuranceTax(bruto); 
 42 :     } 
 43 :     protected abstract float CalcBruto(); 
 44 :     private float CalcIncomeTax(float bruto) 
 45 :     { 
 46 :         return bruto * 0.3f; 
 47 :     } 
 48 :     private float CalcSocialInsuranceTax(float bruto) 
 49 :     { 
 50 :         return bruto * 0.1f; 
 51 :     } 
 52 : } 
שורה 1 - הגדרת המחלקה האבסטרקטית Employee.
שורה 5 - הבנאי של המחלקה Employee מקבל את כל המידע הרלבנטי ליצירת המחלקה Employee ומחלקת הבסיס Person, את המידע הרלבנטי למחלקת הבסיס הוא דואג להעביר לה באמצעות רשימת אתחול (שורה 6). 
שורה 11 - מימוש של המתודה PrintDetails המביא לידי ביטוי את ההתמחות של המחלקה הנגזרת, הדפסת פרטי ה-Department במקרה זה.
שורה 38 - התנהגות חישוב משכורת.
לכל עובד צריך לחשב משכורת, חישוב משכורת מערכת אלגוריתמים כלליים (גנריים) ואלגוריתמים מתמחים.
האלגוריתם הכללי של חישוב נטו הוא אלגוריתם כללי:
חשב שכר ברוטו, החסר ממנו את מס ההכנסה ואת דמי הביטוח הלאומי.
עבור כל העובדים מדובר על אותו אלגוריתם.
 האלגוריתם "חשב שכר נטו" מורכב משלושה אלגוריתמים:
"חשב שכר ברוטו."
"חשב מס הכנסה".
"חשב דמי ביטוח לאומי".
"חשב שכר ברוטו" - הוא אלגוריתם מתמחה, לכל סוג עובד נשתמש בנוסחה ובשיטה אחרת על מנת לחשב את שכר הברוטו שלו.

העקרונות המודגמים במחלקה Employee:
הורשה –המחלקה Employee יורשת את המחלקה Person ומקבלת ממנה תוך כדי ההורשה את כל מה שהוגדר בה, מכיוון שאין אנו חפצים לאפשר הקצאה של אובייקט ממחלקה זו כי אין לא משמעות נגדיר גם אותה כמחלקה אבסטרקטית.
המתודה Employee.CalcSalary מממשת קוד גנרי (קוד משותף), האלגוריתם לחישוב שכר הנטו לכלל העובדים על תפקידיהם השונים הוא זהה:
מחשבים את שכר הברוטו ומפחיתים ממנו את מס ההכנסה ואת מיסי הביטוח הלאומי.ומכיוון שמדובר בקוד גנרי האלגוריתם ממומש במחלקת הבסיס במתודה רגילה.
המתודה ()Employee.CalcBruto מייצגת אלגוריתם מתמחה, לכל אחד מסוגי העובדים נממש נוסחה אחרת לחישוב שכר הברוטו ולכן נגדירה כמתודה אבסטרקטית.
המתודות ()Employee.CalcIncomeTax ו- ()Employee.CalcSocialInsuranceTax ממומשות כמתודות רגילות משום שהן מייצגות אלגוריתמים גנריים, לכל העובדים מחשבים את המיסים על פי נוסחה מתמטית אחידה.

 

Worker הוא כבר יישות ממשית, הוא מייצג תפקיד מאוד מוגדר ומאוד ספציפי, אנחנו יודעים בדיוק מהו "פועל", יודעים איך לחשב לו את השכר, איזה אלגוריתם נדרש, יודעים בדיוק איך להדפיס את פרטיו.
 1  : class Worker : Employee 
 2  : { 
 3  :     private float salPerHour; 
 4  :     private float workingHours; 
 5  :     public Worker(string first, string last, string id, int num, string dpt, float sal, float hours) 
 6  :         : base(first, last, id, num, dpt) 
 7  :     { 
 8  :         SalPerHour = sal; 
 9  :         WorkingHours = hours; 
 10 :     } 
 11 :     public float SalPerHour 
 12 :     { 
 13 :         get 
 14 :         { 
 15 :             return salPerHour; 
 16 :         } 
 17 :         set 
 18 :         { 
 19 :             if (value > 15.5f) 
 20 :                 salPerHour = value; 
 21 :         } 
 22 :     } 
 23 :     public float WorkingHours 
 24 :     { 
 25 :         get 
 26 :         { 
 27 :             return workingHours; 
 28 :         } 
 29 :         set 
 30 :         { 
 31 :             if (value >= 0) 
 32 :                 workingHours = value; 
 33 :         } 
 34 :     } 
 35 :     protected override void PrintDetails() 
 36 :     { 
 37 :         base.PrintDetails(); 
 38 :         Console.WriteLine("Salary Rate = {0}", salPerHour); 
 39 :         Console.WriteLine("Monthly Working Hours = {0}", workingHours); 
 40 :         Console.WriteLine("Salary = {0}", CalcSalary()); 
 41 :     } 
 42 :     protected override float CalcBruto() 
 43 :     { 
 44 :         return salPerHour * workingHours; 
 45 :     } 
 46 : } 
בשורה 1 - המחלקה Worker יורשת את המחלקה האבסטרקטית Employee.
בשורות 3 ו-4 נגדיר את התכונות שיאפשרו לחשב לאובייקט מהמחלקה Worker את שכר הברוטו שלו - שכר לשעה ושעות עבודה חודשיות.
בשורה 5 מוגדר הבנאי שמקבל את כל המידע הנדרש למחלקה Worker ולמחלקות הבסיס שלה, בשורה 6 הבנאי מעביר את המידע הנדרש למחלקת הבסיס Employee וזו בתורה מעבירה את המידע הנדרש למחלקת הבסיס שלה Person.
את תהליך הבניה ניתן לקראת ויזואלית באמצעות UML Sequence Diagram:
תהליך בניית אובייקט Worker


בשורה 35 - המימוש של PrintDetails.
תחילה מופעל המימוש של מחלקת הבסיס (שורה 36) אשר דואגת להשפיס את המידע השייך למחלקת הבסיס Employee,
לאחר מכן מודפסים השדות של המחלקה Worker.
בשורה 40 מתחיל עוד תהליך מעניין שמערב קוד גנרי המביא לידי ביטוי את האלגוריתמים המשותפים לכל המחלקות הנגזרות ואת הקוד הספציפי הממומש במחלקה הנגזרת.
הקריאה ל-()Employee.CalcSalary הגנרית שתפעיל את ()Worker.CalcBruto (שורה 42) המתמחה אשר יודעת לחשב את משכורת הברוטו של אובייקט מהמחלקה Worker.
והנה לנו עוד מטלה המערבת קוד גנרי הממומש במתודה רגילה במחלקת הבסיס (Employee) אשר יודעת לערב ולהשתמש בקוד מתמחה, ספציפי (במחלקה Worker).
וזו אחת הבשורות הגדולות ביותר של פולימורפיזם.
 

התהליכים המתבצעים בשלושת המחלקות הבאות זהים לתהליכים שהודגמו במחלקה Worker.

המחלקה Salesman:

 1  : class SalesMan : Employee 
 2  : { 
 3  :     private float totalSales; 
 4  :     public SalesMan(string first, string last, string id, int num, string dpt, float total) 
 5  :         : base(first, last, id, num, dpt) 
 6  :     { 
 7  :         TotalSales = total; 
 8  :     } 
 9  :     public float TotalSales 
 10 :     { 
 11 :         get 
 12 :         { 
 13 :             return totalSales; 
 14 :         } 
 15 :         set 
 16 :         { 
 17 :             if (value >= 0) 
 18 :                 totalSales = value; 
 19 :         } 
 20 :     } 
 21 :     protected override void PrintDetails() 
 22 :     { 
 23 :         base.PrintDetails(); 
 24 :         Console.WriteLine("Total Sales = {0}", totalSales); 
 25 :         Console.WriteLine("Salary = {0}", CalcSalary()); 
 26 :     } 
 27 :     protected override float CalcBruto() 
 28 :     { 
 29 :         return totalSales * 0.03f; 
 30 :     } 
 31 : } 
 
המחלקה Manager:
 1  : class Manager : Employee 
 2  : { 
 3  :     private float monthlySalary; 
 4  :     public Manager(string first, string last, string id, int num, string dpt, float salary) 
 5  :         : base(first, last, id, num, dpt) 
 6  :     { 
 7  :         Salary = salary; 
 8  :     } 
 9  :     public float Salary 
 10 :     { 
 11 :         get { return monthlySalary; } 
 12 :         set { if (value >= 0)monthlySalary = value; } 
 13 :     } 
 14 :     protected override void PrintDetails() 
 15 :     { 
 16 :         base.PrintDetails(); 
 17 :         Console.WriteLine("Salary = {0}", CalcSalary()); 
 18 :     } 
 19 :     protected override float CalcBruto() 
 20 :     { 
 21 :         return monthlySalary; 
 22 :     } 
 23 : } 

המחלקה SeniorManager:
 1  : class SeniorManager : Manager 
 2  : { 
 3  :     private float bonus; 
 4  :     public SeniorManager(string first, string last, string id, int num, string dpt, float 
 5  :                            sal, float bonus) 
 6  :         : base(first, last, id, num, dpt, sal) 
 7  :     { 
 8  :         Bonus = bonus; 
 9  :     } 
 10 :     public float Bonus 
 11 :     { 
 12 :         get { return bonus; } 
 13 :         set 
 14 :         { 
 15 :             if (value >= 0) 
 16 :                 bonus = value; 
 17 :         } 
 18 :     } 
 19 :     protected override void PrintDetails() 
 20 :     { 
 21 :         Console.WriteLine("Salary = {0}", CalcSalary() + Bonus); 
 22 :     } 
 23 :     protected override float CalcBruto() 
 24 :     { 
 25 :         return base.CalcBruto() + bonus; 
 26 :     } 
 27 : } 

Main:
 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         Worker worker = new Worker("Moshe", "ben moshe", "123456789", 123, "CNC", 32.45f, 181f); 
 6  :         worker.Print(); 
 7  :  
 8  :         SalesMan salesman = new SalesMan("Dina", "batdina", "344555656", 128, "Sales", 180000); 
 9  :         salesman.Print(); 
 10 :  
 11 :         Manager manager = new Manager("Ronen", "Mitlonen", "353444675", 1, "Sales", 24000f); 
 12 :         manager.Print(); 
 13 :     } 
 14 : } 
פלט:
פלט תוכנית הדוגמה המסכמת לפולימורפיזם



 
חלק 10 - ממשקים Interfaces 




כל הזכויות שמורות למחבר ©