Composite design pattern


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

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

نمط التصميم 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- ، و بما انى فاشل دراسيا فيارب ابقى منهم .

الأوسمة: , , ,

5 تعليقات to “Composite design pattern”

  1. almhajer Says:

    كلمة الشكر لاتنفع ولكنا مأمورون بها الله يجازيك ويثيبك
    ثانيا تمتاز هذه الخطوات بدراسة هيكلية البني المرجعية للكود ومفهمو العلاقات الاساسي
    لكن تم تكرار اجزاء من الكود وخاصة ي جزئي الانشاء وهو بذلك لايتبع لمفهوم الالتصاق والمزاوجة
    CompositeForm و ٍSelectField و TextareaField و InputField
    وهذا ادى الى تكبير الكود بشكل كامل ولو انه تم اختصار بدالة create اساسسي مبينة على الصنف وهي تقوم بانشاء كائن بغض النظر عن نوعه مع مراعاة الخصائص
    حتى يتم استخدام العناصر ثم تضمين هذه الخاصية عن البناء جزء من مفهوم لاتكرر بشكل عام
    حسب نظريتي بجب اعادة هيكلية النصف من الفئة القاعدية
    base class
    يعني
    baseclass
    child->1->الانشاء return handel
    2-بناء الخصائص وتعتمد على array||object
    return void
    3- الربط link الكائنات مع الفئة الاساية وهنا الفورم
    4- الحصول على الخصائص )return (string
    6- حذف عنصر return void
    7- عرض الخرج النهائي بدالة build
    بذلك تكون ضمن ان الكود لايكرر وانه يمكن اتخدامه في اي كود اخر وهو يتركز على مفهوم عدم الالتصاق بالفشة الاساسية
    شكرا جزيلا وبارك الله فيك

  2. mostafa farghaly Says:

    لا يوجد تكرار فى الكود ، و قد ظهر ميزه الوراثه عندما اضفنا ميزه جديده للحقول و هى restore ، قمت بإضافه هذه الوظيفه للصنف Field فقط ، و لكن فكرتك فى عمل create ممتازه ، و ياحبذا لو كان يتبع نمط التصميم Factory لبناء الكائنات ، تحليل ممتاز يا مهاجر ، بارك الله فيك ، و لكنى التزمت هنا بالمثال الموجود بالكتاب ، يمكنك عمل قائمه Menu تمثل الكائن ال Composite و بداخلها عناصر القائمه MenuItem تمثل ال Leaf و بما ان بينهما علاقه HAS-A ، يمكن ان تتبع نفس اللى فعلناه بالدرس أو تستخدم فكرتك فى عمل factory ، المهم فى استخدام ال Composite ان يكون هناك علاقه بين الكائنات و الكائن الذى يحتويها ، و ان يكون هناك عمليه لابد من اجرائها على كل الكائنات او جزء منها .

  3. almhajer Says:

    صحيح كما ذكرت اخي انتا بالمقال اعتمدت الشرح
    مشان يبان الفكرة
    من الهندسة وبنية العمل بارك الله فيك

  4. PHP0 Says:

    ما شاء الله يغطيك العافية

  5. http://www.tuladokris.com Says:

    jeux de beaute pour fille gratuit jeux de balles gratuit

أضف تعليقاً

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

WordPress.com Logo

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

صورة تويتر

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

Facebook photo

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

Google+ photo

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

Connecting to %s


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