Posts Tagged ‘oop’

proxy design pattern

22/04/2009

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

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

مقدمه

نمط التصميم Proxy عباره عن كائن يتحكم فى الوصول -control access- لكائن اخر ، حيث يقوم الكائن proxy بإدراج نفس ال Interface الذى يمتلكه الكائن الاخر الذى يسمى real subject و بذلك يمكن إستخدام الاثنين بالتبادل ، يمكننا إنشاء كائن proxy بدلا من الكائن real subject الذى يأخذ وقت طويل عند انشاءه و يستخدم الكثير من الموارد و بذلك سيتم انشاءه عند الحاجه اليه فقط ، او يمكننا ايضا انشاء كائن proxy ليمثل كائن اخر بعيد remote له نفس ال interface و نتعامل معه كما لو كان فى البيئه الحاليه و انه ليس ببعيد .

تركيب ال Proxy

فى ابسط صوره الكائن Proxy يقوم بالتحكم فى الوصول control access إلى الكائن real subject ، الكائن proxy يقوم بإدراج نفس ال interface الذى يمتكله الكائن real subject ، الكائن real subject هو الذى يقوم بكل المهام ، اما الكائن proxy يقوم بتوجيه الاوامر فقط إلى الكائن real subject .
وجب التنويه ان الكائن Proxy لا يعدل او يضيف اى ميزات جديده على الكائن الاخر كما يفعل نمط التصميم decorator ، ولا يقوم بتبسيط ال interface كما يفعل نمط التصميم Facade ، كل ما يفعله ال proxy هو احاطه الكائن real subject بنفس ال interface و يقوم بتوجيه الاوامر له .

أبسط صور ال Proxy

كما ذكرت من قبل ابسط صوره لل proxy عباره كائن يحيط بالكائن الاصلى بنفس ال interface ، هذا النوع ليس مفيدا لأنه لا يزيد أى ميزه ولا يؤثر على الكفائه ولا على تصميم الكود -إلا اضافه طبقه من التعقيد-، كما يوضح الكود البسيط التالى :

/* MyClass constructor */
function MyClass(){};
MyClass.prototype = {
    method1:function(){},
    method2:function(){},
    method3:function(){},
    method4:function(){},
};

/* MyClass proxy (not practical) */
function MyClassProxy(){
    this.realSubject = new MyClass();
}
MyClassProxy.prototype = {
    method1:function(){
        this.realSubject.method1();
    },
    method2:function(){
        this.realSubject.method2();
    },
    method3:function(){
        this.realSubject.method3();
    },
    method4:function(){
        this.realSubject.method4();
    },
};

هذا النوع من ال proxy غير مفيد لأنه يقوم بإحاطه الكائن الاصلى بنفس ال intercafe و لايقوم بعمل اى شىء غير توجيه الاوامر للكائن الاصلى ، و عليه هذا النوع يمكن انشاءه ديناميكيا كما انشأنا Method profiler ديناميكا فى نمط التصميم Decorator .

أنواع ال Proxy

اول نوع هو ال Virtual Proxy ، الذى يعتبر من اهم انواع ال proxy فى الجافاسكربت ، حيق يقوم بتأخير انشاء الصنف ، يقوم بإنشاءه فقط عند الحاجه اليه – مثل استدعاء احدى وظائفه – ، قد يكون هذا الصنف مكلف جدا فى انشاءه او يكون هذا الصنف ليس له فائده إلا عند الحاجه اليه .
ثانى نوع هو ال Remote Proxy ، الذى يستخدم للوصول لكائن اخر فى بيئه مختلفه ، حيث يتم تمثيل الكائن الاخر البعيد بكائن proxy موجود فى بيئه التشغيل يمكن التفاعل معه نيابا عن الكائن البعيد ، فى عالم ال JAVA قد يعنى هذا كائن اخر فى virtual machine اخر ، او كائن فى كمبيوتر فى الجهه الاخرى من العالم ، حيث يكون الكائن البعيد remote object قابل للوصول فى اى وقت من اى بيئه و يكون دائم persistent ، هذا النوع من ال Proxy صعب ترجمته فى بيئه الجافاسكربت لأن بيئه تشغيل جافاسكربت غير دائمه ، حيث يتم بدأ تطبيق الجافاسكربت و إنهائه قى كل طلب لصفحه تحتوى على كود جافاسكربت و عند مغادرتها ، كما انه لا يمكنك الوصول لتطبيق جافاسكربت اخر و التفاعل مع متغيراته بدون بدأه او انهائه فى البيئه الحاليه، لكن بالرغم من ذلك يمكنك إستخدام نمط التصميم proxy للوصول لكائنات مبنيه بلغات برمجه اخرى مثل php ، يمكن ان تكون خدمه ويب web service يتم التفاعل معها عن طريق جافاسكربت و التفاعل مع جميع وظائفها عن طريق كائن proxy .
ثالث نوع هو ال Protection Proxy ، هو ايضا صعب ترجمته لكود جافاسكربت ، لأنه يتححكم فى الوصول لوظائف الصنف من الاصناف الاخرى ، بحيث ان وظائف الكائن real subject لا يستطيع استخدامها الا اصناف معينه ، و هذا شبه مستحيل فى الجافاسكربت ، لانه لاتوجد طريقه لمعرفه من اى صنف صدر هذا الاستدعاء لوظيفه معينه من وظائف ال real subject ،وجب التنويه انه من خلال arguments.callee.caller داخل وظيفه ما ستعرف اى وظيفه قد قامت بإستدعاءها ، لكنك لن تستطيع معرفه الصنف الذى تنتمى له هذه الوظيفه .

مقارنه نمط التصميم Proxy بنمط التصميم Dcorator

كلا من نمط التصميم proxy و decorator يقوموا بإحاطه كائن بكائن اخر له نفس ال interface ، و يقوم الاثنين بتمرير الامر إلى الكائن الاصلى ، لكن الفرق يكمن فى وظيفه الكائن الذى يمثل النمط ، حيث يقوم الكائن الذى يمثل نمط التصميم decorator بتعديل الوظائف او اضافه وظائف جديده للكائن الذى يحيطه بدون انشاء اصناف فرعيه منه ، اما الكائن الذى يمثل ال proxy يقوم بتمرير الامر فقط بدون تغير الكائن الذى يحيطه ، هناك فرق اخر فى كيفيه انشاء الكائن الذى يحيطه النمط ، فى نمط التصميم decorator يتم انشاء الكائن component الذى يحيطه ال decorator منفردا ولا يعتمد على ال decorator اطلاقا ، اما فى نمط التصميم proxy يتم انشاء الكائن real subject من داخل ال proxy او يتم تخير انشاءه فى حاله virtual proxy عند الحاجه اليه فقط ، اى ان انشاء الكائن الذى يعمل عليه نمط التصميم proxy يعتمد اعتمادا كليا على الكائن proxy ، كما ان نمط التصميم decorator يمكن احاطه نفسه اكثر من مره ، اما نمط التصميم proxy لا يمكنه احاطه نفسه :D .

مثال على Remote Proxy

لنفرض ان هناك خدمه ويب web service تقدم احصائيات عن اى صفحه على الانترنت مقدمه من keepondev مثل google statistics ، هذه الخدمه تحتوى على عدد من الروابط التى تمثل وظائف الكائنات التى بالسيرفر و كل الوظائف تقبل عبارات url و startDate و endDate ، لا يهم ما اللغه التى مكتوب بها الخدمه على السيرفر ، لان كل النتائح ستكون فى صيغه JSON ، هناك خمس وظائف فى خدمه الويب كما يمثلهم الروابط التاليه :

http://keepondev.com/stats/getPageViews/

http://keepondev.com/stats/getUniques/

http://keepondev.com/stats/getBrowserShare/

http://keepondev.com/stats/getTopSearchTerm/

http://keepondev.com/stats/getMostVisitedPages/

يمكننا برمجه ال StatsProxy فى صوره Singleton بحيث يكون له نفس ال interface كما الوظائف التى على السيرفر ، كما يوضح الكود البسيط التالى :

/* StatsProxy Singleton */
var StatsProxy = function(){
    /* private methods */
    var xhr = new XMLHttpRequest();
    var urls = {
        pageViews:"/stats/getPageViews/",
        uniques:"/stats/getUniques/",
        browserShare:"/stats/getBrowserShare/",
        topSearchTerms:"/stats/getTopSearchTerms/",
        mostVisitedPages:"/stats/getMostVisitedPages/",
    };

    function fetchData(url, dataCallback, startDate, endDate, page){
        var getVars = [];
        if(startDate != undefined){
            getVars.push("startDate="+ encodeURI(startDate));
        }
        if(endDate != undefined){
            getVars.push("endDate="+ encodeURI(endDate));
        }
        if(page != undefined){
            getVars.push("page="+ encodeURI(page));
        }

        if(getVars.length>0){
            url = url + "?" + getVars.join("&");
        }

        xhr.onreadystatechange = function(){
            if(xhr.readyState == 4){
                dataCallback(xhr.responseText);
            }
        };
        xhr.open("GET",url,true);
        xhr.send(null);
    };

    /* public methods */
    return {
        getPageViews:function(callback, startDate, endDate, page){
            fetchData(urls.pageViews, callback, startDate, endDate, page);
        },
        getUniques:function(callback, startDate, endDate, page){
            fetchData(urls.uniques, callback, startDate, endDate, page);
        },
        getBrowserShare:function(callback, startDate, endDate, page){
            fetchData(urls.browserShare, callback, startDate, endDate, page);
        },
        getTopSearchTerms:function(callback, startDate, endDate, page){
            fetchData(urls.topSearchTerms, callback, startDate, endDate, page);
        },
        getMostVisitedPages:function(callback, startDate, endDate, page){
            fetchData(urls.mostVisitedPages, callback, startDate, endDate, page);
        },
    }

}();

يمككننا الان معامله الكائن Stats الذى على السيرفر كأنه كائن عادى موجود فى بيئه المتصفح كما يوضح الكود البسيط التالى :

