Decorator design pattern


السلام عليكم و رحمه الله تعالى و بركاته

هذا الموضوع يعتمد على ماتم شرحه فى سلسله جافاسكربت الموجهه بالكائنات .

مقدمه

نمط التصميم Decorator يستخدم لإضافه ميزات جديده للكائنات بدون عمل اصناف فرعيه SubClasses ، حيث يقوم ال Decorator بإحاطه تلك الكائنات بكائنات اخرى لها نفس ال Interface ، هذا يمكنك من تعديل او اضافه وظائف جديده او استخدام الوظيفه الاصليه بدون تعديلها .

تركيب نمط التصميم Decorator

فى نمط التصميم Decorator تقسم الكائنات إلى نوعين ، النوع الاول Component و هو الذى يتم العمل عليه لإضافه ميزات جديده او تغير بعض وظائفه ، و النوع التانى Decorator و هو الذى يحيط الكائن Componenet بنفس ال Interface .

الفرق بين نمط التصميم Decorator و Composite و Facade و Adapter

نمط التصميم Composite نمط تركيبى structural pattern يقوم بترتيب الكائنات كلها فى وحده واحده تحت تفرع شجرى ، حيث يمكن المبرمج من معامله هذا العدد الكبير من الكائنات كأنها كائن واحد فقط ، حيث يمرر هذا الامر إلى بقيه التفرع الشجرى حتى يتم تنفيذه .
أما نمط التصميم Decorator فهو أيضا نمط تركيبى ، لكنه يستخدم لإضافه وظائف او تغير الوظائف الحاليه للكائن Component كما وضحت فى تركيب هذا النمط ، هذه الاضافه او التغير لا ينشأ عنها تغير الكود فى الصنف الاصلى ولا انشاء اصناف فرعيه SubClasses منه .
أما نمط التصميم Facade يستخدم Interface بسيط لإستخدام وظائف كائنات ذات Interface مختلف لكنهم متشابهين فى الوظيفه ، و مختلفين فى بيئه عمل التطبيق .
أما نمط التصميم Adapter يستخدم Interface حالى مع كائن اخر غير متوافق مع هذا ال Interface .

مثال : عمل Method profiler عن طريق Decorator Function

لنفرض انك تقوم بزياده كفاءه التطبيق الذى تقوم بالعمل عليه ، و تريد معرفه الوقت الذى تستغرقه كل وظيفه فى اداء عملها ، و من البيانات الناتجه يمكنك تعديل الوظيفه التى تأخذ وقت طويل و تستهلك الكثير من الموارد ، حتى تعرف الوقت الذى تستغرقه كل وظيفه فإنك لابد من احاطتها بمؤقت قبل عملها و بعد نهايه عملها كما شرحت هنا من قبل ، فلنفرض ان لديك وظيفه إسمها doSomething :

function doSomething(){
    /*..code for doing something ...*/
};

يمكنك معرفه الوقت التى تستغرقه عن طريق الوظيفه doSomethigProfiler كما يوضح الكود البسيط التالى :

function doSomethingProfiler(){
    var start = (new Date()).getTime();
    doSomething.apply(this,arguments);
    var finish = (new Date()).getTime();
    return finish-start;
}

الوظيفه doSomethingProfiler تقوم بحساب الوقت اللازم لتنفيذ الوظيفه doSomething لكنها مرتبطه بتلك الوظيفه و لا يمكن استخدامها مع وظيفه اخرى ، بالطبع يمكنك حل هذه المشكله عن طريق عمل genericProfiler يقبل اى وظيفه كعباره لها و العبارات التى تمرر لها كوظيفه اخرى ، و يحسب الوقت اللازم لتنفيذها كما يوضح الكود البسيط التالى :

function genericProfiler(fn){
    var start = (new Date()).getTime();
    fn.apply(this,Array.prototype.slice.call(arguments).slice(1));
    var finish = (new Date()).getTime();
    return finish-start;
}

يمكنك استخدام الوظيفه genericProfiler كما يلى :

function doSomething(){/*...code...*/}
function doSomethingElse(p1,p2){/*...code...*/}
function doSomethingDifferent(p1,p2,p3,p4){/*...code...*/}

genericProfiler(doSomething);
genericProfiler(doSomethingElse,1,2);
genericProfiler(doSomethingDifferent,"one","two","three","four")

عيوب عمل function decorator  أنها ستزيد من تعقيد الكود حيث ان معرفه الوقت الذى تستغرقه كل وظيفه سيتطلب إستدعاء الوظيفه genericProfiler و تمرير تلك الوظيفه لها بالإضافه إلى عباراتها ، و إذا اردت حذف ميزه معرفه التوقيت سيتطلب ذلك منك حذف جميه المواضع التى تتكرر فيها genericProfiler و تعيد الوظيفه الى وضعها الطبيعى ، إذا ما الحل ؟

ال Method Profiler بإستخدام Decorator Object

يمكننا الان عمل كائن Decorator يحيط اى كائن اخر او وظيفه اخرى ، و يتم معامله الاثنين بالتبادل بحيث لن تقوم بتعديل او اضافه اى كود على كود التطبيق الذى تقوم إختباره ، يمكن هذا الكائن decorator بإحاطه اطار عمل مثل jQuery او YUI او الكائن الذى تكتبه بنفسك و يعمل به تطبيقك ، بحيث ينتج عن هذه الاحاطه كائن جديد يمكن معاملته كأنه الكائن الاصلى ، لنفرض انه هناك كائن بإسم Keepondev يحتوى على 100 وظيفه للتعامل مع DOM و AJAX و غيرها كما يوضح الكود البسيط التالى :

