إذا كنت مطلوبًا الآن لتحسين رمز Java الذي تكتبه ، فماذا ستفعل؟ في هذه المقالة ، يقدم المؤلف أربع طرق يمكنها تحسين أداء النظام وقابلية قراءة الكود. إذا كنت مهتمًا بهذا ، فلنلقي نظرة.
مهام البرمجة المعتادة لدينا ليست أكثر من تطبيق نفس الجناح التقني على مشاريع مختلفة. في معظم الحالات ، يمكن لهذه التقنيات تحقيق الأهداف. ومع ذلك ، قد تتطلب بعض المشاريع تقنيات خاصة ، لذلك يتعين على المهندسين أن يدرسوا بعمق لإيجاد أسهل الأساليب ولكن الأكثر فاعلية. في مقال سابق ، ناقشنا أربع تقنيات خاصة يمكن استخدامها عند الضرورة لإنشاء برنامج جافا أفضل ؛ بينما سنقدم في هذه المقالة بعض استراتيجيات التصميم الشائعة وتقنيات تنفيذ الأهداف التي تساعد في حل المشكلات الشائعة ، وهي:
التحسين الهادف فقط
استخدم التعداد قدر الإمكان للثوابت
إعادة تعريف طريقة متساوية () في الفصل
استخدم أكبر عدد ممكن من الأشكال
تجدر الإشارة إلى أن التقنيات الموضحة في هذه المقالة لا تنطبق على جميع الحالات. بالإضافة إلى ذلك ، متى وأين يجب استخدام هذه التقنيات ، فإنها تتطلب من المستخدمين التفكير بعناية.
1. فقط القيام بالتحسين الهادف
يجب أن تكون أنظمة البرمجيات الكبيرة قلقة للغاية بشأن مشكلات الأداء. على الرغم من أننا نأمل أن نكون قادرين على كتابة الكود الأكثر كفاءة ، في كثير من الأحيان ، إذا كنا نريد تحسين الرمز ، ليس لدينا أي فكرة عن كيفية البدء. على سبيل المثال ، هل سيؤثر الكود التالي على الأداء؟
Public Void ProcessIngers (قائمة <integer> الأعداد الصحيحة) {for (قيمة عدد صحيح: الأعداد الصحيحة) {for (int i = imentegers.size ()-1 ؛ i> = 0 ؛ i--) {value += imentegers.get (i) ؛ }}}ذلك يعتمد على الموقف. في الكود أعلاه ، يمكننا أن نرى أن خوارزمية المعالجة الخاصة بها هي O (N³) (باستخدام رموز O الكبيرة) ، حيث N هو حجم مجموعة القائمة. إذا كان N 5 فقط ، فلن تكون هناك مشكلة ، فسيتم تنفيذ 25 تكرارًا فقط. ولكن إذا كان N 100000 ، فقد يؤثر ذلك على الأداء. يرجى ملاحظة أنه على الرغم من ذلك ، لا يمكننا تحديد أنه ستكون هناك مشاكل. على الرغم من أن هذه الطريقة تتطلب مليار تكرار منطقي ، ما إذا كان سيكون لها تأثير على الأداء ، لا يزال يتعين مناقشته.
على سبيل المثال ، لنفترض أن العميل ينفذ هذا الرمز في مؤشر ترابطه الخاص وينتظر بشكل غير متزامن لإكمال الحساب ، ثم قد يكون وقت التنفيذ مقبولًا. وبالمثل ، إذا تم نشر النظام في بيئة إنتاج ولكن لا يوجد عميل يدعوها ، فلا داعي لنا لتحسين هذا الرمز ، لأنه لن يستهلك الأداء العام للنظام على الإطلاق. في الواقع ، سيصبح النظام أكثر تعقيدًا بعد تحسين الأداء ، لكن الشيء المأساوي هو أن أداء النظام لا يتحسن نتيجة لذلك.
الشيء الأكثر أهمية هو أنه لا يوجد غداء مجاني في العالم ، لذلك من أجل تقليل التكلفة ، عادة ما نستخدم التقنيات مثل ذاكرة التخزين المؤقت أو توسيع الحلقة أو القيم المحسوبة مسبقًا لتحقيق التحسين ، مما يزيد بدوره من تعقيد النظام ويقلل من قابلية قراءة الرمز. إذا كان هذا التحسين يمكن أن يحسن أداء النظام ، فإنه يستحق ذلك حتى لو أصبح معقدًا ، ولكن قبل اتخاذ قرار ، يجب أولاً معرفة هاتين المعلومات:
ما هي متطلبات الأداء
أين عنق الزجاجة الأداء
بادئ ذي بدء ، نحن بحاجة إلى معرفة بوضوح ما هي متطلبات الأداء. إذا كان ذلك في النهاية ضمن المتطلبات ولم يثير المستخدم النهائي أي اعتراضات ، فلا داعي لإجراء تحسين الأداء. ومع ذلك ، عند إضافة وظائف جديدة أو يصل حجم بيانات النظام إلى مقياس معين ، يجب تحسينه ، وإلا فقد تنشأ المشكلات.
في هذه الحالة ، لا ينبغي أن يعتمد على الحدس أو التفتيش. لأنه حتى المطورين ذوي الخبرة مثل Martin Fowler هم عرضة لبعض التحسينات الخاطئة ، كما هو موضح في مقالة إعادة إنشاء (صفحة 70):
إذا قمت بتحليل ما يكفي من البرامج ، فستجد الشيء المثير للاهتمام حول الأداء الذي يضيع معظم وقتك في جزء صغير من الكود في النظام. إذا تم تحسين جميع الرموز كما هي ، فإن النتيجة النهائية هي أن 90 ٪ من التحسين يضيع ، لأن الرمز بعد التحسين لا يعمل كثيرًا. الوقت الذي يقضيه في التحسين بدون أهداف هو مضيعة للوقت.
بصفتنا مطورًا مصقولًا للمعركة ، يجب أن نأخذ هذا الرأي على محمل الجد. التخمين الأول ليس فقط أن أداء النظام لم يتم تحسينه ، ولكن 90 ٪ من وقت التطوير ضائع تمامًا. بدلاً من ذلك ، يجب علينا تنفيذ حالات الاستخدام الشائعة في الإنتاج (أو ما قبل الإنتاج) ومعرفة أي جزء من النظام يستهلك موارد النظام أثناء التنفيذ ، ثم تكوين النظام. على سبيل المثال ، 10 ٪ فقط من الكود الذي يستهلك معظم الموارد ، ثم تحسين 90 ٪ المتبقية من الرمز هو مضيعة للوقت.
وفقًا لنتائج التحليل ، إذا أردنا استخدام هذه المعرفة ، فيجب أن نبدأ بالحالات الأكثر شيوعًا. لأن هذا سيضمن أن الجهد الفعلي سيؤدي في النهاية إلى تحسين أداء النظام. بعد كل تحسين ، يجب تكرار خطوات التحليل. نظرًا لأن هذا لا يضمن فقط تحسين أداء النظام ، بل يمكن أيضًا رؤية أي جزء من عنق الزجاجة الأداء هو بعد تحسين النظام (لأنه بعد حل عنق الزجاجة ، قد تستهلك الاختناقات الأخرى المزيد من الموارد الإجمالية للنظام). تجدر الإشارة إلى أن النسبة المئوية للوقت الذي تقضيه في الاختناقات الحالية من المرجح أن تزيد ، حيث أن الاختناقات المتبقية لم تتغير مؤقتًا ، ويجب تقليل وقت التنفيذ الإجمالي عند إزالة عنق الزجاجة المستهدف.
على الرغم من أن الأمر يتطلب الكثير من السعة للتحقق بالكامل من ملفات التعريف في أنظمة Java ، إلا أن هناك بعض الأدوات الشائعة جدًا التي يمكن أن تساعد في اكتشاف النقاط الساخنة للأداء ، بما في ذلك JMeter و AppDynamics و Yourkit. بالإضافة إلى ذلك ، يمكنك أيضًا الرجوع إلى دليل مراقبة أداء DZONE لمزيد من المعلومات حول تحسين أداء برنامج Java.
على الرغم من أن الأداء مكون مهم للغاية في العديد من أنظمة البرمجيات الكبيرة وهو جزء من مجموعة الاختبار الآلية في خط أنابيب تسليم المنتج ، إلا أنه لا يمكن تحسينه بشكل أعمى ودون غرض. بدلاً من ذلك ، يجب إجراء تحسينات محددة على اختناقات الأداء التي تم إتقانها. هذا لا يساعدنا فقط على تجنب زيادة تعقيد النظام ، ولكنه يسمح لنا أيضًا بتجنب التحويلات وتجنب القيام بالتحسينات الزمنية.
2. حاول استخدام التعدادات للثوابت
هناك العديد من السيناريوهات التي يحتاج المستخدمون فيها إلى سرد مجموعة من القيم المحددة أو المستمرة ، مثل رموز استجابة HTTP التي يمكن مواجهتها في تطبيقات الويب. واحدة من أكثر تقنيات التنفيذ شيوعًا هي إنشاء فئة جديدة ، والتي تحتوي على العديد من قيم النوع النهائي الثابت. يجب أن يكون لكل قيمة تعليق يصف معنى القيمة:
الطبقة العامة httpresponsecodes {public static final int ok = 200 ؛ int static int int_found = 404 ؛ int static int forbidden = 403 ؛} if (gethttpresponse ().من الجيد جدًا أن يكون لديك هذه الفكرة ، ولكن لا تزال هناك بعض العيوب:
لا يوجد التحقق الصارم لقيم عدد صحيح واردة
نظرًا لأنه نوع بيانات أساسي ، لا يمكن استدعاء الطريقة في رمز الحالة
في الحالة الأولى ، يتم إنشاء ثابت محدد ببساطة لتمثيل قيمة عدد صحيح خاص ، ولكن لا يوجد أي قيود على الطريقة أو المتغير ، لذلك قد تكون القيمة المستخدمة خارج نطاق التعريف. على سبيل المثال:
الفئة العامة httpresponseHandler {public static void printmessage (int statusCode) {system.out.println ("الحالة المستردة" + statusCode) ؛ }} httpresponsehandler.printmessage (15000) ؛على الرغم من أن 15000 ليست رمز استجابة HTTP صالح ، لا يوجد أي قيود على جانب الخادم يجب على العميل تقديم أعداد صحيحة صالحة. في الحالة الثانية ، ليس لدينا طريقة لتحديد طريقة رمز الحالة. على سبيل المثال ، إذا كنت ترغب في التحقق مما إذا كان رمز الحالة معين هو رمز ناجح ، فيجب عليك تحديد وظيفة منفصلة:
الطبقة العامة httpresponsecodes {public static final int ok = 200 ؛ int static int int_found = 404 ؛ عام ثابت عام محظور = 403 ؛ issuccess boolean الثابتة العامة (int statuscode) {return statusCode> = 200 && statusCode <300 ؛ }} if (httpresponsecodes.issuccess (gethttpresponse ().لحل هذه المشكلات ، نحتاج إلى تغيير النوع الثابت من نوع البيانات الأساسية إلى نوع مخصص والسماح فقط كائنات محددة من الفئة المخصصة. هذا هو بالضبط ما هي جافا التعداد ل. باستخدام التعداد ، يمكننا حل هاتين المشكلتين في وقت واحد:
التعداد العام httpresponsecodes {OK (200) ، Forbidden (403) ، not_found (404) ؛ رمز int النهائي الخاص ؛ httpresponsecodes (int code) {this.code = code ؛ } public int getCode () {return code ؛ ) }} if (gethttpresponse ().وبالمثل ، أصبح من الممكن الآن مطالبة رمز الحالة الذي يجب أن يكون صالحًا عند استدعاء الطريقة:
الفئة العامة httpresponsehandler {public static void printmessage (httpresponsecode statusCode) {system.out.println ("الحالة المستردة" + statusCode.getCode ()) ؛ }} httpresponseHandler.printMessage (httpresponsecode.ok) ؛تجدر الإشارة إلى أن هذا المثال يوضح أنه إذا كان ثابتًا ، فيجب أن تحاول استخدام التعدادات ، لكن هذا لا يعني أنه يجب عليك استخدام التعدادات في جميع الظروف. في بعض الحالات ، قد يكون من المرغوب فيه استخدام ثابت لتمثيل قيمة معينة ، ولكن يُسمح أيضًا بقيم أخرى. على سبيل المثال ، قد يعرف الجميع عن PI ، ويمكننا استخدام ثابت لالتقاط هذه القيمة (وإعادة استخدامها):
الطبقة العامة NumericConstants {Public Static Final PI = 3.14 ؛ الثابتة العامة النهائية double unit_circle_area = pi * pi ؛} سجادة الفئة العامة {private final double area ؛ تشغيل الطبقة العامة (منطقة مزدوجة) {this.area = area ؛ } public double getCost () {return area * 2 ؛ }} // قم بإنشاء سجادة يبلغ قطرها 4 أقدام (نصف قطرها 2 قدم) Rug FourfootRug = New Brug (2 * numericconstants.unit_circle_area) ؛لذلك ، يمكن تلخيص قواعد استخدام التعدادات على النحو التالي:
عندما تكون جميع القيم المنفصلة الممكنة معروفة مقدمًا ، يمكنك استخدام التعداد
خذ رمز استجابة HTTP المذكور أعلاه كمثال. قد نعرف جميع قيم رمز حالة HTTP (يمكن العثور عليها في RFC 7231 ، والذي يحدد بروتوكول HTTP 1.1). لذلك ، يتم استخدام التعداد. عند حساب PI ، لا نعرف جميع القيم الممكنة حول PI (أي مزدوج ممكن) ، ولكن في نفس الوقت نريد إنشاء ثابت للسجاد الدائري لجعل الحساب أسهل (أسهل في القراءة) ؛ لذلك يتم تعريف سلسلة من الثوابت.
إذا كنت لا تستطيع معرفة جميع القيم الممكنة مقدمًا ، ولكنك تريد تضمين الحقول أو الأساليب لكل قيمة ، فإن أسهل طريقة هي إنشاء فئة جديدة لتمثيل البيانات. على الرغم من أنني لم أقل أبدًا أنه لا ينبغي أن يكون هناك أي تعداد في أي سيناريو ، إلا أن مفتاح معرفة مكان عدم استخدام التعداد هو أن تكون على دراية بجميع القيم مقدمًا ويحظر استخدام أي قيمة أخرى.
3. إعادة تعريف طريقة متساوية () في الفصل
يمكن أن يكون التعرف على الكائنات مشكلة صعبة في حلها: إذا كان كائنين يشغلان نفس الموقف في الذاكرة ، فهل هم متماثلان؟ إذا كانت بطاقات هوية الخاصة بهم هي نفسها ، فهل هم متماثلون؟ أو ماذا لو كانت جميع الحقول متساوية؟ على الرغم من أن كل فصل له منطق تحديد الهوية الخاص به ، إلا أن هناك العديد من الدول الغربية في النظام التي تحتاج إلى الحكم على ما إذا كانت متساوية. على سبيل المثال ، هناك فئة أدناه تشير إلى شراء الطلب ...
شراء الفئة العامة {private long id ؛ Public Long getId () {return id ؛ } public void setId (id long) {this.id = id ؛ }}... كما هو مكتوب أدناه ، يجب أن يكون هناك العديد من الأماكن في الكود المتشابهة:
شراء OriginalPurchase = جديد شراء () ؛ شراء updatedpurchase = جديد شراء () ؛ if (OriginPurchase.getId () == updatedpurchase.getid ()) {// تنفيذ بعض المنطق للمشتريات المتساوية}كلما زادت هذه المكالمات المنطقية (بدورها ، تنتهك المبدأ الجاف) ، والشراء
ستصبح معلومات هوية الفصل أكثر وأكثر. إذا تم تغيير الشراء لسبب ما
منطق هوية الفئة (على سبيل المثال ، تم تغيير نوع المعرف) ، لذلك يجب أن يكون هناك العديد من الأماكن التي يتم فيها تحديث منطق الهوية.
يجب علينا تهيئة هذا المنطق داخل الفصل ، بدلاً من نشر منطق هوية فئة الشراء كثيرًا من خلال النظام. للوهلة الأولى ، يمكننا إنشاء طريقة جديدة ، مثل Issame ، التي تعتبر معلمة التضمين كائن شراء ، ومقارنة معرفات كل كائن لمعرفة ما إذا كانت هي نفسها:
شراء الفئة العامة {private long id ؛ Issame boolean العامة (شراء أخرى) {return getId () == Other.gerId () ؛ }}على الرغم من أن هذا حل فعال ، يتم تجاهل الوظيفة المدمجة لـ Java: باستخدام طريقة متساوية. يرث كل فئة في Java فئة الكائن ، على الرغم من أنها ضمنية ، بحيث يرث أيضًا طريقة متساوية. بشكل افتراضي ، تتحقق هذه الطريقة من هوية الكائن (نفس الكائن في الذاكرة) ، كما هو موضح في مقتطف الرمز التالي في تعريف فئة الكائن (الإصدار 1.8.0_131) في JDK:
منطقية عامة تساوي (كائن OBJ) {return (هذا == OBJ) ؛}تعمل هذه الطريقة المساواة كموقع طبيعي لحقن منطق الهوية (يتم تنفيذه عن طريق تجاوز المساواة الافتراضية):
شراء الفئة العامة {private long id ؛ Public Long getId () {return id ؛ } public void setId (id long) {this.id = id ؛ } Override public boolean يساوي (كائن آخر) {if (this == other) {return true ؛ } آخر إذا (! (مثيل آخر من الشراء)) {return false ؛ } آخر {return ((شراء) other) .getID () == getId () ؛ }}}على الرغم من أن هذه الطريقة المتساوية تبدو معقدة ، لأن طريقة متساوية تقبل فقط معلمات كائنات النوع ، إلا أننا نحتاج فقط إلى النظر في ثلاث حالات:
كائن آخر هو الكائن الحالي (أي OriginalPurchase.equals (OriginalPurchase)) ، بحكم تعريف
الكائن الآخر ليس كائن شراء ، في هذه الحالة لا يمكننا مقارنة معرف الشراء ، وبالتالي فإن الكائنين ليسا متساويين
الكائنات الأخرى ليست هي نفس الكائن ، ولكنها مثيلات الشراء. لذلك ، ما إذا كان المساواة يعتمد على ما إذا كان معرف الشراء الحالي وغيره من الشراء متساوين. الآن يمكننا إعادة تشكيل شروطنا السابقة ، على النحو التالي:
شراء OriginalPurchase = جديد شراء () ؛ شراء updatedpurchase = جديد شراء () ؛ if (OriginalPurchase.equals (updatedpurchase)) {// تنفيذ بعض المنطق للمشتريات المتساوية}بالإضافة إلى تقليل النسخ المتماثل في النظام ، فإن إعادة إنشاء طريقة المساواة الافتراضية لها بعض المزايا الأخرى. على سبيل المثال ، إذا قمنا ببناء قائمة بكائنات الشراء وتحقق مما إذا كانت القائمة تحتوي على كائن شراء آخر بنفس المعرف (كائنات مختلفة في الذاكرة) ، فإننا نحصل على قيمة حقيقية لأن القيمتين تعتبر متساوية:
قائمة <Tuftion> المشتريات = جديد arraylist <> () ؛ المشتريات. // حقيقي
عادة ، بغض النظر عن مكان وجودك ، إذا كنت بحاجة إلى تحديد ما إذا كانت الفئتان متساوية ، فأنت بحاجة فقط إلى استخدام طريقة إعادة الكتابة. إذا أردنا استخدام طريقة متساوية ضمنية بسبب وراثة كائن الكائن للحكم على المساواة ، فيمكننا أيضًا استخدام المشغل == ، على النحو التالي:
if (OriginPurchase == updatedpurchase) {// الكائنين هما نفس الكائنات في الذاكرة}تجدر الإشارة أيضًا إلى أنه بعد إعادة كتابة طريقة متساوية ، يجب أيضًا إعادة كتابة طريقة HashCode. مزيد من المعلومات حول العلاقة بين هاتين الطريقتين وكيفية تحديد Hashcode بشكل صحيح
الطريقة ، انظر هذا الموضوع.
كما رأينا ، فإن الكتابة فوق طريقة المساواة لا تهيئة منطق الهوية داخل الفصل فحسب ، بل يقلل أيضًا من انتشار هذا المنطق في جميع أنحاء النظام ، بل يسمح أيضًا بلغة Java باتخاذ قرارات مستنيرة حول الفصل.
4. استخدام الأشكال المتعددة قدر الإمكان
لأي لغة برمجة ، تعد الجمل الشرطية هيكلًا شائعًا للغاية ، وهناك بعض الأسباب لوجودها. نظرًا لأن مجموعات مختلفة يمكن أن تسمح للمستخدم بتغيير سلوك النظام بناءً على القيمة المحددة أو الحالة الفورية للكائن. على افتراض أن المستخدم يحتاج إلى حساب رصيد كل حساب مصرفي ، يمكن تطوير الرمز التالي:
التعداد العام BankAccountType {checking ، Savings ، Certificate_of_deposit ؛} الفئة العامة BankAccount {private Final BankAccountType type ؛ Public BankAccount (نوع BankAccountType) {this.type = type ؛ } public double getInterestrate () {switch (type) {case checking: return 0.03 ؛ // 3 ٪ وفورات الحالة: إرجاع 0.04 ؛ // 4 ٪ CASE CESTRIGATE_OF_DEPOSIT: إرجاع 0.05 ؛ // 5 ٪ افتراضي: رمي جديد غير مدعوم جديد () ؛ }} public boolean supportsDeposits () {switch (type) {case checking: return true ؛ وفورات الحالات: عودة صحيح ؛ CASE CESTRIGATE_OF_DEPOSIT: إرجاع خطأ ؛ الافتراضي: رمي جديد غير مدعوم جديد () ؛ }}}على الرغم من أن الكود أعلاه يفي بالمتطلبات الأساسية ، إلا أن هناك عيبًا واضحًا: يحدد المستخدم فقط سلوك النظام بناءً على نوع الحساب المحدد. هذا لا يتطلب من المستخدمين فقط التحقق من نوع الحساب قبل اتخاذ قرار ، ولكن يحتاج أيضًا إلى تكرار هذا المنطق عند اتخاذ قرار. على سبيل المثال ، في التصميم أعلاه ، يجب على المستخدم التحقق في كلتا الطريقتين. يمكن أن يؤدي ذلك إلى الخروج عن السيطرة ، خاصة عند تلقي الحاجة إلى إضافة نوع حساب جديد.
يمكننا استخدام تعدد الأشكال لاتخاذ القرارات ضمنيًا ، بدلاً من استخدام أنواع الحسابات لتمييزها. للقيام بذلك ، نقوم بتحويل الفئات الملموسة من BankAccount إلى واجهة ونقل عملية اتخاذ القرار إلى سلسلة من الفئات الخرسانية التي تمثل كل نوع من الحساب المصرفي:
/*** Java Learning and Communication QQ Group: 589809992 دعنا نتعلم Java معًا! */الواجهة العامة BankAccount {public double getInterestrate () ؛ ودائع دعم منطقية عامة () ؛} تنفذ الفئة العامة CheckingAccount BankAccount {Override public double getIntestrate () {return 0.03 ؛ } Override Public Boolean Debosits () {return true ؛ }} الفئة العامة SavingSaccount تنفذ BankAccount {Override public double getIntestrate () {return 0.04 ؛ } Override public boolean supportsDeposis () {return true ؛ }} CertificateofDepositActactive تنفذ BankAccount {Override public double getIntestrate () {return 0.05 ؛ } Override Public Boolean SupportDeposis () {return false ؛ }}هذا لا يلف فقط المعلومات الخاصة بكل حساب في فصله الخاص ، ولكنه يدعم المستخدمين أيضًا لتغيير تصميماتهم بطريقتين مهمتين. أولاً ، إذا كنت ترغب في إضافة نوع حساب مصرفي جديد ، فأنت بحاجة فقط إلى إنشاء فئة محددة جديدة ، وتنفيذ واجهة BankAccount ، وإعطاء التنفيذ المحدد للطريقتين. في تصميم الهيكل الشرطي ، يتعين علينا إضافة قيمة جديدة إلى التعداد ، وإضافة عبارة حالة جديدة في كلتا الطريقتين ، وإدراج منطق الحساب الجديد ضمن كل عبارة حالة.
ثانياً ، إذا أردنا إضافة طريقة جديدة في واجهة BankAccount ، فنحن بحاجة فقط إلى إضافة طريقة جديدة في كل فئة ملموسة. في التصميم المشروط ، يتعين علينا نسخ عبارة التبديل الحالية وإضافتها إلى طريقتنا الجديدة. بالإضافة إلى ذلك ، يتعين علينا إضافة منطق لكل نوع حساب في كل عبارة حالة.
من الناحية الرياضية ، عندما ننشئ طريقة جديدة أو إضافة نوع جديد ، يتعين علينا إجراء نفس العدد من التغييرات المنطقية في التصميم متعدد الأشكال والشرط. على سبيل المثال ، إذا أضفنا طريقة جديدة في تصميم متعدد الأشكال ، فيجب علينا إضافة الطريقة الجديدة إلى الفئات الملموسة لجميع الحسابات المصرفية N ، وفي تصميم مشروط ، يتعين علينا إضافة بيانات حالة جديدة في طريقتنا الجديدة. إذا أضفنا نوعًا جديدًا من الحساب في التصميم متعدد الأشكال ، فيجب علينا تنفيذ جميع أرقام M في واجهة BankAccount ، وفي التصميم الشرطي ، يجب علينا إضافة عبارة حالة جديدة إلى كل طريقة M الحالية.
على الرغم من أن عدد التغييرات التي يتعين علينا إجراؤها متساوية ، إلا أن طبيعة التغييرات مختلفة تمامًا. في التصميم متعدد الأشكال ، إذا أضفنا نوعًا جديدًا من الحساب وننسى تضمين طريقة ، يلقي برنامج التحويل البرمجي خطأًا لأننا لا ننفذ جميع الطرق في واجهة BankAccount الخاصة بنا. في التصميم المشروط ، لا يوجد شيك من هذا القبيل للتأكد من أن كل نوع لديه بيان حالة. إذا تمت إضافة نوع جديد ، فيمكننا ببساطة نسيان تحديث كل عبارة تبديل. كلما كانت هذه المشكلة أكثر خطورة ، كلما كررنا بيان التبديل الخاص بنا. نحن بشر ونميل إلى ارتكاب أخطاء. لذلك ، في أي وقت يمكننا الاعتماد على المترجم لتذكيرنا بالأخطاء ، يجب أن نفعل ذلك.
الملاحظة الثانية المهم حول هذين التصميمين هي أنها مكافئة خارجيا. على سبيل المثال ، إذا كنا نريد التحقق من سعر الفائدة لحساب التحقق ، سيبدو التصميم الشرطي هكذا:
BankAccount checkingAccouncount = New BankAccount (BankAccountType.Checking) ؛ System.out.println (checkingAccount.getInterestrate ()) ؛ // الإخراج: 0.03
بدلاً من ذلك ، ستكون التصميمات متعددة الأشكال مماثلة لما يلي:
BankAccount checkingAccouncount = جديد checkingAccount () ؛ system.out.println (checkingAccount.getInterestrate ()) ؛ // الإخراج: 0.03
من وجهة نظر خارجية ، نحن فقط ندعو getIntereunk () على كائن BankAccount. سيكون هذا أكثر وضوحًا إذا قمنا بتجريد عملية الإنشاء في فئة المصنع:
الطبقة العامة MonsteralAccountFactory {public static bankAccount CreateCheCkingAckount () {return New BankAccount (BankAccountType.Checking) ؛ }} الفئة العامة polymorphicAccountFactory {public Static BankAccount CreateCheCkingAckount () {return new checkingAckount () ؛ }} // في كلتا الحالتين ، نقوم بإنشاء الحسابات باستخدام مصنع bankaCincount MondercheckingAckount = monditionalAccountFactory.CreateCheckingAckount () SameSystem.out.println (MonderCheckingAckount.getInterestrate ()) ؛ // الإخراج: 0.03System.out.println (polymorphicCheCkingAccount.getInterestrate ()) ؛ // الإخراج: 0.03من الشائع جدًا استبدال المنطق الشرطي بفئات متعددة الأشكال ، لذلك تم نشر الطرق لإعادة بناء البيانات الشرطية في فئات متعددة الأشكال. هنا مثال بسيط. بالإضافة إلى ذلك ، يصف إعادة إنشاء Martin Fowler (ص 255) العملية التفصيلية لأداء عملية إعادة الإعمار هذه.
مثل التقنيات الأخرى في هذه المقالة ، لا توجد قاعدة صعبة وسريعة حول وقت إجراء الانتقال من المنطق المشروط إلى فئات متعددة الأشكال. في الواقع ، لا نوصي باستخدامه في أي موقف. في التصميم الذي يحركه الاختبار: على سبيل المثال ، صمم Kent Beck نظامًا بسيطًا للعملة بهدف استخدام الطبقات المتعددة الأشكال ، لكنه وجد أن هذا جعل التصميم معقدًا للغاية وأعاد تصميمه في أسلوب غير مشترك. ستحدد الخبرة والحكم المعقول متى الوقت المناسب لتحويل الكود الشرطي إلى رمز متعدد الأشكال.
خاتمة
كمبرمجين ، على الرغم من أن التقنيات التقليدية المستخدمة في الأوقات العادية يمكن أن تحل معظم المشكلات ، في بعض الأحيان يجب علينا كسر هذا الروتين ونطالب بنشاط ببعض الابتكار. بعد كل شيء ، كمطور ، توسيع نطاق معرفته وعمقه لا يسمح لنا فقط باتخاذ قرارات أكثر ذكاءً ، ولكن أيضًا يجعلنا أكثر ذكاءً.