تبديل التابع إلى كائن التابع (Replace Method with Method Object)
المشكلة
وجود تابعٍ طويلٍ بالكثير من المتغيِّرات المحليّة (local variables) المتداخلة والتي تحول دون تطبيق تقنية الحل باستخراج التابع (extract method).
الحل
نقل التابع إلى صنفٍ (class) مستقلٍ بحيث تصبح متغيِّراته المحليّة حقولًا (fields) لهذا الصنف، وتقسيم التابع بعد ذلك إلى عدّة توابع أصغر في الصنف ذاته.
مثال
قبل إعادة التصميم
نلاحظ وجود العديد من المتغيِّرات المحليّة في التابع price()
بالإضافة إلى عملياتٍ أخرى قد تكون طويلةً ومعقَّدة:
في لغة Java:
class Order {
//...
public double price() {
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// عمليات أخرى طويلة
//...
}
}
في لغة #C:
public class Order
{
// ...
public double Price()
{
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// عمليات أخرى طويلة
}
}
في لغة PHP:
class Order {
// ...
public function price() {
$primaryBasePrice = 10;
$secondaryBasePrice = 20;
$tertiaryBasePrice = 30;
// عمليات أخرى طويلة
}
}
في لغة Python:
class Order:
# ...
def price(self):
primaryBasePrice = 0
secondaryBasePrice = 0
tertiaryBasePrice = 0
# عمليات أخرى طويلة
في لغة TypeScript:
class Order {
// ...
price(): number {
let primaryBasePrice;
let secondaryBasePrice;
let tertiaryBasePrice;
// عمليات أخرى طويلة
}
}
بعد إعادة التصميم
يُعرَّف الصنف (class) الجديد باسم PriceCalculator
والذي يحتوي على حقولٍ خاصَّة (private fields) متوافقةٍ مع المتغيِّرات المحليِّة للتابع الأصليّ، كما ويحتوي بانيًا (constructor) يُستخدم للتهيئة الأوليّة (intialization) لتلك الحقول وتابعٌ رئيسيّ (وهو التابع compute()
) يحتوي على العمليات الواردة في التابع الأصليّ (وقد تُجزَّئ لعدَّة توابع)، ثمَّ يُستبدَل محتوى التابع الأصليّ (وهو التابع price()
) ليصبح استدعاءً للتابع الأساسيّ عبر كائنٍ (instance) من الصنف الجديد، لتصبح الشيفرة بعد إعادة التصميم بالشكل الآتي:
في لغة Java:
class Order {
//...
public double price() {
return new PriceCalculator(this).compute();
}
}
class PriceCalculator {
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order) {
// نسخ المعلومات المرتبطة من الكائن
//...
}
public double compute() {
// عمليات أخرى طويلة
//...
}
}
في لغة #C:
public class Order
{
// ...
public double Price()
{
return new PriceCalculator(this).Compute();
}
}
public class PriceCalculator
{
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order)
{
// نسخ المعلومات المرتبطة من الكائن
// ...
}
public double Compute()
{
// عمليات أخرى طويلة
}
}
في لغة PHP:
class Order {
// ...
public function price() {
return (new PriceCalculator($this))->compute();
}
}
class PriceCalculator {
private $primaryBasePrice;
private $secondaryBasePrice;
private $tertiaryBasePrice;
public function __construct(Order $order) {
// نسخ المعلومات المرتبطة من الكائن
// ...
}
public function compute() {
// عمليات أخرى طويلة
}
}
في لغة Python:
class Order:
# ...
def price(self):
return PriceCalculator(self).compute()
class PriceCalculator:
def __init__(self, order):
self._primaryBasePrice = 0
self._secondaryBasePrice = 0
self._tertiaryBasePrice = 0
# نسخ المعلومات المرتبطة من الكائن
# order object.
def compute(self):
# عمليات أخرى طويلة
في لغة TypeScript:
class Order {
// ...
price(): number {
return new PriceCalculator(this).compute();
}
}
class PriceCalculator {
private _primaryBasePrice: number;
private _secondaryBasePrice: number;
private _tertiaryBasePrice: number;
constructor(order: Order) {
// نسخ المعلومات المرتبطة من الكائن
// ...
}
compute(): number {
// عمليات أخرى طويلة
}
}
لم إعادة التصميم؟
لأنّ التوابع الطويلة غالبًا ما تحتوي على الكثير من المتغيِّرات المحليّة المترابطة ويصعُب فصلها عن بعضها، فتكون الخطوة الأولى بعزل التابع بأكمله في صنفٍ (class) جديدٍ مستقلٍّ تصبح متغيِّرات التابع حقولًا له، ويبدو هذا الحل جيدًا لسببين:
- سيصبح حل المشكلة على مستوى الأصناف (classes) بدلًا من التوابع (methods).
- سيمهِّد الطريق لتقسيم التابع الضخم إلى توابع أصغر في الصنف الجديد، ولا علاقة لها بالصنف الأصليّ الذي استُخرِج منه التابع.
فوائد تطبيق الحل
الحدُّ من تضخِّم التابع وسهولة تقسيمه إلى توابع فرعيّة (submethods) أصغر في الصنف ذاته دون العبث بالصنف الأصليَّ الذي قد يحتوي على توابع أخرى.
مساوئ تطبيق الحل
إضافة صنفٍ جديدٍ ممّا يؤدي لزيادة تعقيد (complexity) البرنامج عمومًا.
آلية الحل
- إنشاء صنفٍ (class) جديدٍ، وتسميته بما يناسب هدف التابع المُعاد تصميمه.
- إنشاء حقل خاصّ (private field) بالصنف الجديد، وذلك لتخزين مرجعيّةٍ (reference) إلى كائن (instance) الصنف الأصليّ للتابع، والهدف من ذلك هو الحصول على بعض البيانات المطلوبة من الصنف الأصليّ عند الحاجة إليها.
- إنشاء حقلٍ خاصٍ لكلّ متغيِّرٍ محليٍّ (local variables) من التابع.
- إنشاء بانٍ (constructor) يقبل كافة قيم المتغيِّرات المحليّة كمعاملات (parameters) له ليقوم بالتهيئة الأوليّة للحقول الخاصَّة بالاعتماد عليها.
- التصريح عن التابع الرئيسيّ (main method) ونسخ الشيفرة الأصليّة للتابع إليه مع تبديل كلِّ متغيِّرٍ إلى الحقل (field) الموافق له.
- تعديل محتوى التابع الأصليّ (في صنفه السابق) عبر إنشاء كائن التابع (method object) واستدعاء التابع الأساسيِّ فيه.