Advertisement
  1. Code
  2. Design Patterns

תבניות עיצוב: תבנית אדפטור (Adapter Design Pattern)

Scroll to top
Read Time: 4 min
This post is part of a series called Design Patterns in PHP.
Design Patterns: The Facade Pattern
Design Patterns: The Decorator Pattern

() translation by (you can also view the original English article)

במאמר שעבר, עברנו על אופן שימוש בתבנית חזית - "Facade Pattern", שיכולה לעזור לפשט תמיכה במערכת גדולה ומורכבת באמצעות מחלקת חזית אחת פשוטה.

במאמר זה אנחנו נמשיך את הדיון על תבניות עיצוב ונתמקד בתבנית אדפטור. ניתן להשתמש בתבנית הזו כשהקוד שלכם תלוי ב-API חיצוני או מחלקה חיצונית אחרת שנוטה להשתנות לעתים קרובות. התבנית הזאת נמצאת תחת קטגוריה "תבניות מבנה" מפני שהיא מלמדת אותנו איך הקוד בכלל ומחלקות בפרט צריכות להיות בנויות למטרת תחזוקה והרחבה נוחה.

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

הבעיה

1
class Twitter {
2
3
  public function __construct() {
4
		// Your Code here //

5
	}
6
7
	public function send($msg) {
8
		// Posting to Twitter //

9
		echo $msg;
10
	}
11
}
12
13
$twitter = new Twitter();
14
$twitter->send('Posting on Twitter');

בקוד לעיל אתם יכולים לראות שאנחנו משתמשים במחלקת Twitter לצורך שליחת ציוץ (tweet). אנחנו יוצרים אובייקט של Twitter API ישירות ושולחים ציוצים ל-Twitter. אתם יכולים לפגוש את פיסת קוד הזאת במקומות רבים ברשת. כמו שכבר ראיתם הקוד הזה משתמש בפונקציה $twitter->send('Posting on Twitter'); לצורך זה.

לפני זמן-מה Twitter שינו שם של פונקצית API מ-send ל-sendTweet. השינוי הזה מצביע על הבעיה של כל האנשים שנהגו להשתמש בפונקציה send. מה שנצטרך לעשות זה לשנות את כל הקריאות לפונקציה send ל-sendTweet. תתארו לעמצכם את כמות הזמן העצומה שנשקיע בתיקון קטן לעורך כל האפליקציה ובבדיקתו מחדש.

פתרון

אחד הפתרונות לבעיה המתאורת הוא שימוש בתבנית עיצוב אדפטור (Adapter Design Pattern).

מויקיפדיה:

יצירת שיתוף פעולה בין מחלקות על ידי המרה נכונה בין הממשק המתקבל לממשק המצופה.
In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used from another interface. It is often used to make existing classes work with others without modifying their source code.

במקרה זה אנחנו צריכים ליצור ממשק (interface) עוטף שיאפשר את זה. אנחנו לא נשנה דבר במחלקה חיצונית כי אין לנו שליטה עליה והיא עלולה להשתנות בכל רגע.

אז בואו ניכנס יותר פנימה לקוד ונראה תבנית אדפטור בפעולה:

1
// Concrete Implementation of Twitter Class

2
class Twitter {
3
4
	public function __construct() {
5
		// Your Code here //

6
	}
7
8
	public function send($msg) {
9
		// Posting to Twitter //

10
		echo $msg;
11
	}
12
}
13
14
// Simple Interface for each Adapter we create

15
interface socialAdapter {
16
	public function send($msg);
17
}
18
19
class twitterAdapter implements socialAdapter {
20
21
	private $twitter;
22
23
	public function __construct(Twitter $twitter) {
24
		$this->twitter = $twitter;
25
	}
26
27
	public function send($msg) {
28
		$this->twitter->send($msg);
29
	}
30
}

חיקרו את הקוד לעיל ותוכלו להגיד שלא הכנסנו שינויים למחלקה ראשית Twitter. במקום זה יצרנו ממשק אחד לאדפטור שלנו ומחלקת אדפטור אחת ל-Tweeter.

אחרי כל זה יצרנו אבייקט של מחלקת אדפטור ולא של מחלקה ראשית Twitter. ביצירת אובייקט של מחלקת אדפטור אנחנו מעבירים לה אובייקט של מחלקה ראשית Twitter כארגומנט כך שלמחלקת אדפטור תהיה הפניה למחלקה ראשית והיא יכולה לקרוא למתודות של מחלקה ראשית Twitter.

ועכשיו נבהיר איך נוכל לממש את המתודה ישירות:

1
// Client Code

2
$twitter = new twitterAdapter(new Twitter());
3
$twitter->send('Posting to Twitter');

נדמיין מצב בו Twitter משנים את שם המתודה שלהם מ-send ל-sendTweet. אז מה שנשאר לנו לעשות זה להכניס שינויים ב-twitterAdapter. הביטו בקוד האדפטור המתוקן שכולל שינוי יחיד בלבד.

1
class twitterAdapter implements socialAdapter {
2
3
	private $twitter;
4
5
	public function __construct(Twitter $twitter) {
6
		$this->twitter = $twitter;
7
	}
8
9
	public function send($msg) {
10
		$this->twitter->sendTweet($msg);
11
	}
12
}

שינוי אחד והכל שוב הסתדר.

הוספת אדפטור חדש

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

ואז במקום להשתמש במחלקת Facebook ישירות נפעיל את אותה תבנית אדפטור שממשנו בשביל Tweeter.

1
// Concrete Implementation of Twitter Class

2
class Facebook {
3
4
public function __construct() {
5
		// Your Code here //

6
	}
7
8
	public function updateStatus($msg) {
9
		// Posting to Facebook //

10
		echo $msg;
11
	}
12
}
13
14
// Facebook Adapter

15
class facebookAdapter implements socialAdapter {
16
17
	private $facebook;
18
19
	public function __construct(Facebook $facebook) {
20
		$this->facebook = $facebook;
21
	}
22
23
	public function send($msg) {
24
		$this->facebook->updateStatus($msg);
25
	}
26
}
27
28
29
// Client Code

30
$facebook = new facebookAdapter(new Facebook());
31
$facebook->send('Posting to Facebook');

כמו שאתם יכולים לראות אותם עקרונות באים לידי ביטוי. אתם מגדירים מתודה זמינה למחלקות צד ג' ואז, אם גורם חיצוני משנה את ה-API שלו, אתם פשוט מתקנים מחלקה תלויה מבלי לחשוף את הממשק החיצוני.

מסכנה

יישומים גדולים ומורכבים תמיד תלוים בספריות אחרות וAPI’s לכן אני מציע להשתמש באדפטור וכך להמנע מחוויות לא נעימות כשספריות צד ג' או API’s משנות את הקוד המקורי שלהם.

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

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.