var Keepondev = {
    get:function(selector){/*...code...*/},
    remove:function(elem){/*...code...*/},
    replace:function(elem){/*...code...*/},
    Hide:function(elem){/*...code...*/},
    show:function(elem){/*...code...*/},
    on:function(event,handler){/*...code...*/},
    _getXHR:function(){/*...code...*/},
    load:function(elem,url){/*...code...*/},
    get:function(url){/*...code...*/},
    send:function(url,data){/*...code...*/},
    /*... rest of keepondev functions ...*/
}

يمكنك إستخدام الكائن Keepondev كما يلى :

Keepondev.get("#feed-container").show().load("feed.php");
Keepondev.send("search.php",Keepondev.get("#search").value);

هيا بنا نقوم بعمل Method profiler يعمل على اى كائن ، ولا يجعلنا نغير الكود عند اضافته او حذفه :

/* MethodProfiler Class */
var MethodProfiler = function(component){
    this.component = component;
    this.timers = {};

    for(var key in this.component){
        // ensure that the property is function
        if(typeof key != "function"){
            continue;
        }

        // add the method
        var that = this;
        // add closure to fix dynamic functions problem
        (function(methodName){
            that[methodName]=function(){
                that.startTimer(MethodName);
                var returnValue = that.component[methodName].
                apply(that.component,arguments);
                that.displayTime(methodName,that.getElapsedTime(methodName));
                return returnValue;
            }
        })(key);
    }
};
MethodProfiler.prototype = {
    startTimer:function(methodName){
        this.timers[methodName]=(new Date()).getTime();
    },
    getElapsedTime:function(methodName){
        return (new Date()).getTime() - this.timers[methodName];
    },
    displayTime:function(methodName,time){
        console.log(methodName + ": " + time + " ms");
    }
}

عليك ملاحظه انه فى صنف MethodProfiler يقوم بتوليد وظائف ديناميكيا للكائن الذى يمرر له ، لذلك قمت بإحاطه الكود الذى يولد الوظائف ديناميكيا ب Closure لحل مشكله ال context التى ذكرتها هنا من قبل ، كما ان الصنف MethodProfiler يقوم بالإبلاغ عن الوقت داخل console بفرض انه يعمل داخل firefox ، إن console.log ستعمل على الامتداد Firebug او يمكنك بالطبع برمجه ال console الخاص بك ،عليك ملاحظه ايضا أن الكود السابق لا يستطيع العمل مع الكائنات التى تحت namespace او الوظائف ، لكن اضافه هذه الميزه ستتطلب اضافه خمسه اسطر كود – افعلها بنفسك -،  يمكننا إستخدام ال MethodProfiler مع الكائن Keepondev كالتالى :

var keepondev = new MethodProfiler(keepondev);
Keepondev.get("#feed-container").show().load("feed.php");
Keepondev.send("search.php",Keepondev.get("#search").value);

اهم مافى الكود السابق السطر الاول ، حيث نقوم بتمرير الكائن Keepondev للصنف MethodProfiler و يتم الحصول على كائن جديد بإسم Keepondev له نفس ال Interface لكن الفرق فى ان الكائن الجديد يحتوى على وظائف معدله ، عند إستخدام اى وظيفه سيتم الابلاغ عن الوقت الذى تستغرقه كل وظيفه فى عملها ، و اذا كنت تريد حذف هذه الميزه التى تمكنك من زياده كفاءه تطبيقاتك ، يمكنك حذف السطر الاول فقط😀 .

هذا الموضوع تلخيص للفصل الثانى عشر من كتاب APRESS:Pro JavaScript design patterns ، يمكنك الرجوع للكتاب للمزيد ، حيث لم اذكر فى هذا الموضوع كيفيه زياده وظائف جديده و المشاكل التى تترتب على ذلك ، و كيفيه حلها عن طريق إستخدام نمط التصميم Factory .

خارج النص : قمت بإلغاء تثبيت موضوع شارك فى اختيار السلسله القادمه ، لكن الموضوع مازل موجود و التصويت مازال قائم .

الأوسمة: , , , , , ,

2 تعليقان to “Decorator design pattern”

  1. almhajer Says:

    اولا احب اشكرك من كل اعماق قلبي
    تعقيب على المقال ان الفكرة تتمحور حول منهجية منقالة العمل اي تسليم الكود الى كود اخر يقوم بعملية انشاء بنية وصل بين الكودين وهيا فعلا منهجية قوية لتكامل التطبيقات مع تطبيقات اخرى و احب اسميعها توسيع التطبيق
    شكرا جزلا وبارك الله في الجميع

  2. mostafa farghaly Says:

    نعم صدقت يا مهاجر ، نمط التصميم Decorator يعتمد على التفويض Delegation .

أضف تعليقاً

إملأ الحقول أدناه بالمعلومات المناسبة أو إضغط على إحدى الأيقونات لتسجيل الدخول:

WordPress.com Logo

أنت تعلق بإستخدام حساب WordPress.com. تسجيل خروج   / تغيير )

صورة تويتر

أنت تعلق بإستخدام حساب Twitter. تسجيل خروج   / تغيير )

Facebook photo

أنت تعلق بإستخدام حساب Facebook. تسجيل خروج   / تغيير )

Google+ photo

أنت تعلق بإستخدام حساب Google+. تسجيل خروج   / تغيير )

Connecting to %s


%d مدونون معجبون بهذه: