ראשי > adapter pattern > חלק שלישי
The Adapter

כמה נקודות בקשר למימוש:
  • מימוש של class-adapter ים ב ++C : במימוש של class adapter ב ++C המחלקה Adapter תירש public מ Target ותירש privet מ Adaptee. לכן Adapter הוא subtype של Target אבל לא של Adaptee.
  • Pluggable adapters: נתבונן בשתי דרכים לממש pluggable adapter עבור ה TreeDisplay שתואר לעיל (תזכורת: אובייקט שיודע להציג גרפית מבנים היררכיים שבנויים כעץ). הצעד הראשון, שמשותף לשתי הדרכים, הוא מציאת ממשק מינימלי ל adaptee , כלומר מספר מינימלי של פעולות שה adpatee יקיים, כדי להקל ולפשט את האדפטציה. במקרה שלנו ה adaptee הוא מבנה היררכי ולכן ממשק מינימלי אפשרי יכלול 2 פעולות: פעולה שיודעת לצייר צומת, ופעולה שמחזירה את כל הבנים של צומת מסוים. נתבונן בשתי הגישות למימוש במקרה זה:

    1) שימוש בפעולות אבסטרקטיות:
    הגדרת הפעולות האבסטרקטיות של הממשק המינימלי שהגדרנו בתוך ה TreeDisplay עצמו. כאן נסתכל על ה TreeDisplay גם כעל client וגם כעל target (הפעולה BuildTree() בתוכו היא פעולה של קליינט, והיא קוראת לשתי הפעולות האבסטרקטיות - כלומר לממשק של ה target). לכן ה adapter יהיה subclass של TreeDisplay (כי הוא target). בציור ניתן לראות דוגמה לאדפטציה של TreeDisplay להצגת מבנה היררכי של ספריה. ה adapter הוא ה DirectoryTreeDisplay שמממש את הפעולות האבסטרקטיות של ה target (במקרה זה TreeDisplay כזכור...) כך שאפשר להציג ספריה (ה adaptee) בצורה גרפית.

    לחצו להגדלה
    לחץ להגדלה - pluggable adapter - פעולות אבסטרקטיות

    אם אינכם זוכרים כיצד להסתכל בדיאגרמה זו זה הזמן להתרענן .

    2) שימוש ב delegate object:
    בגישה זו נתייחס ל TreeDisplay כ client שמעביר בקשות ל delegate object , שהוא ה target לצורך העניין. הממשק של ה delegate object הוא הממשק ש TreeDisplay מכיר. ה adapter יירש את ה delegate object ויחזיק הצבעה לadaptee (שאת הממשק שלו אנו רוצים להתאים, זו מטרתנו למי ששכח מה אנו עושים כרגע...). כך, כשנרצה לבצע אדפטציה שתתאים ממשק של אובייקט היררכי כלשהו (אובייקט שבנוי כעץ אם שכחתם...) לממשק שTreeDisplay מכיר, נבנה לו adapter חדש שיורש מה delegate object וכך נוכל להשתמש בTreeDisplay כדי לצייר גם אותו.
    בציור מתוארת דוגמה (קצת מבלבלת..) של directory browser שמשתמש ב TreeDisplay כדי לצייר את מבנה הספריות שלו עצמו. במקרה הזה ה directory browser יוכל לשמש בעצמו כ delegate ולממש את הממשק של שתי הפעולות שהגדרנו (ציור צומת, וגישה לכל הילדים של צומת מסוים) על ידי הפניית הבקשות למבנה הספרייה שהוא מצביע אליו. מכיוון שה directory browser הוא ה delegate שלנו, הוא יורש מTreeAccesorDelegate . אם במקור ה directory browser הוא subclass של מחלקה כלשהי, הוא ימשיך לרשת גם ממנה וזאת נעשה בעזרת הורשה מרובה.

    לחצו להגדלה
    לחץ להגדלה - pluggable adapter - שימוש ב delegate object

    למי שאינו זוכר כיצד להסתכל בדיאגרמה זו זה הזמן להתרענן .
למעלה
קוד לדוגמה:
נעבור בסקירה קצרה על הדוגמה של class adapter ו object adapter עבור הדוגמה מהתחלת הפרק עם המחלקות Shape ו TextView:

class Shape {
public:
     Shape();
     virtual void BoundingBox (
         Point& bottomLeft, Point& topRight
         ) const;
     virtual Manipulator* CreateManipulator () const;
};