var stats = new StatsProxy();
stats.getPageViews(function(data){
    /* do something with data*/
},"01/01/08","01/01/09","index.html");

stats.getTopSearchTerms(function(){
    /* do something with data*/
},null,null,"products.html");

عليك ملاحظه ان البيانات التى ترجع من السيرفر فى صيغه JSON يتم تمريرها للوظيفه التى هى اول عباره للوظيفه getPageViews او getTopSearchTerms فى المثال السابق ، ميزه استخدام ال proxy فى المثال السابق اننا قد تمكنا من التعامل مع الكائن stats  كأنه كائن محلى و تفاصيل الروابط التى ينتج عنها الاحصائيات مخفيه لذلك يمكن تغيرها من نسخه لأخرى من اصدارات StatsProxy بدون التأثير على كود المستخدم .

مثال على Virtual proxy

لنفرض ان هناك كائن مكلف جدا فى انشاءه ، مثلا يقوم بتحميل كم كبير من البيانات من السيرفر عن طريق اجاكس او القيام بإنشاء العديد من الكائنات الاخرى التى تستهلك الكثير من موارد الذاكره ، من الافضل ان يتم انشاء هذا الكائن فقط عند الحاجه إليه ، يمكننا تحقيق ذلك عن طريق انشاء proxy يقوم بإحاطه الكائن الاصلى و لايقوم بإنشائه إلا عند استخدامه كإستدعاء احدى وظائف و اظهار رساله – جارى التحميل… – للمستخدم بدلا من مساحه خاليه توحى للمستخدم بإنه لايوجد شىء يعمل ، لنفرض ان الصنف HeavyLoadingClass يقوم بعمل اتصال اجاكس ليجلب كم هائل من البيانات فور انشاءه و بذلك يؤثر على كفائه التطبيق ، يمكننا عمل proxy يؤجل انشاء هذا الصنف فقط عند الحاجه إليه كما يوضح المثال البسيط التالى :

var HeavyLoadingClass = function(arg1, arg2){
    /*very heavy constructor*/
}
HeavyLoadingClass.prototype = {
    showData:function(data){
    /*show the data*/ 
    },
    hideData:function(){
    /*hide data container */
    }
}

var HeavyLoadingClassProxy = function(arg1, arg2){
    this._initialized = false;
}
HeavyLoadingClassProxy.prototype = {
    _initialize:function(){
        if(!this._initialized){
            this.class = new HeavyLoadingClass();
            this._initialized = true;
        }
    },
    showData:function(data){
        this._initialize();
        this.class.showData();
    },
    hideData:function(){
        this._initialize();
        this.class.hideData();
    },
}

الصنف HeavyLoadingClass مكلف فى إنشائه ، لذلك قمنا بعمل Proxy بإسم HeavyLoadingClassProxy الذى يؤجل انشاء الصنف المكلف الى الوقت الذى يتم إستخدامه فيه عن طريق استدعاء احدى وظائفه ، و بالتالى لن يتم انشاء هذا الصنف إلا عند الحاجه اليه ، هذا المثال بسيط جدا ، يمكنك الرجوع إلى كتاب Apress: JavaScript design patterns للمزيد .

فوائد و عيوب ال proxy

قد ذكرت فوائد إستخدام ال proxy فى نقطه “انوع ال Proxy” بأول الموضوع ، لكن عيوب ال remote proxy مثلا تتمثل فى ان استدعاء وظيفه من وظائف الكائن البعيد تتطلب اتصال اجاكس و الانتظار من اجل البيانات ثم اعربها ثم اعطائها لوظيفه لتعمل عليها ، قد يفترض المطور انها وظيفه عاديه يمكن الحصول على قيمه منها فورا بدون الانتظار او تسجيل وظيفه للعمل على البيانات الناتجه من السيرفر ، لكن من خلال التوثيق الجيد للكود الذى تكتبه يمكن للمطورين تجنب مثل هذه المشاكل ، اما virtual proxy فيقوم بإضافه طبقه منا التعقيد لكيفيه انشاء الكائنات و ماهو الحدث الذى سيؤدى الا انشاء الحدث … الخ ، و بالتالى سيصعب اكتشاف الاخطاء و معالجتها debugging ، لكن هذه العيوب لا تنفى ان لنمط التصميم proxy مميزات كبيره جدا اذا تم استخدامه بطريقه صحيحه و فى الظروف المناسبه .

هذا الموضوع تلخيص للفصل الرابع عشر فى كتاب Apress: Pro JavaScript Design Patterns ارجع للكتاب للمزيد ، الكتاب يحتوى على العديد من الامثله لم اذكرها و كيفيه انشاء virtual proxy ديناميكيا و كيفيه انشاء remote proxy قابل لإعاده الاستعمال لأكثر من كائن بعيد reomte object .

Flyweight design pattern

11/04/2009

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

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

مقدمه

نمط التصميم Flyweight -وزن الذبابه- مفيد جدا فى المواقف التى يتم فيها انشاء عدد كبير جدا من الكائنات من نفس الصنف و بذلك يؤدى إلى مشاكل فى ذاكره التطبيق ، حيث يقلل نمط التصميم Flyweight عدد الكائنات التى يتم انشاءها إلى كائن واحد – او اقل عدد ممكن – و بذلك ستزداد كفاءه التطبيق بطريقه ملحوظه .

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

نمط التصميم Flyweight يستخدم لتقليل عدد الكائنات التى تحتاجها فى تطبيقك ، يتم ذلك عن طريق تقسيم ال internal state للكائن إلى تصنيفين ، الاول intrinsic data و الثانى extrinsic data . النوع الاول من البيانات Intrinsic هو الذى يتم استخدامه بواسطه الوظائف الداخليه للصنف و لايمكن للوظائف ان تعمل جيدا بدون هذه البيانات ،  اما البيانات من نوع extrinsic هى البيانات التى يكمن ازالتها من الصنف و تخزينها خارجه ، هى التى بدورها تختلف من كائن لاخر . يمكننا نمط التصميم Flyweight من استبدال جميع الكائنات التى لها نفس البيانات ال intrinsic بكائن واحد فقط  .

Factory بدلا من constructor

بدلا من إستخدام constructor عادى ، سنستخدم نمط التصميم Factory لتوليد كائنات جديده ، إذا كان هناك كائن قد تم انشاءه من قبل له نفس البيانات ال intrinsic فسيقوم ال factory بإرجاعه ، اما اذا لم يكن هناك كائن تم انشاءه من قبل له هذه البيانات ال intrinsic فإن ال factory سيقوم بإنشاء كان جديد و تخزينه ، كما سيتم انشاء ايضا كائن مدير manager object يقوم بحفظ البيانات من نوع extrinsic ، حيق عند استدعاء اى من وظائف الكائن فإن الكائن المدير سيقوم بتمرير هذه البيانات ال extrinsic كعبارات لتلك الوظائف .

مثال Car Registerations

تخيل انك تقوم ببرمجه نظام يمثل سيارت المدينه ، بحيث تريد تخزين بيانات كل سياره مثل موديل السياره و الصانع و السنه ، و تريد تخزين بيانات ملكيه السياره مثل الإسم و رقم البطاقه و تاريخ التجديد ، من الطبيعى ان تمثل كا سياره بكائن من نوع Car ، الكود التالى يوضح الصنف Car بدون إستخدام نمط التصميم Flyweight :

/* Car function constructor, unoptimized */
var Car = function(make, model, year, owner, tag, renewDate){
    this.make=make;
    this.model=model;
    this.year=year;
    this.owner=owner;
    this.tag=tag;
    this.renewDate = renewDate;
}
Car.prototype = {
    getMake:function(){
        return this.make;
    },
    getModel:function(){
        return this.model;
    },
    getYear:function(){
        return this.year;
    },
    transferOwnership:function(newOwner, newTag, newRenewDate){
        this.owner = newOwner;
        this.tag = newTag;
        this.renewDate = newRenewDate;
    },
    renewRegisteration:function(newRenewDate){
        this.renewDate = newRenewDate;
    },
    isRegisterationCurrent:function(){
    var today = new Date();
    return today.getTime() < Date.parse(this.renewDate);
    }
};

هذا الصنف سيعمل جيدا ، لكن ستظهر المشاكل يوميا بزياده عدد السيارات التى يتم تسجيلها ، بفرض انك تقوم بتسجيل 1000 سياره سيؤدى ذلك إلى انشاء 1000 كائن من نوع Car ، و سيؤدى ذلك إلى كارثه فى اداء التطبيق ، الحل فى إستخدام نمط التصميم Flyweight لتقليل عدد الكائنات التى يتم انشاءها من الصنف Car إللى اقل عدد ممكن ، اول خطوه هى تقسيم البيانات او العبارت التى تمرر للصنف Car إلى intrinsic و extrinsic .

Intrinsic و Extrinsic

فى هذا المثال سيتم اعتبار الmodel و ال make و ال year البيانات ال intrinsic المميزه لكل سياره ، هذا يعنى انه اذا امتلك 1000 شخص سياره ماركه Mercedes مدويل S-class سنه 2009 ، فإنه سيتم انشاء كائن واحد فقط من نوع Car  يمثل هذه السياره ، اما البيانات التى تتعلق بملكيه السيارت سيبتم حفظها فى الكائن المدير manager object لاحقا ، هكذا سيبدوا الكود الخاص بالصنف Car بعد محو ال extrinsic data منه :

/* Car function constructor, optimized as flyweight */
var Car = function(make, model, year){
    this.make=make;
    this.model=model;
    this.year=year;
};
Car.prototype = {
    getMake:function(){
        return this.make;
    },
    getModel:function(){
        return this.model;
    },
    getYear:function(){
        return this.year;
    }
}

كما ترى تم محو كل البيانات التى تتعلق بملكيها السياره extrinsic data من ال function constructor و محو الوظائف المتعلقه بها أيضا ، الان جاء دور ال Factory الذى سيقوم ببناء الكائنات من الصنف Car .

انشاء الكائنات بإستخدام نمط التصميم Factory

