ما هو عدد دوال البناء اذا أردت عمل overloading لأكثر من 4 معاملات بترتيب مختلف؟
هذه المرة الثانية نشن هجوماً على دالة البناء constructor، في مقالنا الأول (هل مللت من دالة البناء ) فضلنا دالة static factory method على ال constructor بسبب مقروئيتها وامكانية دعمها للCaching لو أردت في وقت لاحق.
عندما تريد انشاء كائن من كلاس به متغيرات ضرورية وقت الانشاء وبها اختياريه فسوف نقول أيضاً: لا لل constructors ولا أيضاً لل static factory methods.
للتوضيح: كما نعلم أنه لا يمكن انشاء كائن بدون استدعاء دالة البناء بشكل او أخر، فعندما نقول لا لدالة البناء فنحن نقصد لا بجعلها متاحة للكلاينت public constructor ولكن نريد اخفائها private constructor عن الكلاينت، في مقالة هل مللت من دالة البناء قمنا بجعل دالة البناء private وقمنا باستدعائها من دالة public static في نفس الكلاس، بينما هنا في موضوعنا هذه المقالة سوف نخفى دالة البناء private constructor ونقوم بانشاء الكائن من خلال inner class في نفس الكلاس، لماذا؟ اكمل القرائة الآن.
هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا
- الإسلوب الصحيح لكتابه Utility Classes
- هل مللت من دالة البناء Constructor ؟
- استخدام ال Builder Pattern في دالة البناء (المقال الحالي)
- لا تستخدم Public الا وقت الحاجة
- كيف تصمم الPackages جيداً في تطبيقات جافا
- جافا دائماً Passing by Value
- نظرة حول الدالة Equals
- خارطة طريق لتعلم الجافا
Telescoping Constructor
دوال البناء العادية constructor ودوال الstatic factory قد لا يستطيعوا تقديم الحل الأفضل في حالة كان لديك العديد من المعاملات المرسلة لدالة البناء وخصوصاً لو كانت هناك معاملات اختيارية ،، على سبيل المثال لو أردنا عمل كلاس يمثل بيتزا يقوم العميل بتخصيص طلبها على حسب ما يريد ، ففي البدايه عليه باختيار الحجم ثم يحدد ما يريده من الخيارات المتاحة هي جبنة وهوت دوق وببروني ،،
بطريقة دوال البناء العادية سوف يخرج لدينا كلاس بهذا الشكل:
// Pizza construction public class Telescoping { public static void main(String[] args) { PizzaTelescoping pizza = new PizzaTelescoping(3, false, false, true); } } class PizzaTelescoping { // required private int size; // optional private boolean cheese; private boolean hotdog; private boolean peppernoi; public PizzaTelescoping (int size) { this(size, false); } public PizzaTelescoping (int size, boolean cheese) { this(size, cheese, false); } public PizzaTelescoping(int size, boolean cheese, boolean hotdog) { this(size, cheese, hotdog, false); } public PizzaTelescoping(int size, boolean cheese, boolean hotdog, boolean peppernoi) { this.size = size; this.cheese = cheese; this.hotdog = hotdog ; this.peppernoi = peppernoi; } }
الحل أعلاه (وهو يسمى Telescoping Constructor) قد يكون مقبول الأن ، لكن ماذا لو كانت عدد الخيارات هي 20 مثلاً؟ هل ستقوم بكتابة الكم الهائل من دوال البناء هذه .. ناهيك عن صعوبة قرائه الكود الذي ينشئ الكلاس.. ناهيك عن الأخطاء الناتجة من تمرير هذا الكم الهائل من المعاملات للدالة، فقد يخطئ ممرر القيم ويمرر true للهوت دوق وfalse للجنبه بينما المستخدم يريد العكس (بكل تأكيد المترجم سعيد ولن يصدر رسالة خطأ لهذا الخطأ)، وكل ذلك بسبب صعوبة تذكر هذا الكم من المعاملات.. بنفس الأمر ال static factory لن ينفع في هذه الحالة لأنك ستحتاج لعمل overloads لهذا العدد من الدوال..
جافا بينز Java Beans
الحل الثاني الممكن هو استخدام اسلوب جافا بينز Java Beans وذلك عن طريق انشاء الكائن فارغ أولاً، ثم وضع القيم التي يريدها المبرمج، المثال التالي يوضح ذلك:
// Pizza Construction using Java Beans Style public class PizzaJavaBeans { public static void main(String[] args){ PizzaBeans pizza = new PizzaBeans(); pizza.setSize(10); pizza.setCheese(true); } } class PizzaBeans { // required private int size; // optional private boolean cheese; private boolean hotdog; private boolean peppernoi; public PizzaBeans() { } public void setSize(int size) { this.size = size; } public void setCheese(boolean cheese) { this.cheese = cheese; } public void sethotdog(boolean hotdog) { this.hotdog = hotdog; } public void setPeppernoi(boolean pep) { this.peppernoi = pep; } }
هذا الحل تلافي مشكلة دوال البناء Telescoping Constructor ، فهو سهل القرائة وواضح جداً.. لكن هناك مشكلتين متعلقات بJava Beans Pattern أولها أن الكائن بعد انشائه سوف يكون في حالة غير مستقره حيث أنه لا يستطيع وقت انشاء الكائن التحقق من القيم المدخلة فيه، وبالتالي من الممكن ان ينشىء الكائن وبعد ذلك تمرر له قيم خاطئة ويكون في حالة غير سليمة inconsistent state. المشكلة الثانية هي أن الكلاس بعد استخدام هذا الأسلوب لا يمكن أن يكون كلاس Immutable ويتطلب جهداً من المبرمج للتأكد من أن الكلاس مؤمن من مشاكل الThreads (بمعنى Thread-Safety).
Builder Pattern
الحل الصحيح هو بجمع الأمان والتحقق الذي تحققه دوال البناء و مقروئية الJava Beans Pattern وذلك باستخدام الBuilder Pattern. وفكرتها هي بدلاً من انشاء الكائن مباشرة، سوف تقوم باستدعاء دالة بناء (أو دالة static factory -تذكرها أولاً قبل دالة البناء!-) بالقيم الأساسية المطلوبة وستحصل على كائن Builder وبعد ذلك سوف تستدعي دوال داخل هذا الكائن يمكنك أن تقول بأنها setter method للقيم الاختياريه وأخيراً تقوم باستدعاء الدالة build الموجودة داخل الbuilder لكي ترجع لك الكائن (والذي يكون Immutable) . المثال:
// Pizza Construction using Builder Pattern public class PizzaBuilderPattern { public static void main(String[] args) { Pizza pizza = new Pizza.Builder(10).cheese(true).peppernoi(true).build(); } } class Pizza { // required private int size; // optionals private boolean cheese; private boolean hotdog; private boolean peppernoi; public static class Builder { private int size; private boolean cheese; private boolean hotdog; private boolean peppernoi; public Builder(int size) { this.size = size; } public Builder cheese (boolean cheese) { this.cheese = cheese; return this; } public Builder hotdog (boolean hotdog) { this.hotdog = hotdog; return this; } public Builder peppernoi (boolean pep) { this.peppernoi = pep; return this; } public Pizza build () { return new Pizza(this); } } private Pizza (Builder builder) { this.size = builder.size; this.cheese = builder.cheese; this.hotdog = builder.hotdog; this.peppernoi = builder.peppernoi; } }
انظر لكيفية تكوين الكائن:
Pizza pizza = new Pizza.Builder(10).cheese(true).peppernoi(true).build();
وهكذا حققنا المقروئية والسهوله ويتم تحقيق الأمان والتحقق عن طريق فحص المتغيرات في دالة البناء Pizza ومن ثم عمل throw لException اذا وجد هناك خطأ وفي داخل داله الbuild يتم عمل catch اذا وجد هذا الexception.
مثال أخر لتوضيح الفرق لبناء كائن بطريقة telescoping و builder pattern من كلاس يحتوي على ثلاث متغيرات :
// if no parameters are required and all are optional Box box0 = new Box.Builder().build(); Box box1 = new Box.Builder().length(1).build(); Box box2 = new Box.Builder().length(1).width(2).build(); Box box3 = new Box.Builder().length(1).width(2).height(3).build(); // if length is required and others are optional - that's your Box Box box1 = new Box.Builder(1).build(); Box box2 = new Box.Builder(1).width(2).build(); Box box3 = new Box.Builder(1).width(2).height(3).build(); // For comparison - your constructors. It's less obvious what is length, // width or height Box box1 = new Box(1); Box box2 = new Box(1,2); Box box3 = new Box(1,2,3);
من أحدى المأخذ وهي الأداء حيث لبناء الكائن سوف نحتاج لبناء كائن من نوع Builder أولاً وان كان ذلك طفيفاً، لذلك لا ينظر اليه الا في حالات قليله.
في العالم الحقيقي أستخدم الBuilder Patterns كثيراً جداً فهو اسهل واكثر مقروئية وأفضل للكلاينت، والآن نسخه Netbeans 7.2 أصبحت تضع خيار Replace Constructor with Builder من ضمن قائمة ال Refactoring، ولو ألقيت نظرة على مكتبات الجافا الحديثة مثلاً Guava ستجد الكثير جداً من ال Builder Pattern والتي تفضل على دوال البناء العادية..
لذلك اذا كان لديك مثل هذه الكومة وحتى ان لم يكن لديك overloading :
public class Car { private String manufacturer; private String model; private int horsePower; private int numOfSeats; private Color color; private String cylinder; private double length; private double width; private double price; public Car (String manufacturer, String model, int horsePower , int numOfSeats, Color color, String cylinder, double length , double width, double price) { this.manufacturer = manufacturer; this.model = model; this.horsePower = horsePower; this.numOfSeats = numOfSeats; this.color = color; this.cylinder = cylinder; this.length = length; this.width = width; this.price = price; } }
فقم بتحويلها الى Builder Pattern واستمتع بال New Look والمزايا الجديدة لكلاسك.
خلاصة
يفضل استخدام الBuilder Pattern عن تصميمك لكلاس يحتوي على دالة بناء أو static factory تستقبل أكثر من 4 متغيرات وما فوق وخصوصاً لو كانت هذه المتغيرات اختياريه، سوف تكسب المقروئية من الكود أكثر من Telescoping Constructor وأكثر أماناً من استخدام Java Beans Pattern.
تم تحديث المقالة واضافه التوضيح ، شكر للأخ محمد حسام :).
بسم الله الرحمن الرحيم
جزاكم الله كل خير -تم اضافة المقالة للكتاب وتنسيقها بفضل الله تعالى -اكمل اخى الفاضل متابعيين بأذن الله تعالى
هل تقصد استبدال كل constructor parameter بدالة setValue للقيمة المقصودة به؟
هذه طريقة Java Beans تقوم بعمل كلاس بدالة بناء لا تقوم باستقبال اي معامل ومن ثم تستخدم ال setters لاعطاء القيم، ولقد فضلناها بطريقة ال builder pattern ،
الفكرة هنا أننا سوف ننشىء الكلاس ولكن من خلال الكلاس الداخلي Inner Class (والذي سنقوم بانشائه من خلال دالة بناء عادية أو دالة static factory ) وسنمرر له القيم المطلوبة :
بعد ذلك سنستخدم الbuilder ونقوم بوضع القيم التي نريدها :
ومن ثم ابني الكائن باستخدام الدالة build والتي سترجع الأن الكائن أخيراً:
هكذا اعتمدنا على الbuilder لانشاء الكائن وحصلنا عليه ،، الميزة هي ان الكلاس لا يوجد فيه setter وبالتالي حصلنا على Immutable class (كلاس لا تتغير قيمته بعد الانشاء) وهكذا حصلنا على نفس اداء دالة البناء العادية وسهوله طريقة ال setter .
تحياتي،،
بارك الله فيك
رحم الله والديك في الدنيا والآخرة ،
مقالتان عن الباني حلت الكثير من الألغاز التي كنت أراها في أكواد الجافا
وفقك الله يا أخ وجدي .
الحل :
السلام عليكم أخي وجدي ربنا يبارك فيك ويجزيك عنا خير
لدي سؤالان:
هل الBuilder Pattern يتوافق مع مفهوم الـMVC ؟
كيف يمكن بطريق الـBuilder Pattern تغيير حجم البيتزا في وقت لاحق؟
اهلأً اخي
بالنسبة للتوافق فنعم يمكن استخدام النمطين مع بعض، حيث ان ال MVC هي طريقة لتقسيم المشروع Architectural Pattern، بينما ال Builder Pattern هي طريقة لانشاء الكائن Construction Pattern.. مثلاً كان ال Model لديك فيه كائن يحتاج لقيم الزامية وقيم ليست كذلك ، فيمكنك استخدام ال Builder لذلك الكائن وتبنيه من ال Controller بشكل اوضح من دالة البناء الكبيرة.
بالنسبة للتغيير اي Attribute في الكائن، فتستطيع ذلك من خلال اي Setter ، حيث ان ال Builder تبني لك الكائن فقط وتعيده لك، لكن تستطيع تغيير اي قيمه فيه متى ما شئت الا لو كائن الكائن هو Immutable ففي تلك الحالة تحتاج لبناء كائن بالقيم القديمة مع التحديث الذي تريده.
شكراً لك.