أنماط التصميم
- أساسيات البرمجيات الموجه للكائنات القابلة لإعادة الاستخدام
نمط التصميم هو مجموعة من الاستخدام المتكرر ، والمعروفة لمعظم الناس ، والفهرسة المصنفة ، وتجربة تصميم الكود. يتمثل استخدام أنماط التصميم في إعادة استخدام الرمز ، وجعل الفهم الكود أسهل في الفهم من قبل الآخرين ، وضمان موثوقية الكود. ليس هناك شك في أن أنماط التصميم هي الفوز بأنفسهم والآخرين والأنظمة. أنماط التصميم تجعل تجميع الكود مصممًا حقًا. أنماط التصميم هي حجر الزاوية في هندسة البرمجيات ، تمامًا مثل الطوب والحجارة في المبنى. يمكن للاستخدام العقلاني لأنماط التصميم في المشروع حل العديد من المشكلات. كل نمط لديه الآن مبادئ مقابلة لتتوافق مع ذلك. يصف كل نمط مشكلة تتكرر باستمرار من حولنا والحل الأساسي للمشكلة ، وهذا هو أيضًا السبب وراء استخدامها على نطاق واسع. هذا الفصل هو نموذج التصميم لجمال Java [Evolution from Rookie to Expert]. سندرس هذا الفصل في مجموعة من النظرية والممارسة. آمل أن يتعلم عشاق البرنامج نموذج التصميم جيدًا وأن يكونوا مهندس برمجيات ممتاز!
1. تصنيف أنماط التصميم
بشكل عام ، تنقسم أنماط التصميم إلى ثلاث فئات:
هناك خمسة أنواع من أوضاع الإنشاء: وضع طريقة المصنع ، وضع المصنع التجريدي ، وضع Singleton ، وضع المنشئ ، ووضع النموذج الأولي.
هناك سبع أوضاع هيكلية: وضع المحول ، وضع الديكور ، وضع الوكيل ، وضع المظهر ، وضع الجسر ، وضع المركب ، ووضع التمتع.
الأوضاع السلوكية ، ما مجموعه أحد عشر: وضع السياسة ، وضع طريقة القالب ، وضع المراقب ، الوضع الفرعي التكراري ، وضع سلسلة المسؤولية ، وضع الأوامر ، وضع المذكرة ، وضع الحالة ، وضع الزوار ، وضع الوسيط ، وضع المترجم.
في الواقع ، هناك فئتان أخريان: الوضع المتزامن ووضع تجمع الخيوط. دعونا نستخدم صورة لوصفها ككل:
2. ستة مبادئ نموذج التصميم
1. فتح مبدأ الإغلاق
مبدأ الفتح والإغلاق هو الانفتاح على الامتدادات وقرب التعديلات. عندما يحتاج البرنامج إلى توسيع نطاق ، لا يمكنك تعديل الكود الأصلي لتحقيق تأثير ساكن ساخن. لذلك ، في إحدى الكلمة: من أجل جعل البرنامج أكثر قابلية للتوسع وسهل الصيانة والترقية. لتحقيق مثل هذا التأثير ، نحتاج إلى استخدام الواجهات والفئات المجردة ، والتي سنذكرها في التصميم المحدد لاحقًا.
2. مبدأ استبدال Liskov
مبدأ استبدال Liskov LSP هو أحد المبادئ الأساسية للتصميم الموجهة للكائنات. يقول مبدأ الاستبدال الغني أن أي فئة قاعدة يمكن أن تظهر ، يمكن أن تظهر الفئات الفرعية بالتأكيد. LSP هو حجر الزاوية في الميراث وإعادة الاستخدام. فقط عندما يمكن للفئة المشتقة أن تحل محل الفئة الأساسية ولا تتأثر وظائف وحدة البرامج ، يمكن إعادة استخدام الفئة الأساسية حقًا ، ويمكن أن تضيف الفئة المشتقة أيضًا سلوكيات جديدة تعتمد على الفئة الأساسية. مبدأ استبدال Richter هو ملحق لمبدأ "الإغلاق المفتوح". الخطوة الرئيسية لتنفيذ مبدأ "Open-COSE" هي التجريد. علاقة الميراث بين الطبقة الأساسية والفئة الفرعية هي التنفيذ الملموس للتجريد ، وبالتالي فإن مبدأ الاستبدال الغني هو توحيد للخطوات المحددة لتنفيذ التجريد. - من Baidu Encyclopedia
3. مبدأ انعكاس الاعتماد
هذا هو أساس مبدأ الفتح والإغلاق. المحتوى المحدد: تعتمد البرمجة الحقيقية للواجهات على التجريد بدلاً من الخرسانة.
4. مبدأ الفصل في الواجهة
هذا المبدأ يعني: استخدام واجهات معزولة متعددة أفضل من استخدام واجهة واحدة. وهذا يعني أيضا تقليل درجة الاقتران بين الفصول. من هنا يمكننا أن نرى أن نمط التصميم هو في الواقع فكرة تصميم البرنامج ، بدءًا من بنية برمجيات كبيرة ، لراحة الترقية والصيانة. لذلك ، ظهرت المقالة أعلاه عدة مرات: تقليل الاعتماد وتقليل الاقتران.
5. مبدأ ديميتر
لماذا مبدأ أقل المعرفة؟ وهذا يعني أن كيان واحد يجب أن يتفاعل مع كيانات أخرى أقل من ذلك ، بحيث تكون الوحدات الوظيفية للنظام مستقلة نسبيًا.
6. مبدأ إعادة الاستخدام المركب
المبدأ هو محاولة استخدام طرق التوليف/التجميع بدلاً من الميراث.
3. أنماط تصميم Java 23
ابتداءً من هذا القسم ، نقدم بالتفصيل المفاهيم ، وسيناريوهات التطبيق ، وما إلى ذلك من 23 نمط تصميم في Java ، ونحللها مع خصائصها ومبادئ أنماط التصميم.
1. طريقة المصنع
هناك ثلاثة أنواع من أوضاع طريقة المصنع:
11. نموذج المصنع العادي هو إنشاء فئة المصنع وإنشاء مثيلات من بعض الفئات التي تنفذ الواجهة نفسها. انظر أولاً إلى مخطط العلاقة:
على سبيل المثال: (دعنا نعطي مثالاً على إرسال رسائل البريد الإلكتروني والرسائل النصية)
أولاً ، قم بإنشاء واجهة مشتركة بين الاثنين:
مرسل الواجهة العامة {public void send () ؛ } ثانياً ، قم بإنشاء فئة تنفيذ:
تنفذ الفئة العامة MailSender Sender {Override public void send () {system.out.println ("هذا هو mailsender!") ؛ }} الفئة العامة smssender تنفذ المرسل {Override public void send () {system.out.println ("هذا هو SMS Sender!") ؛ }}أخيرًا ، بناء المصنع:
الفئة العامة sendfactory {public sender produce (نوع السلسلة) {if ("mail" .equals (type)) {return new mailsender () ؛ } آخر إذا ("sms" .equals (type)) {return new smssender () ؛ } آخر {system.out.println ("الرجاء إدخال النوع الصحيح!") ؛ العودة لاغية. }}}دعونا نختبره:
الفئة العامة FactoryTest {public static void main (string [] args) {sendfactory factory = new sendFactory () ؛ المرسل المرسل = المصنع. المرسل. }}الإخراج: هذا مرسل الرسائل القصيرة!
22. أوضاع طريقة المصنع المتعددة هي تحسن في وضع طريقة المصنع العادي. في وضع طريقة المصنع العادي ، إذا كانت السلسلة التي تم تمريرها خاطئة ، فلا يمكن إنشاء الكائن بشكل صحيح. توفر أوضاع طريقة المصنع المتعددة أساليب مصنع متعددة لإنشاء كائنات بشكل منفصل. مخطط العلاقة:
فقط قم بتعديل الرمز أعلاه وتغيير فئة SendFactory ، على النحو التالي:
الفئة العامة sendfactory {public sender producemail () {return new mailsender () ؛ } Sender Public Producesms () {return new smssender () ؛ }} فئة الاختبار كما يلي:
الفئة العامة FactoryTest {public static void main (string [] args) {sendfactory factory = new sendFactory () ؛ المرسل المرسل = المصنع. sproducemail () ؛ المرسل. }}الإخراج: هذا هو Mailsender!
33. وضع طريقة المصنع الثابت ، قم بتعيين الأساليب في أوضاع طريقة المصنع المتعددة أعلاه إلى ثابت ، وليس هناك حاجة لإنشاء مثيل ، فقط اتصل به مباشرة.
الفئة العامة sendfactory {public static sender producemail () {return new mailsender () ؛ } Sender Static Sender Public ServesMs () {return new smssender () ؛ }} الفئة العامة FactoryTest {public static void main (string [] args) {sender sender = sendfactory.producemail () ؛ المرسل. }}الإخراج: هذا هو Mailsender!
بشكل عام ، يكون نموذج المصنع مناسبًا: عندما يحتاج عدد كبير من المنتجات إلى إنشاء واجهة مشتركة ، يمكن إنشاءه من خلال نموذج طريقة المصنع. من بين الأوضاع الثلاثة المذكورة أعلاه ، لا يمكن للأسلوب الأول إنشاء الكائن بشكل صحيح إذا كانت السلسلة التي تم تمريرها غير صحيحة ، ولا يحتاج الثالث إلى إنشاء إنشاء فئة المصنع مقارنةً بالثاني. لذلك ، في معظم الحالات ، سنختار الوضع الثالث - وضع طريقة المصنع الثابت.
2. نمط المصنع التجريدي
هناك مشكلة في نموذج طريقة المصنع ، وهو أن إنشاء الفصل يعتمد على فئة المصنع. وهذا يعني ، إذا كنت ترغب في توسيع البرنامج ، فيجب عليك تعديل فئة المصنع ، التي تنتهك مبدأ الإغلاق. لذلك ، من منظور التصميم ، هناك مشاكل معينة. كيف تحلها؟ هذا يستخدم نمط المصنع التجريدي لإنشاء فئات مصنع متعددة. وبهذه الطريقة ، بمجرد الحاجة إلى وظائف جديدة ، يمكنك إضافة فئات مصنع جديدة مباشرة ، دون تعديل الكود السابق. نظرًا لأن المصانع التجريدية ليس من السهل فهمها ، فإننا ننظر أولاً إلى المخطط ثم نتبع الكود ، وهو ما يسهل فهمه.
يرجى الاطلاع على المثال:
مرسل الواجهة العامة {public void send () ؛ }فئتان للتنفيذ:
تنفذ الفئة العامة MailSender Sender {Override public void send () {system.out.println ("هذا هو mailsender!") ؛ }} الفئة العامة smssender تنفذ المرسل {Override public void send () {system.out.println ("هذا هو SMS Sender!") ؛ }}فئتان في المصنع:
Public Class SendMailfactory Presider {Override Public Sender Produce () {Return New MailSender () ؛ }} الفئة العامة sendSmsfactory Presider {Override Public Sender Produce () {return new smssender () ؛ }} توفير واجهة:
Interface Provider {public sender produce () ؛ }فئة الاختبار:
اختبار الفئة العامة {public static void main (string [] args) {provider provider = new SendMailFactory () ؛ مرسل المرسل = Provider.produce () ؛ المرسل. }}في الواقع ، فإن ميزة هذا النموذج هي أنه إذا كنت ترغب في إضافة وظيفة الآن: إرسال معلومات في الوقت المناسب ، فأنت بحاجة فقط إلى إنشاء فئة تنفيذ ، وتنفيذ واجهة المرسل ، وفي الوقت نفسه ، قم بإنشاء فئة مصنع ، وتنفيذ واجهة المزود ، وهو أمر جيد ، وليس هناك حاجة لتغيير التعليمات البرمجية الجاهزة. القيام بذلك سيجعله أكثر قابلية للتطوير!
3. وضع Singleton
Singleton هو نمط تصميم شائع الاستخدام. في تطبيقات Java ، يمكن لكائن Singleton التأكد من أنه في JVM ، هناك مثيل واحد فقط من الكائن. هذا النموذج له العديد من الفوائد:
1. يتم إنشاء بعض الفئات بشكل متكرر ، وبالنسبة لبعض الأشياء الكبيرة ، هذا هو نظام ضخم.
2. يتم التخلص من المشغل الجديد ، ويتم تقليل تواتر ذاكرة النظام ، ويتم تقليل ضغط GC.
3. بعض الفئات ، مثل محرك التداول الأساسي للبورصة ، تتحكم في عملية التداول. إذا كان من الممكن إنشاء فئات متعددة ، فسيتم إفساد النظام تمامًا. (على سبيل المثال ، إذا ظهر العديد من القادة في أمر الجيش في نفس الوقت ، فسيكون ذلك بالتأكيد في حالة من الفوضى) ، لذلك فقط باستخدام نموذج Singleton ، يمكننا التأكد من أن خادم المعاملات الأساسي يتحكم بشكل مستقل في العملية بأكملها.
أولاً ، دعنا نكتب فصلًا بسيطًا في Singleton:
الطبقة العامة Singleton { /* عقد مثيل ثابت خاص لمنع المرجع. القيمة هنا هي NULL ، مع الغرض من تحقيق التحميل الكسول*/ مثيل Singleton الثابت الخاص = NULL ؛ /* مُنشئ خاص لمنع إنشاء مثيله*/ خاص singleton () {}/* طريقة هندسة ثابتة لإنشاء مثيل*/ Singleton getInstance () {if (مثيل == null) {مثيل = جديد singleton () ؛ } مثيل الإرجاع ؛ } /* إذا تم استخدام الكائن للتسلسل ، فيمكن التأكد من أن الكائن يبقى متسقًا قبل وبعد التسلسل* / الكائن العام readResolve () {return evalue ؛ }}يمكن أن تلبي هذه الفئة المتطلبات الأساسية ، ولكن إذا وضعنا هذا الفئة بحماية أمان لاسلكية لاسلكية في بيئة متعددة الخيوط ، فستكون هناك بالتأكيد مشاكل. كيف تحلها؟ أولاً ، سوف نفكر في إضافة كلمة رئيسية متزامنة إلى طريقة getInstance ، على النحو التالي:
Singleton Singleton GetInstance () {if (مثيل == null) {مثيل = جديد singleton () ؛ } مثيل الإرجاع ؛ }ومع ذلك ، فإن الكلمة الرئيسية المتزامنة تغلق هذا الكائن. سينخفض هذا الاستخدام في الأداء ، لأنه في كل مرة تتصل فيها GetInstance () ، يجب قفل الكائن. في الواقع ، فقط عندما يتم إنشاء الكائن لأول مرة ، ليس من الضروري قفل ، لذلك يجب تحسين هذا المكان. دعنا نغيره إلى ما يلي:
public static singleton getInstance () {if (مثيل == null) {synchronized (مثيل) {if (مثيل == null) {مثيل = جديد singleton () ؛ }}} مثيل الإرجاع ؛ }يبدو أنه يحل المشكلة المذكورة سابقًا ، إضافة الكلمة الرئيسية المتزامنة في الداخل ، أي ، ليست هناك حاجة للقفل عند الاتصال ، لا يلزم القفل إلا عند إنشاء مثيل ويتم إنشاء الكائنات ، والتي لديها تحسن معين في الأداء. ومع ذلك ، في هذه الحالة ، قد تكون هناك مشاكل. انظر إلى الموقف التالي: يتم تنفيذ كائنات وعمليات مهمة في تعليمات Java بشكل منفصل ، أي المثيل = New Singleton () ؛ يتم تنفيذ البيان في خطوتين. ومع ذلك ، فإن JVM لا يضمن ترتيب هاتين العمليتين ، مما يعني أنه من الممكن أن يخصص JVM مساحة لمثيل Singleton الجديد ، ثم تخصيصه مباشرةً إلى عضو المثيل ، ثم تهيئة مثيل Singleton. هذا قد يرتكب خطأ. لنأخذ خيوط A و B كأمثلة:
ترسل مؤشرات الترابط A> A و B الأول إذا كان الحكم في نفس الوقت
B> A أولاً يدخل الكتلة المتزامنة ، نظرًا لأن المثيل لاغير ، فإنه ينفذ مثيل = New Singleton () ؛
C> نظرًا لآلية التحسين داخل JVM ، يرسم JVM أولاً بعض الذاكرة الفارغة المخصصة لمثيل Singleton ويقوم بتعيينها لعضو المثيل (لاحظ أن JVM لا يبدأ في تهيئة هذه الحالة في هذا الوقت) ، ثم يترك الكتلة المتزامنة.
D> B يدخل الكتلة المتزامنة. نظرًا لأن المثيل ليس فارغًا في هذا الوقت ، فإنه يترك على الفور الكتلة المتزامنة ويعيد النتيجة إلى البرنامج الذي يسمى الطريقة.
e> في هذا الوقت ، يعتزم الموضوع B استخدام مثيل Singleton ، لكنه يجد أنه لم يتم تهيئته ، لذلك يحدث خطأ.
لذلك ، لا تزال هناك أخطاء محتملة في البرنامج. في الواقع ، فإن عملية تشغيل البرنامج معقدة للغاية. من هذه النقطة ، يمكننا أن نرى أنه لا سيما في كتابة البرامج في بيئة متعددة الخيوط أكثر صعوبة وصعبة. قمنا أيضًا بتحسين البرنامج:
private static class Singletonfactory {private static singleton extreal = new Singleton () ؛ } Singleton GetInstance () {return singletonfactory.instance ؛ }الوضع الفعلي هو أن نمط Singleton يستخدم الطبقات الداخلية للحفاظ على تنفيذ المفردات. يمكن للآلية الداخلية لـ JVM التأكد من أنه عند تحميل فئة ، تكون عملية تحميل هذه الفئة حصرية بشكل متبادل للخيوط. وبهذه الطريقة ، عندما ندعو GetInstance لأول مرة ، يمكن أن يساعدنا JVM في ضمان إنشاء المثيل مرة واحدة فقط وسيضمن تهيئة الذاكرة المخصصة للمثيل ، لذلك لا داعي للقلق بشأن المشكلات المذكورة أعلاه. في الوقت نفسه ، ستستخدم هذه الطريقة آلية الاستبعاد المتبادل فقط عندما يتم استدعاؤها لأول مرة ، والتي تحل مشكلة الأداء المنخفض. وبهذه الطريقة ، نلخص مؤقتًا نمطًا مثاليًا للفرد:
الطبقة العامة Singleton { /* طريقة مُنشئ خاصة لمنع إنشاء مثيل* / خاص سينغليتون () {} /* استخدم فئة داخلية هنا للحفاظ على Singleton* / Private Static Class Singletonfactory {Private Static Singleton مثيل = New Singleton () ؛ } /* الحصول على مثيل* / Singleton getInstance () {return singletonfactory.instance ؛ } /* إذا تم استخدام الكائن للتسلسل ، فيمكن التأكد من أن الكائن يبقى متسقًا قبل وبعد التسلسل* / الكائن العام readResolve () {return getInstance () ؛ }}في الواقع ، ليس من الصحيح بالضرورة أنه مثالي. إذا تم إلقاء استثناء في المنشئ ، فلن يتم إنشاء المثيل أبدًا وسيكون هناك خطأ. لذلك ، لا يوجد شيء مثالي ، يمكننا فقط اختيار طريقة التنفيذ الأكثر ملاءمة لسيناريو التطبيق الخاص بنا استنادًا إلى الموقف الفعلي. يقوم بعض الأشخاص أيضًا بتنفيذ هذا: لأننا نحتاج فقط إلى المزامنة عند إنشاء الفصل ، طالما أننا نفصل عن الخلق و GetInstance () وإضافة الكلمة الرئيسية المتزامنة إلى الإنشاء بشكل منفصل ، فمن الممكن أيضًا:
الطبقة العامة singletontest {private static singletontest مثيل = null ؛ خاص singletontest () {} private static synchronized void syncinit () {if (مثيل == null) {مثيل = جديد singletontest () ؛ }} public static singletontest getInstance () {if (مثيل == null) {syncinit () ؛ } مثيل الإرجاع ؛ }}إذا كنت تفكر في الأداء ، فإن البرنامج بأكمله يحتاج فقط إلى إنشاء مثيل مرة واحدة ، لذلك لن يكون للأداء أي تأثير.
الملحق: يتم استخدام طريقة "مثيل الظل" لمزامنة خصائص كائنات Singleton
الطبقة العامة singletontest {private static singletontest مثيل = null ؛ خصائص المتجهات الخاصة = فارغة ؛ المتجهات العامة getProperties () {عودة خصائص ؛ } خاص singletontest () {} private static synchronized void syncinit () {if (easty == null) {مثيل = جديد singletontest () ؛ }} public static singletontest getInstance () {if (مثيل == null) {syncinit () ؛ } مثيل الإرجاع ؛ } public void updateProperties () {singletontest Shadow = new SingleTontest () ؛ الخصائص = Shadow.getProperties () ؛ }}من خلال تعلم نمط المفرد ، نخبرنا:
1. من السهل فهم نموذج Singleton ، ولكن لا يزال من الصعب تنفيذه بالتفصيل.
2. الكلمة الرئيسية المتزامنة تغلق كائن. عند استخدامه ، يجب استخدامه في المكان المناسب (لاحظ أن الكائنات والعمليات التي تحتاج إلى قفل ، وأحيانًا لا تحتاج الكائن بالكامل والعملية بأكملها إلى قفل).
في هذه المرحلة ، تم الحديث عن نمط Singleton بشكل أساسي. في النهاية ، فكر المؤلف فجأة في سؤال آخر ، وهو استخدام طريقة فئة ثابتة لتحقيق تأثير نمط Singleton ، وهو أمر ممكن أيضًا. ما هو الفرق بين الاثنين هنا؟
أولاً ، لا يمكن للفصول الثابتة تنفيذ واجهات. (لا بأس من منظور الفصل ، لكن هذا سيدمر الثابت. لأنه لا يوجد طريقة تعديل ثابتة في الواجهة ، فهو غير منتظم حتى لو تم تنفيذه)
ثانياً ، يمكن تأخير تهيئة المفردات ، وعادة ما يتم تهيئة الفصول الساكنة عند تحميلها لأول مرة. سبب التحميل كسول هو أن بعض الفصول كبيرة نسبيًا ، لذلك يساعد التحميل كسول على تحسين الأداء.
مرة أخرى ، يمكن أن تورث فئة Singleton ويمكن كتابة أساليبها. ومع ذلك ، فإن الطرق الداخلية للفئات الثابتة ثابتة ولا يمكن الكتابة عليها.
النقطة الأخيرة ، فصول Singleton أكثر مرونة. بعد كل شيء ، فهي مجرد فئة Java عادية من حيث التنفيذ. طالما أنهم يستوفون الاحتياجات الأساسية للفرد ، يمكنك تنفيذ بعض الوظائف الأخرى كما يحلو لك ، ولكن لا يمكن فصولها الثابتة. من الملخص أعلاه ، يمكننا أن نرى بشكل أساسي الفرق بين الاثنين. ومع ذلك ، من ناحية أخرى ، يتم تنفيذ نمط Singleton الذي قمنا بتطبيقه أعلاه داخليًا مع فئة ثابتة ، لذلك يرتبط الاثنان بشكل كبير ، لكن مستويات نظرنا في المشكلة مختلفة. فقط من خلال الجمع بين الفكرتين يمكن إنشاء حل مثالي. تمامًا مثل HashMap يستخدم المصفوفات + قوائم مرتبطة لتنفيذها ، في الواقع ، أشياء كثيرة في الحياة هي مثل هذا. إن استخدام طرق مختلفة للتعامل مع المشكلات دائمًا له مزايا وعيوب. الطريقة الأكثر مثالية هي الجمع بين مزايا كل طريقة لحل المشكلة بشكل أفضل!
4. وضع البناء
يوفر نموذج فئة المصنع نمطًا لإنشاء فئة واحدة ، بينما يركز نموذج البناء على مختلف المنتجات للإدارة ويستخدمه لإنشاء كائنات مركبة. يشير الكائن المركب المزعوم إلى فئة معينة لها سمات مختلفة. في الواقع ، يتم الحصول على نموذج البناء من خلال الجمع بين نموذج المصنع التجريدي السابق والاختبار النهائي. دعونا نلقي نظرة على الكود:
مثل الواجهة السابقة ، واجهة مرسل واحدة وفئتي التنفيذ Mailsender و SMSSender. أخيرًا ، فئة البناء هي كما يلي:
Public Class Builder {Private List <Sender> list = new ArrayList <Sender> () ؛ Public void producemailsender (int count) {for (int i = 0 ؛ i <count ؛ i ++) {list.add (new mailsender ()) ؛ }} public void producesssender (int count) {for (int i = 0 ؛ i <count ؛ i ++) {list.add (new smssender ()) ؛ }}}فئة الاختبار:
اختبار الفئة العامة {public static void main (string [] args) {builder builder = new Builder () ؛ builder.producemailsender (10) ؛ }}من وجهة النظر هذه ، يدمج نمط البناء العديد من الوظائف في فئة ، والتي يمكن أن تخلق أشياء أكثر تعقيدًا. لذا فإن الفرق في النموذج الهندسي هو أن نموذج المصنع يركز على إنشاء منتج واحد ، بينما يركز نموذج البناء على إنشاء كائن مناسب وأجزاء متعددة. لذلك ، سواء كان اختيار نموذج المصنع أو نموذج البناء يعتمد على الموقف الفعلي.
5. النموذج الأولي
على الرغم من أن نمط النموذج الأولي هو نمط إبداعي ، إلا أنه لا علاقة له بالنمط الهندسي. كما ترون من الاسم ، فإن فكرة هذا النمط هي نسخ واستنساخ كائن كنموذج أولي ، وإنتاج كائن جديد مشابه للكائن الأصلي. سيتم شرح هذا الملخص من خلال نسخ الكائن. في Java ، يتم تنفيذ كائنات نسخ من خلال clone () ، ويتم إنشاء فئة النموذج الأولي أولاً:
يطبق النموذج الأولي للطبقة العامة استنساخ {الكائن العام clone () يلقي clonenotsupportedException {prototype proto = (الأولي) super.clone () ؛ إرجاع بروتو }}الأمر بسيط للغاية. تحتاج فئة النموذج الأولي فقط إلى تنفيذ الواجهة المستنسخة والكتابة فوق طريقة الاستنساخ. هنا يمكن تغيير طريقة الاستنساخ إلى أي اسم ، لأن الواجهة المستنسخة هي واجهة فارغة ، يمكنك تحديد اسم طريقة فئة التنفيذ بشكل تعسفي ، مثل Clonea أو Cloneb ، لأن التركيز هنا هو الجملة Super.clone (). Super.clone () يستدعي طريقة clone () الكائن ، وفي فئة الكائن ، استنساخ () أصلي. كيف تنفذها على وجه التحديد؟ لن أخوضه في مقال آخر حول تفسير مكالمات الأساليب المحلية في جافا. هنا ، سأجمع بين النسخة الضحلة ونسخة عميقة من الكائنات. بادئ ذي بدء ، تحتاج إلى فهم مفهوم نسخة عميقة وضحلة من الأشياء:
نسخة ضحلة: بعد نسخ كائن ، سيتم إعادة إنشاء متغيرات نوع البيانات الأساسية ، بينما يشير نوع المرجع إلى الكائن الأصلي.
نسخة عميقة: بعد نسخ كائن ما ، يتم إعادة إنشاء كل من نوع البيانات الأساسية ونوع المرجع. ببساطة ، يتم نسخ النسخ العميق بالكامل ، في حين أن النسخ الضحل ليس دقيقًا.
هنا ، اكتب مثالًا للنسخ بعمق:
النموذج الأولي للطبقة العامة ينفذ استنساخ ، قابل للتسلسل {{private static final long serialversionuid = 1L ؛ سلسلة سلسلة خاصة خاص serializableObject OBJ ؛ /* نسخة ضحلة*/ كائن عام clone () يلقي clonenotsupportedException {prototype proto = (النموذج الأولي) super.clone () ؛ إرجاع بروتو } /* نسخة عميقة* / كائن عام deepclone () يلقي ioException ، classnotfoundException { /* اكتب الدفق الثنائي إلى الكائن الحالي* / bytearrayoutputstream bos = جديد bytearrayoutputstream () ؛ ObjectOutputStream OOS = New ObjectOutputStream (BOS) ؛ OOS.WriteObject (هذا) ؛ /* اقرأ الكائن الجديد الذي تم إنشاؤه بواسطة الدفق الثنائي*/ bytearrayinputstream bis = جديد bytearrayinputstream (bos.tobytearray ()) ؛ ObjectInputStream OIS = New ObjectInputStream (BIS) ؛ إرجاع OIS.ReadObject () ؛ } السلسلة العامة getString () {return string ؛ } public void setString (سلسلة سلسلة) {this.string = string ؛ } public serializableObject getObj () {return obj ؛ } public void setObj (serializableObject obj) {this.obj = obj ؛ }} class serializableObjectipes serializable {private static final long serialversionuid = 1L ؛ } لتحقيق نسخة عميقة ، تحتاج إلى قراءة الإدخال الثنائي للكائن الحالي في شكل دفق ، ثم كتابة الكائن المقابل للبيانات الثنائية.
سوف نستمر في مناقشة أوضاع التصميم. في المقالة السابقة ، انتهيت من الحديث عن 5 أوضاع خلق. في بداية هذا الفصل ، سأتحدث عن 7 أوضاع هيكلية: وضع المحول ، وضع الزخرفة ، وضع الوكيل ، وضع المظهر ، وضع الجسر ، وضع المركب ، ووضع التمتع. وضع المحول للكائن هو أصل أوضاع مختلفة. دعونا نلقي نظرة على الشكل التالي:
يقوم نمط المحول بتحويل واجهة فئة إلى تمثيل واجهة آخر يتوقعه العميل ، بهدف التخلص من مشكلات التوافق في الفصل بسبب عدم تطابق الواجهة. وهي مقسمة بشكل أساسي إلى ثلاث فئات: وضع محول الفئة ، ووضع المحول للكائن ، ووضع المحول للواجهة. أولاً ، دعنا نلقي نظرة على وضع المحول للفصل وننظر إلى مخطط الفصل أولاً:
الفكرة الأساسية هي: هناك فئة مصدر لها طريقة يجب تكييفها واستهدفها عندما تكون الواجهة الهدف قابلة للاستهداف. من خلال فئة المحول ، يتم توسيع وظيفة المصدر إلى استهداف ، وقراءة الكود:
مصدر الفئة العامة {public void method1 () {system.out.println ("هذه الطريقة الأصلية!") ؛ }} الواجهة العامة قابلة للاستهداف { /* نفس الطريقة في الفئة الأصلية* / public void method1 () ؛ /* طرق الفئة الجديدة*/ public void method2 () ؛ } يمتد محول الفئة العامة للأدوات المصدر التي يمكن استهدافها {override public void method2 () {system.out.println ("هذه هي الطريقة التي يمكن استهدافها!") ؛ }}يرث فئة المحول فئة المصدر وينفذ الواجهة المستهدفة. ما يلي هو فئة الاختبار:
الفئة العامة AdapterTest {public static void main (string [] args) {targetable target = new Adapter () ؛ Target.Method1 () ؛ Target.Method2 () ؛ }}الإخراج:
هذه الطريقة الأصلية!
هذه هي الطريقة المستهدفة!
وبهذه الطريقة ، فإن فئة التنفيذ للواجهة المستهدفة لها وظائف فئة المصدر.
وضع المحول للكائن
الفكرة الأساسية هي نفس وضع محول الفصل. إنه مجرد تعديل فئة المحول. هذه المرة ، لا يتم مورث فئة المصدر ، ولكن يتم الاحتفاظ بفئة المصدر لحل مشكلة التوافق. انظر إلى الصورة:
ما عليك سوى تعديل الكود المصدري لفئة المحول:
غلاف الطبقة العامة يطبق المستهدف {مصدر خاص المصدر ؛ الغلاف العام (مصدر المصدر) {super () ؛ this.Source = Source ؛ } Override public void method2 () {system.out.println ("هذه هي الطريقة المستهدفة!") ؛ } Override public void method1 () {source.method1 () ؛ }}فئة الاختبار:
الفئة العامة AdapterTest {public static void main (string [] args) {source source = new source () ؛ هدف مستهدف = غلاف جديد (المصدر) ؛ Target.Method1 () ؛ Target.Method2 () ؛ }}الإخراج هو نفسه الأول ، لكن طريقة التكيف مختلفة.
وضع المحول الثالث هو وضع محول الواجهة. محول الواجهة هو كما يلي: في بعض الأحيان هناك طرق تجريدية متعددة في واجهة نكتبها. عندما نكتب فئة التنفيذ للواجهة ، يجب أن ننفذ جميع طرق الواجهة. من الواضح أن هذه مضيعة ، لأنه لا توجد حاجة إلى جميع الطرق ، وأحيانًا تكون هناك حاجة إلى بعضها فقط. من أجل حل هذه المشكلة ، قدمنا وضع المحول للواجهة. بمساعدة فئة مجردة ، تنفذ الفئة التجريدية الواجهة وتنفذ جميع الطرق. نحن لا نتعامل مع الواجهة الأصلية ونتواصل فقط مع الفصل التجريدي. لذلك نكتب فصلًا ، ونرث الفصل التجريدي ، ونعد كتابة الأساليب التي نحتاجها. ألق نظرة على مخطط الفصل:
هذا سهل الفهم. في التطور الفعلي ، غالبًا ما نواجه العديد من الطرق المحددة في هذه الواجهة ، بحيث لا نحتاجها في بعض الأحيان في بعض فئات التنفيذ. انظر إلى الكود:
الواجهة العامة المصدرية {public void method1 () ؛ طريقة الفراغ العام 2 () ؛ }مجردة classpper2:
يقوم مجموعة clospper2 المجردة العامة بتنفيذ مصدري {public void method1 () {} public void method2 () {}} تمتد مصادر الفئة العامة إلى Wrapper2 {public void method1 () {system.out.println ("Sub1 Sub1!") ؛ }} الفئة العامة SourceSub2 يمتد Wrapper2 {public void method2 () {system.out.println ("Sub2 Sub2!") ؛ }} clospertest الفئة العامة {public static void main (string [] args) {sourcable source1 = new sourceSub1 () ؛ المصدر المصدري 2 = مصادر جديدة () ؛ source1.method1 () ؛ source1.method2 () ؛ sourt2.method1 () ؛ sourt2.method2 () ؛ }}إخراج الاختبار:
الواجهة القابلة للاستمتاع الأولى Sub1!
الواجهة القابلة للموارد الثانية Sub2!
حقق نتائجنا!
بعد الحديث عنها ، سألخص سيناريوهات التطبيق لثلاث أوضاع محول:
وضع محول الفئة: عندما تريد تحويل فئة واحدة إلى فئة تلبي واجهة جديدة أخرى ، يمكنك استخدام وضع محول الفئة لإنشاء فئة جديدة ، ورث الفئة الأصلية ، وتنفيذ واجهة جديدة.
وضع محول الكائن: عندما تريد تحويل كائن إلى كائن يرضي واجهة جديدة أخرى ، يمكنك إنشاء فئة غلاف ، وعقد مثيل للفئة الأصلية ، وفي طريقة فئة Wrapper ، ما عليك سوى استدعاء طريقة المثيل.
وضع المحول للواجهة: عندما لا ترغب في تنفيذ جميع الطرق في واجهة ، يمكنك إنشاء غلاف فئة مجردة لتنفيذ جميع الطرق. عندما نكتب فصول أخرى ، فقط ورث الفصل التجريدي.
7. ديكور
كما يوحي الاسم ، فإن النمط الزخرفي هو إضافة بعض الوظائف الجديدة إلى كائن ، وهو ديناميكي ، يتطلب الكائن الزخرفي والكائن الزخرفي لتنفيذ الواجهة نفسها. الكائن الزخرفي يحمل مثيل للكائن الزخرفي. مخطط العلاقة كما يلي:
فئة المصدر عبارة عن فئة زخرفية ، وفئة الديكور هي فئة زخرفية يمكنها إضافة بعض الوظائف ديناميكيًا إلى فئة المصدر. الرمز كما يلي:
الواجهة العامة المصدرية {public void method () ؛ } مصدر الفئة العامة يطبق مصدري {Override public void method () {system.out.println ("الطريقة الأصلية!") ؛ }} تزين الفئة العامة Decorator Sourcable {source sourcable source ؛ ديكور عام (مصدر مصادر) {super () ؛ this.Source = Source ؛ } Override public void method () {system.out.println ("قبل الديكور!") ؛ Source.Method () ؛ System.out.println ("بعد ديكور!") ؛ }}فئة الاختبار:
الفئة العامة DecoratorTest {public static void main (string [] args) {sourcable source = new source () ؛ OBJ القابلة للاستمتاع = ديكور جديد (المصدر) ؛ obj.method () ؛ }}الإخراج:
قبل الديكور!
الطريقة الأصلية!
بعد ديكور!
سيناريو تطبيق وضع الديكور:
1. من الضروري تمديد وظائف الفصل.
2. إضافة وظائف ديناميكيًا إلى كائن ، ويمكن أيضًا التراجع عنها ديناميكيًا. (لا يمكن للميراث القيام بذلك. الوظائف الموروثة ثابتة ولا يمكن إضافتها وحذفها ديناميكيًا.)
العيوب: ليس من السهل جدًا استكشاف الأخطاء وإصلاحها!
8. وضع الوكيل (الوكيل)
في الواقع ، يشير كل اسم نموذج إلى وظيفة النموذج. نموذج الوكيل هو إضافة فئة وكيل إضافية لإجراء بعض العمليات على الكائن الأصلي. على سبيل المثال ، عندما نستأجر منزلًا ، نعود للعثور على وكيل. لماذا؟ نظرًا لأنه ليس لديك فهم شامل للمعلومات حول المنازل في المنطقة ، آمل أن أجد شخصًا أكثر دراية به لمساعدتك في القيام بذلك. هذا ما يعنيه الوكيل هنا. على سبيل المثال ، في بعض الأحيان عندما نقاضى ، نحتاج إلى توظيف محام لأن المحامين لديهم خبرة في القانون ويمكنهم العمل نيابة عننا والتعبير عن أفكارنا. دعنا نلقي نظرة على مخطط العلاقة أولاً:
وفقًا للتفسير أعلاه ، يكون وضع الوكيل أسهل في الفهم. دعونا نلقي نظرة على الكود:
الواجهة العامة المصدرية {public void method () ؛ } مصدر الفئة العامة يطبق مصدري {Override public void method () {system.out.println ("الطريقة الأصلية!") ؛ }} يقوم وكيل الفئة العامة بتنفيذ مصدر مصدري {مصدر خاص ؛ الوكيل العام () {super () ؛ this.source = new source () ؛ } Override public void method () {قبل () ؛ Source.Method () ؛ atfer () ؛ } private void atfer() { System.out.println("after proxy!"); } private void before() { System.out.println("before proxy!"); }}测试类:
public class ProxyTest { public static void main(String[] args) { Sourceable source = new Proxy(); source.method(); }}输出:
before proxy!
the original method!
after proxy!
代理模式的应用场景:
如果已有的方法在使用的时候需要对原有的方法进行改进,此时有两种办法:
1、修改原有的方法来适应。这样违反了“对扩展开放,对修改关闭”的原则。
2、就是采用一个代理类调用原有的方法,且对产生的结果进行控制。这种方法就是代理模式。
使用代理模式,可以将功能划分的更加清晰,有助于后期维护!
9、外观模式(Facade)
外观模式是为了解决类与类之家的依赖关系的,像spring一样,可以将类和类之间的关系配置到配置文件中,而外观模式就是将他们的关系放在一个Facade类中,降低了类类之间的耦合度,该模式中没有涉及到接口,看下类图:(我们以一个计算机的启动过程为例)
我们先看下实现类:
public class CPU { public void startup(){ System.out.println("cpu startup!"); } public void shutdown(){ System.out.println("cpu shutdown!"); } } public class Memory { public void startup(){ System.out.println("memory startup!"); } public void shutdown(){ System.out.println("memory shutdown!"); } } public class Disk { public void startup(){ System.out.println("disk startup!"); } public void shutdown(){ System.out.println("disk shutdown!"); } } public class Computer { private CPU cpu; private Memory memory; private Disk disk; public Computer(){ cpu = new CPU(); memory = new Memory(); disk = new Disk(); } public void startup(){ System.out.println("start the computer!"); cpu.startup(); memory.startup(); disk.startup(); System.out.println("start computer finished!"); } public void shutdown(){ System.out.println("begin to close the computer!"); cpu.shutdown(); memory.shutdown(); disk.shutdown(); System.out.println("computer closed!"); }}User类如下:
public class User { public static void main(String[] args) { Computer computer = new Computer(); computer.startup(); computer.shutdown(); }}输出:
start the computer!
cpu startup!
memory startup!
disk startup!
start computer finished!
begin to close the computer!
cpu shutdown!
memory shutdown!
disk shutdown!
computer closed!
如果我们没有Computer类,那么,CPU、Memory、Disk他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了Computer类,他们之间的关系被放在了Computer类里,这样就起到了解耦的作用,这,就是外观模式!
10、桥接模式(Bridge)
桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。桥接的用意是:将抽象化与实现化解耦,使得二者可以独立变化,像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。我们来看看关系图:
实现代码:
先定义接口:
public interface Sourceable { public void method(); }分别定义两个实现类:
public class SourceSub1 implements Sourceable { @Override public void method() { System.out.println("this is the first sub!"); } } public class SourceSub2 implements Sourceable { @Override public void method() { System.out.println("this is the second sub!"); }}定义一个桥,持有Sourceable的一个实例:
public abstract class Bridge { private Sourceable source; public void method(){ source.method(); } public Sourceable getSource() { return source; } public void setSource(Sourceable source) { this.source = source; } } public class MyBridge extends Bridge { public void method(){ getSource().method(); }}测试类:
public class BridgeTest { public static void main(String[] args) { Bridge bridge = new MyBridge(); /*Calling the first object*/ Sourceable source1 = new SourceSub1(); bridge.setSource(source1); bridge.method(); /*Calling the second object*/ Sourceable source2 = new SourceSub2(); bridge.setSource(source2); bridge.method(); }}output:
this is the first sub!
this is the second sub!
这样,就通过对Bridge类的调用,实现了对接口Sourceable的实现类SourceSub1和SourceSub2的调用。接下来我再画个图,大家就应该明白了,因为这个图是我们JDBC连接的原理,有数据库学习基础的,一结合就都懂了。
11、组合模式(Composite)
组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图:
直接来看代码:
public class TreeNode { private String name; private TreeNode parent; private Vector<TreeNode> children = new Vector<TreeNode>(); public TreeNode(String name){ this.name = name; } السلسلة العامة getName () {return name ؛ } public void setName (اسم السلسلة) {this.name = name ؛ } public TreeNode getParent() { return parent; } public void setParent(TreeNode parent) { this.parent = parent; } //Add child node public void add(TreeNode node){ child.add(node); } //Delete child node public void remove(TreeNode node){ child.remove(node); } //Get child node public Enumeration<TreeNode> getChildren(){ return children.elements(); } } public class Tree { TreeNode root = null; public Tree(String name) { root = new TreeNode(name); } public static void main(String[] args) { Tree tree = new Tree("A"); TreeNode nodeB = new TreeNode("B"); TreeNode nodeC = new TreeNode("C"); nodeB.add(nodeC); tree.root.add(nodeB); System.out.println("build the tree finished!"); }}使用场景:将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
12、享元模式(Flyweight)
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight是超类。一提到共享池,我们很容易联想到Java里面的JDBC连接池,想想每个连接的特点,我们不难总结出:适用于作共享的一些个对象,他们有一些共有的属性,就拿数据库连接池来说,url、driverClassName、username、password及dbname,这些属性对于每个连接来说都是一样的,所以就适合用享元模式来处理,建一个工厂类,将上述类似属性作为内部数据,其它的作为外部数据,在方法调用时,当做参数传进来,这样就节省了空间,减少了实例的数量。
看个例子:
看下数据库连接池的代码:
public class ConnectionPool { private Vector<Connection> pool; /*Public properties*/ private String url = "jdbc:mysql://localhost:3306/test"; private String username = "root"; private String password = "root"; private String driverClassName = "com.mysql.jdbc.Driver"; private int poolSize = 100; private static ConnectionPool instance = null; اتصال conn = null ؛ /*Construct method, do some initialization work*/ private ConnectionPool() { pool = new Vector<Connection>(poolSize); for (int i = 0; i < poolSize; i++) { try { Class.forName(driverClassName); conn = DriverManager.getConnection(url, username, password); pool.add(conn); } catch (classNotFoundException e) {E.PrintStackTrace () ؛ } catch (SQLException e) { e.printStackTrace(); } } } /* Return to connect to the connection pool*/ public synchronized void release() { pool.add(conn); } /* Return a database connection in the connection pool*/ public synchronized Connection getConnection() { if (pool.size() > 0) { Connection conn = pool.get(0); pool.remove(conn); return conn; } else { return null; }}}通过连接池的管理,实现了数据库连接的共享,不需要每一次都重新创建连接,节省了数据库重新创建的开销,提升了系统的性能!本章讲解了7种结构型模式,因为篇幅的问题,剩下的11种行为型模式,
本章是关于设计模式的最后一讲,会讲到第三种设计模式――行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。这段时间一直在写关于设计模式的东西,终于写到一半了,写博文是个很费时间的东西,因为我得为读者负责,不论是图还是代码还是表述,都希望能尽量写清楚,以便读者理解,我想不论是我还是读者,都希望看到高质量的博文出来,从我本人出发,我会一直坚持下去,不断更新,源源动力来自于读者朋友们的不断支持,我会尽自己的努力,写好每一篇文章!希望大家能不断给出意见和建议,共同打造完美的博文!
先来张图,看看这11中模式的关系:
第一类:通过父类与子类的关系进行实现。第二类:两个类之间。第三类:类的状态。第四类:通过中间类
13、策略模式(strategy)
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。需要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(可有可无,属于辅助类),提供辅助函数,关系图如下:
图中ICalculator提供同意的方法,
AbstractCalculator是辅助类,提供辅助方法,接下来,依次实现下每个类:
首先统一接口:
public interface ICalculator { public int calculate(String exp); }辅助类:
public abstract class AbstractCalculator { public int[] split(String exp,String opt){ String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; }}三个实现类:
public class Plus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"//+"); return arrayInt[0]+arrayInt[1]; }} public class Minus extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"-"); return arrayInt[0]-arrayInt[1]; }} public class Multiply extends AbstractCalculator implements ICalculator { @Override public int calculate(String exp) { int arrayInt[] = split(exp,"//*"); return arrayInt[0]*arrayInt[1]; }}简单的测试类:
public class StrategyTest { public static void main(String[] args) { String exp = "2+8"; ICalculator cal = new Plus(); int result = cal.calculate(exp); System.out.println(result); }}输出:10
策略模式的决定权在用户,系统本身提供不同算法的实现,新增或者删除算法,对各种算法做封装。因此,策略模式多用在算法决策系统中,外部用户只需要决定用哪个算法即可。
14、模板方法模式(Template Method)
解释一下模板方法模式,就是指:一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法,通过调用抽象类,实现对子类的调用,先看个关系图:
就是在AbstractCalculator类中定义一个主方法calculate,calculate()调用spilt()等,Plus和Minus分别继承AbstractCalculator类,通过对AbstractCalculator的调用实现对子类的调用,看下面的例子:
public abstract class AbstractCalculator { /*Main method, implement calls to other methods of this class*/ public final int calculate(String exp,String opt){ int array[] = split(exp,opt); return calculate(array[0],array[1]); } /* Method rewritten by subclass*/ abstract public int calculate(int num1,int num2); public int[] split(String exp,String opt){ String array[] = exp.split(opt); int arrayInt[] = new int[2]; arrayInt[0] = Integer.parseInt(array[0]); arrayInt[1] = Integer.parseInt(array[1]); return arrayInt; } } public class Plus extends AbstractCalculator { @Override public int calculate(int num1,int num2) { return num1 + num2; }}测试类:
public class StrategyTest { public static void main(String[] args) { String exp = "8+8"; AbstractCalculator cal = new Plus(); int result = cal.calculate(exp, "//+"); System.out.println(result); }}我跟踪下这个小程序的执行过程:首先将exp和"//+"做参数,调用AbstractCalculator类里的calculate(String,String)方法,在calculate(String,String)里调用同类的split(),之后再调用calculate(int ,int)方法,从这个方法进入到子类中,执行完return num1 + num2后,将值返回到AbstractCalculator类,赋给result,打印出来。正好验证了我们开头的思路。
15、观察者模式(Observer)
包括这个模式在内的接下来的四个模式,都是类和类之间的关系,不涉及到继承,学的时候应该记得归纳,记得本文最开始的那个图。观察者模式很好理解,类似于邮件订阅和RSS订阅,当我们浏览一些博客或wiki时,经常会看到RSS图标,就这的意思是,当你订阅了该文章,如果后续有更新,会及时通知你。其实,简单来讲就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化!对象之间是一种一对多的关系。先来看看关系图:
我解释下这些类的作用:MySubject类就是我们的主对象,Observer1和Observer2是依赖于MySubject的对象,当MySubject变化时,Observer1和Observer2必然变化。AbstractSubject类中定义着需要监控的对象列表,可以对其进行修改:增加或删除被监控对象,且当MySubject变化时,负责通知在列表内存在的对象。我们看实现代码:
一个Observer接口:
public interface Observer { public void update(); }两个实现类:
public class Observer1 implements Observer { @Override public void update() { System.out.println("observer1 has received!"); } } public class Observer2 implements Observer { @Override public void update() { System.out.println("observer2 has received!"); }}Subject接口及实现类:
public interface Subject { /*Add observer*/ public void add(Observer observer); /*Delete observer*/ public void del(Observer observer); /*Notify all observers*/ public void notifyObservers(); /*Own operation*/ public void operation(); } public abstract class AbstractSubject implements Subject { private Vector<Observer> vector = new Vector<Observer>(); @Override public void add(Observer observer) { vector.add(observer); } @Override public void del(Observer observer) { vector.remove(observer); } @Override public void notifyObservers() { Enumeration<Observer> enumo = vector.elements(); while(enumo.hasMoreElements()){ enumo.nextElement().update(); } } } public class MySubject extends AbstractSubject { @Override public void operation() { System.out.println("update self!"); notifyObservers(); }}测试类:
public class ObserverTest { public static void main(String[] args) { Subject sub = new MySubject(); sub.add(new Observer1()); sub.add(new Observer2()); sub.operation(); }}输出:
update self!
observer1 has received!
observer2 has received!
这些东西,其实不难,只是有些抽象,不太容易整体理解,建议读者:根据关系图,新建项目,自己写代码(或者参考我的代码),按照总体思路走一遍,这样才能体会它的思想,理解起来容易!
16、迭代子模式(Iterator)
顾名思义,迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟悉的话,理解本模式会十分轻松。这句话包含两层意思:一是需要遍历的对象,即聚集对象,二是迭代器对象,用于对聚集对象进行遍历访问。我们看下关系图:
这个思路和我们常用的一模一样,MyCollection中定义了集合的一些操作,MyIterator中定义了一系列迭代操作,且持有Collection实例,我们来看看实现代码:
两个接口:
public interface Collection { public Iterator iterator(); /*get collection elements*/ public Object get(int i); /*get collection size*/ public int size(); } public interface Iterator { //Move forward public Object previous(); //Move backward public Object next(); public boolean hasNext(); //Get first element public Object first(); }两个实现:
public class MyCollection implements Collection { public String string[] = {"A","B","C","D","E"}; @Override public Iterator iterator() { return new MyIterator(this); } @Override public Object get(int i) { return string[i]; } @Override public int size() { return string.length; } } public class MyIterator implements Iterator { private Collection collection; private int pos = -1; public MyIterator(Collection collection){ this.collection = collection; } @Override public Object previous() { if(pos > 0){ pos--; } return collection.get(pos); } @Override public Object next() { if(pos<collection.size()-1){ pos++; } return collection.get(pos); } @Override public boolean hasNext() { if(pos<collection.size()-1){ return true; } آخر {return false ؛ } } @Override public Object first() { pos = 0; return collection.get(pos); }}测试类:
public class Test { public static void main(String[] args) { Collection collection = new MyCollection(); Iterator it = collection.iterator(); while(it.hasNext()){ System.out.println(it.next()); }}}输出:ABCDE
此处我们貌似模拟了一个集合类的过程,感觉是不是很爽?其实JDK中各个类也都是这些基本的东西,加一些设计模式,再加一些优化放到一起的,只要我们把这些东西学会了,掌握好了,我们也可以写出自己的集合类,甚至框架!
17、责任链模式(Chain of Responsibility)
接下来我们将要谈谈责任链模式,有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求,所以,责任链模式可以实现,在隐瞒客户端的情况下,对系统进行动态的调整。先看看关系图:
Abstracthandler类提供了get和set方法,方便MyHandle类设置和修改引用对象,MyHandle类是核心,实例化后生成一系列相互持有的对象,构成一条链。
public interface Handler { public void operator(); } public abstract class AbstractHandler { private Handler handler; public Handler getHandler() { return handler; } public void setHandler(Handler handler) { this.handler = handler; }} public class MyHandler extends AbstractHandler implements Handler { private String name; public MyHandler(String name) { this.name = name; } @Override public void operator() { System.out.println(name+"deal!"); if(getHandler()!=null){ getHandler().operator(); }}} public class Test { public static void main(String[] args) { MyHandler h1 = new MyHandler("h1"); MyHandler h2 = new MyHandler("h2"); MyHandler h3 = new MyHandler("h3"); h1.setHandler(h2); h2.setHandler(h3); h1.operator(); }}输出:
h1deal!
h2deal!
h3deal!
此处强调一点就是,链接上的请求可以是一条链,可以是一个树,还可以是一个环,模式本身不约束这个,需要我们自己去实现,同时,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
18、命令模式(Command)
命令模式很好理解,举个例子,司令员下令让士兵去干件事情,从整个事情的角度来考虑,司令员的作用是,发出口令,口令经过传递,传到了士兵耳朵里,士兵去执行。这个过程好在,三者相互解耦,任何一方都不用去依赖其他人,只需要做好自己的事儿就行,司令员要的是结果,不会去关注到底士兵是怎么实现的。我们看看关系图:
Invoker是调用者(司令员),Receiver是被调用者(士兵),MyCommand是命令,实现了Command接口,持有接收对象,看实现代码:
public interface Command { public void exe(); } public class MyCommand implements Command { private Receiver receiver; public MyCommand(Receiver receiver) { this.receiver = receiver; } @Override public void exe() { receiver.action(); } } public class Receiver { public void action(){ System.out.println("command received!"); } } public class Invoker { private Command command; public Invoker(Command command) { this.command = command; } public void action(){ command.exe(); } } public class Test { public static void main(String[] args) { Receiver receiver = new Receiver(); Command cmd = new MyCommand(receiver); Invoker invoker = new Invoker(cmd); invoker.action(); }}输出:command received!
这个很哈理解,命令模式的目的就是达到命令的发出者和执行者之间解耦,实现请求和执行分开,熟悉Struts的同学应该知道,Struts其实就是一种将请求和呈现分离的技术,其中必然涉及命令模式的思想!
其实每个设计模式都是很重要的一种思想,看上去很熟,其实是因为我们在学到的东西中都有涉及,尽管有时我们并不知道,其实在Java本身的设计之中处处都有体现,像AWT、JDBC、集合类、IO管道或者是Web框架,里面设计模式无处不在。因为我们篇幅有限,很难讲每一个设计模式都讲的很详细,不过我会尽我所能,尽量在有限的空间和篇幅内,把意思写清楚了,更好让大家明白。本章不出意外的话,应该是设计模式最后一讲了,首先还是上一下上篇开头的那个图:
本章讲讲第三类和第四类。
19、备忘录模式(Memento)
主要目的是保存一个对象的某个状态,以便在适当的时候恢复对象,个人觉得叫备份模式更形象些,通俗的讲下:假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。做个图来分析一下:
Original类是原始类,里面有需要保存的属性value及创建一个备忘录类,用来保存value值。Memento类是备忘录类,Storage类是存储备忘录的类,持有Memento类的实例,该模式很好理解。直接看源码:
public class Original { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public Original(String value) { this.value = value; } public Memento createMemento(){ return new Memento(value); } public void restoreMemento(Memento memento){ this.value = memento.getValue(); } } public class Memento { private String value; public Memento(String value) { this.value = value; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } } public class Storage { private Memento memento; public Storage(Memento memento) { this.memento = memento; } public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; }}测试类:
public class Test { public static void main(String[] args) { // Create original class Original origin = new Original("egg"); // Create memo Storage storage = new Storage(origi.createMemento()); // Modify the status of the original class System.out.println("Initialization status is: " + origini.getValue()); origini.setValue("niu"); System.out.println("Modified status is: " + origini.getValue()); // Reply to the original class's state origini.restoreMemento(storage.getMemento()); System.out.println("The state after recovery is: " + origini.getValue()); }}输出:
初始化状态为:egg
修改后的状态为:niu
恢复后的状态为:egg
简单描述下:新建原始类时,value被初始化为egg,后经过修改,将value的值置为niu,最后倒数第二行进行恢复状态,结果成功恢复了。其实我觉得这个模式叫“备份-恢复”模式最形象。
20、状态模式(State)
核心思想就是:当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。看图:
State类是个状态类,Context类可以实现切换,我们来看看代码:
package com.xtfggef.dp.state; /** * Core class of the state class* 2012-12-1 * @author erqing * */ public class State { private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public void method1(){ System.out.println("execute the first opt!"); } public void method2(){ System.out.println("execute the second opt!"); } } package com.xtfggef.dp.state; /** * Switching class for state mode2012-12-1 * @author erqing * */ public class Context { private State state; public Context(State state) { this.state = state; } public State getState() { return state; } public void setState(State state) { this.state = state; } public void method() { if (state.getValue().equals("state1")) { state.method1(); } else if (state.getValue().equals("state2")) { state.method2(); }}}测试类:
public class Test { public static void main(String[] args) { State state = new State(); Context context = new Context(state); //Set the first state.setValue("state1"); context.method(); //Set the second state.setValue("state2"); context.method(); }}输出:
execute the first opt!
execute the second opt!
根据这个特性,状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。
21、访问者模式(Visitor)
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。访问者模式适用于数据结构相对稳定算法又易变化的系统。因为访问者模式使得算法操作增加变得容易。若系统数据结构对象易于变化,经常有新的数据对象增加进来,则不适合使用访问者模式。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。―― From 百科
简单来说,访问者模式就是一种分离对象数据结构与行为的方法,通过这种分离,可达到为一个被访问者动态添加新的操作而无需做其它的修改的效果。简单关系图:
来看看原码:一个Visitor类,存放要访问的对象,
public interface Visitor { public void visit(Subject sub); } public class MyVisitor implements Visitor { @Override public void visit(Subject sub) { System.out.println("visit the subject:"+sub.getSubject()); }}Subject类,accept方法,接受将要访问它的对象,getSubject()获取将要被访问的属性,
public interface Subject { public void accept(Visitor visitor); public String getSubject(); } public class MySubject implements Subject { @Override public void accept(Visitor visitor) { visitor.visit(this); } @Override public String getSubject() { return "love"; }} امتحان:
public class Test { public static void main(String[] args) { Visitor visitor = new MyVisitor(); Subject sub = new MySubject(); sub.accept(visitor); }}输出:visit the subject:love
该模式适用场景:如果我们想为一个现有的类增加新功能,不得不考虑几个事情:1、新功能会不会与现有功能出现兼容性问题?2、以后会不会再需要添加?3、如果类不允许修改代码怎么办?面对这些问题,最好的解决方法就是使用访问者模式,访问者模式适用于数据结构相对稳定的系统,把数据结构和算法解耦,
22、中介者模式(Mediator)
中介者模式也是用来降低类类之间的耦合的,因为如果类类之间有依赖关系的话,不利于功能的拓展和维护,因为只要修改一个对象,其它关联的对象都得进行修改。如果使用中介者模式,只需关心和Mediator类的关系,具体类类之间的关系及调度交给Mediator就行,这有点像spring容器的作用。先看看图:
User类统一接口,User1和User2分别是不同的对象,二者之间有关联,如果不采用中介者模式,则需要二者相互持有引用,这样二者的耦合度很高,为了解耦,引入了Mediator类,提供统一接口,MyMediator为其实现类,里面持有User1和User2的实例,用来实现对User1和User2的控制。这样User1和User2两个对象相互独立,他们只需要保持好和Mediator之间的关系就行,剩下的全由MyMediator类来维护!基本实现:
public interface Mediator { public void createMediator(); public void workAll(); } public class MyMediator implements Mediator { private User user1; private User user2; public User getUser1() { return user1; } public User getUser2() { return user2; } @Override public void createMediator() { user1 = new User1(this); user2 = new User2(this); } @Override public void workAll() { user1.work(); user2.work(); } } public abstract class User { private Mediator mediator; public Mediator getMediator(){ return mediator; } public User(Mediator mediator) { this.mediator = mediator; } public abstract void work(); } public class User1 extends User { public User1(Mediator mediator){ super(mediator); } @Override public void work() { System.out.println("user1 exe!"); } } public class User2 extends User { public User2(Mediator mediator){ super(mediator); } @Override public void work() { System.out.println("user2 exe!"); }}测试类:
public class Test { public static void main(String[] args) { Mediator mediator = new MyMediator(); mediator.createMediator(); mediator.workAll(); }}输出:
user1 exe!
user2 exe!
23、解释器模式(Interpreter)
解释器模式是我们暂时的最后一讲,一般主要应用在OOP开发中的编译器的开发中,所以适用面比较窄。
The Context class is a context environment class. Plus and Minus are implementations used for calculations respectively. الرمز كما يلي:
public interface Expression { public int interpret(Context context); } public class Plus implements Expression { @Override public int interpret(Context context) { return context.getNum1()+context.getNum2(); } } public class Minus implements Expression { @Override public int interpret(Context context) { return context.getNum1()-context.getNum2(); } } public class Context { private int num1; private int num2; public Context(int num1, int num2) { this.num1 = num1; this.num2 = num2; } public int getNum1() { return num1; } public void setNum1(int num1) { this.num1 = num1; } public int getNum2() { return num2; } public void setNum2(int num2) { this.num2 = num2; } } public class Test { public static void main(String[] args) { // Calculate the value of 9+2-8 int result = new Minus().interpret((new Context(new Plus() .interpret(new Context(9, 2)), 8))); System.out.println(result); }}最后输出正确的结果:3。
基本就这样,解释器模式用来做各种各样的解释器,如正则表达式等的解释器等等!
原文链接:http://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.