دور ال Factory هنا بسيط جدا ، سيقوم بفحص هل تم انشاء سياره بنفس المواصفات -make,model,year – من قبل ام لا ، اذا تم انشاءها سيتم ارجاعها ، اما اذا لم يتم انشائها سيتم انشاء سياره جديده بهذه المواصفات و يخزنها للإستخدام لاحقا ، و هذا ال factory سيؤكد على ان كل سياره يتم انشائها ذات مواصفات intrinsic فريده ، كما يوضح الكود البسيط التالى :

/* CarFactory Singleton */
var CarFactory = (function(){
    var createdCars = {};
    return {
        createCar:function(make, model, year){
            // if created before, return it.
            if(createdCars[ make + "-" + model + "-" + year ]){
                return createdCars[ make + "-" + model + "-" + year ];
            }
            // otherwise create new car, save it, return it.
            else{
                var car = new Car(make, model, year);
                createdCars[ make + "-" + model + "-" + year ] = car;
                return car;
            }
        }
    };
})();

تخزين ال extrinsic data فى object manager

اخر كائن سيتم انشاءه لإنهاء عمليه التحسينات optimization هو الكائن المدير manager object الذى سيحفظ البيانات ال extrinsic التى تم محوها من الصنف Car ، و الوظائف المتعلقه بهذه البيانات ال extrinsic ، سنستخدم نمط التصميم Singleton لإنشاء ال manager object كما يوضح الكود البسيط التالى :

var CarRecordManager = (function(){
    var carRecordDatabase = {};
    return {
        // add new car in the system
        addCarRecord:function(make, model, year, owner, tag, renewDate){
            var car = CarFactory.createCar(make, model, year);
            carRecordDatabase[tag]{
                owner:owner,
                renewDate:renewDate,
                car:car,
            };
        },
        // methods previously contained in Car Calss
        transferOwnership:function(tag, newOwner, newTag, newRenewDate){
            var record = carRecordDatebase[tag];
            record.owner = newOwner;
            record.tag = newTag;
            recoed.renewDate = newRenewDate;
        },
        renewRegistration:function(tag, newRenewDate){
            carRecordDatabase[tag].renewDate = newRenewDate;
        },
        isRegsiterationCurrent:function(tag){
            var today = new Date();
            var renewDate = carRecordDatabase[tag].renewDate;
            return today.getTime() < Date.pares(renewDate);
        }
    };
})();

كل ال extrinisc data تم تخزينها فى الخاصيه الخاصه carRedcordDatabase بالإضافه إلى الكائن car المتعلق بهذه البيانات ، بالطبع تخزين البيانات فى object literal افضل من عدد غير محدود من الكائنات ، كما ترى التحسينات قد أدت إلى زياده تعقيد الكود ، بدلا من صنف واحد لأنشاء الكائنات من نوع Car ، الان لدينا صنف Car و factory لإنشاء الكائنات من نوع Car و singleton لحفظ البيانات ال extrinsic ، لكن اذا وازنت هذا التعقيد مع التحسينات التى ستطرأ على كفاءه التطبيق ، فإن الكفاءه هى التى ستربح .

هذا الموضوع هو تلخيص الفصل الثالث عشر من كتاب APRESS: Pro JavaScript design patterns إرجع إليه للمزيد ، حيث قام الكتاب بشرح مثالين ، الاول قامووا فيه بعمل تقويم Calendar يحتوى على صنف Year و صنف اخر Month و صنف اخر Day من نوع flyweight بدلا من انشاء 30 كائن من نوع Day لكل شهر ، و 365 كائن Day لكل سنه ، حيث أدى هذا التحسين إلى تقليل الكائنات إلى كائن واحد فقط :D ، المثال التالى Tooltip و هو التلميح الذى يظهر عندما تقف بالماوس على عنصر ما ، حيث يقوم بإظهار نص يصف هذا العنصر ، و مادام ان ال tooltip تظهر مره واحده ، اى انه لايمكن ظهور اكثر من واحده فى نفس الوقت ، فهذا يؤهلها لأن تكون flyweight ، بدلا من انشاء 1000 كائن Tooltip ل 1000 عنصر ، يمكننا انشاء كائن واحد فقط tooltip و فصل البيانات ال intrinsic و ال extrinsic كما وضحت فى مثال ال Car registeration ، بالطبع لا يمكننى شرح هذه الامثله هنا لأنها طويله جدا .

خارج النص :  انتهى john resig مطور اطار عمل jQuery من 12 فصل فى كتابه الجديد Manning: Secrets of the JavaScript Ninja أسرار نينجا الجافاسكربت ، طبعا الكتاب موجه للمستوى المتقدم ، فى انتظار انتهاء الكتاب ، احتمال اتوقف عن التدوين بعد انتهاء سلسله انماط تصميم جافاسكربت عشان الامتحانات ، و احتمال اتكلم عن البرمجه الوظيفيه بالجافاسكربت :) كل كم يوم بدلا من التوقف عن التدوين ، البرمجه الوظيفيه شيقه جداااااا ، ادعولى بالتوفيق .

Decorator design pattern

09/04/2009

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

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

مقدمه

نمط التصميم 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 لكن الفرق فى ان الكائن الجديد يحتوى على وظائف معدله ، عند إستخدام اى وظيفه سيتم الابلاغ عن الوقت الذى تستغرقه كل وظيفه فى عملها ، و اذا كنت تريد حذف هذه الميزه التى تمكنك من زياده كفاءه تطبيقاتك ، يمكنك حذف السطر الاول فقط :D .

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

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

adapter design patterns

01/04/2009

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

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

نمط التصميم Adapter يمكنك من إستخدام -او تبنى- Interface موجود مع اصناف اخرى غير متوافقه مع هذا ال Interface .

The Adapter pattern allows you to adapt existing interface to classes the would otherwise be incompatible .

ال Adapters يضاف للكود الموجود حتى يوفق بين ال Interfaces الغير متوافقه .
بالرغم من ال Adapters و ال Facade يتشابهوا فى انهم يقوموا بإحاطه الكائنات و تغيير ال Interface الذى يمثلهم للعالم الخارجى ، عليك ملاحظه ان ال Adapter يحول ال Interface التى تستخدم للوصول لصنف ما إلى Interface اخر ، بعكس ال Facade الذى يستخدم لعمل Interface أسهل للعمليات المعقده .
يمكنك اعتبار اتنين Interfaces غير متوافقين مثل فيشه كبيره اوى و كبس كهربائى ذات فتحات صغيره ، فى هذه الحاله لا يمكن ادخال الفيشه فى الكبس ، لكن يمكنك إستخدام Adapter -فى المحلات الكهربائيه تقريبا إسمه محول- يقبل فتحه الفيشه الكبيره من ناحيه و يخرج فيشه صغيره من الناحيه الاخرى ، هذا هو طريقه عمل ال Adapter .

لنفرض أن هناك وظيفه بإسم interfaceMethod فى صنف ما تقبل ثلاث عبارات من نوع String كما يوضح الكود البسيط التالى :

function interfaceMethod(str1, str2, str3){
    /*...code...*/
}

ماذا إذا اردت فى اى وقت فى المستقبل لأى من الاسباب أن يمرر لها object literal بدلا من 3 عبارت من نوع String ، تغير الكود الداخلى ل interfaceMethod حل غير عملى اطلاقا (لماذا) ، الافضل من ذلك عمل Adpater يحيط الوظيفه interfaceMethod بحيث يقبل ال object literal بالنيابه عنها ، ثم يمرر لها 3 عبارات String كما يحدث فى الوضع الطبيعى .

var obj = {
    string1:"foo",
    string2:"bar",
    string3:"baz",
}
function interfaceMethodAdapter(o){
    interfaceMethod(o.string1, o.string2, o.string3);
};

هذا المثال مجرد تطبيق بسيط جدا لل Adapter ، فى الواقع بعض أطر العمل تقوم بعمل Adapters لاطر العمل الاخرى ، بمعنى بفرض انك تستخدم اطار عمل Prototype منذ 3 اعوام و تريد الانتقال إلى YUI بدون تغير طريقه كتابتك للكود ، ولا تغير أى سطر كتبته من قبل ، بمنتهى البساطه يمكنك استخدام ال Adapter التى تقدمه لك YUI لإستخدام المكتبه بنفس ال Interface المستخدم فى jQuery ، هههههههه هذه ميزه اكثر من رائعه ، اذا كيف يبدو ال Adapter فى اطر العمل العملاقه ؟ لنفرض ان الوظيفه YAHOO.util.DOM.get تقبل عباره واحده ، قد تكون نص او HTMLElement او مصفوفه من نصوص او HTMLElements ، اما الوظيفه $ فى اطار عمل prototype تقبل اى عدد من العبارات من نوع نص او HTMLElement كما يوضح الكود البسيط التالى :

// prototype $ function
function $(){
    var elements = new Array();
    for(var i=0; i<arguments.length; i++){
        var element = arguments[i];
        if(typeof element == "string")
            element = document.getElementById(element);
        if(arguments.length == 1)
            return element;
        elements.push(element);
    }
    return elements;
};

// YUI get method
YAHOO.util.Dom.get = function(el){
    if(YAHOO.lang.isString(el)){
        return document.getElementById(el);
    }
    if(YAHOO.lang.isArray(el)){
        var c = [];
        for(var i=0, len=el.length; i<len; i+=1){
            c[c.length]=Y.Dom.get(el[i]);
        }
        return c;
    }
    if(el){
        return el;
    }
};

لنفرض انك فى المستقبل اردت الانتقال من prototype إلى YUI لأنها أسرع و اكثر امانا و مجتمع المطورين الذى يستخدمها كبير جدا و جوده التوثيق بكل صغيره و كبيره متعلقه بالمكتبه و لوفره ال cheat sheets و لأن ياهو تستخدمها فى تطبيقاتها … الخ ، ستلاحظ تغير كبير فى ال Interface بين prototype و YUI ، و اذا نظرنا إلى الوظيفه get فى YUI سنجد انها لا تقبل إلا عباره واحده فقط كما ان $ فى prototype لا تقبل عبارات من نوع Array ، هذا بالإضافه إلى التغير فى ال Interface بين الوظائف التى تتناول الاحداث و ال Ajax … الخ ، الحل فى إستخدام Adapter بدلا من اعاده كتابه الكود بواسطه YUI ، كما يوضح الكود البسيط التالى :

