אין עיצוב ארכיטקטורי או עיצוב קוד הפותר כל בעיה. כל פתרון תקף לבעיה ספציפית.
אבל, מתכנתים הם בדרך כלל אנשים חכמים והם גילו שפתרון יכול להוות פתרון, עם שינוי, לבעיה אחרת. אז כעת פתרון אחד פותר 2 בעיות, ועל מנת שהוא ייעבוד הפתרון הזה צריך להיות כללי יותר.
איך לעצב קוד כללי - קוד גנרי
כתבו קוד שמקבל 2 מספרים ומחבר בינהם ומחזיר את התוצאה.
כתבו קוד שיודע לבצע חיסור
לפני שאתם ממשיכים הלאה אני מזמין אתכם לנסות בעצמכם את התרגיל.
הסעיף הראשון הוא פשוט:
1 2 3 4
intSum(int a, int b) { return a + b; }
בסעיף השני עלינו “להרחיב” את האופקים של הקוד ולתת מענה גם לפעולות אחרות. אבל… כעת נחשוב “רגע, אם אנחנו צריכים פעולת חיסור מה הלאה? כפל? חילוק? בשלב הזה נתחיל לחשוב במושגים “כלליים” ולהתאים פתרון שיכול לעזור לנו גם בהמשך:
ואם נצטרך פרמטרים רבים ולא רק 2, ניתן אפילו להרחיב את זה יותר. זה קוד C++ אז לא חייב להבין בדיוק מה עשיתי כאן, בסופו של דבר זה נותן לנו יכולת להעביר יותר מפרמטר בודד לפנוקציה:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include<iostream>
template<class Operation, class ...NumType> intCalculate(Operation op ,NumType&&... a) { returnop(a...); }
/* Fix divide - check if one of them is zero. template<typename ...Args> int Divide(Args... args) { return (args / ...); } */
intmain() { auto toSum = true; auto result = toSum? Sum(1 , 3, 4) : Substract(4,3,1); std::cout << result; }
מהן 2 הבעיות הטמונות לנו בקוד הזה?
בעיות
הפונקציה Multiply לא בשימוש.
הפונקציה Divide בהערה עם הערות נוספות לתיקון.
בקיצור - אפשר למחוק את 2 הפונקציות האלו…
אבסטרקציית יתר
אבסטרקציה עוזרת להוריד תלותיות בין 2 רכיבים ובכך ליצור אינטרקציה עקיפה. המטרה של אבסטרקציה היא לאפשר לשנות את הקוד מבלי לשנות את האינטרקציה בין 2 רכיבים:
intmain() { auto calculator = std::make_unique<ProductCalculator>(GetProduct()); auto kwUsagePerDay = calculator->CalculateProduct(24h); }
ElectricalProduct מבצע את החישוב. אך העיצוב נבחר כדי למסך את השימוש ב-ElectricalProduct. שימוש כזה נעשה באופן עקבי ב-Domain driven design הוא בעיצובים שבוחרים למסך כמה שיותר אובייקטים. במידה ו-ElctricalProduct היה פרטי ולא היה ניתן להשתמש בו מחוץ לקוד שלנו, אז היינו צריכים לבצע אבסטרקציה נוספת כדי לאפשר גישה.
לעיתים אין צורך באסטרקציה זו, תנסו לוותר על ה-Adapter, Factory, Manager וכדו’…
תבניות עיצוב
תבניות עיצוב אלו פתרונות כלליים שיצאו מתוך הפשטה של פתרונות ספציפיים יותר. למשל ה-Strategy הוא פתרון יעיל להחלפת לוגיקה בזמן ריצה:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
interfaceIStrategy { voidDo(); }
classStrategy1 : IStrategy { publicvoidDo() { }}
classStrategy2 : IStrategy { publicvoidDo() { }}
classStrategy3 : IStrategy { publicvoidDo() { }}
לעיתים נוכל להחליף אבסטרקציה כזו בקונפיגורציה עם מימוש כללי יותר. ואת העקרון הזה ניתן לפשט לעקרון שונה -
הכלה עדיפה מהורשה
לעיתים הורשה יכולה להוביל לקוד מסורבל יותר. ומכיוון שהרבה תבניות עיצוב משתמשות בהורשה כפתרון - זה מוביל אותנו לקוד מסובך יותר ככל שאנחנו מכניסים עוד תבניות עיצוב לפתרון שלנו.
ולפי העקרון שהזכרתי איך ניתן לפתור את זה?
לא להכניס תבניות עיצוב לא מתואמות. כל תבנית עיצוב צריכה להיות מותאמת לפתרון הספציפי. בלי להתאים אותו התבנית עיצוב תהיה “מגושמת”.
שימוש בתבנית עיצוב בודדת, והעדפת הכלה במקום הורשה תפשט לכם את הקוד.
מסדי נתונים
כמעט כל ארכיטקטורה מכילה מסדי נתונים. מסד נתונים מבטיח לנו גישה מהירה ובטוחה לנתונים האמורים להישמר לטווח ארוך, רוב מסדי הנתונים נותנים גם יכולות מעבר לשמירת מידע:
טרנזקציוניות
שמירה ארוכת טווח וקצרת טווח
תשאול המסד עבור נתון מסוים - או אף שפה לשאילתות
גיבויים
כל מנהל יגיד לכם “מה ברור שמסד, איפה עוד נשמור את הנתונים?”. הוא חלקית צודק - עדיף להשתמש במסד נתונים קיים מאשר להמציא את הגלגל מחדש.
הבעיתיות מתחילה כאשר המסד נתונים הוא הראשון שנבחר, כי אז כל העיצוב הוא סביב המסד נתונים. זה במיוחד לא טוב כאשר המסד נתונים בנוי מטבלאות:
ניתן לראות תבניות רבות בקוד המרוכז במסד נתונים:
קוד שקשור לשאילתות נדחף לתוך קוד הלוגיקה
1 2 3 4 5 6 7
MyRow GetData(string nameOfEmployee) { var statement = "select * from employes where name = :name"; /// ... some code