تعريف التوابع الدخيلة (Introduce Foreign Methods)
المشكلة
الحاجة إلى تابعٍ غير موجودٍ في الصنف المساعد (utility class) ومن غير الممكن إضافته إلى ذلك الصنف.
الحل
إضافة التابع المطلوب إلى صنف العميل (client class) وتمرير كائنٍ (object) من الصنف المساعد إليه كوسيط (argument).
مثال
قبل إعادة التصميم
يحتوي الصنف Report
تابعًا باسم sendReport
والذي يستخدم الصنف المساعد Date
لإنشاء تاريخ اليوم التالي عبر إضافة القيمة 1 إلى اليوم الحالي، كما يلي:
في لغة Java:
class Report {
//...
void sendReport() {
Date nextDay = new Date(previousEnd.getYear(),
previousEnd.getMonth(), previousEnd.getDate() + 1);
//...
}
}
في لغة C#:
class Report
{
//...
void SendReport()
{
DateTime nextDay = previousEnd.AddDays(1);
//...
}
}
في لغة PHP:
class Report {
//...
function sendReport() {
$previousDate = clone $this->previousDate;
$paymentDate = $previousDate->modify('+7 days');
//...
}
}
في لغة Python:
class Report:
#...
def sendReport(self):
nextDay = Date(self.previousEnd.getYear(),
self.previousEnd.getMonth(), self.previousEnd.getDate() + 1);
#...
في لغة TypeScript:
class Report {
// ...
sendReport(): void {
let nextDay: Date = new Date(previousEnd.getYear(),
previousEnd.getMonth(), previousEnd.getDate() + 1);
// ...
}
}
بعد إعادة التصميم
خُصِّصَ تابعٌ باسم nextDay
للحصول على اليوم التالي وذلك بتمرير الكائن الحالي باسم previousDay
كوسيطٍ له، يعيد هذا التابع كائنًا من نوع الصنف المساعد Date
كما في الشيفرة الآتية:
في لغة Java:
class Report {
//...
void sendReport() {
Date newStart = nextDay(previousEnd);
//...
}
private static Date nextDay(Date arg) {
return new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}
}
في لغة C#:
class Report
{
//...
void SendReport()
{
DateTime nextDay = NextDay(previousEnd);
//...
}
private static DateTime NextDay(DateTime date)
{
return date.AddDays(1);
}
}
في لغة PHP:
class Report {
//...
function sendReport() {
$paymentDate = self::nextWeek($this->previousDate);
//...
}
private static function nextWeek(DateTime $arg) {
$previousDate = clone $arg;
return $previousDate->modify('+7 days');
}
}
في لغة Python:
class Report:
#...
def sendReport(self):
newStart = self._nextDay(self.previousEnd)
#...
def _nextDay(self, arg):
return Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1)
في لغة TypeScript:
class Report {
// ...
sendReport() {
let newStart: Date = nextDay(previousEnd);
// ...
}
private static nextDay(arg: Date): Date {
return new Date(arg.getFullYear(), arg.getMonth(), arg.getDate() + 1);
}
}
لم إعادة التصميم؟
تأتي أهمية إعادة التصميم عندما ترى بأن الشيفرة المعتمدة على صنفٍ ما ستبدو (بل وستعمل) بشكلٍ أفضل بإضافة تابعٍ لهذا الصنف ولكنك لا تملك القدرة على إضافته، كأن يكون ذلك الصنف مثلًا أحد الأصناف المساعدة في المكتبة، وأكثر ما يفيد تطبيق تقنية الحل هذه عندما يتكرَّر ذلك الجزء الذي تريد إفراد تابعٍ له عدّة مرات في مواضع متفرَّقة من الشيفرة.
وتكمن ميّزة الحل بتمرير الكائن إلى التابع المُنشَأ، إذ إنّك بهذا تملك الوصول إلى كافة حقوله (fields) وتنفيذ كلِّ ما تريده داخل التابع كما لو كان جزءًا من الصنف المساعد ذاته.
فوائد تطبيق الحل
تقليص حجم شيفرة البرنامج عبر التخلُّص من التكرار (duplication) فيها، إذ أصبح بالإمكان تبديل كل تكرارٍ إلى استدعاءٍ للتابع الجديد، وهذا أفضل بكثيرٍ من انتشار جزءٍ مكررٍ دون داعٍ، حتى وإن لم يكن التابع في مكانٍ مثاليٍّ بالنسبة للشيفرة.
مساوئ تطبيق الحل
لن يكون الأمر واضحًا لمَن يقوم بأعمال الصيانة للشيفرة مِن بعدك، فقد يتساءل: لِمَ هذا التابع موجودٌ هنا بدلًا من أن يكون في الصنف المساعد (utility class)؟
وإن كان من المفيد استخدام هذا التابع في أصناف أخرى فبالإمكان إنشاء صنف تغليف (wrapper class) للصنف المساعد ووضع التابع في صنف التغليف، وهذا مفيدٌ أيضًا لدى وجود الكثير من التوابع المماثلة، ولمزيدٍ من التفاصيل راجع تعريف الإضافات المحليّة (introduce local extensions).
آلية الحل
- إنشاء تابعٍ (method) جديدٍ في صنف العميل (client class).
- إنشاء معاملات (parameters) لهذا التابع ليُمرَّر إليها كائن الصنف المساعد (utility class object)، وما من داعٍ للمعاملات إن كان بالإمكان الحصول على الكائن من الصنف المساعد مباشرةً.
- استخراج الشيفرة المطلوبة ونقلها إلى هذا التابع المُنشَأ وتبديل كل تكرارٍ لها إلى استدعاءٍ للتابع.
- إضافة تعليقٍ (tag) يوضِّح أنَّ هذا التابع تابعٌ دخيل (foreign method) ويُنصَح بنقل هذا التابع إلى الصنف المساعد (utility class) بمجرد أن يُتاح ذلك، إذ يصبح من السهل فهمُ ما يهدف إليه التابع بدلًا من وجوده في صنف العميل، وخاصّةً بالنسبة لمَن يقوم بصيانة الشيفرة في مراحلَ تالية.