function $toGetAdapter(){
    return YAHOO.util.Dom.get(arguments);
}

function getTo$Adapter(el){
    return $.apply(window,el);
    // $.apply fix prototype array problem
}

إذا كنت تكتب prototype و تريد الانتقال إلى YUI بدون تغيير ما كتبته يمكنك استخدام ال Adapter المناسب كما يوضح الكود التالى :

$ = $toGetAdapter;

و إذا كنت تكتب YUI و تريد الانتقال إلى prototype بدون تغيير ما كتبته يمكنك إستخدام ال Adapter المناسب كما يوضح الكود التالى :

YAHOO.util.Dom.get = getTo$Adapter;

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

هذا الموضوع تلخيص للفصل الحادى عشر فى كتاب Apress: Pro JavaScript design pattern يمكنك الرجوع اليه للمزيد ، انتظروا نمط التصميم decorator فى الدرس القادم ان شاء الله .

خارج النص : قمت بوضع لينكات للسكرين كاست التى أشاهدها فى العمود الجانبى للمدونه ، و سوف انوه عن اى حلقه ذات اهميه قصوى فى موضوع منفصل كما تعودت ، لما كان عندك 12 سنه كنت بتعمل ايه ؟؟؟ ياترى كنت بتحضر مؤتمرات و تتكلم عن jQuery ذى Dmitri Gaskin شوف بنفسك ، اووووووووف .

Facade design pattern

27/03/2009

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

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

نمط التصميم Facade – ينطق فصاد – يستخدم لعمل Interface بسيط للصنف Class الذى يقوم بالكثير من العمليات المعقده أو الكثيره المتكرره ، و يزيل العلاقه بين الصنف و الكود الذى يستخدمه و يجعلهم loosely coupled ، كما ان نمط التصميم Facade هو لب كل اطر عمل الجافاسكربت ، حيث يقوم اطار العمل بفحص الفروق بين المتصفحات و فحص الاخطاء و عمل تفرع فى الكود و فى النهايه يعطى لك Interface للتعامل معه ، خذ عندك مثلا وظيفه $ فى اطار عمل jQuery ماهى إلا عباره عن Facade كبير جدا يجلب لك عناصر ال DOM عن طريق CSS3 selectors بالرغم من ان المتصفح – مثل IE و النسخ القديمه من Firefox و Safari و Opera- لا يفهم هذه ال selectors ، كل ماعليك هو معرفه كيف يكتب ال selector اما طريقه عمل jQuery ليس من اختصاصك ، حيث يقوم الكود داخل jQuery بإعراب ال selector الذى قمت بإدخاله و تصنيفه و جلب عناصر ال DOM من خلال الكثير من ال loops و المقارنات ووضع و جلب من ال cache بدون علمك ، الذى تطلبه ستجده ، اما تفاصيل عمل الكود الداخلى فسيتغير من نسخه للأخرى بدون ان يؤثر على عملك لأنك تتعامل مع Interface ثابت لن يتغير ، و مثال اخر لل Facade الذى يقوم بالعمليات المكرره ايقونه الاختصار فى انظمه التشغيل ، حيث تمكنك من الوصول لملف او اخر عن طريق الضغط عليها بدلا من الدخول لمسار داخل مسار داخل مسار داخل مسار داخل مسار فى كل مره تحاول الوصول لهذا الملف ، مثال اخر لل Facade الذى يقوم بفحص الفروق و القيام بالعمليات المكرر الوظيفه addEvent ، حيث أن اضافه حدث داخل تطبيق جافاسكربت يعمل فى بيئه المتصفح يتغير من متصفح لاخر ، فى المتصفحات التى لا تطبق قواعد W3C مثل متصفح IE يمكنك إضافه الحدث عن طريق الوظيفه attachEvent لعنصر ال DOM ، اما فى المتصفحات التى تطبق قواعد W3C مثل Firefox و Safari و Opera يمكنك غضافه الحدث لعنصر ال DOMعن طريق addEventListenr ، اما المتصفحات القديمه فيمكنك إضافه حدث عن طريق onEvent حيث Event هو الحدث ، تقوم الوظيفه البسيطه addEvent بتقديم Interface بسيط ثابت فى كل المتصفحات و تزيل عنك الفحص المتكرر فى كل مره تتعامل فيها مع الاحداث ، كما يوضح الكود البسيط التالى :

function addEvent(elem,type,fn){
    // W3C browsers way
    if(window.addEventListener){
        elem.addEventListener(type, fn, false);
    }
    // IE way
    else if(window.attachEvent){
        elem.attachEvent("on"+type, fn);
    }
    // old browsers way
    else{
        elem["on"+type] = fn;
    }
}

و يمكننا إستخدامها كالتالى فى اى متصفح :

addEvent(document,"click",clickHandler);
addEvent(document.body,"move",function(){
    /* ...code... */
});

أفضل ميزات نمط التصميم Facaed انك غير مقيد بكيفيه عمل الصنف ، مثلا google maps تستخدم canvas فى متصفحات W3C و تتسخدم VML فى متصفح IE ، فى اى وقت فى المستقبل ممكن ان تستخدم XAML فى متصفح IE ، او تستخدم SVG فى متصفحات W3C بدون علم مستخدمين Google maps api لأنهم يتعاملون مع Interface ثابت ، اما كيفيه عمله و ال  implementation ليس من شأنهم ، كما ان من مميزات ال Facade انه organizational pattern أى نمط تنظيمى يقوم بوضع الكود الذى يختلف فى مكان واحد و يعطى لهذا الكود Interface بسيط للإستخدام .

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

Composite design pattern

26/03/2009

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

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

نمط التصميم Composite يستخدم لإجراء العمليات المتكرره recursive على عدد كبير من الكائنات عن طريق امر واحد فقط بدون معرفه عدد هذه الكائنات ، كما يستخدم ايضا لعمل تراكيب البيانات data structures المعقده ، حيث يتم معامله الكائنات كلها كأنها كائن واحد يمرر له الامر ، ثم يقوم هذا الكائن بدوره بتمرير الامر للكائنات التى يملكها و اسفل منه فى تفرع ال Composite الشجرى ، حيث تنقسم الكائنات فى نمط تصميم Composite إلى نوعين : الاول من نوع Composite و هو الذى يحتوى على كائنات بداخله ، و النوع الثانى هو Leaf الذى لا يحتوى على كائنات بداخله و يكون متفرع من Composite ، كما توضح الصوره التاليه :

composite pattern structure

صوره تركيب نمط التصميم Composite مأخوذه من كتاب Apress: Pro JavaScript Design Patterns

عندما يتم تمرير أمر ما إلى الكائن Composite الذى يعلوا هذا التفرع الشجرى ، فإنه يقوم بتمرير هذا الامر إلى الكائنات التى يحتويها -تحت منه فى الشجره- ، إذا كانت من نوع leaf فإنها ستنفذ هذا الامر ، اما إذا كانت من نوع Composite فإنها ستمررها للكائنات التى تحتويها … الخ حتى ينتهى تمرير الامر إلى كل الكائنات الموجوده فى الشجره ، و بما ان الامر سيمرر لكل هذه الكائنات فإنه يتوقع منها أن تقوم بإدراج Interface معين ، و لذلك لن يتم ادخال كائن ضمن شجره ال Composite إلا بعد التأكد من انه يدرج Interface معين أو مجموعه كما سنرى فى الامثله اللاحقه .

هناك علاقه من نوع HAS-A بين ال Composite و الكائنات التى يحتويها ، مثلا Car has a wheel ، او Form has a field ، أى ان ال Form كائن من نوع Composite يحتوى على كائن اخر Field من نوع Leaf .

لا توجد علاقه من نوع IS-A بين ال Composite و الكائنات التى يحتويها ، لأن IS-A علاقه وراثه و الكائنات التى يحتويها ال Composite لا ترث منه ، مثلا Dog is an animal ، اى ان الكائن Dog يرث من الكائن Animal .

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

المثال الاول – Form Validation :

لنفرض انك قد كلفت بعمل form يحتوى على عدد من المدخلات input و ال textarea و ال select غير معروف وقت التشغيل ، يمكن لهذا ال form تخزين البيانات التى تحتويها هذه المدخلات و يمكن أيضا استرجاعها إذا تم تخزينها من قبل ، و يمكنه اختبار مطابقتها لنمط بيانات معين validation ، قد تبدوا هذه المهمه سهله ، لكنها ليست كذلك ، لأن عدد المدخلات غير معروف وقت التشغيل ، لذلك لا يمكن عمل form واحد يناسب احتياجات كل مستخدم ، مثلا هناك فورم للتسجيل register ، و فورم اخر للدخول Login و فورم اخر لإرسال بيانات إلى فريق العمل contact …. الخ .

لكنك بعد التفكير أدركت ان هناك علاقه تركيبيه بين هذه الكائنات ، اذ ان الفورم يحتوى على fieldset و كل fieldset يحتوى على عدد معين -غير معروف-من المدخلات بأنواعها المختلفه input و textarea و select ، أى ان لديك كائين من نوع composite و هما form و fieldset ، و لديك ثلاث كائنات من نوع leaf و هم input و textarea و select كما توضح الصوره التاليه :

العلاقه بين الكائنات فى مثال ال Form Validation مأخوذه من كتاب Apress: Pro JavaScript Design Patterns

العلاقه بين الكائنات فى مثال ال Form Validation مأخوذه من كتاب Apress: Pro JavaScript Design Patterns

الكائنات التى من نوع Composite يمكنها إضافه add و محو remove و إرجاع getChild الكائنات التى من نوع Leaf بادخلها ، اما الكائنات من نوع leaf فيمكننا حفظ القيم save التى بداخلها ، لن نقوم بعمل validate و restore فى هذا المثال و لن نقوم بعمل كائنات fieldset للتسهيل، و لنفرض اننا قمنا بعمل فورم به عشره مدخلات ، و تريد تخزين القيم بهذه المدخلات فى cookie على سبيل المثال ، فأنك سوف بإستدعاء Form.save التى بدورها ستقوم بتمرير الأمر Save إلى كل المدخلات Input.save .