class TextView {
public:
     TextView();
     void GetOrigin(Coord& x, Coord& y) const;
     void GetExtent(Coord& width, Coord& height) const;
     virtual bool IsEmpty() const;
};

Shape מניחה שה bounding box מוגדרת על ידי שתי פינות נגדיות שלה. בניגוד לכך, TextView מוגדרת על ידי נקודת ראשית, גובה ורוחב. כמו כן Shape מגדירה פעולת CreateManipulator() שיוצרת אובייקט Manipulator שיודע לשנות אותה בתגובה לקלט מהמשתמש, כמו למשל גרירה עם עכבר, שינוי גודל וכו'. ב TextView אין פעולה מקבילה. המחלקה TextShape היא adapter בין הממשקים השונים האלה.
Class adapter משתמש בהורשה מרובה לצורך ביצוע אדפטציה בין ממשקים. כפי שראינו הוא יורש ממחלקה אחת את הממשק (public) וממחלקה שניה את המימוש (privet):

class TextShape : public Shape, private TextView {
public:
     TextShape();

     virtual void BoundingBox (
         Point& bottomLeft, Point& topRight
     ) const;
     virtual bool IsEmpty() const;
     virtual Manipulator* CreateManipulator() const;
};

נשים לב למספר נקודות:
נממש את פעולת ה BoundingBox() כך שתבצע אדפטציה מהממשק של TextView לממשק של Shape (תפקידה הוא לעדכן שתי נקודות שהיא מקבלת by reference בפינות הקופסה החוסמת את הצורה. כאן היא עושה זאת תוך שימוש במימוש המוכן של TextView):

void TextShape::BoundingBox (
     Point& bottomLeft, Point& topRight
     ) const {
         Coord bottom, left, width, height;

     GetOrigin(bottom, left);
     GetExtent(width, height);

     bottomLeft = Point(bottom, left);
     topRight = Point(bottom + height, left + width);
};

פעולת ה IsEmpty() מדגימה העברה ישירה של פקודה הלאה, דבר שמקובל במימוש של adapter- ים:

bool TextShape::IsEmpty () const {
     return TextView::IsEmpty();
}

לבסוף, נגדיר את CreateManipulator() , פעולה שאינה נתמכת על ידי TextView. נניח שכבר יש לנו מחלקה TextManipulator שיודעת לשנות אובייקט TextShape:

Manipulator* TextShape::CreateManipulator () const {
     Return new TextManipulator (this);
}

לעומת ה class adapter שראינו, ה object adapter משתמש בהרכבת אובייקטים כדי לבצע אדפטציה בין ממשקים. בגישה זו, ה adapter ( TextShape ) יחזיק פוינטר ל TextView:

class TextShape : public Shape {
public:
     TextShape(TextView*);

     virtual void BoundingBox(
         Point& bottomLeft, Point& topRight
     ) const;
     virtual bool IsEmpty() const;
     virtual Manipulator* CreateManipulator() const;
private:
     TextView* _text;
};

TextShape חייב לאתחל פוינטר לאובייקט TextView והוא עושה זאת בconstructor . הוא גם קורא לפעולות של האובייקט TextView בכל פעם שקוראים לפעולות שלו. בדוגמה זו נניח שה client יוצר אובייקט מסוג TextView ומעביר את זה ל constructor של TextShape:

TextShape::TextShape (TextView* t) {
     _text = t;
}


void TextShape::BoundingBox (
     Point& bottomLeft, Point& topRight
     ) const {
     Coord bottom, left, width, height;

     _text->GetOrigin(bottom, left);
     _text->GetExtent(width, height);

     bottomLeft = Point(bottom, left);
     topRight = Point(bottom + height, left + width);
}


bool TextShape::IsEmpty () const {
     return _text->IsEmpty();
}

המימוש של CreateManipulator() יהיה כמו במקרה הקודם מכיוון שאינו קשור ל TextView, אלא הוא הרחבה שמגיעה מ Shape. ה object adapter הוא מעט יותר ארוך לכתיבה, אבל יותר גמיש וקל לשינויים. למשל, הוא יעבוד גם עם subclasses של TextView בניגוד ל class adapter - הפוינטר Text מוכן לקבל אותם כצאצאים של TextView.

למעלה







 
מה בעמוד:
 
מימוש
קוד לדוגמה