|
|
|
כמה נקודות בקשר למימוש:
- הגדרת הממשק של ה Strategy ושל ה Context - הממשקים של ה strategy ושל ה context חייבים לאפשר ל ConcreteStrategy גישה יעילה לכל data שהוא יצטרך מה Context ולהיפך.
גישה אחת אומרת שה Context יעביר את ה data שלו כפרמטרים לפעולות של ה Strategy, כלומר לוקחים את ה data אל ה strategy. זה שומר על תלות נמוכה בין ה Strategy וה Context. מצד שני, ה Context עלול להעביר data שה Strategy אינו צריך.
גישה אחרת אומרת שה Context יעביר את עצמו כארגומנט, וה Strategy יבקש ממנו data באופן מפורש. באופן אלטרנטיבי, ה Strategy יכול להחזיק בתוכו הצבעה ל Context שלו וכך נמנע הצורך להעביר כל פרמטרים לפעולותיו, מכיוון שגם כאן הוא יבקש data באופן מפורש. בכל אחת משתי הדרכים בגישה זו ה Strategy יכול לבקש בדיוק את מה שהוא צריך ולא יותר מכך. אבל כעת Context צריך להגדיר ממשק יותר מתוכנן לגישה ל data שלו, מה שמגדיל את התלות בינו לבין ה Strategy.
הצרכים של אלגוריתם מסוים והדרישות שלו ל data יקבעו באיזו משתי הטכניקות נשתמש.
- Strategy ים כפרמטרים ל template - ב ++C ניתן להשתמש ב templates כדי להתאים Strategy ל class. הטכניקה הזו ישימה רק כאשר מתקיימים שני התנאים הבאים:
תנאי אחד הוא שניתן לבחור את ה Strategy בזמן קומפילציה, ותנאי שני הוא שה Strategy לא אמור להשתנות בזמן ריצה. אם אכן מתקיימים התנאים, ה class שנרצה להתאים לו Strategy יוגדר כ template שיש לה Strategy כפרמטר:
template <class AStrategy>
class Context {
void Operation() {
theStrategy.DoAlgorithm();
}
// ...
private:
AStrategy theStrategy;
};
כך ה class מקבל אובייקט Strategy בזמן היצירה שלו:
class MyStrategy {
public:
void DoAlgorithm();
};
Context<MyStrategy> aContext;
בשיטה זו אין צורך להגדיר abstract class אשר מגדיר את הממשק ל Strategy. כמו כן, שימוש ב Strategy כפרמטר ל template מאפשר לנו להתאים את ה Strategy ל Context באופן סטטי, דבר שמגדיל את היעילות.
- הפיכת השימוש באובייקט Strategy לאופציונלי - ניתן לפשט את המחלקה Context אם היתה משמעות למקרה שאין לה אובייקט Strategy. Context יבדוק אם יש לו אובייקט Strategy לפני שינסה לגשת אליו. אם יש לו אובייקט, אז הוא ישתמש בו כרגיל. אם אין לו אובייקט, אז Context יבצע התנהגות ברירת מחדל שלו. היתרון בגישה זו הוא שהקליינט כלל אינו צריך להתעסק עם אובייקטי Strategy, אלא אם כן התנהגות ברירת המחדל אינה מתאימה לו.
קוד לדוגמה:
ניתן דוגמה של קוד עבור הדוגמה שנתנו בסעיף המוטיבציה. מחלקת ה Composition מטפלת באוסף של מופעי
Component , אשר מייצגים טקסט ואלמנטים גרפיים במסמך.
Composition מארגן אובייקטים מסוג Component לשורות תוך שימוש במופע של subclass של
Compositor שמרכז בתוכו אסטרטגיה לשבירת שורות. לכל component יש size, stretchability, ו shrinkablility. ה stretchability מגדירה עד כמה component יכול לגדול מעבר לגודל שלו, ו shrinkability מגדירה עד כמה ניתן לכווץ אותו. ה Composition מעביר את הערכים הללו ל compositor אשר נעזר בהם על מנת לקבוע את המיקומים הטובים ביותר לשבירת שורות.
class Composition {
public:
Composition(Compositor*);
void Repair();
private:
Compositor* _compositor;
Component* _components; // the list of components
int _componentCount; // the number of components
int _lineWidth; // the Composition's line width
int* _lineBreaks; // the position of linebreaks
// in components
int _lineCount; // the number of lines
};
כאשר נזדקק לסידור חדש, ה composition יבקש מה compositor שלו לקבוע היכן למקם את שבירת השורות. ה composition מעביר ל compositor שלושה מערכים שמגדירים size, stretchability, ו shrinkability של כל הcomponent ים. הוא גם מעביר את מספר ה component ים, רוחב שורה, ומערך שה compositor ימלא במיקומים של כל שבירות השורות. ה compositor מצדו מחזיר את מספר השבירות שמחושב על ידיו.
הממשק של Compositor מאפשר ל composition להעביר לו את כל האינפורמציה שהוא זקוק לה. זו דוגמה לגישה של "לקחת את ה data אל ה Strategy":
class Compositor {
public:
virtual int Compose (
Coord natural[], Coord stretch[], Coord shrink[],
int componentCount, int lineWidth, int breaks[]
) = 0;
protected:
Compositor();
};
שימו לב ש Compositor הוא abstract class. ה
concrete subclasses שלו מגדירים אסטרטגיות ספציפיות לשבירת שורות. ה composition קורא ל compositor שלו בפעולת ה
Repair() . תחילה היא מאתחלת מערכים עם ה size, ה stretchability, ו ה shrinkability של כל component (פרטים אלה נשמיט למען הקיצור וכדי שלא תאבדו ריכוז...). לאחר מכן היא קוראת ל compositor כדי לקבוע את נקודות שבירת השורות, ולבסוף מסדרת את ה component ים בהתאם לשבירות (גם פרט זה הושמט כאן למען הקיצור, זה רק למענכם...):
void Composition::Repair() {
Coord* natural;
Coord* stretchability;
Coord* shrinkability;
int componentCount;
int* breaks;
// prepare the arrays with the desired
// component sizes
// ...
// determine where the breaks are:
int breakCount;
breakCount = _compositor->Compose(
natural, stretchability, shrinkability,
componentCount, _lineWidth, breaks
);
// lay out components according to breaks
// ...
}
הבה ונסתכל ב subclass ים של Compositor. ה SimpleCompositor בוחן component ים שורה אחר שורה וקובע היכן השבירות ימוקמו:
class SimpleCompositor : public Compositor {
public:
SimpleCompositor();
virtual int Compose(
Coord natural[], Coord stretch[], Coord shrink[],
int componentCount, int lineWidth, int breaks[]
);
// ...
};
TeXCompositor משתמש באסטרטגיה יותר גלובלית. הוא מסתכל על פסקה שלמה בו זמנית ולוקח בחשבון את ה size וה stretchability של component. הוא מנסה גם לתת לפסקה "צבע" אחיד על ידי הקטנת האזורים הלבנים ככל שניתן.
class TeXCompositor : public Compositor {
public:
TeXCompositor();
virtual int Compose(
Coord natural[], Coord stretch[], Coord shrink[],
int componentCount, int lineWidth, int breaks[]
);
// ...
};
ArrayCompositor שובר את ה component ים לשורות עם רווחים קבועים בין ה component ים.
class ArrayCompositor : public Compositor {
public:
ArrayCompositor(int interval);
virtual int Compose(
Coord natural[], Coord stretch[], Coord shrink[],
int componentCount, int lineWidth, int breaks[]
);
// ...
};
שימו לב שלא כל ה compositor ים משתמשים בכל ה data שנשלח אליהם:
SimpleCompositor מתעלם מה stretchability, ולוקח בחשבון רק את הרוחב הטבעי שלהם.
TeXCompositor משתמש בכל המידע שנשלח אליו, ו
ArrayCompositor מתעלם מהכל. כדי ליצור Composition נעביר לו את ה Compositor שנרצה שישתמש בו:
Composition* quick = new Composition(
new SimpleCompositor
);
Composition* slick = new Composition(new TeXCompositor);
Composition* iconic = new Compostion(
new ArrayCompositor(100)
);
הממשק של Compositor תוכנן בקפידה על מנת לתמוך בכל האלגוריתמים אשר אפשרי שימומשו על ידי subclass ים שלו. לא נרצה לשנות את הממשק עם כל מימוש חדש, כמובן, כי זה ידרוש שינוי של מחלקות קיימות. בגדול, הממשק של Strategy ושל Context יקבעו עד כמה ה pattern משיגה את מטרתה.
|
|
|
|