سنقوم بعمل Interface بإسم Composite يحتوى على الوظائف add,remove,getChild ، و سنقوم بعمل Interface اخر بإسم FormIntem يحتوى على الوظيفه save ، كل الاصناف ستقوم بإدراج ال Composite Interface و ال FormItem Interface ،  كما يوضح الكود البسيط التالى :

var Composite = new Interface("Composite",['add','remove','getChild']);
var FormItem = new Interface("FormItem",['save']);

الشكل التالى يوضح ال UML Class Diagram للأصناف التى سنقوم بإنشاءها :

UML class diagram للأصناف التى سيم انشائها فى مثال ال Form Validation مأخوذه من كتاب Apress: JavaScript Design Pattrns

UML class diagram للأصناف التى سيم انشائها فى مثال ال Form Validation مأخوذه من كتاب Apress: JavaScript Design Pattrns

من الشكل السابق سنقوم بعمل صنف بإسم CompositeForm ، و ثلاثه اصناف اخرى بإسم InputField و TextareaField و SelectField يرثوا من الصنف Field ، هذه الوراثه ليست لها علاقه بطريقه عمل النمط   Composite و لكنها لعدم تكرار الكود -لاتكرر نفسك DRY – كما ان هذه الاصناف بينها علاقه من نوع IS-A .

هيا بنا نقوم بعمل الصنف CompositeForm الذى يمثل الفورم فى تطبيق ال HTML كما يوضح المثال التالى :

// CompsiteForm Implements Compiste,FormItem Interfaces
var CompositeForm = function(id,method,action,parent){
    // store children in formComponents
    this.formComponents = [];

    // create form element
    this.element = document.createElement("form");
    this.element.id = id;
    this.method = method || "POST";
    this.action = action || "#";
    // append the form element
    parent.appendChild(this.element);
};

// add element to the form 
CompositeForm.prototype.add = function(child){
    // make sure that [child] implements Composite,FormItem Interfaces .
    Interface.ensureImplements(child,Composite);
    Interface.ensureImplements(child,FormInterface);
    //add [child] it to the composite structure
    this.formComponents.push(child);
    // append [child] element to the form
    this.element.appendChild(child.getElement());
};

//remove element from the form
CompositeForm.prototype.remove = function(child){
    for(var i=0, len = this.formComponents.length; i<len; i+=1){
        if(this.formComponents[i]==child){
            // remove the chilld from the array , more ...
            this.formComponents.splice(i,1);
            //exit the loop
            break;
        }
    }
};

// get reference to element from inside the form
CompositeForm.prototype.getChild = function(){
    return this.formComponents[i];
};

//save form fields data
CompositeForm.prototype.save = function(){
    // pass the save command to the children of the composite structure
    for(var i=0, len = this.formComponents.length; i<len; i+=1){
        this.formComponents[i].save();
    }
};

// get reference to the form element
CompositeForm.prototype.getElement = function(){
    return this.element;
}

انتهينا من كتابه الصنف CompositeForm حيق يقوم ال function constructor بعمل ال HTML form ثم بعد ذلك يدرج الصنف كل من Composite Interface و FormItem Interface عن طريق ادراج الوظائف الوجوده فى كل من هاذين ال Interface ، الوظيفه add تقوم بإضافه العنصر child داخل الفورم و إضافته إلى المصفوفه formComponents بعد التأكد من انه يدرج كلا من Composite و FormItem Interface ، اما الوظيفه remove فتقوم بحذف العنصر child من داخل form و من داخل مصوفه formComponents ، اما الوظيفه getChild فتقوم بإرجاع مرجع لأحد العناصر داخل formComponents بناءا على ال id الذى مرر لها ، و الوظيفه save تقوم بتمرير الامر إلى العناصر الموجوده بمصفوفه formComponents مع ملاحظه ان هذه الوظيفه من FormItem Inteface ، الصنف Composite يقوم بإدراج هذه الوظيفه لكنه يقوم بتفويض عملها delegation إلى الاصناف التى ترث من الصنف Field كما سنرى لاحقا ، واخيرا الوظيفه getElement ترجع مرجع لعنصر الفورم .

هيا بنا نكتب الكود الخاص بالصنف Field كما هو موجود فى ال UML Class Diagram :

var Field = function(id){
    this.id = id;
    this.element;
}

// methods from Composite Interface
Field.prototype.add = function(){};
Field.prototype.remove = function(){};
Field.prototype.getChild = function(){};

// method from FormItem Interface
Field.prototype.save = function(){
    setCookie(this.id, this.getValue);
};

Field.prototype.getElement = function(){
    return this.element;
};
Field.prototype.getValue = function(){
    throw new Error("Unsupported operation on the class Field");
};

هذا الصنف Field سيرث منه و يزيد عليه الاصناف الاخرى مثل InputField و TextareaField و SelectField ، لا يحتوى ال function constructor الخاص به الا الخاصيتين id و element سنقوم بإضفتهم فى الاصناف الاخرى ، الوظائف add,remove,getChild فارغه لأن الحقل Field لا يمكن اضافه او مسح حقول اخرى داخله ، و لكننا نضيف هذه الوظائف حتى تطابق ال Composite Interface ،اما الوظيفه save فتقوم بحفظ قيمه الحقل داخل cookie بإستخدام setCookie ، بارغم من عيوب تخزين القيم فى ال cookie إلا انها الاسهل للشرح ،  اما الوظيفه getElement فتقوم بإرجاع العنصر الذى يمثل الحقل ، اما الوظيفه getValue ترجع خطأ لأننا سندرجها فى كل صنف فرعى بشكل مختلف ، هيا بنا نكتب الكود الخاص بالصنف InputField :

var InputField = function(id,label){
    Field.call(this,id);

    //create the input element
    this.input = document.createElement("input");
    this.input.id = id;

    // create the label element
    this.label = document.createElement("label");
    var labelTextNode = document.createTextNode(label);
    this.label.appendChild(labelTextNode);

    // wrap input,label inside div element
    this.element = document.createElement("div");
    this.element.appendChild(this.label);
    this.element.appendChild(this.input);
};
// InputField class extends Field class
extend(InputField,Field);

// return reference to the input element
InputField.prototype.getElement = function(){
    return this.input;
}
// get the input element value
InputField.prototype.getValue = function(){
    return this.input.value;
}

الصنف InputField يرث الصنف Field عن طريق extend و يقوم بالتعديل على function constructor و الوظيفه getValue و getElement .

هيا بنا نقوم بكتابه الكود الخاص بالصنف TextareaField :

var TextareaField = function(id,label){
    Field.call(this,id);

    //create the textarea element
    this.textarea= document.createElement("textarea");
    this.textarea.id = id;

    // create the label element
    this.label = document.createElement("label");
    var labelTextNode = document.createTextNode(label);
    this.label.appendChild(labelTextNode);

    // wrap textarea,label inside div element
    this.element = document.createElement("div");
    this.element.appendChild(this.label);
    this.element.appendChild(this.textarea);
};
// InputField class extends Field class
extend(TextareaField,Field);

// return reference to the textarea element
TextareaField.prototype.getElement = function(){
    return this.textarea;
}
// get the textarea element value
TextareaField.prototype.getValue = function(){
    return this.textarea.value;
}

لن يختلف الصنف TextareaField كثيرا عن InputField إلا فى العنصر textarea الذى تم انشاءه و كيفيه جلب مرجع و القيمه من العنصر .

هيا بنا نكتب الكود الخاص بالصنف SelectField :

var ٍSelectField = function(id,label,options){
    Field.call(this,id);

    //create the select element
    this.select= document.createElement("select");
    this.select.id = id;
    // adding options to the select is your mission

    // create the label element
    this.label = document.createElement("label");
    var labelTextNode = document.createTextNode(label);
    this.label.appendChild(labelTextNode);

    // wrap select,label inside div element
    this.element = document.createElement("div");
    this.element.appendChild(this.label);
    this.element.appendChild(this.select);
};
// InputField class extends Field class
extend(SelectField,Field);

// return reference to the select element
SelectField.prototype.getElement = function(){
    return this.select;
}
// get the select element value
TextareaField.prototype.getValue = function(){
    return this.select.value;
}

يمكننا الان استخدام ال CompositeForm كالاتى :

var contactForm = new CompositeForm("contact-form","POST","contact.php",body);
contactForm.add( new InputField("name","what's your name ?"));
contactForm.add( new InputField("email","what's your email adress ?"));
contactForm.add( new TextareaField("message","tell us what you think ?"));
contactForm.add( new InputField("captcha","what's 1+12 result ?"));

window.onunload = function(){
    contactForm.save();
}

المثال يوضح كيفيه انشاء الكائنات داخل ال Composite و كيفيه تنفيذ عمليه على جميع كائنات ال composite عن طريق امر واحد فقط contactForm.save ، طبعا حفظ القيم عند اغلاق المتصفح حل غير عملى و لكنه لإعطاء مثال بسيط فقط ، يمكنك عمل صنف جديد بإسم Button و تسجيل الحدث click له لتنفيذ نفس الامر السابق .

ماذا إذا أردنا إضافه وظيفه جديده إلى الحقول مثل استرجاع القيم التى تم حفظها من قبل ؟ اولا سنعدل على ال Interface الذى يمثل الحقول FormItem Interface و نضيف له الوظيفه restore كما يوضح الكود التالى :

var FormItem = new Interface("FormItem",['save','restore']);

ثانيا نضيف هذه الوظيفه إلى الصنف Field الذى ترث منه جميع الحقول InputField و TextareaField و SelectField كما يوضح الكود التالى :

Field.prototype.restore = function(){
    thie.getElement().value = getCookie(this.id);
}

ثالثا نضيف هذه الوظيفه إلى ال CompositeForm حتى يمكن استدعائها من الكائن ال composite الذى يعلوا التفرع الشجرى كما يوضح الكود التالى :

CompositeForm.prototype.restore = function(){
    for(var i=0, len=this.formComponents.length; i<len; i+=1){
        this.formComponents[i].restore();
    }
}

و اخيرا يمكننا استخدامها كالتالى :

window.onload = function(){
    contactForm.restore();
}

هناك ملاحظه واحده فقط أن ال Composite الحقيقى يمكنه احتواء اصناف مثله ، لكن هذا المثال مثال الفورم لا يمكن لل HTMLFormElement ان تحتوى HTMLFormElement اخر ، قام الكتاب بعمل مثال اخر Image gallery يوضح كيف يمكن لل composite ان يحتوى نفسه ، يمكنك الرجوع لكتاب Apress: Pro JavaScript design patterns للمزيد .

خارج النص : الصفه الوحيده التى تجمع عباقره و مبدعين و مخترعين العالم انهم فاشلين دراسيا -من كتاب the myths of innovation- ، و بما انى فاشل دراسيا فيارب ابقى منهم .

Factory design pattern

17/03/2009

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

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

غالبا ما يحتوى الصنف class على العديد من الكائنات objects التى يتم انشاءها من اصناف اخرى ، من السهل انشاء هذه الكائنات عن طريق الكلمه المفتاحيه new و ال function constructor ، لكن هذا يؤدى إلى اعتماد هذا الصنف على الاصناف التى تم توليد منها هذا الكائنات ، فى هذا الموضوع سننظر إلى النمط Factory الذى يقلل من اعتماد الاصناف على بعضها ، وجب التنويه ان نمط التصميم factory يعتمد على الواجهات البرمجيه Interfaces بشكل كبير جدا ، يمكنك قراءه موضوع ال Interface من هنا ، حتى تتضح الفكره اكثر لنفرض ان هناك متجر لبيع السيارات -  لها نفس ال Interface – يمكننا تمثيله كما يوضح الكود البسيط التالى :

/* CarShop Class */
var CarShop = function(){};
CarShop.prototype = {
    sellCar:function(model){
        var car;
        switch(model){
            case "bmw":
                car = new BMW();
                berak;
            case "mercedes":
                car = new Mercedes();
                break;
            case "jaguar":
                car = new Jaguar();
                break;
            case "lexus":
                car = new Lexus();
                break;
            default:
                car = new Fiat();
        }
        Interface.ensureImplements(car,Car);
        car.assemble();
        car.wash();
        return car;
    }
};

الصنف CarShop يحتوى على الوظيفه sellCar التى تقبل عباره model تمثل نوع السياره ، يتم انشاء كائن جديد يمثل نوع السياره ، لكن لا يتم ارجاعها من الوظيفه sellCar إلا بعد التأكد من انها تطابق ال Interface الذى بإسم Car عن طريق Interface.ensureImplements التى شرحتها من قبل هنا ، يمكن تمثيل السياره BMW كما يوضح الكود التالى :

/* Car Interface */
var Car = new Interface("Car",['start','stop','accelerate','openDoor','closeDoor']);
/* BMW Class */
var BMW = function(){};
/* implements Car Interface */
BMW.prototype = {
    start:function(){/*code*/},
    stop:function(){/*code*/},
    accelerate:function(){/*code*/},
    openDoor:function(){/*code*/},
    closeDoor:function(){/*code*/},
    /* ...rest of BMW features... */
}

يمكنك الان عمل متجر سيارات و البيع كما يوضح الكود التالى :

var myShop = new CarShop();
var yourCar = myShop.sellCar("bmw");

يمكننى انشاء متجر اخر و بيع السيارات منه :

var keepondevShop = new CarShop();
var myCar = myShop.sellCar("lexus");

لكن ماذا اذا كنت اريد اضافه سياره جديده ؟ سأضطر إلى تغيير الكود فى CarShop بالرغم من ان وظائفه لن تتغير ، ثم ان صنع السيارات ليس مسؤليه CarShop ، و بهذا نحن نخالف مبدأ من مبادىء البرمجه الموجهه بالكائنات : your class should have only one reason to change لابد من سبب منطقى لتغير اى صنف ، الحل الافضل هو انشاء مصنع سيارات CarFactory يتولى صنع السيارات و بذلك اضافه سياره جديده سيكون فى المكان الطبيعى لصنعها و ليس فى متجر السيارات ، يمكننا انشاء CarFactory عن طريق نمط التصميم Singleton ، بحيث سيعطى namespace للوظيفه createCar كما يوضح المثال البسيط التالى :

CarFactory = {
    createCar:function(model){
        var car;
        switch(model){
            case "bmw":
                car = new BMW();
                berak;
            case "mercedes":
                car = new Mercedes();
                break;
            case "jaguar":
                car = new Jaguar();
                break;
            case "lexus":
                car = new Lexus();
                break;
           case "bugati": /* new car added */
               car = new Buggati();
               break;
            default:
                car = new Fiat();
        }
        Interface.ensureImplements(car,Car);
        return car;
    }
};

و سيتغير الكود ب CarShop كالتالى :

/* CarShop Class */
var CarShop = function(){};
CarShop.prototype = {
    sellCar:function(model){
        var car = CarFactory.createCar(model);
        car.assemble();
        car.wash();
        return car;
    }
};

و بذلك يمكننا صنع و بيع السيارات كالتالى :

var yourShop = new CarShop();
var myBrandNewCar = yourShop.sellCar("buggati");

الان يمكننى عمل اكثر من متجر من CarShop و اضافه سيارات جديده للمصنع CarFatory ، و بالطبع يمكن للمتاجر الاخرى التعامل مع المصنع CarFactory .

نمط تصميم Factory الحقيقى يختلف عن ال CarFactory الذى انشأناه من Singleton فى ان مسئوليه انشاء الكائنات داخل الصنف يتولاها صنف فرعى Subclass و ليس صنف اخر منفصل .

Factory Pattern is to “Define an interface for creating an object, but let the subclasses decide which class to instantiate. The Factory method lets a class defer instantiation to subclasses.

و عليه يمكننا انشاء CarShop فى صوره abstract class لا يمكن انشاءه ، به وظيفه createCar لاتؤدى وظيفه و سوف نعطيها وظيفه فى ال subClass كما يوضح الكود التالى :

/* abstract class */
var CarShop = function(){};
CarShop.prototype = {
    sellCar:function(model){
        var car = this.createCar(model);
        car.assemble();
        car.wash();
        return car;
    },
    createCar:function(){
        throw new Error("method not implemented in abstract class");
    }
}

هذه الوظيفه بمثابه abstract class لا يمكن انشائها ووظيفه createCar لا تؤدى وظيفه بها ، يمكننا ادراج هذه الوظيفه فى ال subClass من خلال extend كما يلى :

var BMWShop = function(){};
extend(BMWShop,CarShop);
BMWShop.prototype.createCar=function(model){
    var car;
    switch (model){
        case "city":
            car = new BMW1Series();
            break;
        case "compact":
            car = new BMW3Series();
            break;
        case "executive":
            car = new BMW5Series();
            break;
        case "luxury":
            car = new BMW7Series();
            break;
    }
    Interface.ensureImplements(car,Car);
    return car;
}

هذا المتجر يبيع سيارات BMW يمكننا عمل متجر اخر يبيع سيارات Mercedes بمثابه subClss ل CarShop عن طريق extend كما يوضح الكود التالى :

var MercedesShop = function(){};
extend(MercedesShop,CarShop);
MercedesShop.prototype.createCar=function(model){
    var car;
    switch (model){
        case "city":
            car = new MercedesBClass();
            break;
        case "compact":
            car = new MercedesCClass();
            break;
        case "executive":
            car = new MercedesEClass();
            break;
        case "luxury":
            car = new MercedesSClass();
            break;
    }
    Interface.ensureImplements(car,Car);
    return car;
}

و يمكننا استخدام المتجرين كما يلى :

var myShop = new BMWShop();
myShop.sellCar("luxury"); // BMW 7 Series car

var yourShop = new MercedesShop();
yourShop.sellCar("luxury"); // Mercedes S class

متى تستخدم نمط تصميم Factory ؟
اذا كان هناك اصناف كتير لها نفس ال Interface كما فى مقال CarShop السابق يمكنك تسهيل عمليه انشاء الصنف المطلوب من بين تلك الاصناف المتشابه فى Interface عن طريق نقل كود الانشاء داخل Factory ، او عندما تكون الاصناف يتم انشائها وقت التشغيل كما فى حاله تطبيقات Ajax حيث ان هناك اكثر من كائن يمكن انشاءه حسب البيئه التى يعمل بها التطبيق ، مثلا فى متصفحات ال W3c يمكنك استخدام XMLHttpRequest ، انا فى متصفحات IE يمكنك استخدام Microsoft.XMLHTTP او Msxml2.XMLHTTP ، فى كتاب APRESS:JavaScript design patterns قام الكاتب بعمل ثلاث انواع من الاتصالات التى تعتمد على Ajax ، الاول SimpleHandler يستخدم للسرعات العاليه ، و QueueHandler يستخدم اذا كانت السرعه بطئيئه ، و الثالث OfflineHandler يستخدم اذا كان الاتصال مقطوع ، و قام بعمل Factory لتحديد اى من هذه الاصناف سوف يتم انشاءه على حسب ظروف المتصفح الذى يستخدمه الزائر ، يستخدم ايضا Factory لتجميع الكائنات الصغيره لعمل كائن كبيره ، مثلا فى مثال CarFactory تم تجميع السياره مباشره ، يمكنك تجميع السياره من كائنات صغيره يحددها خيارات المستخدام مثل الاطارات بأنواعها و الجنوط بأنواعها و المواتير و انواعها وغيره من مواصفات السياره .

فوائد نمط نصميم Factory ؟
يقوم بمحو اى اعتماد بين الكائنات بين بعضها الى اقصى حدود ، و بذلك سيكون التغيير فى الكود متمركز فى المنطقه التى تتغير فيها الخاصيه او الميزه ، كما انه يجمع الكود الخاص بإنشاء الكائنات فى مكان واحد و بذلك يمكن انشاء هذه الكائنات ديناميكيا وقت التشغيل اعتمادا على الظروفه المحيطه او خيارت المستخدم .

متى لا تستخدم نمط التصميم Factory ؟
اذا كان هناك صنف واحد فقط لا يمكن تغيره بأصناف اخرى ، فى هذه الحاله إنشاء الكائن عن طريق new مباشرا افضل بدلا من متابعه ال factory عن طريق debugger او غيره لمعرفه اى كائن تم انشاءه ، لأن زياده الكود لن تزيد اى مرونه ولا فائده .

الموضوع تلخيص الفصل السابع من كتاب APRESS: Pro JavaScript design patterns .

خارج النص : انا ان شاء الله هروح مؤتمر WordCamp Egypt 2009 يوم 28 مارس ، اللى عايز ييجى معايا او نتقابل يكلمنى على الموبايل او يبعتلى ميل من صفحه contact ، سلااااااااااااام .

Bridge design pattern

16/03/2009

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

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

Bridge – الجسر -هو نمط تصميم يستخدم لفصل ال abstraction – كيف يعمل الصنف – عن ال implementation  – البيئه التى يعمل بها – و بذلك يمكن تغيير الاثنين بدون اعتماد احداهما على الاخر.

Bridge design pattern decouple an abstraction from its implementation so that the two can vary independently .
WikiPedia

انظر الى المثال البسيط التالى حتى تتضح الفكره قليلا :

myElem.addEventListener("click",getClientById);

function getClientById(event){
    var id = this.id;
    ajaxRequest("GET","clients.php?id="+id , function(resp){
        console.log(resp.responseText);
    });
}

المثال السابق يفترض ان هناك عنصر من عناصر HTML له مرجع بإسم myElem ، تم تسجيل مستمع لحدث click لهذا العنصر ، عند حدوث هذا الحدث يتم استدعاء الوظيفه getClientById و يتم تمرير الكائن الذى يمثل الحدث event لها ،هذه الوظيفه تقوم بإرجاع client بناءا على id كما يتضح من اسمها ،  داخل هذه الوظيفه يتم الحصول على ال id من خلال this.id بفرض ان عنصر ال html هذا يحتوى على id attribute ، لأن this تشير الى العنصر الذى صدر منه الحدث ، ثم يتم الحصول على client من خلال طلب عن طريق ajax لصفحه clients.php مع تمرير ال id لها ، و عندما يأتى الرد يتم طبع هذا الرد داخل ال console بفرض ان هناك console مثل firebug console .

هذه الوظيفه ترجع client بناءا على id ، فليس من الطبيعى تمرير كائن event لها ، كما لا يمكن استخدامها الا كمستمع للحدث لأن this ستشير إلى ال global object اذا لم يتم استخدامها كمستمع للحدث ، كما ان نتيجه طلب ال ajax يتم طباعتها فقط فى console ، ماذا اذا كنا نريد عمل شىء اخر بنتيجه طلب ال ajax ، كما ترى هذا ال api غير مرن اطلاقا ، سيكون من الصعب عمل unit test لها ، و من الصعب ايضا استخدامها من command line .

يمكننا كتابه getClientById أفضل عن طريق تمرير لها id و callback ، و عمل bridge يقبل كائن الحدث event و لا يوثر على كيفيه عمل الوظيفه الاصليه كما يوضح المثال البسط التالى :

function getClientById(id,callback){
    ajaxRequest("GET","clients.php?id"+id,function(resp){
        callback(resp.responseText)
    });
}

يمكننا استخدام النسخه الجيديده من getClientById داخل ال bridge كالتالى :

myElem.addEventListener("click",getClientByIdBridge,false);
function getClientByIdBridge(event){
    getClientById(this.id,function(client){
        console.log("the client is : " + client );
    })
}

الوظيفه getClientById اصبحت الان تقبل id و هذا منطقى اكثر بدلا من event object و تقبل أيضا وظيفه تقوم بإستدعائها عند الحصول على ال client ، و بذلك getClientById لا تعتمد اطلاقا على ال implementation ، لأن ال bridge الذى يمثل الوظيفه getClientByIdBridge قام بتولى مسؤليه اى implementation و بذلك يمكن تغير الكود داخل ال bridge بدون التأثير على تخصص الوظيفه getClientById ، و بذلك ايضا يمكننا ادخال getClientById داخل unit test و استخدامها من command line لأنها لا تعتمد على احداث ، فقط تستقبل المدخلات id و callback و تنفذ المطلوب ، اما اى زياده او تغيير فيتولاه ال bridge .

كما ان الجسور فى الواقع تصل الاشياء ببعضها فإن ال bridge يستطيع ربط اى صنفين معا كما يوضح المثال البسيط التالى :

var Class1 = function(a,b){
    this.a = a;
    this.b = b;
}
var Class2 = function(c,d){
    this.c = c;
    this.d = d;
}

var BridgeClass = function(a,b,c,d){
    this.one = new Class1(a,b);
    this.two = new Class2(c,d);
}

يعتبر أيضا ال priviledged method التى تستخدم للإستعلام عن المتغيرات الخاصه بمثابه bridge لأنها تربط ال public code بال private implementation كما يوضح المثال التالى :

function Public(){
    var secret = "Iam not the one";
    this.privilegedGetter = function(){
        return secret;
    }
}

var o = new Public;
o.privilgedGetter(); // Iam not the one

فوائد استخدام النمط Bridge :
يقلل من اعتماد الكائنات على بعضها ، و يقلل من اعتماد عمل الكائن على البيئه التى يعمل بها ، و يسهل ال debugging لأن كل وظيفه متخصصه فى عمل شىء واحد فقط بناء على مدخلات محدده ، و تغيير البيئه التى يعمل فيها الكود لن يؤثر على طريقه عمل الكائنات او الوظائف التى تدرج النمط bridge .

عيوب النمط Bridge :
كل Bridge يتم كتابته يزيد ب function جديده كما وضحت بالأمثله السابقه ، و قد يؤثر ذلك على كفاءه التطبيق ، و قد يزيد من تعقيد الكود ، لذلك لابد من حساب الفائده التى تعود من استخدامه و ضريبه استخدامه .

هذا الموضوع هو تلخيص الفصل الثامن من كتاب APRESS: Pro JavaScript Design Patterns مع حذف مثال connection queue لأنه 8 صفحات كود ، يمكنك قرائته من الكتاب أذا كنت تريد المزيد ، تم  إضافه رابط لسلسله انماط تصميم جافاسكربت بالعمود الجانبى للمدونه .

Interface فى الجافاسكربت

04/03/2009

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

ال Interface هو وسيله لتحديد الوظائف التى يجب ان يمتلكها الكائن ، لكنه لا يحدد كيف تم تنفيذ هذه الوظيفه ، هذا يمكنك من استخدام كائنات مختلفه بالتبادل لأداء نفس الوظيفه مادام لهم نفس ال Interface ، لنفرض مثلا ان هناك ريموت كنترول يقوم بفتح و قفل تلفيزيون من نوع معين ، اذا قمت بتوجيه هذا الريموت كنترول لتلفيزيون لا يماثل interface -وظائف- التليفزيون الاول فإن الريموت لن يعمل مع هذا التلفيزيون ، لكن اذا كان التلفيزيونين يحتويان على نفس ال interface التى يستخدمها الريموت او كان كل تلفيزيونات العالم كلها لها نفس ال intreface الذى يستخدمه الريموت فإن هذا الريموت سيفتح و يغلق التلفيزيونات كلها و سيكون highly reusable ، و لن نحتاج الى تصميم ريموت خاص لكل تلفيزيون، انظر الى المثال البسيط التالى :

function SonyTV(){};
SonyTV.prototype={
    open:function(){/*code*/},
    close:function(){/*code*/},
    /*rest of SonyTV functions*/
}

function SonyRemote(tv){
    this.tv = tv;
};
SonyRemote.prototype = {
    open:function(){
        this.tv.close();
    },
    close:function(){
        tv.close();
    },
    tuneChannel:function(){/*code*/},
    adjustColor:function(){/*code*/},
    adjustVolume:function(){/*code*/},
    mute:function(){/*code*/},
}

يمكننا استخدام SonyRemote من SonyTV مع العلم انه ليس من شأننا كيف يتم قفل او فتح او ميزه اخرى داخل التلفيزيون Implementation ، نحن نتعامل مع اسماء الوظائف فقط Interface كما يوضح المثال البسيط التالى :

var myTV = new SonyTV();
var myRemote = new SonyRemote(myTV);
myRemote.open();
myRemote.close();

هيا بنا ننشأ كائن جديد لتلفيزيون LG مع العلم ان الكود الخاص بكيفيه تنفيذ كل وظيفه ليس من شأننا ، لأننا سوف نتعامل مع Interface كما يوضح الكود البسيط التالى :

function LGTV(){};
LGTV.prototype = {
    openTV:function(){/*code*/},
    closeTV:function(){/*code*/},
    adjustChannel:function(){/*code*/},
    tuneColors:function(){/*code*/},
    setVolume:function(){/*code*/}
}

سنختبر هل يعمل SonyRemote مع تلفيزيون LG كما يوضح الكود البسيط التالى :

var myTV = new LGTV();
var myRemote = new SonyRemote(myTV);
myRemote.open(); // Error
myRemote.close(); // Error

اسماء الوظائف التى يستخدمها تلفيزيون LG تختلف تماما عن الوظائف التى يستخدمها تلفيزيون SONY و يتعامل معها الريموت ، هذا الريموت لا يهمه كيف يتم تشغيل او اطفائه – هذا شغله مهنسدين ال hardware- لكن مايهمه انه سيجد الوظيفه المناسبه بالإسم المناسب حتى يستدعيها ، اذا نظرت للكود الخاص بالريموت ستجد ان ال constructor تبعه يقبل instance من التلفيزيون الذى سيتعامل معه .

يمكن عمل Interface بين الكائنات بمنتهى السهوله فى اللغات مثل JAVA و C# و PHP و غيرهم ، و الكود التالى يوضح كيفيه عمل Interface فى لغه PHP :

interface MyInterface {
    public function interfaceMethod($argumentOne, $argumentTwo);
}

هذا الكود يقوم بعمل Interface بإسم MyInterface ، اى class سيقوم بادراج هذا ال Interface لابد من ان يدرج الوظيفه interfaceMethod التى تقبل عبارتين argumentOne و argumentTwo ، الكود التالى يوضح كيفيه ادرج هذا ال Interface فى صنفين الاول يوافقه MyClass و التانى BadClass يخالفه :

class MyClass implements MyInterface {
    public function interfaceMethod($argumentOne, $argumentTwo){
        return $argumentOne . $argumentTwo;
    }
}

لا توجد مشكله فى هذا الصنف لأنه يدرج الوظيفه interfaceMethod التى صرح بها MyInterface ، لكن الصنف التالى BadClass ستنشأ عنه مشاكل :

class BadClass implements MyInterface {
    // No Methods
}

سينتج عن هذا الكود error فى وقت التشغيل runtime لأن المترجم يفصح اى صنف يقوم بأنه يدرج MyInterface عن طريق كتابه implements MyInterface فى تعريف الكلاس كما يوضح الكود السابق ، رساله الخطأ من الكود السابق كما يلى :

Fatal error: Class BadClass contains 1 abstract method and must therefore be declared (MyInterface::interfaceMethod)

اى صنف يقوم بادراج ال MyInterface بطريقه صحيحه ، يمكن استخدامه بالتبادل مكان الكائن الحالى الذى يدعم MyInterface .

فى جافاسكربت لا توجد خاصيه فى اللغه تقوم بعمل Intreface ولا يقوم المترجم بفحص هل الكائن صادق فى كونه يدعم هذا ال interface ام لا ، لكن لكون لغه الجافاسكربت مرنه جدا ، يمكننا تقليد عمل ال Interface و استغلال امكانياته من اجل كتابه API مرن و قابل لاعاده الاستعمال .

يمكننا تقليد عمل ال Intreface عن طريق Duck typing هذه التسميه جاءت من المقوله :

If it walks like a duck and quacks like a duck, it's a duck

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

function Interface(name,methods){ // Interface function constructor
    this.name=name;
    this.methods=methods;
}

// public static method
Interface.ensureImplements = function(object,interface){
    for(var i=0, len=interface.methods.length; i<len; i++){
        var method = interface.methods[i];
        if(!object[method] || typeof object[method] != 'function'){
            throw new Error ("The Interface "+interface.name+ " method >"+ method+
            "is not implemented");
        }
    }
}

يمكننا استخدام هذا ال Interface constructor كما يلى :

var remoteInterface = new Interface("remoteInterface",['open','close']);

function GlobalRemote(tv){
Interface.ensureImplements(tv,remoteInterface);
this.tv = tv;
}
GlobalRemote.prototype= {
    open:function(){
    this.tv.open();
    },
    close:function(){
    this.tv.close();
    }
}

قمت بعمل inerface بإسم remoteInterface يحتوى على وظيفتين open و close ، و داخل ال GlobalRemote constructor قمت بفحص هل ال tv الذى يمرر لها يطابق ال remoteInterface ام لا ، لو طابق ال interface سيعمل الكود طبيعى ، اما اذا لم يطابق سيتم ارسال error يقول انه هناك وظيفه لم يتم ادراجها من هذا ال interface ، و لنفرض ان تلفيزيون LG و SONY جميعهم ادرجوا ال interface الذى بإسم globalInterface فإن الريموت سيعمل معهم جميعا ميعادا تلفيزيون Toshiba الذى لم يدرجه كما يوضح الكود التالى :

function SonyTV(){};
SonyTV.prototype = {
    open:function(){},
    close:function(){},
}

function LGTV(){};
LGTV.prototype= {
    open:function(){},
    close:function(){},
}

function ToshibaTV(){};
ToshibaTV.prototype = {
    openTV:function(){},
    closeTV:function(){},
}

var myRemote = new GlobalRemote(new SonyTV);
myRemote.open();
myRemote.close();

var myRemote = new GlobalRemote(new LGTV)
myRemote.open();
myRemote.close();

var myRemote = new GlobalRemote(new ToshibaTV);
myRemote.open(); // Error : The Interface remoteInterface 
method > open is not implemented

ال Interface مهم للغايه فى بعض ال design patterns مثل Factory و Composite و Decorator و Command ، كما انه لا بد منه عند العمل على مشاريع كبيره يشترك فيها اكثر من مبرمج حيث ستكون وسيلها التواصل بينهم هى ال Interface ، مادام هناك كائنات تدرج نفس ال Interface اذا لا يوجد مشكله من استخدام اى منها ، و اذا تطور ال api الذى تكتبه فإنه لا يوجد مشكله فى عمله على الانظمه القديمه مادام يدرج نفس ال Interface الموجود فى الانظمه القديمه ، انا قد قمت بمسح بعض الكود الخاص ب type checking فى Interface و Interface.ensureImpelemnts للتسهيل ،  يمكنك ايضا عمل وظيفه ensureImpelemnts تقبل اكثر من interface لتفحص هلى الكائن يطابقهم ام لا ، اذا كان ال interface لا يضيف قيمه للكود التى تكتبه إذا فلا تضيفه لأنه سيزيد من تعقيد الكود نسبيا و سيصعب على المبرمجين المبتدئين معرفه ماهيه ال interface ،هذا الموضوع هو الفصل الثانى من كتاب APRESS: Pro JavaScript design patterns الموجود فى العمود الجانبى فى المدونه .

Mixin class فى الجافاسكربت

28/02/2009

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

هناك وسيله اخرى لإعاده إستخدام الكود بدون إستخدام الوراثه ، لأن هناك بعض الحالات تريد إعاده إستخدام وظيفه بعينها ، لاتريد إعاده إستخدام كل الوظائف و الخصائص من صنف لصنف اخر ، و عليه يمكن إستخدام وظيفه من صنف واحد فى عده اصناف اخرى عن طريق augmentation ، فى الواقع يمكنك عمل صنف يحتوى على وظائف ذات اغراض عامه general purposes methods ثم استخدام ما تريده من هذه الوظائف فى الاصناف الاخرى بدون إستخدام الوراثه و هذا الصنف الذى يحتوى على الوظائف العامه يسمى Mixin class ، انظر إلى ال Mixin class البسط التالى :

var Mixin = function(){};
Mixin.prototype = {
    serialize:function(){
        var output = [];
        for(key in this){
            output.push(key+':'+this[key]);
        }
        return output.join(', ');
    }
}

هذا ال Mixin يحتوى على وظيفه واحده serialize يمكننا استخدامها مع اى صنف اخر بدون إستخدام الوراثه inheritznce ، كما يوضح المثال البسيط التالى :

function User(name,age){
    this.name = name;
    this.age=age;
}
User.prototype = {
    getName:function(){
        return this.name;
    }
}

يمكننا استخدام الوظيفه serialize من الصنف Mixin بدون إستخدام الوراثه عن طريق إضافتها إلى ال User.prototype كما يوضح الكود البسيط التالى :

User.prototype.serialize = Mixin.prototype.serialize;

و يمكننا استخدامها كما يوض الكود البسيط التالى :

var me = new User("Mostafa Farghaly",21);
me.serialze(); // "name:Mostafa Farghaly, age:21, getName: Mostafa Farghaly"

يمكننا عمل وظيفه بإسم augment حتى تسهل علينا اضافه وظائف من اى صنف إلى صنف اخر كما يوضح الكود التالى :

function augment(recievingClass, givingClass){
    for(methodName in givingClass){
        if(! recievingClass.prototype[methodName]){
            recievingClass.prototype[methodName] = givingClass.prototype[methodName];
        }
    }
}

الوظيفه تقوم بإستخدام for … in حتى تنقل اى وظيفه او خاصيه من givingClass.prototype إلى recievingClass.prototype فقط بعد التأكد أنها غير موجوده فى recievingClass.prototype . يمكننا استخدامها كما يوضح المثال البسيط التاى :

augment(User,Mixin);

لكن عيب هذه الوظيفه أنها تنقل كل الخصائص و الوظائف من Mixin إلى User ، ربما فى هذا المثال Mixin لا يحتوى إلا على وظيفه واحده serialize و لكن إذا كان يحتوى على اكثر من وظيفه قد لا نحتاجها ، لذلك يمكننا اعاده كتابه الوظيفه augment حتى تنقل وظائف معينه من Mixin إلى User أو نقل الوظائف كلها ، كما يوضح الكود البسيط التالى :

function augment(recievingClass,givingClass){
    if(argument[2]){ // only give certain methods
        for(var i=2, len=arguments.length; i<len; i+=1){
            var methodName = arguments[i];
            recievingClass.prototype[methodName] = givingClass.prototype[methodName];
        }
    } else { // augment all methods
        for(methodName in recievingClass){
            if(! recievingClass.prototype[methodName]){
                recievingClass.prototype[methodName] = givingClass.prototype[methodName];
            }
        }
    }
}

تم زياده كود لفحص هل يوجد عباره ثالثه ام لا ، و هى التى تمثل الوظيفه التى تريد اضافتها بعينها من Mixin إلى User ، او ربما تكون اكثر من وظيفه و لذلك قمنا بعمل loop بعد العباره التانيه var i=2 و يمكننا استخدام النسخه المعدله من augment كالتالى :

augment(User,Mixin,'serialize');

كما ذكرت اهميه Mixin class عندما يكون هناك صنف عام الاغراض ، لأنه ليس من المنطقى ان يرث User من Mixin ، انما من الطبيعى ان يرث User من Person ، او ان يرث Dog من Animal .

بالطبع يمكنك اضافه الوظيفه augment إلى الصنف Function حتى ترثه كل الوظائف و يستخدم كالتالى :

User.augment(Mixin,'serialize');

Follow

Get every new post delivered to your Inbox.