# انفورماتيك | Informatic > معرفة عربية لمن يبني البرمجيات ويقود فرقها. مقالات في هندسة البرمجيات، والذكاء الاصطناعي، والقرارات التقنية. Public Ghost content for AI and LLM tooling. This file includes a bounded export of public pages first, then recent public posts. Append `.md` to any post or page URL to get the content in Markdown (for example, `/example-post.md`). ## Pages ### عن انفورماتيك URL: https://informatic-ar.com/about/ Last updated: 2026-10-04T07:02:07.000Z خلال العشرين سنة الماضية تغيرت البرمجة كثيراً، تغيرت الأدوات واللغات، وطريقة بناء الأنظمة وتشغيلها، وحتى شكل الفرق والأدوار داخلها. واليوم، مع دخول الذكاء الاصطناعي AI إلى العمل اليومي، تتغير مرة أخرى طريقة تحويل الأفكار إلى منتجات. ومع هذا التغير، نحتاج أن نفهم الخيارات المتاحة أمامنا، ومتى نختار كل خيار، وما الذي يمكن أن نتعلمه من تجارب الآخرين. لم يعد السؤال فقط كيف ننفذ الحل، بل لماذا نختاره، وما تكلفته، وكيف سيؤثر في النظام والفريق والمستخدم، وما الذي نفعله إذا لم يحقق النتيجة التي توقعناها. لهذا عدنا إلى انفورماتيك بهذا التوجه، مساحة عربية للتجارب الهندسية والرؤى والتحليل، نناقش فيها القرارات التي تواجه المطور والقائد التقني ومدير الهندسة وCTO، وما تعنيه هذه القرارات للمنتج والفريق ومستقبلهما. نكتب عن تجارب بناء الأنظمة وتشغيلها، وعن قيادة الفرق، واستخدام الذكاء الاصطناعي في العمل، والتغيرات في صناعة التقنية. نريد أن نفهم ما حدث، ولماذا حدث، وما الذي نجح أو لم ينجح، وفي أي ظروف يمكن الاستفادة من التجربة. المحتوى الجديد يركز على الخبرة وفهم الخيارات واتخاذ القرار، بدلاً من الشروحات المتتابعة لتنفيذ أداة أو كتابة كود. ونرحب بتجارب من فرق وشركات عربية، وبقراءات للتجارب العالمية توضح مصادرها وحدود ما نعرفه عنها. أما مقالات انفورماتيك السابقة، بما فيها شروحات البرمجة والمقالات التعليمية، فما زالت متاحة في [الأرشيف](https://informatic-ar.com/archive/) بروابطها وأسماء كتابها وتواريخها. تبقى هذه المقالات جزءاً من تاريخ المدونة، ويمكن الوصول إليها والبحث فيها كما اعتدتم. إذا كانت لديك تجربة تستحق المشاركة، أو قرار هندسي تريد مناقشته، أو ملاحظة على مقال، [تواصل معنا](https://informatic-ar.com/contact/). ### البرمجة والأدلة URL: https://informatic-ar.com/guides/ Last updated: 2026-10-03T20:48:36.000Z قراءات عملية من مكتبة انفورماتيك. اختر المسار الذي يخدم ما تبنيه، وتحقّق من إصدارات الأدوات قبل تنفيذ أمثلة الأرشيف. ## الحاويات والتشغيل - [مقدمة حول Docker](https://informatic-ar.com/docker-introduction/) - [البداية مع Docker](https://informatic-ar.com/getting-started-docker/) - [مشاركة المجلدات](https://informatic-ar.com/docker-volumes/) - [بناء الصور](https://informatic-ar.com/building-docker-images/) - [تشغيل البرامج باستخدام Docker Compose](https://informatic-ar.com/docker-compose/) ## تصميم الكود والبيانات - [Repository Pattern باستخدام Interface](https://informatic-ar.com/interface-repository/) - [الترابط والتماسك في تصميم البرمجيات](https://informatic-ar.com/coupling-and-cohesion/) - [التزامن في Entity Framework](https://informatic-ar.com/entity-framework-concurrency/) ## أساسيات تدوم - [تعقيد الخوارزميات](https://informatic-ar.com/big-o-complexity/) - [البحث الثنائي](https://informatic-ar.com/binary-search/) - [بروتوكول HTTP](https://informatic-ar.com/http-introduction/) ### للقادة التقنيين URL: https://informatic-ar.com/leadership/ Last updated: 2026-10-03T20:48:36.000Z مسار لمن يقود العمل الهندسي: القائد التقني، ومدير الهندسة، والمدير التقني. نبدأ بقراءات من الأرشيف تساعد على فهم تكلفة القرارات وتعقيدها. ## قراءات للبدء - [كيف يتحمل الموقع ملايين الزوار؟](https://informatic-ar.com/web-scalability/) - [بناء أنظمة قابلة للتوسع: بين النظرية والتجربة](https://informatic-ar.com/scalable-systems-in-practice/) - [كيف نفكّر في مشكلات الأداء؟](https://informatic-ar.com/performance-tuning/) - [مقدمة حول الخدمات المصغّرة](https://informatic-ar.com/microservices/) - [فهم تكلفة الترابط في البرمجيات](https://informatic-ar.com/coupling-and-cohesion/) - [الاستضافة والحوسبة السحابية](https://informatic-ar.com/hosting-and-cloud/) ## اتجاهنا القادم نعمل على توسيع هذا المسار ليشمل قرارات المعمارية، وجودة التسليم، وتطوير الفرق، واستخدام الذكاء الاصطناعي بمسؤولية في العمل الهندسي. تظهر المقالات الجديدة هنا عند نشرها. ### نشرة انفورماتيك URL: https://informatic-ar.com/newsletter/ Last updated: 2026-10-04T09:48:33.000Z قراءات مختارة في هندسة البرمجيات والقرارات التقنية وقيادة الفرق، تصل إلى بريدك عند إصدار النشرة. [اشترك في نشرة انفورماتيك](https://lists.informatic-ar.com/subscription/form?ref=informatic-ar.com) سنرسل رسالة لتأكيد بريدك قبل تفعيل الاشتراك. يمكنك إلغاء الاشتراك من الرابط في كل رسالة. لا نستخدم تتبعاً فردياً للقراءة. يمكنك أيضًا متابعة المقالات عبر [RSS](https://informatic-ar.com/rss/). ### عن المدونة URL: https://informatic-ar.com/about-blog/ Last updated: 2012-09-23T10:21:56.000Z أحد أسهل وأسرع الطرق الناجعة في التعلم هو مجالسة من هم أكثر خبرة منك، حقيقة هذا أفضل شيء تحصل عليه في عملك اذا كانوا متواجدين لديك، فهؤلاء الأشخاص قد مروا بكثير من التجارب وقاموا بعمل ما تريد عمله أو تعلم ما تريد أن تتعلمه. فاقضى اكبر وقت تستطيع مع هؤلاء، تعلم منهم من خلال مساعدتهم، واحصل ايضاً على مساعداتهم في عملك. هذا الوقت الذي قضيته مع هؤلاء سوف يفيدك جداً في مسيرتك العلمية وحتى في وظيفتك (بغض النظر عن نوع الوظيفة). لسوء الحظ هؤلاء الذين اتحدث عنهم (ال Pragmatic Programmers فهذه مدونة لل Developers) امثال Kent Beck و Martin Fwoler و Josha Block ومن وضعوا أسس البرمجة الحديثة (ان صح التعبير) لا يتواجدوا بكل مكان. لكن مؤلفاتهم وكتبهم موجودة ومتوفرة وحتى محاضراتهم اصبحت متوفر هذه الأيام.. لذلك رجاء لا تتوقف عن التعلم منهم، تعلم لغه جديدة في Paradiam مختلف، تعلم منهجية تطوير جديدة Process وخلال فترة تعلمك قم بايجاد اين يمكنك تطبيق هذه الأفكار خلال عملك اليومي. بقيامك بهذا الأمر فلن تحسن بيئة عملك فقط ولكن قمت بالأهم وهو تحسين نفسك أنت. هذه المدونة سوف تعرض الكثير من النصائح والارشادات السليمة في البرمجة، وحتى الادوات المستخدمة في التطوير، وكثير من الأفكار التي جربها الكاتب من هؤلاء المبرمجين وصلحت معه وأتت أكلها، لذلك يمكنك تجربتها في عملك ، وتعلم كيف تقوم بها بالشكل الصحيح فالاجابة السريعه السهله التي نسمعها دوماً هي لا (لا تصلح هذه الطريقة، دعنا نكن مثل ما نحن عليه،، الخ) لذلك اجتهد اكثر في ذلك، اذا لم تجد اي طريقة لتطبيقها فأسأل من هو اكثر خبرة منك، أقرأ مقالاً عنها ، ابحث اكثر وفي النهايه تعلم الاستفادة من خبرات الغير هو من صفات الشخص الناجح في عمله. هذه مدونة تقنية موجهه للمبرمجين ومهندسي البرمجيات (آاه ما الفرق بينهم ؟) ، وسوف تحتوى على مقالات متخصصه في لغات البرمجة وفي نظم التشغيل ، وبها مآرب أخرى (كـ Malware Analysis فهي هواية قبل كل شيء ). وقتاً سعيداً. ### خطواتك الأولى في دوكر URL: https://informatic-ar.com/first-steps-in-docker/ Last updated: 2026-10-03T22:32:36.000Z ![](https://informatic-ar.com/wp-content/uploads/2018/11/cover_small.png) # خطواتك الأولى في دوكر ![](https://informatic-ar.com/wp-content/uploads/2018/11/docker_website_cover.png) ## المقدمة تقنية الدوكر Docker تعتبر من التقنيات الحديثة نسبياً إلا أنها أحدثت ضجة كبيرة عند ظهورها، حيث عززت فكرة ال Containerization والتي تسمح لك بتشغيل البرامج (حالياً ال Server-Side Applications) بداخل Container تكون معزولة عن البرامج أو ال Containers الأخرى في نظام التشغيل، بالإضافة الى تقديم البرامج على شكل Image بدلاً من أن تكون بأشكال مختلفة، مثلاً بداخل ملف مضغوط أو Jar أو exe وغيرها. وهذه المفاهيم سهلت لنا طريقة تطوير البرمجيات، طريقة نشرها على السيرفرات المختلفة وحتى ال Clouds، طريقة تشغيلها بسهولة في بيئات مختلفة. ولأن تقنية الدوكر كبيرة وبها العديد من الجوانب فقد يكون من الصعب لأي مبتدئ أن يبدأ بها، فهناك مصادر تتحدث عنها ولكن باستخدام لغة أو تقنية معينة مثلاً ASP.NET & Docker أو Python & Docker وقد يكون من الصعب لمبتدئ لا يتقن تلك اللغة أن يستخدم هذه المصادر. وأحياناً تكون المصادر أيضاً موجهة بلهجة مدراء الأنظمة ويكون من الصعب مثلاً للمطورين البدء معها، أو تكون مفصلة للغاية بحيث تعرض كل التفاصيل والأوامر الدقيقة وقد لا تناسب من يريد البدء أو تجربة أول أمثلة بها. ومن هنا كانت فكرة الكتاب، حيث هو يمثل الخطوة الأولى لك في عالم الدوكر Docker، وسوف نستعرض فيه بعض المفاهيم الأساسية فيه، والفوائد منه، بحيث تكون مناسبة سواءً للمطورين بغض النظر عن لغة البرمجة وأيضاً لمدراء الأنظمة أو لأي شخص يعمل في مجال تقنية المعلومات. ## الكاتب في سطور ### وجدي عصام عبدالرحيم مهندس أنظمة ويعمل على تطوير البرمجيات منذ 8 سنوات في العديد من المجالات مثل تطبيقات الويب والموبايل، اهتماماته الحالية في تصميم الخدمات الالكترونية Microservices وحماية ال APIs، تحليل التطبيقات والبرامج الخبيثة، التشفير وتطبيقاته المختلفة. **هل أعجبك الكتاب؟** قم بزيارة موقع انفورماتيك بشكل مستمر، وسنقوم بوضع المزيد من المقالات في التقنيات الحديثة والبرمجة، ونقوم بتحويلها الى كتب مصغرة بعد أن تكتمل باذن الله. ![](https://informatic-ar.com/wp-content/uploads/2016/05/site_logo-1.jpg) ### تجربة URL: https://informatic-ar.com/legacy-layout-example/ Last updated: 2026-10-03T22:32:35.000Z ![](https://informatic-ar.com/wp-content/uploads/2021/09/mh-maj-ad-728.png) من المهم أن يكون الموقع جاذبا من الناحية البصرية فهو يساعد المتصفح على فهم المحتوى أكثر. ### تواصل معنا URL: https://informatic-ar.com/contact/ Last updated: 2026-10-04T09:07:10.000Z لديك سؤال، أو ملاحظة، أو تجربة تريد الكتابة عنها؟ يسعدنا أن نسمع منك. ### سياسة التحرير URL: https://informatic-ar.com/editorial-policy/ Last updated: 2026-10-04T06:43:53.000Z ## ما الذي ننشره؟ نركّز على التجارب المباشرة والتحليل والدروس المستفادة في هندسة البرمجيات وقيادة الفرق والذكاء الاصطناعي وصناعة التقنية. المقالات التعليمية السابقة متاحة في الأرشيف؛ النشر الجديد موجّه إلى فهم القرارات ونتائجها. ## السياق والأدلة توضّح مقالات التجربة المشكلة والقيود والبدائل والقرار والنتائج. نذكر طريقة القياس وحدودها عند الاستناد إلى أرقام، ونوضّح ما لم نختبره أو نتحقّق منه. ## المصادر ونوع المقال نميّز بوضوح بين تجربة الكاتب المباشرة، والمقابلات، والرأي، والتحليل المبني على مصادر منشورة. تُنسب الاقتباسات إلى أصحابها وتُربط المصادر حيثما أمكن. لا نقدّم تجربة منقولة على أنها تجربة شخصية، ولا ننشر نتائج مختلقة. ## الاستقلال والخصوصية نطلب الإفصاح عن الرعاية والعلاقات التي قد تؤثر في موضوع المقال. يجب الحصول على إذن قبل مشاركة معلومات سرية تخص عميلًا أو فريقًا، وحماية بيانات الأفراد عند عرض الأمثلة. ## المراجعة والتصحيح يبقى الكاتب مسؤولًا عن دقة المعلومات والمراجع، بما فيها المواد التي ساعدت أدوات الذكاء الاصطناعي في إعدادها. نرحّب بالتصحيحات، ونوضّح التصحيح عندما يغيّر استنتاجًا أو معلومة جوهرية. يمكن إرسال الملاحظات عبر [تواصل معنا](https://informatic-ar.com/contact/). ### الخصوصية URL: https://informatic-ar.com/privacy/ Last updated: 2026-10-04T20:45:58.000Z المقالات متاحة للقراءة دون إنشاء حساب. يحتفظ خادم الموقع بسجلات تشغيل للمساعدة في اكتشاف الأعطال وحماية الخدمة. نستخدم إحصاءات قراءة المقالات لمعرفة الصفحات الأكثر زيارة وتحسين الموقع. تُجمع بيانات الزيارة دون ملفات تعريف ارتباط، ولا نطلب اسم القارئ أو بريده لهذا الغرض. عند التواصل معنا بالبريد، نتلقى عنوانك ومحتوى الرسالة للرد على طلبك. وعند الاشتراك في النشرة، نطلب تأكيد بريدك قبل إرسال المقالات إليك، مع رابط لإلغاء الاشتراك في كل رسالة. تظهر أسماء كتّاب المقالات وسيرهم العامة بجوار أعمالهم. لطلب تصحيح بياناتك أو حذف اشتراكك، تواصل مع [hello@informatic-ar.com](mailto:hello@informatic-ar.com). ### تجارب هندسية URL: https://informatic-ar.com/section-engineering/ Last updated: 2026-10-04T06:43:50.000Z _No content available._ ### الذكاء الاصطناعي URL: https://informatic-ar.com/section-ai/ Last updated: 2026-10-04T06:19:27.000Z _No content available._ ### قيادة الفرق URL: https://informatic-ar.com/section-leadership/ Last updated: 2026-10-04T06:43:50.000Z _No content available._ ### صناعة التقنية URL: https://informatic-ar.com/section-industry/ Last updated: 2026-10-04T06:43:51.000Z _No content available._ ## Posts ### هل انتهى عصر البرمجة حقاً… أم بدأ عصر مطور المنتجات؟ URL: https://informatic-ar.com/ai-and-product-developer/ Last updated: 2026-10-04T09:12:46.000Z في عام 1911، قدم [Frederick Taylor](https://www.ibiblio.org/eldritch/fwt/t2.html?ref=informatic-ar.com) الأب الروحي لعلم الإدارة، فكرة ثورية أعادت تعريف العمل داخل المصانع، تقوم على فصل التفكير عن التنفيذ وتقسيم العمل إلى مهام صغيرة، بحيث تخطط الإدارة وينفذ العامل. هذا النموذج الذي أصبح لاحقاً أساس خطوط الإنتاج Production Lines نجح لأنه جاء في وقت كان فيه العامل يبحث عن الأمان والاستقرار أكثر من بحثه عن الإبداع، فحين تكون الأولوية لتأمين لقمة العيش وضمان دخل ثابت، قد يكون العمل بمهام واضحة وتعليمات محددة، مع ترك التخطيط واتخاذ القرار للإدارة، أقرب إلى ما يبحث عنه العامل، وهو ما يتماشى مع ما شرحه [Abraham Maslow](https://psychclassics.yorku.ca/Maslow/motivation.htm?ref=informatic-ar.com) في هرم ماسلو حيث يتقدم الأمان على غيره من الدوافع. لم يكن نموذج المصنع هو الأفضل بقدر ما كان الأنسب لحل مشكلة حقيقية في ذلك الوقت. ومع نجاح هذا النموذج داخل المصانع بدأت موجات متتالية من محاولات التحسين Continuous Improvement، لم يكن الهدف منها فقط زيادة الإنتاجية Productivity بل تقليل الاعتماد على العمال أنفسهم، فظهرت مفاهيم تحسين الجودة Quality Control وتقليل الهدر Lean Thinking، ثم توسعت الأتمتة Automation، لكن النتيجة لم تكن اختفاء دور الإنسان، بل انتقاله من التنفيذ إلى الفهم واتخاذ القرار Decision Making، ومع الوقت اتضح أن التحدي الحقيقي لم يكن في التنفيذ بقدر ما كان في التعقيد الكامن داخل الأنظمة Complexity. هذا النموذج انتقل لاحقاً إلى عالم البرمجيات Software Engineering، فتم تقسيم العمل إلى أدوار منفصلة Product Manager و Developer و QA و DevOps، ولكل منهم مهامه ومسؤولياته، ولم يكن هذا لأن النموذج مثالي، بل لأنه كان حلاً عملياً لمشكلة حقيقية، وهي بطء كتابة الكود وصعوبة تحويل الأفكار إلى أنظمة. كان التخصص Specialization أنسب وسيلة لتسريع العمل، لكنه في جوهره كان حلاً مؤقتاً workaround وليس نموذجاً نهائياً، يزداد تعقيداً مع زيادة عدد الفريق وارتفاع مستوى التنسيق Coordination بينهم، ويتحول جزء كبير من العمل إلى انتظار بين مراحل الربط Integration بين مخرجات الفرق المختلفة. ومع تطور البرمجة ظهرت محاولات متكررة لتقليل الحاجة إلى المطورين كما حدث في المصانع، بدءاً بلغة [COBOL](https://www.ibm.com/think/topics/cobol?ref=informatic-ar.com) التي وعدت الـ business بإمكانية بناء الأنظمة بدون الحاجة إلى المبرمجين، مروراً بـ CASE Tools، ثم Visual Basic وبيئات Drag & Drop، وصولاً إلى Low-Code و No-Code، وكلها وعدت بجعل البرمجة أسهل، لكن النتيجة كانت واحدة: لم تختف الحاجة إلى المطور، لأن المشكلة لم تكن في Coding بل في تصميم وهندسة الأنظمة System Thinking و Architecture Design والقرارات المرتبطة بها. هذا التخصص أدى أيضاً إلى إضعاف كثير من المبرمجين فنياً، فأصبح من الشائع أن يعمل Backend Developer دون فهم حقيقي للبنية التحتية Infrastructure، أو Frontend Developer دون فهم كاف لـ Data Modeling، وهو ما عزز النظرة إلى المبرمج كمنفذ أو عامل فقط يمكن استبداله بسهولة كأن البرمجيات هي خط إنتاج مثل المصانع، بينما البرمجيات بطبيعتها ليست كذلك، بل System مترابط، وكل قرار يؤثر في بقية الأجزاء. استمر هذا النموذج لسنوات طويلة، كنا عالقين في Syntax و Coding، نترجم الأفكار إلى أوامر، ونقضي وقتنا في الImplementation، وهذا ما جعلنا نعمل كطبقة ترجمة Translation Layer بين الفكرة والتنفيذ. لكن اليوم تغيرت المعادلة، الذكاء الاصطناعي AI ألغى عبء الكتابة وجعل الترجمة (تحويل الفكرة إلى أكواد) شبه مجانية، وأصبح بالإمكان بناء ما كان يستغرق أسابيع خلال ساعات، وهنا سقط السبب الذي بني عليه النموذج القديم. لكن المشكلة أعمق من ذلك، فمنذ ما عرف بأزمة البرمجيات Software Crisis، كان واضحاً أن التحدي الحقيقي هو [Complexity وليس Coding](https://worrydream.com/refs/Brooks%5F1986%5F-%5FNo%5FSilver%5FBullet.pdf?ref=informatic-ar.com)، ومع إزالة عائق الكتابة أصبح هذا التعقيد أكثر وضوحاً، حيث يمكن اليوم توليد كميات كبيرة من الكود بسرعة، لكن بدون نفس القدرة على فهمه أو التحكم فيه. وهذا ما عبر عنه Fred Brooks في مقالته [No Silver Bullet](https://worrydream.com/refs/Brooks%5F1986%5F-%5FNo%5FSilver%5FBullet.pdf?ref=informatic-ar.com) بقوله: > The hard thing about building software is deciding what to say, not saying it. أي أن الصعوبة في بناء البرمجيات تكمن في تحديد ما نريد قوله، وليس في قوله. وهنا يظهر التحول الحقيقي، لم يعد النجاح في التخصص الواحد، بل في القدرة على الجمع بين العمق المعرفي والمهارات المختلفة Deep skills و Broad skills، لم يعد المطلوب مختص يعرف جزءاً واحداً فقط، بل Product Developer يفهم النظام بعمق، ويرى الصورة كاملة، يتعاطف مع المستخدم، يمتلك حساً تصميمياً، يستطيع التواصل مع أصحاب المصلحة stakeholders، يلاحظ ويحلل، يفهم المشكلة Problem Space، يصمم الحل، يبنيه، يطلقه، ويراقب نتائجه عبر Metrics و Logs و Feedback. وهذا هو الفرق، لم يعد المطلوب أن تعرف كيف تكتب كل شيء، بل أن تعرف ماذا يجب أن يبنى، وكيف تتأكد أنه يعمل، وكيف تطوره وتشغله وتعمل على تحسينه باستمرار. الخطر الآن هو لكل من يقتصر دوره على التنفيذ، سواء كان Coder أو Analyst، فهذه الأدوار بالشكل التقليدي لم تعد آمنة، لأن AI اليوم قادر على تنفيذ جزء كبير منها، والاستمرار في نفس هذا الدور دون تطوير سيؤدي إلى الاستبدال، والمشكلة ليست في AI، بل في محاولة البقاء في نفس النموذج القديم. انتهى عهد ما كنا نسميه بالمبرمج Coder الذي يكتب الكود فقط وينتظر Jira Ticket، ثم ينتظر QA ليخبره إن كان الكود قد اجتاز الاختبارات أم لا، هذا الدور لم يعد كافياً اليوم، لأن القيمة لم تعد في التنفيذ، بل في اتخاذ القرار Decision Making، وفهم ما يجب بناؤه، وتحويله إلى أنظمة فعالة تخدم المستخدمين وتحمل المسؤولية عن نتائجها. لطالما كان هذا هو الجزء الأصعب، واليوم أصبح هو الجزء الوحيد المهم. ### الاستضافة والحوسبة السحابية Hosting and Cloud URL: https://informatic-ar.com/hosting-and-cloud/ Last updated: 2026-10-03T22:31:09.000Z عندما تقوم بزيارة أي موقع في الانترنت من جهازك الشخصي او الجوال، فالمتصفح Browser (أو التطبيق) الذي تستخدمه سوف يقوم بطلب الصفحة المطلوبة من الجهاز (السيرفر Server) الذي يحتوي ذلك الموقع. وهكذا فكل مواقع الانترنت والتطبيقات التي نزورها يومياً متواجدة على أجهزة (سيرفرات) في منطقة ما في الكرة الأرضية. هذه الأجهزة تسمى أجهزة استضافة Hosting، وأي جهاز حاسب مهما كان صغيراً أم كبيراً يمكن أن يكون Hosting، طالما توفرت فيه بعض الأمور (مثل برنامج Web Server وبعض الأشياء الأخرى) لكن أهم هذه الأمور هو اتصال انترنت سريع دائم لا ينقطع أبداً، فبكل تأكيد أنت تريد الموقع يعمل 24 ساعة، ولا يغلق أو ينطفئ عندما تذهب للنوم. غالب الظن فأنت لن تقوم باستضافة المواقع داخل حواسيبك الشخصية وإنما سوف تقوم بتأجير خدمة متواجدة على الانترنت (السحابة) للقيام بذلك، تكون أجهزتها سريعة ولديها مواصفات عالية، وسوف توفر على نفسك وجود اتصال عالي السرعة بشكل دائم، وأيضاً بعض الكهرباء إذا كنت من النوع الذي يطفئ أجهزتك قبل النوم. شركات الاستضافة من الجانب الآخر، لن تقوم بشراء حواسيب شخصية وتوصلها بالإنترنت لكي توفر استضافة لك ولأي عميل آخر، وانما سوف تقوم بجلب أجهزة مخصصة بسعات عالية حتى تستوعب استضافة عشرات العملاء، هذه الأجهزة غالباً ما تكون Racks لا توجد بها لوحة مفاتيح متصلة بها ولا حتى فأرة، وأيضاً نظام التشغيل بها غالباً لا توجد به واجهات رسومية فهي غير ضرورية وأيضاً الكثير من البرامج الأخرى كالألعاب وغيرها. بعد أن تشترك مع أحد شركات الاستضافة، فقد تعطيك لوحة تحكم لإدارة تلك المساحة، وأيضاً قد توفر لك إمكانية الدخول لملفاتك على تلك الاستضافة من خلال برامج نقل الملفات File Transfer Protocol واختصاراً FTP. وهذه البرامج تسهل نقل الملفات من جهازك الى المساحة الخاصة بك من خلال واجهات رسومية سهل العمل عليها. # معايير اختيار شركة الاستضافة إذا بحثت عن شركات استضافة Hosting Companies فسوف تجد عشرات الشركات سواءً المحلية او العالمية، وقد يكون الاختيار صعباً، لذلك يجب أن تراعي النقاط التالية: - **نظام التشغيل وبرنامج الاستضافة Server Type**: هناك سيرفرات تعمل على نظام التشغيل لينوكس، وسيرفرات تعمل على نظام التشغيل ويندوز، وأيضاً هناك عدة اختيارات من ال Web Servers (البرامج التي تتعامل مع موقعك مثل الأباتشي أو IIS) هذه التفاصيل قد لا تهم زائر الموقع، لكنها تهم من سوف يعمل على إدارة السيرفر وطبيعة الموقع المراد استضافته على السيرفر. - **مساحة القرص Disk Space**: حجم ملفات الموقع وقاعدة البيانات تحددان المساحة المطلوبة التي سوف تحتاجها في السيرفر. - **حجم البيانات المتبادلة Bandwidth**: عندما يزور أي مستخدم موقعك فسوف يقوم المتصفح بطلب ملفات من السيرفر ويعرضها في الشاشة عندما تصل هذه الملفات، كمية البيانات المتبادلة بين جهاز المستخدم والسيرفر تسمى ال Bandwidth، وكلما زاد عدد المستخدمين للموقع، كلما زاد الطلب على ملفات موقعك (الصور، الملفات، الخ) وهكذا سوف يزيد حجم البيانات المتبادلة. وبعض شركات الاستضافة لديها أسعار متفاوتة بحسب حجم هذه البيانات، وهذا منطقي لأنه بالتأكيد سوف يكون مكلفاً خدمة ملايين الزوار مقارنة بعشرات فقط. - **الموثوقية والجاهزية Reliability and Uptime**: تأكد من أن شركة الاستضافة أن لديها أجهزة متصلة بالإنترنت بدون أي انقطاع نهائياً (24 ساعة في اليوم، 7 أيام في الأسبوع، 365 يوم في السنة). - **السعر Price**: تختلف الأسعار من شركة لشركة، من دولة لأخرى، لذلك احرص على تفحص عدة عروض واختيار الأنسب لك. - **البريد الالكتروني Email Addresses**: بعض شركات الاستضافة تزودك ببريد الكتروني بالدومين الخاص بك ([youname@domain.com](mailto:youname@domain.com)) وبعضها لا يقدم وعليك استخدام حلول أخرى (مدفوعة، مجانية، مفتوحة المصدر الخ). # أنواع شركات الاستضافة Hosting Types هناك عدة أنواع من شركات الاستضافة وهي: ### **الاستضافة المشتركة Shared Hosting** وهو غالباً سيرفر واحد يتم وضع عليه أكثر من عميل (موقع) ولذلك يسمى مشترك Shared. وسوف تقوم شركة الاستضافة بتقسيم السيرفر وحجز مساحة لكل عميل بشكل منفصل (بدون تضارب بينهم). ولأن السيرفر مشترك، فهو يكون على نظام تشغيل معين لا تستطيع تغييره او ترقيته، وأيضاً به مجموعة من البرامج الضرورية لعمل المواقع، ولا تستطيع تنزيل برامج على ذلك السيرفر، سوى استخدام المتواجد. عموماً هذا النوع هو الأرخص سعراً (والأقل مرونة) وهو خيار مناسب للمواقع الشخصية أو التي لا تتغير مع مرور الوقت، وبعضها قد يكون مجاني لفترة محدودة ولكن ملئ بالإعلانات. ### **سيرفر خاص Dedicated Server** وهو كما يتضح من الاسم سوف يتم حجز سيرفر كامل لك، وهذا سوف يكون مكلفاً، لكن لديك الحرية لعمل أو تغيير اي برامج فيه، ولا يوجد مواقع أخرى معك على هذا السيرفر. هذا النوع يناسب في حال كان لديك عدة مواقع تريد استضافتها في سيرفر واحد أو في حال أردت أداء عالي وعزل عن أي استخدام لسيرفرك بشكل كامل. ### **سيرفر افتراضي تخيلي Virtual Dedicated Server** وهذا النوع يستفيد من تقنية ال Virtualization، والتي تمكن تنزيل أنظمة تشغيل مختلفة على نفس السيرفر وتكون معزولة كلياً عن بعضها البعض، وفي هذا النوع فعلياً هناك سيرفر واحد مشترك، ولكن بداخله هناك أنظمة تخيلية معزولة، وهكذا دمجت بين فائدة السعر الأقل (لأنه السيرفر مشترك)، وأيضاً فائدة العزل (لأنه معزول تقنياً)، وتستطيع تخصيص وتنزيل البرامج التي ترغب بها على سيرفرك الوهمي. ### **الاستضافة المجمعة Collocated Hosting** وفي هذا النوع سوف تملك السيرفر لكن سوف تفوض ادارته لدى مزود الخدمة (بسبب توفر كافة الأدوات لديه) وسوف تقوم بدفع تكاليف استخدام البيانات Bandwidth بالإضافة الى رسوم الصيانة والتبريد وخلافه. # السحابة The Cloud فكرة ال Cloud ليست جديدة أو وليدة اليوم، وانما كفكرة فهي بدئت منذ 1950م حيث كانت الأجهزة العملاقة Mainframe باهظة الثمن، ومتوفرة للحكومات وبعض الجامعات العريقة، وبدلاً من أن يعمل المستخدمين على تلك الأجهزة بشكل مباشر، فهم يتصلوا بها من خلال شاشات عرض نصية Terminal لكي يصلوا الى كل ملفاتهم وبرامجهم. وهكذا كل شيء موحد في ذلك الجهاز العملاق. وبعدما حصلت الثورة الصناعية الثالثة وأصبحت أسعار الحواسيب قليلة انتشرت الحواسيب الشخصية PC. فلماذا نستخدم شاشات طرفية للوصول الى أجهزة عملاقة بدلاً من أن نستخدم هذه الأجهزة الشخصية ونضع بها كل ملفاتنا وبرامجنا، ونحملها لأي مكان. ومع تقدم تقنية الشبكات والقدرات الحسابية للمعالجات، وطبيعة الأعمال التي تتطلب تواجد البيانات من أي مكان وجهاز، فأصبحت عملية نقل الخدمات من الأجهزة الشخصية الى الانترنت أمر مغري، مثلاً بدلاً من تخزين الملفات على حاسوبك الشخصي فقط، يمكنك نقل الملفات لخدمة الملفات عبر الانترنت وبالتالي تستفيد من العديد من المزايا. وهكذا ظهر مفهوم السحابة بشكله الحالي، وهو عبارة عن نفس المودل القديم ولكن بطريقة مختلفة، فبدلاً من أن تخزن الملفات على جهاز عملاق واحد، فسوف يتم استخدام شبكة من الأجهزة الموزعة عن بعد والمتصلة في آن واحد. وبالتالي نستطيع القول بأن الحوسبة السحابية Cloud Computing هي مجموعة من الأجهزة المتصلة في شبكة كبيرة بحيث تكون لا مركزية ولكنها في نفس الوقت قادرة على التفاعل بشكل تعاوني في الانترنت، والتي يمكن الوصول واستخدام المعلومات والبرمجيات بها بواسطة الأجهزة سواءً المحمولة او الهواتف النقالة في أي وقت. وفي حال قال أحدهم "أخزن ملفاتي على الكلاود" أو "وصلت للملفات عبر الكلاود" فهذا يعني انه من خلال الانترنت قام بالوصول الى خدمة الملفات والتي يضع بها ملفاته الشخصية. وهكذا تخزن الكلاود أي معلومات تريدها، سواءً صور، ملفات مسموعة، مواقع انترنت وبرمجيات وغيرها. اذاً الحوسبة السحابية هو عمل كل شيء كنا نعمله في السابق على أجهزتنا الشخصية (البرامج، التخزين، المعالجة) ولكن بفضل سرعات الانترنت المتزايدة، أصبح الآن بإمكاننا عمله في مجموعة من الأجهزة المتصلة ببعض ما عبر الشبكة بكل سهولة. وبحسب تعريف NIST فان ال Cloud Computing Model هو "المنظومة التي يمكن الوصول اليها عبر الشبكة في أي وقت وأي مكان، وهي مكونة من مجموعة من الموارد المشتركة القابلة للإعداد والتخصيص (مثل الشبكات، الخوادم، التطبيقات، مساحات التخزين، الخدمات) والتي يمكن طلبها وانشائها بأقل جهد إداري أو يدوي من مزود الخدمة" ويتكون النمط بحسب تعريف NIST الى 5 خصائص أساسية Essential Characteristics، 3 نماذج عمل Service Models، و4 طرق للوصول Deployment Model، كما يلي: ## **خصائص أساسية Essential Characteristics** ### **خدمات ذاتية عند الطلب On-Demand Self-Service** بحيث يستطيع المستخدم طلب انشاء أي من الموارد (الشبكات، الخوادم الخ) بشكل تلقائي بدون أي تدخل بشري أو مع مقدم الخدمة. ### **الوصول عبر الشبكة Broad Network Access** بحيث يستطيع المستخدم الوصول لهذه الموارد عبر الشبكة بسهولة من خلال الأنظمة المختلفة (أجهزة جوال، حواسيب، حواسيب محمولة، سيرفرات وغيرها) ### **تجميع الموارد Resource Pooling** بحيث يستطيع مزود الخدمة خدمة العديد من المستخدمين سواءً كانت موارد افتراضية Virtual او فيزيائية Physical ويقوم بتخصيصها او الغائها بحسب رغبة المستخدم، وكما يمكن للمستخدم تحديد الموقع الجغرافي لذلك المورد (مثلاً المدينة أو الولاية). ### **المرونة العالية Rapid Elasticity** بحيث يمكن طلب أو الغاء هذه الموارد بسهولة، وايضاً زيادة قدرتها أو تقليلها Scaling up or down بحسب رغبة المستخدم، بحيث يستطيع طلبها بأي كمية يرغب بيها في الوقت الذي يريده. ### **خدمة قابلة للقياس Measured Service** الأنظمة السحابية تتحكم وتحسن هذه الموارد وذلك عن طريق قياس الاستهلاك المناسب مع نوع المورد (مثلاً مساحات التخزين، حجم البيانات المستهلكة Bandwidth، عدد المستخدمين المفعلين) ويتم مراقبة هذه التفاصيل وابلاغها للمستخدم، بحيث يوفر شفافية للطرفين سواءً المستخدم أو مزود الخدمة، بالإضافة الى تسعير الخدمة بحسب الاستخدام Pay as you Go، وليس كما هو كان في السابق عند تنزيل البرمجيات على النظام، فسوف تشتري البرنامج حتى إذا لم تستخدمه بشكل يومي. ## **نماذج عمل Service Models** ### **البرمجيات كخدمة Software as a Service SaaS** حيث يستطيع مزود الخدمة توفير برمجيات للمستخدم تعمل على البنية السحابية التي يمتلكها مزود الخدمة، وهذه التطبيقات يمكن الوصول لها من خلال واجهات متعددة خفيفة Thin Interface كمتصفح الويب او برمجيات خاصة للوصول. وفي نفس الوقت لا يمكن للمستخدم التحكم وإدارة البنية السحابية التي تزوده بهذه البرمجيات (كالشبكات، الخوادم، أنظمة التشغيل، مساحات التخزين) ولا ايضاً خصائص البرمجيات الا التي تكون خاصة للمستخدم. على سبيل المثال خدمات التخزين السحابي ك Dropbox تصنف تحت هذا النوع. ### **النظام كخدمة Platform as a Service PaaS** يوفر مزود الخدمة الأدوات التي تمكن المستخدم من نشر Deploy البرمجيات التي قام بإنشائها في البنية السحابية لدى مزود الخدمة، وتكون هذه البرمجيات متوافقة ومدعومة من مزود الخدمة من ناحية لغات البرمجة، الأدوات والمكتبات المستخدمة، الخدمات التي يستخدمها البرنامج. ولا يمكن للمستخدم إدارة او التحكم بالبنية السحابية (مثلاً السيرفرات، مساحات التخزين، نظام التشغيل) لكن لديه القدرة على التحكم على برمجياته التي قام بنشرها، وايضاً بعض الإعدادات والمتغيرات التي يحتاجها البرنامج. على سبيل المثال منصة Heroku. ### **البنية التحتية كخدمة Infrastructure as a Service IaaS** يوفر مزود الخدمة الأدوات التي تمكن المستخدم من انشاء الخدمات، الشبكات، مساحة التخزين، المعالجات الحسابية، والتي يقوم فيها بعد بتشغيل برمجياته بها كما يرغب. ولا يمكن للمستخدم إدارة او التحكم بالبنية السحابية ولكن يستطيع التحكم بنظام التشغيل، مساحة التخزين، والبرامج التي تعمل، بالإضافة الى تحكم محدود في مكونات الشبكة مثلاً الجدار الناري الخاص بالموارد التي يملكها. على سبيل المثال Amazon AWS. ## **طريقة الوصول Deployment Models** ### **السحابة الخاصة Private Cloud** البنية الحسابية تكون داخلية لجهة واحدة On Premises وتخدم العديد من الوحدات الداخلية أو الإدارات، وعادة ما تكون مملوكة وتدار بواسطة نفس الجهة، او مزود خارجي Third Party، او الاثنان معاً. ### **السحابة لمجموعة Community Cloud** وهنا تكون البنية السحابية مملوكة وتدار بواسطة مجموعة من الجهات لديهم اهتمامات او أدوار مشتركة مثلاً بعض متطلبات الأمان Security Requirements أو بسبب الالتزام بأحد المتطلبات Compliance. ### **السحابة العامة Public Cloud** وتكون هذه البنية السحابية متاحة للجميع، وهي مملوكة وتدار بواسطة جهة معينة (شركة، جامعة، جهة حكومية). ### **السحابة الهجينة Hybrid Cloud** وهي التي تخدم أكثر من طريقة (خاصة، عامة، لمجموعة) معاً، ولا يكون فيها تطبيقات او برامج، وانما تربط بين ال Private مع ال Public بحيث تعمل معاً في آن واحد، مثلاً تقوم بتشغيل قاعدة البيانات في ال Private Cloud وتقوم بتشغيل الموقع على ال Public Cloud، ويتواصلوا فيما بينهم. ![](https://informatic-ar.com/wp-content/uploads/2021/11/Clouds-300x168.png) # فوائد الحوسبة السحابية Cloud Computing - **القدرة على الوصول والاستقلالية Accessibility and Independence**: حيث يمكنك الوصول الى البرمجيات أو البيانات من أي جهاز متصل بالشبكة، وبالتالي لن تحتاج الى الاعتماد على أجهزة معينة، فكل شيء متواجد على هناك. - **النسخ الاحتياطي Backup**: لأن ملفاتك محفوظة هناك، فلن تحدث أي مشاكل في حال تعطل جهازك أو اللابتوب الشخصي، فالملفات ستكون بأمان - **الأسعار بحسب الاستخدام Prorated Pricing**: فالأسعار عادة تكون مختلفة وذلك بحسب كمية وحجم البيانات المستخدمة. - **تستطيع استخدامها من أي أجهزة بمواصفات صغيرة Lower Hardware Costs**: فكل الحسابات والمعالجات تجري على الأجهزة المتواجدة في السحابة، وبالتالي لن تحتاج لأجهزة حاسب سريعة أو مكلفة، وانما يكفي أي جهاز متصل بالإنترنت بسرعة معقولة. - **أداء أفضل للأجهزة Better Performance**: حيث لن تعد تحتاج لتحميل العديد من البرامج والملفات على جهازك، وهذا يعني أن أداء جهازك سوف يكون أفضل. - **مشاكل صيانة أقل Fewer Maintenance Issues**: لأن البرمجيات سوف تكون في السحابة، فبالتالي لن تحدث مشاكل تنصيب البرمجيات او التعارض مع البرامج الأخرى - **استخدام النسخ المحدثة Access to Latest Version**: فعندما تستخدم الموقع الخاص بالخدمة او البرنامج سوف تحصل على النسخة الأخيرة المحدثة دائماً. - **سهولة العمل مع المجموعات Easier Group Collaboration**: فعندما يتعامل الجميع مع السحابة، فيمكن تبادل الملفات وتشاركها بشكل أكثر كفاءة، من خلال الروابط والمشاركة التي توفرها منصات التخزين السحابية. # عيوب الحوسبة السحابية قد لا يكون الحوسبة السحابية مناسباً للحالات التالية: - **مشاكل الانترنت Internet Issues**: إذا انقطع الاتصال بالإنترنت او لم يكن الاتصال جيداً فلن تستطيع الوصول الى ملفات او برامجك، وبعض الخدمات السحابية تمكنك من تحميل البيانات الى جهازك، ولكن بعضها قد يحاسبك مادياً على ذلك. - **مشاكل الأمان والخصوصية Security and Privacy**: الخدمات السحابية أصبحت آمنة، ولكن بعض الشركات لديها مخاوف من نقل البيانات عبر الانترنت، فهل هذه البيانات سوف تكون في مأمن تماماً مع عدم وجود إمكانية الاطلاع عليها، خصوصاً مع تزايد الهجمات والاختراقات عموماً. # خاتمة تنوع وسهولة خدمات الاستضافة ساهم في انتشار المواقع على الانترنت بشكل كبير، بحيث يستطيع رواد الأعمال الاستفادة من هذه الخدمات بأقل تكاليف، وفي نفس الوقت يتجه المستخدمين لتخزين بياناتهم على الخدمات السحابية. ### عمل ال Repository Pattern باستخدام ال Interface URL: https://informatic-ar.com/interface-repository/ Last updated: 2026-10-03T22:31:13.000Z هذه المقالة ستبين كيف يمكن أن تستخدم ال Interface في جعل الكود قابل للتطوير بسهولة Extensibility، وسوف نأخذ مثال على ال Repository Pattern. في هذه السلسلة سوف نوضح ما يلي: - [الفرق بين ال Abstract Class وال Interface](https://informatic-ar.com/interface-vs-abstract-classs/) - [دور ال Interface في قابلية الصيانة Maintainbility](https://informatic-ar.com/interface-maintainability/) - عمل ال Repository باستخدام ال Interface (المقالة الحالية) - ما هو ال Dynamic Factory ## **مثال عملي يوضح فائدة ال** **Interface** مثلاُ لدينا تطبيق نريد تشغيله لدى أكثر من جهاز، وكل جهاز سوف يتم تخزين/قراءة البيانات بطريقة مختلفة (سواء في قاعدة بيانات مختلفة أو في مكان مختلف) أي different data store، وبالتالي نريد بناء التطبيق بحيث يعمل مع هذه ال data store / storages المختلفة الصورة التالية تبين انواع ال data stores التي يمكن أن تستخدم، بدئاً من قواعد البيانات العلائقية وهناك عدة انظمة فيها، ومروراً بقاعدة بيانات ال NO SQL والملفات النصية وبخدمات الويب سواء SOAP أو RESTful وانتهاءً بتخزين البيانات على ال Cloud، وكل هذه انواع من طرق التخزين التي يمكن تستخدم. ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-72d6253c2909a117c7e133c5747492c8997d78e85c5317f96024a6a88ce8ded1.png) البرنامج سوف يقرأ معلومات المستخدمين Persons وهي الاسم الأول والثاني وبضعه معلومات لكل مستخدم، بالإضافة إلى امكانية اضافة مستخدم جديد، حذف مستخدم، تحديث بيانات مستخدم. هذا العمليات تسمى CRUD وسوف نبين هذا الاختصار بعد قليل. **حالياً للتسهيل سوف يكون المطلوب أن يعمل البرنامج على ثلاثة** **أنواع من ال** **data sources:** - قاعدة بيانات SQL Server - ملفات نصية من نوع CSV - خدمات الويبمن نوع SOAP بدون استخدام أي تصميم للكود، فيمكن كتابته مباشرة عن طريق جمل ال If وفحص الطريقة المطلوبة بطريقة تشابه ما يلي ```csharp public IEnumerable ReadData(int source) { List persons = new List(); if (source == 1) { // Read from SQL Server Database } else if (source == 2) { // Read from CSV File } else if (source == 3) { // Read from SOAP Web Service } return persons; } ``` بعد أن قمت بعمل كلاس يمثل ال Person واتبعت نصيحة Programming to Abstraction قمت بعمل ارجاع لل IEnumerable، ولكن داخل الدالة قمت بعمل فحص للقيمة التي تقرأها من المستخدم أو من ملف خارجي وتقوم على اساسها بقراءة البيانات من المكان المطلوبة. الكود السابق به العديد من المشاكل: - في حال اضفت أي source جديد سوف تحتاج تضيفها في هذه الدالة، وتضيفها ايضاً في بقية الدوال التي تخزن وتحذف وتحدث لأنها سوف يكون بها نفس الشرط وسوف تكون مكررة في مواضع أخرى. - الدالة سوف تصبح كبيرة وبالتالي لها مسؤوليات كثيرة الحل المناسب والذي يعد من أسهل طريقة للعمل مع عدة data sources مختلفة هو باستخدام ال pattern المعروف بالاسم Repository Pattern والذي يستخدم ال interface بشكل اساسي. # **ال** **Repository Pattern** وهو من طرق التصميم المعروفة Design Pattern ويستخدم لأضافه طبقة من ال Layer of Abstraction، وهذا تعريفه من الكتاب المعروف [Patterns of Enterprise Application Architecture](http://martinfowler.com/books/eaa.html?ref=informatic-ar.com) للمؤلف Martin Fowler: > Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects. **لتبسيط التعريف:** وهو طبقة وسيطة بين البرنامج وآلية التخزين، وبالتالي كود التطبيق يتعامل مع هذه الطبقة بدلاً من أن يعتمد على الية التخزين مباشرة والتي يمكن أن تتغير، كما في الصورة التالية سوف تجد أن طبقة التخزين Repository الآن وسيطة بين الكود وبين قاعدة البيانات. ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-67b797e4dd77600d5ca7106c3460262748d8d336313da3a796098838b559f25f.png) فنحن لا نريد التطبيق أن يتصل مباشرة بالقاعدة أو Data source (لأن في تلك الحالة التطبيق يجب أن يعرف كيف سيتعامل مع نوع ال data source المعين مثلاً عمل SQL Query أو القيام بال Web service Call)، وبدلاً من ذلك سوف نضيف طبقة Layer بين التطبيق وال data source وهذه هي ال Repository، وسوف يتصل التطبيق بها بدون أن يعرف كيف ستتعامل ال repository مع ال data source، بعباره أخرى التطبيق فقط سوف ينادي ال contract المطبق في ال repository وهو لا يهتم بأي Implementation. عملياً قد تكون هناك layers بين ال Application (وهي هنا UI Layer) وال Repository (مثلاً domain layer أو BLL) وهي تتعامل مع ال Repository ولكن للتبسيط ولتوضيح فائدة ال interface في ال Extensibility سوف تعمل ال Application مباشرة مع ال Repository ونتجاهل بعض ال Layers، والفصل الخامس سوف نأخذ مثالاً على ال Layers. اذاً: التطبيق سوف يعتمد على ال interface وهو يتوقع أي Implementation موجود أن يطبقها قبل أن يتعامل معه، بغض النظر كونه: - WCF Service Repository للتعامل مع SOAP service - أو CSV Service Repository للتعامل مع ال TEXT FILE - أو SQL Service Repository للتعامل مع RDBMS أو أي نوع آخر (مثلاً يتعامل مع ال Azure SQL) طالما يطبق نفس ال Interface، فكل شيء سيعمل مباشرة بدون تغيير أي كود ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-786ad444cd83ad07e96b8506801750c20bcfeebc1647aa1256a282b171c6c8a4.png) إذا لم تضح لك الصورة بعد، فلا تقلق وسوف تتضح لك كل الأمور مع المثال، لنوضح الآن ماذا نقصد بال CRUD # **ماذا نعني بال** **CRUD** هي مجموعه من الدوال طالما تكون موجودة في أي تطبيق يريد التعامل مع أي جدول في القاعدة أو عموماً مع أي Data Storage، وال CRUD هي اختصار للعمليات الإضافة Create، القراءة Read، التحديث Update، الحذف Delete. ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-f88d7fba77d2add581184ca50bec377577b29262f8c367dd9808ead1e49a4e83.png) **مثال ال** **Repository** **باستخدام ال** **Interface** لنبدأ الآن بالبرمجة، وسوف نقوم بتعريف المستخدم Person ```csharp public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } } ``` بعد ذلك سوف نقوم بعمل Repository لكي يتعامل مع ال CRUD الخاص بال Person وسوف نعرف ال Contract كما يلي: ```csharp public interface IPersonRepository { void AddPerson(Person person); IEnumerable GetPeople(); Person GetPerson(string firstName); void UpdatePerson(int personId, Person updatedPerson); void DeletePerson(string firstName); void UpdatePeople(IEnumerable updatedPeopele); } ``` هنا أنشئنا ال IPersonRepository وفيها دالة للإضافة، ودالة للقراءة سواء لشخص أو مجموعه، وأيضاً دالة للتحديث، وأخيراً دالة لحذف الشخص، وهكذا نقوم بعمل كل ال operations التي نريدها على البيانات. لاحظ العائد من دالة ارجاع الأشخاص هو IEnumerable وبالتالي كل الكلاسات التي تطبق هذه ال Interface تستطيع ارجاع List، Array أو أي كائن يطبق ال IEnumerable، ونفس الامر في دالة التحديث والتي يتم تمرير مجموعه من الأشخاص (وهنا نطبق ال Program to abstraction). الآن نريد أن نتعامل مع أكثر من Data Source والبرنامج سوف يجلب البيانات من: - جلب من ال Service Repository - جلب من ال CSV Repository - جلب من SQL Repository سوف يتم وضع تعريف الكود الخاص بجلب ال People أي دالة واحدة GetPeople فقط في كل كلاس حتى نركز على الفكرة العامة وليس على حول كيف يمكن ادخال أو حذف البيانات من CSV أو Service. ## **التعامل مع الملفات** **CSV Repository** سوف نقوم بعمل كلاس جديد وليكن اسمه CVSRepository لكي يتم جلب البيانات من الملف، بالطبع هذا الكلاس سوف يطبق ال IPersonRepository كما يلي: ```csharp public class CSVRepository : IPersonRepository { public void AddPerson(Person person) { throw new NotImplementedException(); } public IEnumerable GetPeople() { List persons = new List(); // fill persons from file return persons; } public Person GetPerson(string firstName) { throw new NotImplementedException(); } public void UpdatePerson(int personId, Person updatedPerson) { throw new NotImplementedException(); } public void DeletePerson(string firstName) { throw new NotImplementedException(); } public void UpdatePeople(IEnumerable updatedPeopele) { throw new NotImplementedException(); } } ``` سوف نقوم بكتابة كود جلب البيانات من الملف، والملف اسمه data.txt وبنيته بهذا الشكل: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-3224cb639a0f970a0d13b36d24533462447e147533d15bbc7420ea7ea2a0eed7.png) سوف يكون كود استخراج البيانات منها كالتالي (لا داعي للتركيز حول الكيفية وليست هي الطريقة الأفضل): ```csharp public IEnumerable GetPeople() { List people = new List(); string[] lines = File.ReadAllLines("data.txt"); foreach (string line in lines) { string[] tokens = line.Split(','); Person person = new Person { Id = int.Parse(tokens[0].Trim()), FirstName = tokens[1].Trim(), LastName = tokens[2].Trim(), BirthDate = DateTime.ParseExact(tokens[3].Trim(), "dd/MM/yyyy", CultureInfo.InvariantCulture) }; people.Add(person); } return people; } ``` بهذا الشكل سوف يتم تعريف بقية الدوال الموجودة في ال CSVRepository وبالتالي يستطيع هذا الكلاس اضافة، عرض، تحديث، حذف الأشخاص من الملف (يمكنك تطبيقها كتمرين في البرمجة). ## **التعامل مع قاعدة البيانات** **SQL Repository** سوف يتم الآن تطبيق الكلاس الذي يتعامل مع قاعدة البيانات ويقوم بنفس المهام ولكن التخزين سوف يكون على القاعدة. وسنقوم بعمل Implement لل Interface والتركيز على دالة GetPeople ويبقى تطبيق البقية اليك كتمرين أيضاً. ولكتابة الدالة التي تجلب من قاعدة البيانات يمكن أن نستخدم ال ADO.NET ونقوم بفتح الاتصال والاستعلام بجمل SQL وجلب البيانات أو يمكن أن نستخدم أي من ال ORMs الموجودة مثلاً LINQ To SQL أو Entity Framework، وفي هذا المثال سوف نستخدم الطريقة التقليدية. ```csharp public IEnumerable GetPeople() { List people = new List(); using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["PeopleDB"].ToString())) { connection.Open(); using (SqlCommand cmd = new SqlCommand("SELECT * FROM People", connection)) { using (SqlDataReader reader = cmd.ExecuteReader()) { if (reader != null) { while (reader.Read()) { Person person = new Person() { Id = int.Parse(reader["Id"].ToString()), FirstName = reader["FirstName"].ToString(), LastName = reader["LastName"].ToString(), BirthDate = DateTime.Parse( reader["BirthDate"].ToString()) }; people.Add(person); } } } } } return people; } ``` يتم جلب البيانات من قاعدة البيانات التي تم تحديد ال Connection String لها في ملف ال App.config: ```markup ``` المهم حالياً أن القاعدة PeopleDB موجودة وبها جدول اسمه People والبيانات التي توجد بها: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-32012a146c7c2fe241ff4e13e567140b544399a48bc126e1e7c6e0adaf26e318.png) بقية الدوال الأخرى يمكنك أن تقوم بتطبيقها ايضاً (للحذف والتعديل والاضافة). ## **التعامل مع الويب سيرفس** **Service Repository** هناك أنواع كثيرة لتطبيق الويب سيرفس في ال .NET، فمثلاً يمكن أن تكون WCF أو Web API أو حتى ASMX، ولكن الهدف من هذا الموضوع هو توضيح كيف أن ال Interfaces يجعلك تتعامل مع الجميع بطريقة واحدة وليس شرح كيفية عمل ال Web service، لذلك سوف يتم جلب البيانات من List في الذاكرة فقط. للنظر الأن لتعريف ال Service Repository ولاحظ أنها ارجعت بيانات ثابتة على فرض أنها تأتي من خدمة ويب. ```csharp public IEnumerable GetPeople() { // simulate call to web service then retreive the result return new List { new Person { Id = 1, FirstName = "Bahi", LastName = "Salem", BirthDate = DateTime.Now }, new Person { Id = 2, FirstName = "Omar", LastName = "Omar", BirthDate = DateTime.Now }, new Person { Id = 3, FirstName = "Khalid", LastName = "Nour", BirthDate = DateTime.Now }, new Person { Id = 4, FirstName = "Ahmed", LastName = "Essa", BirthDate = DateTime.Now } }; } ``` ## **العمل على ال** **Solution** انتهينا من كتابة ال Repositories الثلاثة، لكن قبل المضي في استخدامها، سوف نقوم بوضع كل منهم على Project منفصل (من نوع Class Library Project) ومن ثم نضيف ال Assembly الخاصة بكل مشروع إلى البرنامج الذي يحتوي على الدالة main. لمن لم يستخدم ال Class Project من قبل أو يحتاج لعمل مراجعة فننصح بمراجعة الملحق 1 الآن، وبعدها يكمل هنا، حيث به بعض المعلومات المهمة حول المكتبات وكيفية عملها ومتى تحمل للذاكرة. ال Class Library هو مشروع ولكن ليس له أي مخرج وانما سيخرج ملف DLL تستطيع استخدامه في مشاريع اخرى، ودائماً يفضل أن تقسم المشروع إلى عدة اقسام مثلاً Class Library Project يحتوي على الكود الخاص بالتعامل مع قواعد البيانات، ومشروع Class Library Project آخر يحتوي على الأشياء العامة التي تحتاجها في مواضع أخرى مثلاً ارسال البريد، عمل معالجة للنصوص، وهكذا تستطيع اعادة استخدامها بسهولة من مشروع لأخر بدون الحاجة لإعادة كتابة نفس الكود مرة اخرى أو القيام بعمليات النسخ واللصق. الفصل الخامس سوف نتحدث بمثال عملي عن التقسيم وال Layers. شكل المشروع بعد عمل عدة مشاريع Class Libraries سوف يكون كالتالي: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-bf8fe5a1256f1f2f2caf43794c1b66f090e424af3391ccdee2aa14287af93ebc.png) - اولاً المشروع Program وهو Console Application والذي فيه الدالة الرئيسية Main ومنه يعمل البرنامج. ولاحظ أنه Startup Project (تستطيع اختيار أي مشروع بالزر الايمن وتحديد أنه Startup Project، وبالطبع يجب أن يكون Console Project أو Web Project والا فلن يعمل إذا كان Class Library Project) - المشروع Shared ووضعنا فيه ال Interfaceبالإضافة إلى الكلاس Personوهي الاشياء المشتركة أو الكلاسات التي تمثل ال Domain في المشروع. - مجلد Repositoryويحتوي على 3 مشاريع وهي التي تعمل على ال Data Sources المختلفة، وتم وضعهم داخل المجلد للتنظيم فقط لا أكثر. كل من هذه المشاريع الثلاثة سوف تحتاج الوصول إلى ال IPersonRepository وال Person حتى تعمل صحيحاً، لذلك سوف نضيف مشروع ال Shared إلى كل من هذه المشاريع الثلاثة والخطوات كالتالي: لنبدأ بمشروع CSV والبقية ستكون بنفس الخطوات، سوف تجد قائمة ال References وهي المكتبات التي يتعامل معها هذا المشروع، قم بفتحها واختر Add Reference ثم اذهب ال Solution على اليسار واختر Shared (كما في الصورة التي تليها) وهو المشروع الذي نريد أن يوصل لها ال CSVRepository اليه، وحدده ومن ثم اضغط على OK ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-664de395f255c251f0bdd8988ca1a386be5184c43576f5d9fd31f421d1c497a9.png) هنا لتحديد المشروع: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-9d780c3a01eefb17e11c5135e19487cf1e0c97358656079d1a9138d556fd3c81.png) **معلومة**: تستطيع اضافة المكتبات عموماً (بالأصح ال Assemblies) بعدة طرق منها: - ال Solution في حال كانت المكتبة في نفس المشروع (كما هو في مثالنا الآن) - أو من خلال ال Assemblies وهي المتوفرة في .NET حيث في الوضع الافتراضي لا يتم تحميلها كلها في أي مشروع وانما بضعه Assemblies مهمة - أو تستطيع تحديد مسارها Browse من جهاز - أو استخدام ال Nuget Package Manager وهو الأسلوب الأفضل خصوصاً في المكتبات الخارجية Third-Party Libraries. حالياً سوف نضع ال Shared Assembly في المشروعين الباقين وهم SQLRepo وال ServiceRepo بنفس الطريقة اعلاه. أخيراً سوف نضيف ال Assembly الخاصة بالمشاريع الثلاثة SQLRepo، ServiceRepo، CSVRepo إلى المشروع Program حتى نستطيع استخدامهم جميعاً عند تشغيل البرنامج. وكما يتبين الآن أن ال Program يستخدم تلك المكتبات ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-e2a21ddfb66924f5708748e43f8b02ec6bc4f4f41745929aef13d7612d186e8b.png) ## **كود الدالة الرئيسية** **Main** سوف نسأل المستخدم عما يريده أولاً، ومن ثم بناءً على اختيار المستخدم سوف نقوم بطباعة معلومات الأشخاص ونقوم بإنشاء ال Repository المناسب: ```csharp class Program { static void Main(string[] args) { Console.Write("Enter your choice: "); int choice = Convert.ToInt32(Console.ReadLine()); if (choice == 1) { IPersonRepository repo = new CSVRepository(); IEnumerable people = repo.GetPeople(); displayPeople(people); } else if (choice == 2) { IPersonRepository repo = new SQLRepository(); IEnumerable people = repo.GetPeople(); displayPeople(people); } else if (choice == 3) { IPersonRepository repo = new ServiceRepository(); IEnumerable people = repo.GetPeople(); displayPeople(people); } Console.ReadKey(); } private static void displayPeople(IEnumerable people) { foreach (var person in people) { Console.WriteLine(person.Id + " " + person.FirstName + " " + person.LastName); } } } ``` الآن لو جربت البرنامج سوف تجد أن الخيار يطبع البيانات التي توجد على ال CSV بينما 2 يطبع على ال SQL و3 يطبع التي توجد على ال Service. وهكذا توحدت ال API ولكن ال Implementation يختلف على حسب نوع ال Repository. طبعاً سوف يكون هذا الأمر مطبق على جميع الدوال وليس فقط GetPeople. وبهذا الشكل فإن المشروع يتعامل مع هذه ال Repositories الثلاثة، وكل ذلك من خلال ال Interface، **رائع**! # **حذف الكود المكرر باستخدام ال** **Factory Method** لاحظ وجود الكود المكرر داخل جمل ال IF في الكود السابق، والكود متشابه فقط الإختلاف في سطر عمل ال Implementation وهو شيء واحد فقط يتغير في الثلاثة دوال، لذلك كما قلنا سابقاً هناك تصميم غير جيد، ويجب التحسين Refactoring، وسوف نقوم بعمل Refactor لهذه الأكواد وبالتالي يتم مسح التكرار، وسوف نستخدم Factory Method للقيام بذلك. سوف نضيف كلاس Repository Factory وفيه دالة ترجع نوع ال IPersonRepsotiry وتأخذ قيمة نوع ال repo المطلوب وباستخدام جملة if أو switch يتم ارجاع ال Implementationالمطلوب، وفي حال أرسلت نوع خاطئ سوف يتم عمل throwلل exception كما في الشكل التالي: للاستزادة عن ال Static Factory Method يمكن قراءة هذه المقالة وهي بالجافا ولكن المبدأ نفسه [هل مللت من دالة البناء Constructor؟](https://informatic-ar.com/constructor-vs-factory/) ```csharp public class RepositoryFactory { public static IPersonRepository GetRepository(string type) { IPersonRepository repo = null; switch (type) { case "Service": repo = new ServiceRepository(); break; case "SQL": repo = new SQLRepository(); break; case "CSV": repo = new CSVRepository(); break; default: throw new ArgumentException("Invalid Repository Type"); } return repo; } } ``` الآن الدالة الرئيسية Main سوف تكون بهذا الشكل: ```csharp class Program { static void Main(string[] args) { Console.Write("Enter your choice: "); int choice = Convert.ToInt32(Console.ReadLine()); if (choice == 1) FetchData("CSV"); else if (choice == 2) FetchData("SQL"); else if (choice == 3) FetchData("Service"); Console.ReadKey(); } private static void FetchData(string type) { IPersonRepository repo = RepositoryFactory.GetRepository(type); IEnumerable people = repo.GetPeople(); displayPeople(people); } private static void displayPeople(IEnumerable people) { foreach (var person in people) { Console.WriteLine(person.Id + " " + person.FirstName + " " + person.LastName); } } } ``` كل شيء يعمل كما كان بالضبط، ولكن بكود أفضل وبدون تكرار. رائع**!** الدالة FetchData لا تهتم بنوع ال type الذي يرجعه ال Factory سواء كان csv أو sql فهذا الكود يعمل بنفس الطريقة (لأنه يعمل باستخدام ال contract). أيضاً تستطيع إضافة أي repo جديد بسهولة، فقط سوف تغير في ال factory وتضيف النوع الجديد الذي ترغب فيه واعادة ترجمة المشروع. وهكذا بال interface تحقق فكرة سهولة ال Extensibility. فقط هناك عيب بسيط، ماذا لو كنا نريد أن يعمل البرنامج على Repository واحد فقط، ومن خلال تغيير في ملف خارجي Configuration يتم تحديد نوع ال Repository، مثلاً في جهاز العميل الأول سوف يعمل البرنامج باستخدام ال CSV File ويخزن ويستعرض البيانات. بينما نفس البرنامج يعمل باستخدام ال SQL Database فقط بتغيير اسم ال Repository في ملف ال Configuration بدون تغيير أي جزئية في الكود وإعادة ترجمة المشروع؟ بمعنى أن تكون الدالة الرئيسية بهذا الشكل ```csharp class Program { static void Main(string[] args) { FetchData(); Console.ReadKey(); } private static void FetchData() { IPersonRepository repo = RepositoryFactory.GetRepository(); IEnumerable people = repo.GetPeople(); displayPeople(people); } private static void displayPeople(IEnumerable people) { foreach (var person in people) { Console.WriteLine(person.Id + " " + person.FirstName + " " + person.LastName); } } } ``` وعلى حسب القيمة التي توجد في ال Configuration File يتم استخدام ال Repository المناسب. بالإمكان عمل ذلك عن طريق اضافة فكرة ال Dynamic Loading وبالتالي لا تحتاج لعمل hard coded لل Implementations في ال Factory Method، وسيتم تحميل ال Implementation تلقائياً وقت التشغيل إذا أردت على حسب ما يتم تحديده في الملف. # **خلاصة** - تعلمنا مفهوم ال Repository pattern وما هي ال CRUD - تعلمنا كيف تقوم بعمل custom interface وتقوم بتطبيقه في عدة أماكن - لإنشاء ال Repository - يمكن استخدام ال Factory لأنشاء ال Repositoryبطريقة ال hard coded أي Compile time - أو من خلال ال Dynamic loading لل Repository في وقت التشغيلكما سيأتي الفصل المقبل. ### تحويل بيانات من ملف نصي الى اكسل excel باستخدام NotePad++ URL: https://informatic-ar.com/convert-file-csv-excel-notpad/ Last updated: 2026-10-03T22:31:14.000Z سوف نأخذ في هذه المقالة أحد الطرق لمعالجة الملف وتحويله من شكل الى اخر (اكسل excel)، وذلك باستخدام محرر النصوص NotePad++ بالاضافة الى طريقة استخدام ال regex في البحث والاستبدال المثال الذي سوف نعمل عليه به تجد أن هناك حوالي 500 شركة بمعلومات حولها ولكن طريقة العرض عن طريق الأسطر Row Based والأسهل هو تحويلها للاكسل ك Columns Based حتى يتم البحث والفلترة بها بسهولة، فالمهمة هو تحويل ملف في الصورة رقم 1 ملف الاكسل شيت في الصورة رقم 2. ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-451a22863a6dc7d2944e23c13d098af31b287148081b594c15719ff88e5e8645.png) في هذه المقالة سوف نوضح الطريقة لتحويل ذلك الملف، ويمكن اتباع هذه الأليه في التحويل لغيرها من الملفات مع الأخذ بالاعتبار ان بعض الخطوات قد تختلف بناء على بنية الملف المراد تحويله. سوف يتم استخدام برنامج notepad++ في عمليات التحويل هذه، مع العلم بأنه يوجد برامج بديله له سواء في بيئة ويندوز أو لينوكس أو ماك، وبشكل عام اي برنامج محرر نصوص يتوفر فيه خاصية البحث والاستبدال بواسطة Extended Mode أو Regular Expression Mode فهو مناسب في الأمر. نقطة أخرى وهو أن برنامج notepad++ لا يتعامل مع الملفات الضخمة بشكل جيد وقد يهنق Crashing البرنامج اثناء فتح اي ملف كبير أو اثناء القيام بخطوات التحويل، لذلك عند تعاملك مع أي ملف كبير يفضل أن تستخدم البرامج المخصصة للعمل مع الملفات الكبيرة مثل EditPad Lite وهو مجاني في ويندوز. # نبدأ الخطوات: **اولاً:** دائماً الأفضل اظهار Symbols الموجودة والمخفية في الملف والتي عادة اغلبها يكون في أخر كل سطر لمعرفة نوعية ال New Lines، فبعد أن تقوم بفتح الملف تقوم عن طريق قائمة View ومن ثم اختيار Show all Characters ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-e61fc74d46f273c560b22adc023738023a027df2f9de477a37fdd9de3605a95b.png) ويمكنك اخفائها عن طريق إزالة الصح على Show all Characters بعدما تعرف نوعية ال New Line، بعد أن قمت باظهارها، سوف تجد الان الرموز الموجودة في أخر كل سطر مثلاً CRLF أو CR أو LF على حسب طريقة الNew Line المستخدمة في الملف. لماذا نقوم بمشاهدتها؟ لأنه أحياناً تجد ملفات مستخدم بها أكثر من طريقة New Line في نفس الوقت، وحتى تقوم بعملية التحويل المناسبة عليك أن تعرف نوعية ال New Line المستخدمة، في أخر هذه الملف سوف نضع مثال على ملف أخر به أكثر من New Line مستخدمة بطريقتي ويندوز ولينوكس ونعرف كيف يتم اصلاح ذلك من خلال ال notepad++. **ثانياً:** بما أن برنامج الأكسل يستطيع قرائة ملفات CSV واختصاراً Comma Separated Value وهي تعني أن هناك معامل الفاصلة يفصل بين كل معلومة وأخرى، فنحن نحتاج أن نحول الملف لذلك الفورمات. لذلك سوف نقوم بتحويل كل الأسطر New Lines الى فاصلة، وبالتالي جميع البيانات تكون في سطر واحد وذلك عن طريق عمل Find And Replace من خلال CTRL+F والذهاب للReplace ومن ثم اختيار Extended Mode والذي يسمح البحث من خلال \\n,\\r,\\t وغيرها، وسوف تكون جملة البحث هي \\r\\n (أي CRLF) ويتم استبدالها بالفاصلة كما في الصورة التالية، وتقوم بعمل Replace All لتغيير كل الأحرف التي طابقت جملة البحث في هذا الملف. ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-7587a21e72d404bdf321f903a8d9323e0c8a9b7dcede3d701fd6753e7e64fe03.png) **ثالثاً:** بعد عملية التحويل سوف تجد أن جميع البيانات هي في سطر واحد، وسوف نستفيد من علامة ==== حتى نفرق بين كل بيانات شركة على حدة ، لذلك سوف نقوم بعمل فصل بهذه العلامة واستبدالها بسطر جديد من خلال البحث عن ==== وتبديلها ب \\r\\n وعمل Replace All كما في الصورة التالية بعد تطبيق زر التبديل. ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-6022e66ed7ad8b255749b8e86c4ad65a6d278a77075478cfe1843ac1f6dfe476.png) وهذا هو الملف ايضاً تم فتحه عن طريق النوت باد بوضع الRight To Left (بعد فتحه بالنوت باد نقوم ب CTRL+SHIFT التي على اليمين)، وايضاً تستطيع عمل ال Right To Left في ال Notepad++ ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-df8f5e0264af7b8236504fb50340dde3c437afd3a17845602fe163a1d9be700f.png) كما هو واضح الملف أصبح مرتب الان وكل شركة في سطر، ونريد تحويله الى صيغة اكسل. **رابعاً:** قبل التحويل سوف نحتاج لمسح البيانات المكررة في كل سطر (اسم المنشأة، رقم السجل التجاري والخ) لأنها سوف تكون كإسم للعمود في الأكسل ولا توجد حاجة لكتابتها في كل سطر، لذلك نقوم بمسح تلك الكلمات من خلال ايضاً البحث عن كل اسم ثابت ومسحه، وطريقة المسح هي عن طريق استبدال النص بنص فارغ كما بالصورة التالية ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-07bf824ad954544f71d04db3cc8bcb34b9384b1f9e858254dce7b2a6bf3ce5c0.png) تم حذف اسم المنشأة: ولاحظ وجود : لأنها داخلة في النص الثابت في الملف، وسوف نكرر هذه الخطوة لجميع الأسماء الثابتة في الملف، اسم المنشأة: رقم السجل التجاري: ، الخ والتي تتواجد في بداية أي سطر في الصورة الأولى قبل التحويل: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-63104cf9b26dc9e8b84516e629ae9083e9e7a76799c79fae1d3a0e5b8798a58a.png) **خامساً:** فقط للتأكد من ال Encoding وأنه UTF-8 (والتي تدل على وجود العلامة BOM للمزيد حول هذه العلامة أقرء هنا [أساسيات لابد لأي مبرمج معرفتها الترميز](https://informatic-ar.com/unicode%5Fencoding/)) فهذه العلامة BOM ضرورية لملفات ال Office حتى تستطيع قرائه ال encoding بشكل صحيح (شاهد المثال هنا [حل مشكلة توليد ملفات CSV بالعربي برمجياً وفتحها بالأكسل Excel](https://informatic-ar.com/csv-arabic-excel/)) قم بفتح الملف بالNotepad++ والنظر ل Encoding وأنه UTF-8 وغيرها اذا كانت مثلاً UTF-8 Without BOM الى UTF-8. **سادساً:** نقوم بتحويل امتداد الملف الى CSV عن طريق عمل Rename للملف وضع الامتداد csv، ومن ثم نقوم بفتحه بالاكسل بشكل عادي Double Click لأنه يقبل فتح ملفات CSV ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-31f8964322f17f421538cfb5e33f69b8268eb9c8b7186b7a4f358135878b73b4.png) الملف بعد فتحة بالأكسل سوف يكون بهذا الشكل، والأفضل حفظه بصغية الأكسل حتى يسهل العمل عليه في الأكسل فيما بعد. ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-3c99d63bebbece4a051d2e9f5f74a5c325efeee6265c3df42f3c1a1d90f35bfe.png) **سابعاً:** حفظ الملف بصيغة أكسل xlxs من خلال File->Save As حتى يتحول الملف لصيغة الاكسل ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-4adccd07b532f34c60af94e330dcac21e5d8ff4ffe5e21b3df4c45ab148ba7bb.png) يمكنك اخيراً إضافة السطر الأول واضافة أسماء الاعمدة وتعديل لونها قليلا بما تشاء ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-fdf5597a23e079c80bf0b56f533772fe7d51d381e794bd1205d114c5d847df95.png) وهكذا تكون قد تمت عملية تحويل الملف بنجاح! وتستطيع بعدها بسهولة تطبيق اي Filter من خلال الأكسل حتى تفلتر الأسطر وتظهر من تريد بالضغط على الهيدر في العمود الذي تريده واختر Filter By Value وستجد القائمة المنسدلة وحينها اختر القيمة التي تريد وستجد أن الأسطر قم تمت فلترتها بناء على ما اخترته. # لماذا تحتاج لأن ترى كل الأحرف احياناً في الملف؟ هذا مثال لأحد الملفات فيه ايضاً مجموعه من البيانات وكان المطلوب هو تحويلها الى Excel، وعندما تفتح الملف بالنوت الباد العادي سوف تجد الصورة على اليمين ، ولكن عندما تفتحه بالنوت باد بلس بلس سوف تجد على اليسار، ولاحظ الفرق بأن LF لا يعرضه Notepad كمسافة بعكس ال Notepad++ والذي يعرض الLF بأنها مسافة (طبعاً مع تفعيل خاصية Show all Characters) التي سبق توضيحها ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-019e5a941816f3160949f1c0f0681a60fd4543ce5ce0350018afed81c109b00f.png) قبل أن تتعامل مع هذا الملف بالطريقة السابقة اعلاه، يجب أن يتم اعتماد New Lines واحدة في الملف وجعل الملف كله اما LF أو CRLF وال CRLF هي الأفضل في ويندوز، والأمر يتم كالتالي عن طريق تحويلها جميعها الUnix Format ومن ثم ارجاعها الى Windows Format ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-b9b049e9fdf7ff167dd5760fc349cdd8a328df187230eec66dfa1ca9641ed4c9.png) بعد ذلك سوف تتحول كلها الى LF قم بإعادة الخطوات مجدداً واختر Windows Format وهكذا جميعها سوف تتحول الى CRLF ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-1778201c26360fe8f424232907a5f31500664434fb446f8033fe72a47d10b624.png) الخطوة التالية هي حذف كل الاسطر الفارغه ويمكن استخدام خاصية البحث والتبديل ، ولكن كطريقة اخرى فأن الNotepad++ فيه مجموعه من الاضافات الرائعه التي تقوم بالعديد من المهام الاضافية لمحرر النصوص، وأحد هذه الاضافات هي TextFX حيث فيها العديد من المهام التي تفيد اثناء العمل على الملفات النصية، وأحدها هو أمر يقوم بحذف كل الأسطر الفارغه، فقط قم بتظليل كامل الملف CTRL+A واختر الخيار بعد تحميل الاضافة بالطبع ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-2c722fe4f6cd98ddb484a7a6999c1798cd3d00e0cfc7245c20d0f613c1b4d826.png) الان تحول الملف ورجع للشكل الصحيح ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-c6d64d6cc87f187c7322afd06ef16fecb43176a321af89a648f199f00428f3ee.png) **أحياناً،** قد يكون في الملف فيه فاصلة , بين البيانات ، فقبل أن تحول الملف الى CSV عليك بحذف هذه الفاصلة (والا ستخرب عليك البيانات بعد تحويلها حيث ستفصل البيانات الأصلية عن بضعها) فيجب ان تقوم بعمل بحث عن , واستبدالها بأي شيء أخر وليكن مثلاً علامة الشرطة - ، وهكذا تكون قد عملت Cleanup للبيانات وجهزتها حتى تتحول الى CSV بعد ذلك تستطيع القيام بنفس الطريقة الموضحة في هذا الملف في تحويل البيانات ، وذلك عن طريق استبدال ال CRLF بالفاصلة وبعدها تبديل ال == بالCRLF ومن ثم حفظ الملف CSV وفتحه بالأكسل وحفظه بصيغة الأكسل. ### أهمية إدارة الهوية في التطبيقات Identity Management URL: https://informatic-ar.com/identity-management/ Last updated: 2021-10-05T21:22:09.000Z كل التطبيقات تحتاج للتعامل مع بيانات المستخدمين وادارتها Identity Management وقد تبدوا العملية من الوهلة الأولى بسيطة، ولا يتم مناقشتها كثيراً في أغلب المشاريع، كونها ليست جزءاً أساسياً من متطلبات المشروع، وانما فقط وسيلة للدخول والحماية. لكن بالرغم من ذلك فال Identity Management يعتبر من أكثر الأجزاء تعقيداً في المشاريع، وكثير من الاختراقات التي نشاهدها ونسمع بها وسرقة الحسابات ناتجة عن سوء تطبيق لإدارة الحسابات، ومن هنا نحتاج ان نعرف كيف نخطط وننفذ هذه الجزئية في المشاريع. # التحديات في ادارة الهوية Identity Challenges قد تبدوا عملية إدارة الهوية بسيطة نظرياً، لكن في الواقع فهي غير كذلك، وتحتاج التخطيط الجيد، والتصميم والتنفيذ مع موازنة بين المتطلبات في المشروع مع الحماية وفي نفس الوقت المحافظة على تجربة المستخدم User Exprience. ومن هنا تظهر أهمية دراسة موضوع ال Identity Management بحيث نستطيع ان نتماشى مع متطلبات المشروع Business Requirements فلا يوجد حل واحد لكل المتطلبات وسيتم ذكر بعض أنواع المشاريع والطرق التي تناسبهم. ### **مشروع موجه للمستخدمين Consumer-Facing Application** في هذه الحالة قد تحتاج الى ان يقوم المستخدمين بالدخول بواسطة الشبكات الاجتماعية Social Providers، وايضاً قد يتطلب ربط أكثر من حساب بنفس الهوية، بحيث يستطيع المستخدم الدخول لحسابه من خلال ال Facebook وال Twitter وغيرها بسهولة. وإذا لم تستطيع تنفيذ هذه المتطلبات في هذا النوع من التطبيقات فقد يواجه المستخدمين مشاكل في الدخول والتسجيل ويزعجهم وقد يتسبب في عدم استخدامهم للخدمة. ### **مشروع داخلي في شركة Internal System** في المشاريع الداخلية بفضل أن يقوم الموظفين بالدخول من خلال حسابهم في العمل (مثلاً Active Directory Account) ويريدوا تجربة سهلة للدخول Single Sign-On. وقد يكون للشركة متطلبات معقدة في ال Authorization بناء على ال Roles والتي تعطي الصلاحيات للموظفين للقيام بالمهام المطلوبة. ### **مشروع ببيانات حساسة** قد يحتاج طريقة للدخول اقوى من مجرد كلمة السر Stronger Authentication، وتكون في نفس اللحظة مناسبة لمستخدمي ذلك النظام. وهناك العديد من الخيارات لتطبيق ال Strong Authentication تبدأ من كلمة السر المؤقتة One Time Password يتم توليدها في تطبيق معين مثلاً Authenicator Application أو ترسل كتنبيه لتطبيق معين مخصص لها، الى استخدام أجهزة مخصصة Hardware Token تحتوي على مفاتيح التشفير. لذلك قد تحتاج الى حلول سهلة للمستخدمين لكي يتأقلموا معها بدلاً من ان تكون مانعاً لهم لاستخدام النظام. ### **أكثر من مشروع للعديد من المستخدمين** اذا كنت تقدم أكثر من مشروع لفئة من المستخدمين، فالمستخدمين قد يرغبوا بخاصية الدخول الموحد Single Sign-On بحيث يدخلوا مرة واحدة ويستطيعوا الوصول لكل من تلك التطبيقات (مثلاً عند دخولك ل Gmail سوف تدخل مباشرة على Youtube وغيرها من خدمات قوقل). وهذا ما يجعل عملية الدخول سهلة للمستخدمين فهي بوابة واحدة للدخول، ولكن في نفس الوقت تحتاج أن تتأكد من أنها تعمل دائماً Highly Available ولها قدرة على التوسع الذاتي Scaling Up في حال زاد الضغط عليها، والا فسوف تكون هذه البوابة للدخول هي السبب لعدم دخول المستخدمين للنظام. ## قيود أخرى يجب وضعها في الإعتبار يجب أن يستوعب تصميم المشروع للقيود الموجودة في البيئة التي يعمل بها Delivery Platform، مثلاً في الويب فيتوقع المستخدم ان تقوم بنقله Redirect الى صفحة الدخول Sign-In، بينما في برامج سطح المكتب فقد يفضل المستخدم الدخول مباشرة من التطبيق أو استخدام ال Session في نظام التشغيل. في تطبيقات الموبايل فهناك عدة طرق، منها ما يقوم بعمل Redirect المستخدم الى ال Identity Provider لكي يقوم بالدخول، ومنها ما يطلب بيانات الدخول Credentials مباشرة في التطبيق، ولذلك يجب ان تقوم بالموازنة بين هذه الطرق المختلفة وإيجاد الطريقة الأنسب بحسب نوعية المشروع او النظام والبيئة التي سوف يعمل بها. سوف تجد أنك بحاجة الى الإجابة على كل هذه الأسئلة وفي نفس الوقت تقوم بأخذ في الحسبان بمراعاة الأمن وتلبية رغبات ومتطلبات المشروع Business Requirements ([هل تهتم لحماية التطبيق قبل أو بعد الانتهاء من البرمجة؟](https://informatic-ar.com/designing-in-security/)) وإذا لم تقوم بأخذ القرارات الأنسب فهذا قد يؤثر على تجربة المستخدم لديك، مثلاً قمت بتنزيل تطبيق لمشاهدة صور من الطبيعة او الحيوانات، وفي صفحة التسجيل قام بسؤالك عن رفع صفحة من الجواز Passport ورفع فيديو يحدد ملامحك Selfie Video. فهذا أمر مثير للشك والريبة، فلماذا يحتاج مثل هذا التطبيق صورة من الجواز الشخصي. ولذلك سوف يؤثر هذا القرار على استخدام التطبيق، وقد يكون من الأفضل الدخول بواسطة الشبكات الاجتماعية. وفي المقابل قد يطلب نظام او تطبيق مالي صورة من الجواز لكي يتحقق من الهوية الخاصة بك وهذا يبدوا معقولاً، لأنه قد يكون جزءاً من السياسة الخاص بها، وهذا الأمر فعلياً مطبق في أحد الأنظمة البنكية Monzo كجزء من التسجيل On Boarding Experience (رفع فيديو سيلفي وقراءة بعض النصوص). من النقاط الأخرى التي يجب وضعها في الاعتبار هي الخصوصية والتعامل مع البيانات الحساسة وحمايتها، وهناك الكثير من التشريعات مثل GDRP (اختصاراً General Data Privacy Regulation) في الاتحاد الأوربي وغيرها من التشريعات المختلفة. فأي مشروع يقوم بأخذ بيانات المستخدمين ومعالجتها فيجب أن تتوافق مع هذه التشريعات والا فسوف تتعرض لعقوبات وغرامات مالية. ولذلك التحديات أعلاه ما هي الا عينة بسيطة من التحديات التي تواجهك عند تصميم وبناء Identity Management لأنظمتك. ## أسئلة مهمة قبل أن تبدأ في تنفيذ المشروع، تحتاج للإجابة على هذه الأسئلة وتكون لديك إجابة واضحة بها: - من هم المستخدمين لديك؟ موظفين Employees، مستخدمين عاديين Consumers؟ - كيف سيقوم المستخدمين بالدخول؟ هل سيتم استخدام حسابات موجودة ويقوموا باستخدامها - هل يمكن ان يستخدم التطبيق بدون هوية Anonymously او يحتاج للتحقق منها Authentication - ما هي بيئة العمل Delivery Platform هل هو موقع ويب او تطبيق موبايل التي سيعمل بها مشروعك - هل سيحتاج ان ينادي أي APIs؟ إذا كان كذلك فمن يملك البيانات التي سوف يستدعيها تطبيقك - ما هي مدى حساسية البيانات التي يتعامل بها نظامك؟ - ما هي متطلبات الدخول Access Control المطلوبة في المشروع؟ - كم هي فترة الجلسة للمستخدم Session Time - هل هناك أكثر من نظام أو تطبيق في مشروعك؟ إذا كان كذلك فهل سيتم تطبيق Single Sign-On؟ - ماذا يحدث عندما يقوم المستخدمين بالخروج Log out؟ - هل هناك تشريعات ومتطلبات يجب الإيفاء بها؟ ## خلاصة عند تطوير ال Identity Management للتطبيقات والمواقع فيجب معرفة العديد من الأمور الهامة، حتى يتم تصميم نظام يلبي رغبات المستخدمين، حيث يتوقعوا تجربة استخدام مميزة عند استخدام النظام، وال Identity Management يفترض ان يساعدهم للدخول للنظام بسرعة. - تفرض ال Identity Management العديد من التحديات للمطورين عند تطوير التطبيقات الحديثة - يجب ان تكون ال Identity Management مناسبة لحماية البيانات، وتوفر تجربة استخدام مميزة، وتعمل في البيئة التي يعمل بها التطبيق أو النظام. ### حل مشكلة توليد ملفات CSV بالعربي برمجياً وفتحها بالأكسل Excel URL: https://informatic-ar.com/csv-arabic-excel/ Last updated: 2026-10-04T04:35:33.000Z ## مشكلة ملف ال CSV العربي مع ال Excel كثير من الأحيان نحتاج لتوليد ملفات اكسل شيت Excel برمجياً من داخل المشاريع مثلاً لتصدير بعض التقارير، وبدلاً من أن نقوم باستخدام مكتبات خاصة لكي تولد ملفات Excel فنقوم بعمل ملفات CSV والتي يمكن فتحها بسهولة بواسطة الأكسل، أو حتى يمكن قرائتها بواسطة البرمجيات الأخرى. في هذه المقالة سوف نطبق المثال بلغة سي#، ولكن نفس المفهوم يمكن تطبيقها بأي لغة أخرى مثل جافا او PHP أو بايثون او غيرها من اللغات. سوف نرى البيانات التي نريد كتابتها على الأكسل شيت وهي Hardcoded حتى نركز على المشكلة والحل: ![](https://informatic-ar.com/wp-content/uploads/2021/10/Pasted-10.png) الآن عادة ما يقوم المبرمج بفتح ملف أو Stream وكتابة هذه البيانات بترميز UTF-8 ووضع علامة الفاصلة , حتى يتم اعتباره Comma Seperate Value، كما هنا: ![](https://informatic-ar.com/wp-content/uploads/2021/10/Pasted-11.png) لاحظ في المثال الأعلى: - قام المطور في أول 3 أسطر بتجهيز نوع المعامل وهو الفاصلة - قام بتحديد الترميز في السطر الذي يليه - في السطر الاخير قمنا بتحويل النصوص الى مصفوفة بايت encoding string حتى نقوم بكتابة البيانات في ملف بشكل binary الان عند تشغيل المثال في جهاز ويندوز باللغة الانجليزية (المنطقة regional)، وفتح الملف الناتج عن طريق الاكسل شيت، وعن طريق برنامج NotePad++ سوف تجد المخرج التالي: ![](https://informatic-ar.com/wp-content/uploads/2021/10/Pasted-5.png) الملاحظات: - تم كتابة البيانات بشكل سليم (وذلك لأننا نستخدم ترميز unicode وهو UTF8) - النوت باد++ قام بقراءة الملف صحيحاً وحدد الترميز السليم - الاكسل شيت قام بالفصل كل بيانات في عمود بشكل سليم، ولكن الترميز لم يخرج بشكل صحيح. كما تحدثنا في الموضوع ([كل ما يحتاجه المطور معرفته عن الترميز](https://informatic-ar.com/unicode%5Fencoding/)) ذكرنا أن الاكسل شيت حتى يتعرف على الملف النصي بأي من ال unicode encoding يجب أن يتم وضع علامة في الملف تعرف بال BOM ، لذلك لحل المشكلة سوف نحتاج أن نعدل من القائمة في برنامج notepad++ ونقوم بوضع الترميز على UTF-8 BOM وقم بحفظ الملف واعادة فتحه بالاكسل وستجد المشكلة تم حلها. ## وضع علامة BOM برمجياً في ملف CSV الحل اليدوي جيد، لكن نحن نريد حلها برمجياً حتى لا تتطلب أي تدخل من المستخدم، وهكذا سوف نقوم باضافة ال BOM الكود السابق وهي عبارة عن مصفوفة من البايت، وسوف نقوم اولاً بعمل دالة لدمج مصفوفتين البايت التي تخص العلامة BOM مع التي تخص البيانات حتى نكتبها مرة واحدة على الملف: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-ebb2a9a9978aa92be495ce948dccbe6c08c9d63ee46aca3f91c931119014121c.png) الأن سوف يكون المثال كالتالي: ![](https://informatic-ar.com/wp-content/uploads/2021/10/Pasted-7.png) ملاحظة: يمكن أن نستخدم الل bytes مباشرة ولكن سوف نستخدم الدالة الجاهزة GetPreamble وهي ستولد ال BOM المناسب. شاهد النتيجة الآن: ![](https://informatic-ar.com/wp-content/uploads/2021/10/Pasted-6.png) كل شيء الآن على ما يرام وأصبح البرنامج يعمل ويستطيع من يستخدم البرنامج اصدار التقرير وفتحه بالاكسل وحفظه مجدداً اذا اراد أن يحوله ل .xslx لكن هناك مشكلة في حال أردنا تشغيل نفس الملف على نظام ويندوز عربي، نشاهد الصورة التالية عند فتح الملف في نظام مثل هذا، حيث كل البيانات تظهر في العمود الأول من الملف والأعمدة الثانية فارغة: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-1bcb879a2803dedf0f82ddc8ae3757bd615e43af629f4c3f21e669c477d9465f.png) المشكلة تكمن في أن النظام ويندوز باللغة العربية معامل الفصل هو ليس الفاصلة الانجليزية Comma وانما تكون ; تستطيع مشاهدة المعامل الافتراضي الذي يقرأه الاكسل من النظام لكي يستخدمه كمعامل للفصل من اعدادت اللغة في الجهاز: ![](https://informatic-ar.com/wp-content/uploads/2021/10/Pasted-8.png) بهذه الطريقة العملاء الذي لديهم ويندوز عربي لن يستطيعوا فتح الملف csv الناتح من موقعك بالاسكل شيت، ويتوجب اما معالجة الملف اولاً بفتحه ب notepad++ واستبدال المعامل بالمعامل الافتراضي في هذا النظام، او تغيير المعامل "فاصلة القائمة" في اعدادات اللغة الى فاصلة وبالتالي سوف يعمل بلا مشاكل. لكن ماذا في حالة لا نريد المستخدم التغيير في اعدادت النظام، ونريد ملف ال csv أن يعمل على النظام العربي والانجليزي بدون أي تغييرات؟ ## حل المشكلة بشكل نهائي الحل المستخدم هو استخدام ال Tab مع ترميز UTF 16 LE ، وسوف نقوم الان بالتغييرات على الكود ونرى المخرج: ```csharp public static void Run() { string sep = "\t"; Encoding unicode = Encoding.Unicode; byte[] BOM = unicode.GetPreamble(); // Preprate Data StringBuilder sb = new StringBuilder(); var books = GetBooks(); foreach (var book in books) { string line = String.Format("{0}{3}{1}{3}{2}", book.Name, book.Author, book.Price, sep); sb.AppendLine(line); } // Write to file string path = @$"C:\Users\WajdyEssam\Desktop\test"; string fileName = $@"{path}\Report_{Guid.NewGuid().ToString()}.csv"; byte[] encoededData = unicode.GetBytes(sb.ToString()); byte[] combinedBytes = Combined(BOM, encoededData); File.WriteAllBytes(fileName, combinedBytes); } ``` لاحظ الحل في أول سطرين في الكود أعلاه، قمنا بتديل معامل الفصل الى \\t وقمنا بتحويل الترميز الى UTF-16 LE نشاهد المخرج على النظام الانجليزي: ![](https://informatic-ar.com/wp-content/uploads/2021/10/Pasted-9.png) وعلى النظام العربي: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-306ae9f1784f653e2e20ff76838da287b64b1e639b74eab28068889147c669dd.png) سوف تكون الاعمدة والبيانات بشكل سليم تماماً. ## أخيراً هناك حلول اخرى موجودة مثلاً كتابة sep=, في أول الملف، لكن بعد التجربة هذه الطريقة سوف تجعل الاكسل شيت يتجاهل ال BOM في الملف، فعلاً لا أدري السبب لماذا ترميز utf-8 به مشاكل، ولكن حالياً هذه الحل مناسب ويؤدي الغرض تماماً. اذا كانت لديك أي طريقة أخرى يسعدنا مشاركتك لنا، واذا تواجدت اي مشاكل يسعدنا أن نساعدك في حلها. ### دور ال Interface في قابلية الصيانة Maintainability URL: https://informatic-ar.com/interface-maintainability/ Last updated: 2026-10-04T04:35:35.000Z واحدة من أهم الأسباب في استخدام ال Interface هو أنه يساعد في جعل الكود أكثر قابلية للصيانة Maintainability، أي الكود المرن في التغيير وليس الذي يحتاج تغييره كاملاً بسبب تغيير بسيط، وبالتالي يضمن هذا الأمر أن الكود يعمل للمستقبل Future Proof Code. فالتطبيقات دائماً تتغير إما بتغيير المتطلبات أو بالإضافة المزيد من الخصائص الجديدة، والفكرة هنا هي أنه عندما نريد العمل على جزء واحد من التطبيق فنحن نريد تقليل التغييرات التي سوف تطرأ على بقية الأجزاء في التطبيق. وسوف نرى كيف يمكن أن تفيدنا ال Interface في ذلك. في هذه السلسلة سوف نوضح ما يلي: - [الفرق بين ال Abstract Class وال Interface](https://informatic-ar.com/interface-vs-abstract-classs/) - دور ال Interface في قابلية الصيانة Maintainbility (المقالة الحالية) - [عمل ال Repository باستخدام ال Interface](https://informatic-ar.com/interface-repository/) - ما هو ال Dynamic Factory ## لماذا ال Interface قبل أن نبدأ، لنرى أحد العادات الجيدة في البرمجة، وال Interface لها نصيب من ذلك، وهي: > Program to an abstraction rather than a concrete type كما ذكرنا في الفصل السابق أنه يمكن أن تستخدم ال Interface ك Abstraction وبالتالي تستطيع أن تفهم العادة على أنها: > Program to an Interface rather than a concrete type لذلك عليك البرمجة بال Contract بغض النظر من سوف يطبقه كأن هناك شخص آخر سوف يتولى هذه المهمة. وتذكر أن ال Concrete Class هي الكلاسات العادية التي تقوم بعملها أو موجودة ضمن مكتبة اللغة أو في مكتبة خارجية أخرى، مثلاً في ال Collections API كل ال Classesالتالية هي Concrete: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-2b8a63f06e8f20944044f51c07e26d96dbbc1ea6c55718181336fad6d3a299d1.png) وكل هذه الكلاسات أعلاه تقوم بعمل Implements للكثير من ال Interfaces حتى تقوم بعملها بطريقة جيدة وتقدم Abstraction لك، مثلاً الكلاس List يقوم بعمل Implements لكل ال Interfaces التالية: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-f9313a63da5bd0ee89cbf614cace9e3d381b8e1f4bd99b1169b71c013d8459e3.png) لاحظ أن List Class يطبق كل هذه ال Interfaces (بدلاً من وضعها في واحدة كبيرة، وهذا مثال ل Interface Segaration Principle والفكرة أن ال Interface يستطيع أن يرث من أي Interface أخرى وبالتالي لا تحتاج لتعديل أي Interface بعد أن يخرج لل Production وصار الكثير من ال clients يعتمدوا عليها). ال IEnumerable أحد ال Interface التي تطبقها ال List والكثير من ال Concrete Collection وهي تستخدم عندما تريد ال loop على ال container، ولذلك لتطبيق القاعدة Program to abstraction فيمكنك استخدام ال interface بدلاً من اسم الكلاس عندما تستخدم أي من ال Containers. # **الفرق بين** **Programming to Concrete** **و** **Programming to Abstraction** لو لاحظت لدالة ارجاع الاشكال ExtractShapes في الدارس السابق، كانت بهذا الشكل: ```csharp static void Main(string[] args) { IRegularPolygon[] shapes = ExtractShapes(); foreach (IRegularPolygon shape in shapes) { Console.WriteLine("Area: " + shape.GetArea()); } Console.ReadKey(); } private static IRegularPolygon[] ExtractShapes() { // Read from external sources and return the polygons // parsing the file and extract the shapes return new IRegularPolygon[] { new Sequare(3), new Triangle(4), new Sequare(5) }; } ``` طريقة استخراج الأشكال خارج نطاق الموضوع، لذلك افترض أنك حصلت عليهم سواء من قراءتك لملف أو من قاعدة بيانات أو أي مصدر يكن، ومن ثم ترجع تلك الأشكال. الذي يهم هنا طريقة ارجاع المجموعة ولاحظ أنه تم ارجاعها على شكل مصفوفة (وهي تعتبر Concrete Type)، فماذا يحدث لو قمت بتغيير الكود ورجعت Data Structure أفضل منها، مثلاً List أو Set لأنك لا تريد تكرار تلك الأشكال التي لها نفس الطول مثلاً؟ لذلك حتى تطبق تلك العادة عليك بإرجاع ال Abstraction (باستخدام ال IEnumerable) وبالتالي تضمن أن أي Implementation يطبق ال IEnumerable سوف يعمل بغض النظر عن ماهيته والسبب كونك تعتمد على ذلك ال Abstraction. نلاحظ الكود الآن بعد التغيير إلى IEnumerable ```csharp static void Main(string[] args) { IEnumerable shapes = ExtractShapes(); foreach (IRegularPolygon shape in shapes) { Console.WriteLine("Area: " + shape.GetArea()); } Console.ReadKey(); } private static IEnumerable ExtractShapes() { // Read from external sources and return the polygons // parsing the file and extract the shapes return new IRegularPolygon[] { new Sequare(3), new Triangle(4), new Sequare(5) }; } ``` الكود سوف يعمل كمخرج بدون أي تغيير، ولكن الآن جرب غير الكود داخل الدالة وبدل أن ترجع Array جرب أن ترجع List أو Queue، وستجد أن ال Main (ال Client Code) لم تتغير ابداً، وهكذا تستطيع تغيير ال Implementation طالما يطبق ال Abstraction بدون تغيير ال client code، وهذا ما أطلقنا عليه اول الفصل بالكود المستقبلي Future Proofing code. # **طرق أخرى في ال** **Maintainability** استخدام ال Interface ليس الطريق الوحيدة لجعل الكود أكثر قابلية للصيانة، فهذه الخاصية لها أكثر من مسار تستطيع تطبيقه حتى يكون كودك أفضل، مثلاً: - جعله أكثر مقروئية Readability من ناحية الأسماء والتنظيم وعدم استخدام الدوال والكلاسات الطويلة ([ما هي رائحة الكود Code Smell؟](https://informatic-ar.com/code-smell/)) - جعل الدوال والكلاسات لديك مسؤولة عن شيء واحد فقط Single Responsibility ([ما هو مفهوم Single Responsibility Principle](https://informatic-ar.com/single-responsibility-principle/)) وهكذا لن تكون مضطر لتغيير تلك الأمور إلا لسبب واحد فقط - كلما حولت مشروعك لفكرة ال Modules) Layers أو Packages أو Namespaces) وقللت التداخل Coupling بين تلك الوحدات [ماذا تعرف عن ال Coupling وال Cohesion في تصميم البرمجيات؟](https://informatic-ar.com/coupling-and-cohesion/) - اخفاء كل شيء بقدر الامكان Hide Implementation أي جعل المتغيرات والدوال private، لذلك النصيحة العامة [لا تستخدم public إلا عندما تحتاجها فقط](https://informatic-ar.com/inaccessible%5Fclass/). ### **المقروئية ليست في التعليقات** المقروئية Readability لا تكون من خلال التعليقات فقط والتي يفضل أن تعتبرها أخر ورقة لديك تقوم بها عندما لا تستطيع كتابه كود أفضل من الذي قمت بكتابته. خصوصاً لو كان مجرد Business Logic وليس عباره عن خوارزمية ما (مثلاً حساب تقويم أو معالجة لصورة بطريقة ما). وقد تصل للمقروئية الجيدة في حال اتبعت التسميات الجيدة والتي فيها الغالب لكل لغة Convention خاص بها، وأيضاً إذا استخدمت بعض ال Patterns لكي تحسن ال API لديك، على سبيل المثال لماذا تقوم بعمل دالة بناء Constructor تستقبل 20 متغير؟ فيمكنك بدلاً من ذلك [أن تستخدم Builder Pattern أو Static Factory](https://informatic-ar.com/builder%5Fpattern/) وذلك لتحسين ال API. المقالة التالية سوف نرى أحد اهم الأسباب لاستخدام ال Abstraction وبالأخص ال Interface وهي في جعل الكود قابل للتطوير Extendibility. # **خلاصة** - تحدثنا عن مفهوم Program to abstraction rather than a concrete type والذي يجعل الكود أكثر صموداً مع التغيير، وليس كما هو الحال مع ال concrete type والذي يحدث فيه Broken بسهولة وقد تحتاج لعمل التغييرات. - ال Interface يبعدك عن تفاصيل ال Implementation وتجعلك تركز على ال Contract الموجود. - يفضل بدل ارجاع list أو Array أن تقوم بإرجاع IEnumerable، لأن الراجع قد يتغير من مصفوفه إلى list وسوف تحتاج لتعديل ال client ولكن عندما تستخدم ال interface فلن يحتاج ال client للتغير لأن الاثنين يطبقوا ال IEnumerable وهكذا لكل API التي تقوم بعملها. ### ما هو الفرق بين ال Abstract class وال Interface في البرمجة؟ URL: https://informatic-ar.com/interface-vs-abstract-classs/ Last updated: 2026-10-03T22:31:15.000Z استخدام ال Interface وال Abstract Class يضيفان طبقة من ال Abstraction في الكود، والفروقات التي بينهم هي التي تحدد أي Abstraction سوف تفضلها للمشكلة التي تريد حلها. في هذه السلسلة سوف نوضح ما يلي: - الفرق بين ال Abstract Class وال Interface (المقالة الحالية) - [دور ال Interface في قابلية الصيانة Maintainbility ](https://informatic-ar.com/?p=9455&preview=true) - [عمل ال Repository باستخدام ال Interface](https://informatic-ar.com/interface-repository/) - ما هو ال Dynamic Factory هذه المقالة سوف تجيب عن السؤال الشائع "ما هو الفرق بين ال Abstract Class وال Interface" وسوف نأخذ مثال ويتم برمجته بعدة طرق: الطريقة التقليدية باستخدام الدوال Procedures ثم الانتقال لل Classes واستخدام ال Abstraction بأنواعها الثلاثة سواء الوراثة من الكلاس العادي Concrete class أو الوراثة من Abstract Class أو استخدام ال interface، ومن ثم يتم النظر لتلك الحلول ومشاهدة الانسب للمشكلة، وأخيراً يتم طرح الفروق بين ال Abstract class وال Interface ومتى تستخدم كل منهم. ملاحظة سوف نستخدم لغة سي# في توضيح الأمثلة، لكن اذا كنت مبرمج جافا أو اي لغة كائنية OO أخرى، فلا تقلق يمكنك التكملة في المقال وستعي كل المفاهيم بكل سهولة. وسنوضح قدر الامكان أي فروقات بين اللغتين في المقالة اذا وجدت. # **ما هو ال** **Interface** ببساطة هو Abstract type لا يوجد فيه أي Implementation أو Data فقط تصاريح للدوال Declarations. وأفضل طريقة للتفكير في ال interface هو أنه عقد contract وله أعضاء members مثل ال methods وال Properties (ال Properties هي في السي# ويمكن اعتبار وجودها في ال Interface على أنه بمثابة تصريح لمتغير)، وعندما يقوم أي كلاس أخر بعمل Implements لذلك العقد فيجب عليه أن يقوم بتطبيقه بالكامل ويعرف كل ما هو موجود في ذلك ال interface ![](https://informatic-ar.com/wp-content/uploads/2021/10/Pasted.png) **لاحظ أن** كل الأعضاء سوف تكون public تلقائياً (بفكرة العقد فلا يمكن أن يكون هناك عقد غير معروف بنوده، وأن جميع بنوده يجب أن تكون معروفة public). ## **المثال الأول** سوف نبدأ بمثال عملي بسيط نوضح فيه المفاهيم والأساسيات بين ال Interface مع ال Abstract class والكلاس العادي Concrete (سوف نطلق على أي class عادي ليس ب Abstract بالاسم Concrete class)، وليكن لدينا برنامج لحساب مساحة ومحيط المربع من خلال ادخال طول الضلع وتقوم بإظهار المحيط والمساحة. ![مربع متساوي الاضلاع](https://informatic-ar.com/wp-content/uploads/2021/10/Pasted-2.png) بالطبع هذا الموضوع ليس في الرياضيات (واطمئن لن نكمل كثيراً في هذا المثال) وانما هو مجرد بداية توضيحية، وقوانين حساب المحيط والمساحة للمربع كالتالي: - **مساحة المربع** \= طول الضلع في نفسه - **محيط المربع** \= 4× طول الضلع بمعنى أن المدخل سوف يكون طول الضلع، والمطلوب منك كتابة برنامج يقرأ المدخل ومن ثم يطبع المساحة والمحيط. بالطبع أي مبرمج سوف يبدأ من ال mainويكتب كود بسيط يطبق تلك المعادلات، كما في الشكل: ```csharp static void Main(string[] args) { // Triangle int numberOfSides = 3;   // Calculate Area Console.Write("Enter Side Length: "); int sideLength = int.Parse(Console.ReadLine()); double area = sideLength * sideLength * Math.Sqrt(3) / 4; Console.WriteLine("Area: " + area);   // Calculate Perimeter double permieter = numberOfSides * sideLength; Console.WriteLine("Permieter: " + permieter);   Console.ReadKey(); } ``` **تعديل 1:** الآن لو طلبنا من اضافة خاصية جديدة لهذا البرنامج وهي حساب محيط ومساحة المثلث متساوي الأضلاع ايضاً من خلال معرفه طول أحد الأضلاع، من خلال المعادلة التالية: - **مساحة المثلث** \= ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-1ddafdc3aa07e3a07b64e1aeff7f812152ce9672d0c472e055e7f586659d153a.png) حيث a هي طول الضلع. - **محيط المثلث** \= 3 \* طول الضلع قد تكتب نفس الكود التالي: ```csharp static void Main(string[] args) { // Triangle int numberOfSides = 3; // Calculate Area Console.Write("Enter Side Length: "); int sideLength = int.Parse(Console.ReadLine()); double area = sideLength * sideLength * Math.Sqrt(3) / 4; Console.WriteLine("Area: " + area); // Calculate Perimeter double permieter = numberOfSides * sideLength; Console.WriteLine("Permieter: " + permieter); Console.ReadKey(); } ``` إلى هنا الأمر جميل وأنت تعمل بطريقة اجرائية Procedure أو لنقل بأن برنامج بسيط للغاية ولا يخرج من كونه Toys Example. **لاحظ** أن الفرق بين الكودين (الأول والثاني) وهو يكون فقط في الأسطر (4 و9) وبقية الكود هو كود متكرر. و**دائماً** في حال هناك تكرار في الكود فعليك أن تعرف أن هناك **تصميم غير جيد**، وأن الكود قد يقع في مشاكل عديدة مع أي تغيير أو اضافة تحدث. والتغييرات دائماً ما تحصل، قد تتساءل كيف سيحصل التغيير في هذا الكود البسيط؟ الجواب كما حدث في تعديل 1 حيث تم اضافة المثلث فقد يمكن اضافة اشكال اخرى، أو ايضاً يمكن أن يحدث في عدة أماكن أخرى، مثلاً طريقة الإدخال، فبدلاً من أنك تدخل طول الضلع من لوحة المفاتيح، سوف يطلب منك أن تقرأ طول الضلع من ملف خارجي به وصف لأشكال هندسية مثل مربع ومستطيل ومثلث ونريد أن تقوم بحساب المساحة والمحيط لكل شكل موجود في الملف. **تعديل 2:** الآن طلب أن يتم قراءة معلومات الأشكال من ملف خارجي (سواء كان XML أو أي ملف بأي صيغة) المهم به تعريف تلك الاشكال وعليك أن تقرأها جميعاً من ذلك الملف ومن ثم تحسب المساحة والمحيط. هذه اللحظة سوف تحتاج لأن تتعامل مع المفهوم Concept بدلاً من أن تتعامل مع متغيرات مثلاً numberOfSide وarea فالأفضل أن تتعامل مع المفهوم نفسه (الشكل الهندسي) سواء كان Triangle أو Square وبالتالي تستطيع أن ترجع من دالة القراءة من الملف مجموعه من الأشكال Shapes، وهذه أولى فوائد ال Object Oriented حيث تستطيع تمثيل المفاهيم بطريقة أفضل بدلاً من التعامل مع متغيرات. لكن المشكلة أنك الأن سوف تحتاج لأن ترجع جميع الأشكال في الملف مع بعضها، والا سوف تقوم بعمل دالة لإرجاع المثلثات، ودالة لإرجاع المربعات والخ، ولقد تم اخبارك أن هذه الاشكال سوف يتم عمل مجموعه من العمليات عليها فيما بعد مثلاً عرضها على الشاشة أو تطبيق مجموعه من الحسابات بها. لذلك يجب أن تستخدم مفهوم موحد Abstraction يشمل هذين الشكلين وبقية الأشكال في المجموعة. لتطبيق مفهوم الشكل Abstraction قد تقوم بأكثر من طريقة: 1. عمل Concrete class(كلاس عادي) يمثل الشكل مثلاً نسميه Regular Polygon(يمكنك تسميته بأي اسم) ومن ثم كلاس المربع والمثلث يرثان Inheritingمن ذلك الكلاس. 2. عمل Abstract class يمثل الشكل Regular Polygonومن ثم والمربع والمثلث يرثان منه 3. عمل Interface اسمه Regular Polygon ويقوم المربع والمثلث باستخدامه Implement سوف نتناول هذه الحلول ولنرى ماهي الفوائد والعيوب في كل حل منهم. ## **تطبيق ال** **Abstraction** **باستخدام الكلاس العادي** **Concrete Class** لاحظ الكود التالي يقوم بعمل class يمثل الشكل وفيه عدد الأضلاع وطول الضلع، بالإضافة إلى دالة حساب المحيط والتي هي مشتركة بين المثلث والمربع، اما دالة حساب المساحة فطالما الشكل غير محدد فسوف يتم عمل throw لل Exception في حالة تم استدعاء هذه الدالة. ```csharp public class ConcreteRegularPolygon { public int NumberOfSides { get; set; } public int SideLength { get; set; } public ConcreteRegularPolygon(int sides, int length) { NumberOfSides = sides; SideLength = length; } public double GetPerimeter() { return NumberOfSides * SideLength; } public virtual double GetArea() { throw new NotImplementedException(); } } ``` الآن نقوم بتعريف الكلاس Square ولاحظ أنه يرث من الشكل ويقوم بعمل override للدالة المساحة. ```csharp public class Square : ConcreteRegularPolygon { public Square(int length) : base(4, length) { } public override double GetArea() { return SideLength * SideLength; } } ``` نفس الأمر مع الكلاس ال Triangle: ```csharp public class Triangle : ConcreteRegularPolygon { public Triangle(int length) : base(3, length) { } public override double GetArea() { return SideLength * SideLength * Math.Sqrt(3) / 4; } } ``` لننظر الآن إلى استخدام تلك الكلاسات في الدالة الرئيسية، ولتجربة كلاس المربع Square: ```csharp static void Main(string[] args) { Square square = new Square(5); Console.WriteLine("Area: " + square.GetArea()); Console.WriteLine("Permieter: " + square.GetPerimeter()); Console.ReadKey(); } ``` النتيجة سوف تكون صحيحة بعد تشغيل الكود، سوف تجد انه قام بحساب المساحة والمحيط وتم طباعتهم على الشاشة. **الذي يهم** في هذه الطريقة هو أن الدالة GetArea في الكلاس (الأب) ConcreteRegularPolygon يقوم بعمل throw exception في حال تم استدعائها، وبالتالي إذا قمت بعمل الكلاس المثلث Triangle ونسيت اعادة تعريف هذه الدالة فسوف تحصل على ال Exception. للننظر إلى ال class التالي وهو تعريف آخر للمثلث ونسى المبرمج تعريف الدالة GetArea، وتم استخدامه: ```csharp class Triangle : ConcreteRegularPolygon { public Triangle(int length) : base(3, length) { } static void Main(string[] args) { Triangle sequare = new Triangle(5); Console.WriteLine("Area: " + sequare.GetArea()); Console.WriteLine("Permieter: " + sequare.GetPerimeter()); Console.ReadKey(); } } ``` في حال قمت بتشغيل الكود أعلاه سوف تحصل على ال Exception ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-9fd3f5c0383219c22efa24cdcd9b18da3ade5ba9802276ccb1d3008008149cf2.png) بالطبع هذا ال Exception نحن الذي كتبناه في كلاس الأب ولكن بسبب أن الابن لم يقوم بتغييره أي عمل Override (**والمترجم لم يجبره على ذلك**) لذلك حدث هذا الخطأ. فيما يلي سوف نستعرض الحل الثاني في ال Abstraction وهو يحل المشكلة السابقة (المترجم لم يجبر المبرمج على اعادة التعريف) ويجبرك على أن تعيد التعريف override وإلا فلن تتم ترجمة الكود. ## **تطبيق ال** **Abstraction** **باستخدام ال** **Abstract Class** **للتذكير** فإن ال Abstract Class هو الكلاس الذي يحتوي على دالة أو أكثر abstract ولا يمكن عمل كائن منه. الكود التالي يعرض تعريف الشكل بطريقة ال Abstract. ```csharp public abstract class AbstractRegularPolygon { public int NumberOfSides { get; set; } public int SideLength { get; set; } public AbstractRegularPolygon(int sides, int length) { NumberOfSides = sides; SideLength = length; } public double GetPerimeter() { return NumberOfSides * SideLength; } public abstract double GetArea(); } ``` هذا مشابه لل Concrete Class لكن فقط الاختلاف في ال GetArea حيث يوجد تعريف فقط Declaration ولا يوجد body في الكود، أي هي abstract ويجب عمل الكلاس abstract (بالطبع هذا يعني أنه لا يمكن عمل كائن من هذا class). كلاس المربع والمثلث سوف يكونا متشابهين وبالطبع سوف يلزمك المترجم أن تقوم بعمل تعريف للدالة GetArea وإلا فسوف تحصل على خطأ في الترجمة ```csharp public class Square : AbstractRegularPolygon { public Square(int length) : base(4, length) { } public override double GetArea() { return SideLength * SideLength; } } public class Triangle : AbstractRegularPolygon { public Triangle(int length) : base(3, length) { } public override double GetArea() { return SideLength * SideLength * Math.Sqrt(3) / 4; } } ``` الكود في ال main سوف يكون مشابه ولا يوجد عليه تغيير: ```csharp static void Main(string[] args) { Triangle triangle = new Triangle(5); Console.WriteLine("Area: " + triangle.GetArea()); Console.WriteLine("Permieter: " + triangle.GetPerimeter()); Console.ReadKey(); } ``` جرب أن تقوم بوضع تعليق على أي من دوال ال GetArea في كلاس المثلث أو المربع (على اعتبار أنك نسيت كتابتها) وستجد رسالة الخطأ واضحة، كما يلي: ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-c0e3fe68bde401a82dd703662b37084cb340df407363a1301ee6ef04cf329960.png) هكذا بطريقة ال Abstract class فإن المترجم يجبرك على عمل override لكل ال Abstract methodsوإلا فسوف تحصل على خطأ اثناء عملية الترجمة Compile Time Error. ## **تطبيق ال** **Abstraction** **باستخدام ال** **Interface** هذه المرة سوف نستخدم ال interface في نفس المثال، وهو مشابه لل abstract class ولكن بدون Implementation (فقط يوضح العقد Contract)، والكود التالي يعرض ال Interface: ```csharp public interface IRegularPolygon { int NumberOfSides { get; set; } int SideLength { get; set; } double GetPerimeter(); double GetArea(); } ``` **من ال** **Convention في سي#** أن تبدأ ال Interface بالحرف I، ومن العادات الجيدة أن تتبعه في تسميتك، لذلك قمنا بتسميته IRegularPolygon **لاحظ أن:** 1. ال Interface فقط يحتوي على declaration بدون Implementation، وحتى المتغيرين هم فقط declaration وليس تعريف لمتغير property ويجب أن تكون في الكلاس الذي يطبق ذلك ال Interface 2. عدم وجود أي Access Modifier حيث كل ال Members هم public تلقائياً، وإذا حاولت اضافه أي Modifier سوف تحصل على خطأ في الترجمة مثال على كلاس المربع الذي يستخدم ذلك ال Interface ```csharp public class Square : IRegularPolygon { public int NumberOfSides { get; set; } public int SideLength { get; set; } public Square(int length) { NumberOfSides = 4; SideLength = length; } public double GetPerimeter() { return NumberOfSides * SideLength; } public double GetArea() { return SideLength * SideLength; } } ``` لاحظ الآن وجود المتغيرات Automatic Property وهي ال Implementation لما هو موجود، بالإضافة إلى الدوال ايضاً. مثال ال main أيضاً هو نفسه (كالمثال السابق في ال Abstract class أو الاول) ولا يحتاج لأي تغيير مع الأمثلة السابقة. **ملاحظة:** إذا حاولت أن لا تطبق كل الدوال الموجودة في ال Interface سوف تحصل على خطأ وقت الترجمة ايضاً (مثل طريقة ال Abstract class). لنعد لتعديل رقم 2 وهو دالة جلب جميع الأشكال، فسواء قمت بأي طريقة من ال Abstraction السابقة فيمكنك أن تقوم بتطبيق تلك الدالة بسهولة كما يلي: ```csharp private static IRegularPolygon[] ExtractShapes() { // Read from external sources and return the polygons // parsing the file and extract the shapes return new IRegularPolygon[] { new Sequare(3), new Triangle(4), new Sequare(5) }; } ``` طبعاً بغض النظر عن طريقة ال Parsing والتعامل مع الملف، المهم هو انه استطعنا جلب جميع الاشكال من تلك الدالة، وفي المثال اعلى تم استخدام Interface ك Abstraction. لكن مرة أخرى أي طريقة منهم سوف تفي بالغرض مكانها. # **الاختيار بين** **Concrete Class** **و** **Abstract Class** **و** **Interface** **حتى هذه اللحظة** نكون قد عرضنا الحلول الثلاثة لنفس المشكلة والسؤال الآن من تختار فيما بينهم؟ - ال concrete class لا تلزمك بعمل Implementation (وقد تحصل على خطأ على حسب الكود الموجود) - ال abstract class وال Interface تلزمك بكتابة التعريف Implementation والا سوف تحصل على خطأ وقت الترجمة ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-f8d71f98038ed504057d971646b2d9ff4e98fcda08b73d50d580f4b37625c2f0.png) طبعاً أن تجد الأخطاء وقت الترجمة أفضل وأسهل لك بكثير من أن تجدها وقت التشغيل، ولذلك فال Concrete Classes ك Abstraction **لا يفضل أن يستخدم،** ويبقى الفرق بين ال Abstract class وال Interface ## **الفرق بين ال** **Abstract class** **وال** **Interface** والفرق يكمن في هذه النقاط: 1. ال abstract قد تحتوي على كود Implementation، بينما ال Interface لا تحتوي على كود وانما تحتوي على تصاريح declaration 2. عندما تستخدم ال abstractفإن كل الكلاسات تستطيع الوراثة منه ولغة البرمجة سي# وجافا تسمح بالوراثة فقط من كلاس واحد، لكن ال interface تستطيع عمل Implements لأكثر من interface 3. الأعضاء members في ال abstract class قد تحتوي على Access Modifier، بينما في ال Interface كلها تعتبر Public 4. ال abstract class تحتوي على دوال بناء وهدم ومتغيرات، بينما ال interface لا تحتوي على دالة بناء ولا هدم ولا متغيرات **أول فرقين** سوف يعطيك سهولة القرار فيمن سوف تستخدم في تصميمك، فميزة ال Abstract Class سوف يحتوي على الكود الذي تتشارك فيه جميع الأبناء، وهذا مفيد في حالة كان جميع الأبناء سوف يأخذوا نفس ال Implementation لكن مشكلته أنك لا تستطيع أن ترث من أكثر من كلاس واحد لذلك قد لا يجدي معك إذا كان كذلك قد يرث من شيء آخر. بعكس ال Interface التي يمكن أن تطبق على أكثر من Class، ولكنها بدون Implementation. ![](https://informatic-ar.com/wp-content/uploads/migrated/embedded-58955e788c2e04b06d056207b573e3b601072cc9c716915633d9eed054adb5ea.png) نقطة أخرى مهمة في اختيار ال Interface ك Abstraction هي أن الوراثة لا تجدي في كثير من الحالات ويفضل ال Composition عليها ([ما الفرق بين ال Composition و ال Aggregation](https://informatic-ar.com/aggregation%5Fvs%5Fcomposition/))، واحياناً تكون خيار خاطئ في حال لم تحقق الشرط IS-A. في مثالنا الاول (مثال الأشكال) وجدنا أن استخدام ال interface أو ال abstract class يعطي نفس النتيجة، وقد تجد أن ال Abstract مناسب هنا لأن هناك تشارك في الكود (حساب المساحة) بين المثلث والمربع. # **خلاصة** - ال Interface هي Contract بدون Implementation وتحتوي على Public Members - المترجم يلزم جميع مستخدمي هذا ال Interface بتطبيق كل ال Declaration - تمت المقارنة بين كل من ال Interface وال Abstract Class ومعرفة ميزة كل منهم. في مقالات قادمة سوف تعرف استخدامات انسب لل interface وهكذا سوف يكون لديك كل المعرفة لكي تتخذ القرار المناسب باختيار ال interface وال abstract class. ### برتوكول الـ HSTS وتطبيقه مع الـ ASP.NET MVC URL: https://informatic-ar.com/hsts-aspnet-mvc/ Last updated: 2026-10-04T04:35:36.000Z الـ HSTS او ما يعرف بالـ HTTP Strict-Transport Security هو بروتكول يختص بتنبيه المتصفحات Browsers بإستخدام بروتكول الـ HTTPS وهو مثل الحلقة ما بين تطبيق الذي يعمل على الويب وما بين المتصفح. [![](https://informatic-ar.com/wp-content/uploads/2021/04/R430b9128b0f27626a84889dcf16d8c82-1024x683.png)](https://informatic-ar.com/wp-content/uploads/2021/04/R430b9128b0f27626a84889dcf16d8c82.png) الـ Header Type لهذا البرتكول ( HSTS ) هو Response وسوف نرى ذلك في المثال القادم. لكن دعنا نشرح بإستفاضة كيفية عمل البرتكول. ## نظرة سريعة على برتوكول الـ HTTP لكي نفهم البرتكول HSTS لابد لنا ان نضع برتكول الـ HTTP في سياقه اولا، وهو برتكول الإنترنت، بعبارة بسيطة هو المسؤول عن ربط تطبيقات الإنترنت بمزودات الخدمة ( Servers ) فمثلا عندما تقول بكتابة [http://foo.com](http://foo.com/?ref=informatic-ar.com) ففعليا هنا انت تستخدم الـ HTTP فهنا انت تقوم بطلب الصفحة وارسال هذا الطلب لمزود الخدمة وهو يقوم بالرد عليك بتنفيذ هذا الطلب. الشكل التالي يبين كيفية عمل هذا البرتكول: [![](https://informatic-ar.com/wp-content/uploads/2021/04/htp.png)](https://informatic-ar.com/wp-content/uploads/2021/04/htp.png) ## مشكلة الـ HTTP في مجال الحماية دائما ما يتم اختراق التطبيقات التي تعتمد على برتكول الـ HTTP ففعليا عندما تقوم بتعبئة حقول ببعض البيانات؛ تكون هذه الحقول مرئية بالنسبة لمخترقي التطبيقات فلا يتم تشفير البيانات التي قمت بملئها في تلك الحقول (الشكل التالي يوضح ذلك على سبيل المثال فقط) [![](https://informatic-ar.com/wp-content/uploads/2021/04/fill-form.png)](https://informatic-ar.com/wp-content/uploads/2021/04/fill-form.png) بإمكانك قراء المقالة [مقدمة عن بروتوكول ال HTTP](https://informatic-ar.com/http-introduction/) للمزيد حول بروتوكول HTTP ### حل مشكلة تشفير البيانات من خلال الـ HTTPS ولحل مشكلة عدم تشفير تلك البيانات ظهر برتكول الـ HTTPS ومن مهام هذا البرتكول تشتفيرالبيانات إضافة لعمله من خلال Certificate فتكون هنالك موثوقية أكثر ما بين المتصفح والتطبيق ومزود الخدمة. ### طلب البيانات الأولى من خلال الـ HTTP عندما تقوم بكتابة أي رابط ويب على المتصفح في الغالب دائما ما يتم هذا الطلب من خلال الـ HTTP حتى وإن كنت تستخدم الـ HTTPS ( هذا في حالة قام المستخدم بكتابة [**HTTP**://domain.com](http://domain.com/?ref=informatic-ar.com) بدلا من [**HTTPS**://Domain.com](https://domain.com/?ref=informatic-ar.com)) فبعد أن تتم عملية الطلب الأول لمزود الخدمة ( Server ) يقوم مزود الخدمة بعمل إعادة توجيه لبرتكول الـ HTTPS هنا فعليا المستخدم معرض لخطر الطلب الأول والذي تم بدون تشفير للبيانات. ## حل مشكلة الطلب الأول من خلال برتوكول الـ HSTS حسنا لقد تدرجت في الشرح وأعتقد الآن انك فهمت الفرق بين الـ HTTP/HTTPS والآن لقد وصلنا للبرتكون محل هذه المقالة وهو برتكول الـ HSTS وبدون أي شرح إضافي، أعتقد تقنيا لقد فهمت المهمة الأساسية لهذا البرتكول وهي فرض اول طلب Initial Request للعمل على برتكول الـ HTTPSوتشفير البيانات. بحسب الـ MDN لهذا البرتكول ثلاثة معاملات ( توجيهات - Directive ) وإحدى هؤلاءِ الثلاثة لا يُعتبر رسمي من قِبل الـ MDN - **Max-age** وهي المدة التي سيقوم المتصفح بالإلتزام بها وعمل Redirect لأي طلب لا يستخدم الـ HTTPS وهي تلقائيا 30 يوم ومن الأفضل جعلها سنة او سنتان - **IncludeSubDomains** وعو عبارة عن معامل من النوع Boolean ففي حال كان True فإنه يقوم بتطبيق الـ HSTS على الـ SubDomain المتفرعة من الـ Domain الرئيسي - **Preload** وهو معامل غير رسمي وسوف أستعرض وظيفته في نهاية المقالة. ويكون بهذا الشكل: ```http Strict-Transport-Security: max-age= Strict-Transport-Security: max-age=; includeSubDomains Strict-Transport-Security: max-age=; preload ``` والـ Header Type يكون عبارة عن Response كما ذكرت في بداية المقالة. ## المثال العملى سأقوم بعرض عمل البرتكول HSTS من خلال الـ ASP.NET Core والـ ASP.NET MVC 5 في الـ ASP.NET Core يمكنك تطبيق هذا البرتكول من خلال ملف الـ Startup.cs من من خلال تسجيله في الـ ConfigureService method ومن خلال تطبيقه على الـ pipeline من خلال الـ Configure method وكما مبين في الصورة التالية: [![](https://informatic-ar.com/wp-content/uploads/2021/04/hstscore-1024x515.png)](https://informatic-ar.com/wp-content/uploads/2021/04/hstscore.png) في حالة الـ ASP.NET MVC 5 لا توجد أي Extension جاهزة للعمل عليها لكن سأقوم بعمل Implementation بصورة Custom لـ Filter ويتم تطبيقها على مستوى المشروع بصورة كاملة مهمتها العمل ببرتكول الـ HSTS بعد أن تقوم بإنشاء المشروع، قم بعمل فئة ( class ) بإسم HSTSMetaData وستكون كالتالي: ```csharp public class HSTSHeaderMetaData { public TimeSpan? MaxAge { get; set; } public bool IncludeSubDomains { get; set; } = true; public bool Preload { get; set; } } ``` بعدها قم بإنشاء Attribute بإسم HSTSAttribute وستكون كالتالي: ```csharp public class HSTSAttribute : RequireHttpsAttribute { HSTSHeaderMetaData data = new HSTSHeaderMetaData(); public HSTSAttribute(Action action) : base() { action(data); } public override void OnAuthorization(AuthorizationContext filterContext) { if (filterContext.HttpContext.Request.IsSecureConnection) { StringBuilder sb = new StringBuilder($"max-age={data.MaxAge}"); if (data.IncludeSubDomains) { sb.Append("; includeSubDomains"); } if (data.Preload) { sb.Append("; preload"); } filterContext.HttpContext.Response.Headers.Add("Strict-Transport-Secutiry", sb.ToString()); } else { HandleNonHttpsRequest(filterContext); } } } ``` [![](https://informatic-ar.com/wp-content/uploads/2021/04/atrrib-1024x501.png)](https://informatic-ar.com/wp-content/uploads/2021/04/atrrib.png) هذه الفئة ترث من RequireHttpsAttribute وتقوم بعمل override للأسلوب ( Method ) OnAuthorization وقمت بالوراثة من هذه الفئة حتى استطيع ان أتاكد بأن العمل يتم أولا على برتكول الـ HTTPS ```csharp filterContext.HttpContext.Request.IsSecureConnection ``` فإن كان كذلك سأقوم بعمل إدراج للـ HSTS protocol للـ Header + Response بإضافة الـ Strict-Transport-Security في الـ Constructor الخاص بالفئة قمت بإستخدام الـ Action Delegate من النوع HSTSMetaData هي الفئة التي أنشأناها أولا، وذلك حتى يتسنى لي العمل من خلال الـ Lambda Expression داخل الفئة الـ FilterConfig.cs كتالي: [![](https://informatic-ar.com/wp-content/uploads/2021/04/mvc-1024x521.png)](https://informatic-ar.com/wp-content/uploads/2021/04/mvc.png) قمت بإستخدام الـ Action Delegate حتى تكون الطريقة لتفعيل البرتكول شبيه بالموجودة في الـ ASP.NET Core فقط لا غير، وبإمكانك إستخدام المعاملات بدلا من الـ Action Delegate إذا أردت) والنتيجة ستكون في المتصفح كالتالي: [![](https://informatic-ar.com/wp-content/uploads/2021/04/mvc5-screen-1024x535.png)](https://informatic-ar.com/wp-content/uploads/2021/04/mvc5-screen.png) لقد ذكرت في أنواع المعاملات الخاصة ببرتكول الـ HSTS ان النوع الثالث preload بأنه غير رسمي من الـ MDN، وهذا المعامل هو من قبل Google وهي أتاحت منصة لتسجيل الـ Domain الخاص في هذه المنصة، وستقوم جميع المتصفحات بإستخدم جميع التطبيقات التي تم تسجيلها في هذه المنصة وستوقم بعمل Check للتأكد من عمل تحويل للـ Request الأول أنه تم عن طريق HTTPS بإمكانك الدخول من هذا الرابط [https://hstspreload.org/](https://hstspreload.org/?ref=informatic-ar.com) ومعرفة الكثير عن الـ preload تحياتي - محمد ### التعامل مع الـ Caching في الـ ASP.NET Core من خلال Redis URL: https://informatic-ar.com/aspnet-core-redis-caching/ Last updated: 2026-10-04T04:35:36.000Z كل البرمجيات لديها بيانات يتم جلبها من قاعدة البيانات، هنالك بيانات يتم جلبها بإستمرار، في البرمجيات الضخمة لابد أن تكون سرعة الإستجابة لجلب هذه البيانات كبيرة ، فالثواني قد تفرق في حال برمجيات المبيعات التي تتعامل بصورة مباشرة من الزبون مثل أمازون أو غيرها من البرمجيات الضخمة. بالتالي عندما يقوم المستخدم بجلب البيانات في المرة الأولى، يتوجب علينا تخزين هذه البيانات لفترة محددة من الزمن، في البرمجيات الصغيرة يتم تخزين هذه البيانات في الذاكرة الخاصة بالسيرفر. لكن ماذا في حال كانت هذه البيانات متداولة بين عدة سيرفرات (خوادم) Web Farm بالتالي سنحتاج آلية لتخزين هذه القطع الصغير من البيانات في مكان آخر. توجد قاعدة بيانات مصغرة تُسمى Redis وهي مفتوحة المصدر، وهي موجهة لأنظمة التشغيل من النوع Unix-Like تقوم بتخزين هذه القطع من البيانات على الـ Disk بطريقة معينة. ولكي نقوم بالعمل مع Redis على نظام التشغيل Windows يجب علينا تنزيل هذا الملف الذي توجد به ملفات exe من خلال الرابط التالي [https://github.com/microsoftarchive/redis/releases/tag/win-3.0.504](https://github.com/microsoftarchive/redis/releases/tag/win-3.0.504?ref=informatic-ar.com) وكما هو مبين في الصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2020/11/1-1024x413.png) في هذه المقالة سنري كيفية التعامل مع Redis من خلال ASP.NET Core5.0 أولا بعد إنشاء مشروع ASP.NET Core Web Application قم بفتح الـ Nuget وقم بتنزيل المكتبة التالية Microsoft.Extensions.Caching.StackExchangeRedis للتعامل مع الـ Cache في الـ ASP.NET Core5.0 توجد واجهة interface تسمى IDistributedCache وبها مجموعة من الأساليب Methods في هذه المقالة سنحتاج لإسلوبين من هذه الواجهة ```csharp (SetStringAsync(key,value,options (GetStringAsync(key ``` هذان الإسلوبان يقومان بالتعامل مع البيانات على صيغة JSON وتوجد منها أساليب أخرى تتعامل مع البيانات بصيغة Bytes حسنا سأقوم بإنشاء ملف بإسم Helpers وسأقوم بإنشاء واجهة Interface بإسم IDistributedCacheAdapter وفي نفس هذا المجلد ساقوم بإنشاء فئة Class يرث من الواجهة التي قمنا بإنشاءها بإسم DistributedCacheAdapter ستكون الشفرة بهذه الطريقة: ```csharp public interface IDistributedCacheAdapter { Task Store(string key, T data, TimeSpan? absoluteExpirationRelativeToNow = null, TimeSpan? slidingExpiration = null); Task Retrieve(string key); } ``` ![](https://informatic-ar.com/wp-content/uploads/2020/11/13-1024x405.png) ```csharp public class DistributedCacheAdapter : IDistributedCacheAdapter { IDistributedCache _cach; public DistributedCacheAdapter(IDistributedCache cache) { _cach = cache; } public async Task Store(string key, T data, TimeSpan? absoluteExpirationRelativeToNow = null, TimeSpan? slidingExpiration = null) { var distributedCacheEntryOptions = new DistributedCacheEntryOptions(); distributedCacheEntryOptions.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow ?? TimeSpan.FromSeconds(60); distributedCacheEntryOptions.SlidingExpiration = slidingExpiration; var dataToSerialize = System.Text.Json.JsonSerializer.Serialize(data); await _cach.SetStringAsync(key, dataToSerialize); } public async Task Retrieve(string key) { var result = await _cach.GetStringAsync(key); if (result is null) { return default(T); } return System.Text.Json.JsonSerializer.Deserialize(result); } } } ``` ![](https://informatic-ar.com/wp-content/uploads/2020/11/12-1024x576.png) يوجد لدي في هذه الفئة اسلوبان **الأول** يقوم بتخزين البيانات، **والثاني** يقوم بإسترجاعها، في اسلوب التخزين Store يوجد متغيران أخيران من النوع TimeSpan الاول: **absoluteExpirationRelativeToNow** يقوم بتخزين البيانات على الـ Cache لمدة دقيقة الثاني: **slidingExpiration** يقوم بحذف البيانات في حال لم يكون هنالك أي إستدعاء لتلك البيانات في الوقت الذي تقوم أنت المبرمج بتحديده، فرضا في حال قمنا بتحديد دقيقية واحدة ولم يكن هناك أي حركة على شاشة المستخدم *In-Active* لمدة دقيقة سيقوم الـ Redis بحذف البيانات من على الـ Cache storage توجد فئة بإسم **DistributedCacheEntryOptions** وهي التي بها ثلاثة خصائص يتم من خلالها تحديد هذه المدد الزمنية. لابد من تهيئة Redis من خلال ملف الـ startup.cs وسيكون كالتالي: ```csharp services.AddStackExchangeRedisCache(redisOptions => { redisOptions.Configuration = Configuration.GetConnectionString("RedisConn"); redisOptions.InstanceName = "MyApp_"; }); ``` وهنا قم بعمل DI للواجهة والفئة داخل منلف الـ Startup.cs ```csharp services.AddScoped(); ``` الخاصية **Configuration** وهي تُعنى بالربط مع الـ Redis-Server والخاصية **InstanceName** وهي اسم فريد نقوم بإعطاءه للبيانات التي يتم تخزينها للتطبيق الذي نعمل عليه، لأنه قد يكون لديك أكثر من تطبيق يقوم بإستخدام نفس الـ Redis-Server بالتالي ستقوم بالتفريق بينهم بهذا الإسم. أيضا بالنسبة للربط بين Redis وقاعدة البيانات التي تعمل عليها سيكون كالتالي داخل ملف الـ appSettings ```json "ConnectionStrings": { "DBConnectionString": "RedisConn": "localhost:6379" } ``` في الـ Controller سيكون الكود كالتالي: ```csharp [Route("api/[controller]")] [ApiController] public class TestController : ControllerBase { private readonly DataContext _context; private readonly IDistributedCacheAdapter _distributedCacheAdapter; public TestController(DataContext dataContext, IDistributedCacheAdapter distributedCacheAdapter) { _context = dataContext; _distributedCacheAdapter = distributedCacheAdapter; } [HttpGet] public async Task Get() { var post = await _distributedCacheAdapter.Retrieve>("MyBlogPost"); if (post == null) { post = await _context.Posts.ToListAsync(); await _distributedCacheAdapter.Store("MyBlogPost", post); return new ObjectResult(post); } else { return new ObjectResult(post); } } } ``` ![](https://informatic-ar.com/wp-content/uploads/2020/11/11-1024x504.png) في هذا الكود، قمت بالتأكد من وجود البيانات في الـ Cache فإذا كانت موجودة سيقوم الـ Redis-server بجلبها، وإن لم تكن موجودة سيقوم النظام بجلبها من قاعدة البيانات وفي نفس الوقت قمت بتخزينها على الـ Redis-server وحتى يتسنى لي في الرة القادمة في حالقام المستخدم بإستدعائها مرة أخرى حينها سيتسنى لي جلبها من الـ Redis-server. حسنا قبل عمل تشغيل للنظام قم بفتح الملف الذي قمت بتنزيله في بداية المقالة وقم بتشغيل ملف *redis-server.exe* سيكون كالتالي كما موضح بالصورة، وهذه سيتيح لنا إمكانية التعامل مع الـ Redis كسيرفر (خادم) نسبة لأننا نعمل على بيئة Windows ![](https://informatic-ar.com/wp-content/uploads/2020/11/2.png) يقوم الخادم بالعمل على الـ IP 127.0.0.1 وعلى المنفذ 6379 وبإمكانك تغييرهم إذا أردت. (قم بالذهاب لآخر المقالة لمعرفة من أين يمكن تغيير هذه الإعدادت) بعد فتح الـ redis-server.exe قم بفتح الـ redis-cli.exe وقم بتنفيذ الأمر ping وفي حال كان الإتصال يعمل بصورة طبيعية سيقوم بالرد برسالة PONG. ![](https://informatic-ar.com/wp-content/uploads/2020/11/3.png) وكما ترى في الصورة لقد قمت بتنفيذ الأمر scan 0 وذلك حتى أتأكد من أنه لا توجد أي بيانات مخزنة مسبقا، وبعد تنفيذ التطبيق سنقوم بتنفيذ هذا الأمر مجددا لنرى أنه هنالك بيانات تم تخزينها بعد أول Request فبالتالي عندها نقوم بعمل Request ثاني سيتم جلب البيانات من الـ Redis server. أقوم بإستخدام قاعدة بيانات من النوع PostgreSQL ولدي جدول واحد في قاعدة بيانات بإسم Test\_DB والجدول بإسم Posts وبه أربعة حقول وهذا الجدول به 1000 سطر من البيانات، و كما موضح الـ Schema الخاصة بهذا الجدول في الصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2020/11/4-1024x479.png) الآن إذا قمت بتنفيذ النظام وفتح الـ Post man وعمل إستدعاء للـ Action Methods سيقوم النظام بمخاطبة قاعدة البيانات في المرة الاولى، وسيقوم بتخزين البيانات على الـ Cache وفي المرة الثانية سيقوم بإستدعائها من الـ Cache بدلا من مخاطبة قاعدة البيانات مرة أخرى، لاحظ للمدد الزمنية في الـ Request الأول، والـ Request الثاني والـ Request الثالث الـ Request الاول مدته 3 ثواني والثاني 159 ملي ثانية والثالث 84 ملي ثانية ![](https://informatic-ar.com/wp-content/uploads/2020/11/8-1024x576.png) ![](https://informatic-ar.com/wp-content/uploads/2020/11/9-1024x576.png) ![](https://informatic-ar.com/wp-content/uploads/2020/11/10-1024x576.png) (أعتقد أن الفرق بين الـ Requests كافي لأن يعمل النظام بصورة بها أداء عالي جدا من الكفاءة) **أخيراً**: أقوم بإستخدام Redis من خلال السيرفر الخاص بي (الخادم) والذي يعمل على نظام التشغيل FreeDSB ومن خلاله أقوم بتثبيت الـ IP الذي أود التعامل معه من على الـ Production وأقوم بعمل الإعدادات داخل ملف redis.conf وكما موضح بالصور التالية: ![](https://informatic-ar.com/wp-content/uploads/2020/11/5.png) ![](https://informatic-ar.com/wp-content/uploads/2020/11/6.png) تحياتي. ### مفهوم الـ Automated من خلال GitHub Actions URL: https://informatic-ar.com/github-actions-automation/ Last updated: 2026-10-03T22:31:17.000Z بناء البرمجيات الضخمة ليس بالسهولة التي يتصورها البعض، بل تحتاج لقواعد ومجهود متكامل يحقق الإستمرارية بصورة متوازية لمرحلة نشر تلك البرمجيات. **CI/CD** لا أريد أن أطيل الحديث عن مفهوم الـ Automated فهو ببساطة جعل العمل يحقق الاستمرارية أثناء مرحلة التطوير وأيضا الإستمرارية أثناء مرحلة نشر تلك البرمجيات – وذلك حتى يتسنى لك الإنتهاء من الإعمال التي تقوم بها في وقتها المحدد وبجدول زمني محدد Work Items. > **حسنا في هذه المقالة سأقوم بعمل تطبيق بسيط يقوم بإنشاء حقول في أحد قواعد البيانات بعد كل دقيقة.** أثناء مرحلة إنشاء هذا التطبيق بالتأكيد أنت كمطور ستحتاج لإضافة بعض التحسينات بين وفترة وأخرى وبعد الإنتهاء من كل إضافة تقوم بها ستحتاج لنشر التطبيق حتى يقابل تلك التحسينات التي قمت بعملها. كل هذا سيتم بصورة يدوية، ففي كل مرة يُطلب منك أن تقوم بإضافة خاصية ما على التطبيق، ستحتاج لنشر هذه الإصافات ورفعها للعمل بصورة حقيقة، وهكذا مع كل إضافة ستقوم بعمل النشر بصورة يدوية. حسنا ماذا لو قمت بعمل النشر بصورة ديناميكية، فبمجرد عمل merge للعمل الذي قمت به للـ branch الرئيسي سيقوم الـ GitHub بعملية النشر بصورة تلقائية. هذه المقالة لن أتطرّق لكيفية إستخدام الـ GitHub في عملية إنشاء المشروع أو طريقة عمل الـ Branches أو طريقة عمل الـ Pull Requests فإذا لم تستخدم الـ GitHub من بإمكانك الذهاب للتوثيق الخاص بالمنصة وبداية التعلم ومن ثم بإمكانك الرجوم لهذا المقالة وتعلم كيفية النشر بصورة ديناميكية Automated Deployment. **حسنا الـ GitHub Actions تستطيع إستخدامها مع جميع برمجياتك سواء كنت تستخدم Java / Nodej / Angular / C# / Vuejs وبإمكانك ان تقوم بنشر البرمجيات على الأنظمة السحابية أو على الأجهزة المحلية، وما سنتطرق له في هذه المقالة كيفية عمل النشر على جهازك المحلي Local machine** حسنا التطبيق الذي أقوم بإستخدامه عبارة عن Worker Services يعتمد على .Net Core3.1 وهو عبارة عن Services ونريد أن نقوم بنشر هذا التطبيق على الـ Windows Services أولا بعد رفع المشروع على الـ GitHub لابد من عمل الإعدادت التي تتيح لي عمل نشر للبرنامج بصورة ديناميكية، بعد رفع المشروع قم بالذهاب لـ Settings ومنها أسفل اليسار قم بالضغط على Actions ستجد مربع باللون الأخضر مكتوب على Add runner وهي التي ستقوم بالتعريف بين المشروع الذي نعمل عليه والجهاز أو السيرفر الذي نريد عمل نشر Deploying عليه. كما موضح بالصورة أدناه ![](https://informatic-ar.com/wp-content/uploads/2020/11/actions-runners-1024x384.png) بعد الضغط على هذا الزر سيقوم بإعطاءك أربعة أسطر من الأوامر ستقوم بعمل تنفيذ لها من خلال أي محرر أوامر لديك كمثال الـ Power Shell لكن قبل هذا قم بإنشاء مجلد في محرك الأقراص C بإسم actions-runner وقم بتنفيذ تلك الأوامر من خلال هذا المسار وبعد تنفيذ آخر أمر سترى النتيجة بهذه الطريقة الموضحة في الصورة أدناه. ![](https://informatic-ar.com/wp-content/uploads/2020/11/success-pipeline-1024x383.png) قم بالضغط على Actions من القائمة الرئيسية للمشروع، سيقوم الـ GitHub Action بعملية مسح Scan للتطبيق وسيقوم بتقديم مقترح لك بالتقنية التي ستحتاجها لعمل نشر التطبيق وكما ترى في الصورة أدناه بإختيار تقنية .Net Core وهي التقنية التي قمت بإستخدامها في إنشاء التطبيق. ![](https://informatic-ar.com/wp-content/uploads/2020/11/actionpress.png) حسنا قم بالضغط على زر set up this work-flow سيقوم الـ GitHub Actions بعرض ملف yaml وهو عبارة عن ملف يستخدم في التعامل مع إعدادات الـ CI/CD وإذا كنت من مستخدمي Azure DevOps ستجد هذا الملف أيضا، وهو عبارة عن ملف بسيط يساعدك على كتابة الأوامر التي تريد تنفيذها والتي تقوم بنشر التطبيق الذي تود نشره. الأوامر التي سأحتاجها لنشر التطبيق وعمل Deployment للتطبيق ستكون كالتالي وبعدها سأقوم بالضغط على زر Start commit لتنفيذ العملية ومن ثم Commit new file وكما موضح بالصورة التالية: ```bash name: .NET Core on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build-and-deploy-to-win-services: runs-on: self-hosted steps: - uses: actions/checkout@v2 - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: 3.1.301 - name: Install dependencies run: dotnet restore - name: Build run: dotnet build --configuration Release --no-restore - name: dotnet publish run: | dotnet publish -c Release -o hostedservicesapp ``` ![](https://informatic-ar.com/wp-content/uploads/2020/11/yaml-1024x412.png) ماذا يعني هذا؟ الآن لقد قمت بأول خطوة وهي عملية Publish للتطبيق، وكما ترى في الصورة أدناه أن جميع الخطوات تمت بكل سلاسة. ![](https://informatic-ar.com/wp-content/uploads/2020/11/publishdone-1024x410.png) > ملاحظات في حال واجهت مشاكل: أولا قبل عملية التنفيذ هذه تأكدة من أن جهازك المحلى مربوط بالـ GitHub Actions وتظهر لك العلامة الخضراء. كما موضح بالصورة أدناه ![](https://informatic-ar.com/wp-content/uploads/2020/11/after-run-in-github-1024x268.png) معنى idle بأن الربط بين الـ Actions والجهاز المحلي تعمل بصورة جيدة، وهي إعدادات الـ self-hosted > ثانيا: في حال واجهت عملية فشل في خطورة الـ Dependency قم بإعطاء الـ Power Shell لديك صلاحية العميل بصورة Remotely وقم بتنفيذ هذا السطر من على ال Power Shell: **Set-ExecutionPolicy RemoteSigned** > بإمكانك بعد الإنتهاء غلق هذه الخاصية بالسطر التالي: **Set-ExecutionPolicy Restricted** **في حال أردت أن تكون هذه الخاصية مفتوحة بصورة دائمة بإمكانك ربطها مع جهازك فقط من خلال بعض الأوامر (وهي لحمايتك من التعرض لأي إختراق خارجي لا غير).** حسنا الخطوة الأولى لقد انتهت وهي أننا قمنا بنشر التطببيق في المسار الذي أنشأناه سابقا على القرص C والذي سميناه actions-runner وكما تري في الصورة التالية، موضح المسار الذي قمت بتنفيذ الأسطر التي قام الـ GitHub Actions بإعطائي لها ( في حالتي هنا اسم المسار actions-runner2 نسبة لأنه لدي تطبيق آخر يعمل على المسار actions-runners)، وكما موضح أيضا في الصندوق الأحمر ملف الـ exe والذي سنقوم بعملية التنفيذ له وتثبيته على الـ windows services من خلال التعديل على ملف ال yaml في المشروع نفسه. ![](https://informatic-ar.com/wp-content/uploads/2020/11/after-publish-1024x452.png) ملاحظة: إذا لاحظت هنا في ملف الـ yaml ان أمر النشر الذي قمت بكتابته هنا هو نفسه الذي نستخدمه في ال Visual Studioفي نشر برمجياتنا، فقط الإختلاف هنا هو أن العملية هنا ستكون ديناميكية، فعندما أقوم بالتعديل على أي جزئية في المشروع وبمجرد عمل merge للـ Branch الرئيسي سيقوم الـ GitHub Actions بعملتي النشر والتنفيذ Publish and Deploy بصورة ديناميكية. حسنا سيكون ملف الـ yaml مكتملا بهذه الصورة بعد التعديل الأخير، وهو إضافة خاصية تنفيذ الـ Services وتشغيلها مباشرة Start Services. ```bash name: .NET Core on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build-and-deploy-to-win-services: runs-on: self-hosted steps: - uses: actions/checkout@v2 - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: 3.1.301 - name: Install dependencies run: dotnet restore - name: Build run: dotnet build --configuration Release --no-restore - name: dotnet publish run: | dotnet publish -c Release -o hostedservicesapp - name: execute to windows services run: | sc.exe create HostedServicesApp binpath= C:\actions-runner2\_work\HostedServicesApp\HostedServicesApp\HostedServicesApp start= auto - name: start service run: | sc start HostedServicesApp ``` وكما ترى أيضا بعد التنفيذ قام الـ GitHub Actions بإظهار خطأ ، وهو أن المستخدم الذي أقوم بالعمل عليه ليس له صلاحية لتثبيت هذه الـ Services، طبعا بإمكانك تشغيل الجهاز كـ Administrator وعمل تنفيذ للمشروع أو بإمكانك عمل إعدادات لجعل المستخدم الحالي ان يقوم بالعمل على الـ Power Shell كـ Administrator ![](https://informatic-ar.com/wp-content/uploads/2020/11/error-1024x417.png) وكما ترى بعد تشغيل الجهاز كـ Administrator وعمل تنفيذ للـ Actions مرة أخرى ستكون النتيجة كما موضح بالصورة التالية، وأيضا ستري في الصورة التالية لها أن الخدمة Service قد تم تثبيتها. ![](https://informatic-ar.com/wp-content/uploads/2020/11/admi-1024x418.png) ![](https://informatic-ar.com/wp-content/uploads/2020/11/serv.png) بعد تثبيت الخدمة من خلال الـ Actions حسنا مالذي استفدناه من هذا العمل؟ عندما تقوم بالتعديل على المشروع مرة أخرى في الشفر ة المصدرية وبعد أن تقوم بعمل Pull Request للجزء Task/Work item الذي قمت بعمله، سيقوم الـ GitHub Actions بعمل Publish و Deploy or Execute بصورة ديناميكية بدلا عنك. تحياتي. ### الـ Concurrency مع الـ ASP.NET Core Web API من خلال ETag Header URL: https://informatic-ar.com/aspnet-concurrency-etag/ Last updated: 2026-10-04T04:35:37.000Z جميعنا يعلم أهمية التزامن في البرمجيات فمعظم الـ API حالتها ضعيفة بعد جلبها من المصدر، فإذا كنت تريد ان تحكم سيطرتك على البيانات فبالتالي انت مطالب بعمل إضافي في برمجياتك. نفترض ان لدينا مجموعة بيانات قام المستخدم A بجلبها من قاعدة البيانات، وبعدها بلحظات قام المستخدم B بجلب نفس البيانات لعمل تعديل عليها ماذا تتوقع أن يحدث في تلك الحالة إذا قام المستخدم B بتغيير البيانات. ماهي حالة المستخدم A هنا؟ من المؤكد أن المستخدم A في هذه الحالة سيقوم بالتعامل مع بيانات قديمة. > هذه المقالة تعبتر متقدمة نوعا ما Advanced وموجهة للمبرمجين المحترفين بصورة عامة، فإن كنت تواجه صعوبة كبيرة في المتابعة فبإمكانك تركها وعدم قراءتها فقط يكفيك أن تقوم بإنشاء حقل في كل جداول قاعدة البيانات خاصتك يأخد النوع rowversion وبالتالي سيقوم الـ SQL Server بالتحكم في جميع البيانات سواء التي تم جلبها والتي يريد المستخدم حفظها حسنا إذا كنت مواصل معي في القراءة فمرحبا بك. ماهو الـ ETag هو عبارة عن معرف (في الغالب GUID) يتم تخزينه بصورة مؤقتة مع جميع الـ Requests والـ Responses فيساعد في سرعة التقليل من الـ BandWidth بحيث أنه في حال قمت بطلب جلب بيانات لم تتغير منذ الـ Request الأول بالتالي سيتم عرض نفس البيانات بدون توليدها مرة أخرى، أيضا يساعد في عدم حدوث overwrite للبيانات كما شرحنا سابقا في حالتي المستخدمين A و B. قم بإنشاء مشروع ASP.NET Core جديد وقم بإنشاء Class بإسم داخل المشروع بإسم HashFactory وقم بإنشاء Method داخل نفس الـ Class بإسم GetHashFromProprites الشفرة كاملة لهذا الـ Class ستكون كتالي: ```csharp public static class HashFactory { public static string GetHashFormProperties(Type type) { if (type == null) { return string.Empty; } string propertyToHash = string.Empty; var getProperites = from m in type.GetProperties() select m.Name.ToString(); // Use Reflection foreach (var data in getProperites) { propertyToHash = data.ToList().ToString(); } using var md5 = MD5.Create(); byte[] dataRow = md5.ComputeHash(Encoding.UTF8.GetBytes(propertyToHash)); StringBuilder sb = new StringBuilder(); for (int i = 0; i < dataRow.Length; i++) { sb.Append(dataRow[i].ToString("x2")); } return sb.ToString(); } ``` مهمة هذا الـ Class هي ان يقوم بتوليد رقم من النوع Guid وهي الرقم الذي يتم إسناده للـ ETag header وقمت بإستخدام الخوازمية MD5 وبها Method تسمى CreateHash، ومحتوى الـ Guid الذي يتم توليده عبارة عن جميع الـ Property للـ Class الذي سيتم عليه عمليتا الـ Request والـ Response وقد قمت بإستخدام الـ Reflection لجلب جميع الـ Properties للـ Object الذي نريد التعامل معه وقمت بإستخدام تقنية Linq لإختيار اسم الـ Property فقط. قم بإنشاء Controller وبها اسلوبين الأول عبارة عن GET والآخر عبارة عن PUT الشفرة ستكون كالتالي، سأقوم بالشرح بعد تنفيذ المشروع: ```csharp const string ETAG_HEADER = "ETag"; const string MATCH_HEADER = "If-Match"; [HttpGet("{id}", Name = "GetPostById")] public IActionResult GetById(int id) { var post = _context.Posts.Where(x => x.Id == id).FirstOrDefault(); var eTag = HashFactory.GetHashFormProperties(typeof(Post)); HttpContext.Response.Headers.Add(ETAG_HEADER, eTag); if (HttpContext.Request.Headers.ContainsKey(MATCH_HEADER) && HttpContext.Request.Headers[MATCH_HEADER].Contains(eTag)) { return new StatusCodeResult(StatusCodes.Status304NotModified); } return new ObjectResult(post); } [HttpPut("{id}", Name = "UpdatePostById")] public async Task Update(int id, [FromBody] Post post) { if (post == null || post.Id != id) { return BadRequest(); } var postToUpdate = _context.Posts.FirstOrDefault(x => x.Id == id); if (postToUpdate != null) { return NotFound(); } var contextTag = HashFactory.GetHashFormProperties(typeof(Post)); if (!HttpContext.Request.Headers.ContainsKey(MATCH_HEADER) || !HttpContext.Request.Headers[MATCH_HEADER].Contains(contextTag)) { return new StatusCodeResult(StatusCodes.Status412PreconditionFailed); } postToUpdate.Name = post.Name; _context.Posts.Update(post); await _context.SaveChangesAsync(); return new NoContentResult(); } ``` حسنا هنا في الحدث GET قمت بإضافة الـ ETag header مع الـ Request وقبل جلب البيانات . بعدها قمت بالتأكد في حال كان الـ Header قام بإرسال الـ Etag من خلال If-Match header والذي سيقوم بالتأكد من أن المعرف GUID الذي تم إرساله مع الـ Request الأول هو نفسه، هنا لدينا حالتين **الأولى:** إذا كان تم إرساله من قبل فيسقوم الـ API بإرجاع 304NotModified Status فبالتالي لن يقوم السيرفر بمخاطبة قاعدة البيانات او XML او أي Provider لجلب البيانات بل سيقوم بجلبها من البيانات المخزنة لديه من Chache **الحالة الثانية:** إذا أراد مستخدم آخر أن يعدل في البيانات لابد أن يقوم بإرسال الـ If-Match header مع الـ Request وذلك حتى لايحدث تضارب في جلب البيانات بعد تعديلها بالنسبة للمستخدم الأول الذي قام بعمل Request. ![](https://informatic-ar.com/wp-content/uploads/2020/11/1-1024x580.jpg) هنا قمت بعمل Request لجلب البيانات من قاعدة البيانات ![](https://informatic-ar.com/wp-content/uploads/2020/11/2-1-1024x576.jpg) إنظر هنا للـ ETag header حيث تم توليد Guid جديد مع الـ Request ![](https://informatic-ar.com/wp-content/uploads/2020/11/3-1024x582.jpg) هنا قمت بجلب البيانات مرة أخرى مع إسناد If-Match مع الـ Request وكما موضح في المستطيل الأحمر بأن نتيجة الـ Response هي 412 وتعني أنه لك يتم تغيير الـ Guid من أول Request بإمكانك التجربة من خلال الـ Postman ورؤية ما قمنا بشرحه بصورة عملية. في الحدث PUT قمت بالتأكد من أنه لابد أن يقوم المستخدم بإرسال If-Match header وذلك حتى يتسنى لي مقارنة الـ Guid الذي تم تخزينه مع الـ Request مع نفس الـ Guid الذي سيتم إرساله في هذا الحدث PUT فإذا كانا مختلفين فحينها ستم إجهاض عملية التعديل وسيتم إرجاع 412PreConditionFailed status تحياتي ### بناء انظمة قابلة للتوسع , بين النظرية والتجربة URL: https://informatic-ar.com/scalable-systems-in-practice/ Last updated: 2026-10-03T22:31:18.000Z شهد التطور الكبير في مجال الانترنت والاجهزة الذكية واللوحية طفرة كبيرة خلال السنين العشر الاخيرة , ادى ذلك لدخول الكثير من المستفيدين لهذه الخدمات لعالم الانترنت ومحاولة نقل اعمالهم الي المنصات , ولم يكتفو فقط بنقل الاعمال بل حتى المتعة entertainment والتواصل الاجتماعي social media platforms واصبحت حياة معظم الناس تعتمد بشكل اساسي على هذه الخدمات . الإقبال الكبير على هذه الخدمات وضع مطوريها في تحدي دائم نحو "**قابلية التوسع**" اي كيفية جعل النظام او المنصة قادرة على إستيعاب أكبر كمية من المستخدمين دون التأثير على سرعة الاستجابة **latency** وتوقف الخدمات **down time** , **availability** وتماسك هذه الخدمات والحفاظ على بيانات المستخدم بشكل دائم **durability** لكن يظل السؤال المركزي في قابلية التوسع , كيف نقوم بذلك.؟ هل هنالك شكل محدد ام قواعد وتوجيهات للقيام بذلك ؟ , كيف نوفق بين الكتب النظرية والمشاكل العملية التي تواجه المطورين في جعل انظمتهم قابلة للتوسع ؟ في هذا المقال سنشرح بعض النصائح التي لو اتبعتها اختصرت جهدآ وسنوات كثيرة في التعلم من الأخطاء والمشاكل التي تصاحب هذه العملية . **1- المقارنة بين نظامك وبين الانظمة الاخرى غالبا غير مجدية :** بعض المطورين يسئ التقدير والمقارنة بين الخدمات المختلفة , مثلا قياس تطبيق يقوم بالإستجابة لـ 10 الف طلب في الثانية ولكن هذه الطلبات مجرد صفحة تحتوي على HTML ولا يوجد بها اي عمليات في ال **backend** ولا استعلامات من قواعد البيانات querying from databases او البحث في ملايين الملفات النصية full text search , مثل هذه المقارنات لا تعطي اي تصور ولا فائدة وقد تزيد التشويش على المطور , ايضا من اشكال هذه المقارنات , المقارنات التي يقوم بها مطوري او مروجي خدمة معينة او بروتوكل او لغة برمجة معينة او حتى إطار عمل مثل عمل اكثر من 2000 اتصال على بروتكول X بنفس **الوقتsimultaneously connections** (قد تكون الظروف مختلفة , البيانات المرسلة صغيرة لا تتعدى 10 بايت مثلا , العتاد عالي جدآ اثناء الاختبار ) , او حتى اختبار إطار عمل معين يقوم بارجاع نتيجة خلال اقل من ثانية (وفي الغالب النتيجة عبارة عن نص ولا يوجد اي معالجة للطلب من قواعد بيانات او تنفيذ خوارزميات تستهلك وقت وموارد) , مثل هذه المقارنات تعطي التصور الخاطئ والمحصلة النهائية لها انك تنتج نظامآ غير قابلآ للتوسع لانك تعتمد على امور اكاديمية علمية غير دقيقة , كانت على اساس بنية معينة **specific environment** او على هوى الذي قام بها لكي يروج لمنتج او اداة معينة. **2- قابيلة التوسع تبدأ من كتابة الكود الجيد :** كثير من المبرمجين يهمل تفاصيل الاكواد , والكثير منهم يقوم بنسخ الأكواد فقط من مواقع مثل stack overflow وغيرها , مثل هذه الممارسات تضر بالكود والنظام عند المحاولة لجعله قابلآ للتوسع , فمثلا كتابة اسطر معينة بلغة java( او نسخها من مكان ما) قد يكلفك سرفر بإمكانيات قيمتها 100 دولار , وان قمت بتحسين الكود بما يتناسب مع المشكلة فقد تقلل هذه القيمة الي 25 دولار فقط , ايضا الوقت الطويل الذي ياخذه الكود السئ (وان كان سطرآ واحدآ) يؤثر حتمآ على سرعة الاستجابة في الخدمة > دائما إهتم بالتفاصيل الصغيرة , قم بكتابة الكود بافضل طريقة بحيث يكون سريعآ في التنفيذ , سريع في الاستجابة, بدون مشاكل وأخطاء , ونظيفآ وسهل القراءة **3- الطفل الذي يولد قبل أوانه في الغالب يموت :** كثير من المطورين يريد كتابة انظمة واكواد قابلة للتوسع من اليوم الاول day one , وهذا المفهوم خاطئ ويجعل المبرمج يركز مع التفاصيل التقنية واهمال المتطلبات الفعلية من البزنس **business requirements** وفي الغالب لا يستطيع تسليم المشروع في الوقت المحدد **deadline** ولا الحصول على قابيلة التوسع المناسبة. > حاول ان تبدأ بكود بسيط واهتم بالتحسينات الاولية premature optimizations وقم مع الوقت بعمل ال refactoring اللازم واضافة الميزات التي تجعل النظام قابلآ للتوسع. **4- وهم التعقيد في البنية المعمارية :** كثير من المطورين يتبنو عبارة complexity implies sophistication and simplicity implies primitiveness ( أن التعقيد يعني ان النظام قوي وقابل للتوسع , وان البساطة تعني البدائية) وهذا المفهوم خاطئ تمامآ ف كثير من المشاكل قد تحل بابسط الطرق دون اللجوء للتعقيد وكتابة مئات الاسطر البرمجية , ان هذا الوهم الذي يصاحب المطورين يجعل من المطور المبالغة في كتابة الكود وصياغة المعمارية overengineering . > إبدأ بسيطآ وحسن مع الوقت , اذا كان الكود البسيط يحل المشكلة فلما التعقيد والتغير ؟ **5- عنق الزجاجة دائمآ في قواعد البيانات :** في معظم الانظمة القابلة للتوسع تمثل قواعد البيانات "عنق الزجاجة" **bottleneck** للخدمة ومنعها من ان تكون قابلة للتوسع بشكل سريع وجيد , معظم اطر العمل تستطيع ان تستقبل اكثر من مليوني طلب في الثانية اذا كانت ترجع او تعالج نصآ دون الرجوع لقواعد البيانات, ما ان ظهرت قواعد البيانات وتزيد كمية البيانات فيها عندها لن تستطيع من تلبية 5 الف طلب بشكل سريع , لذلك لا بد من الاهتمام بمجال ال **data system** ومعرفة انواع قواعد البيانات (base , acids) , معرفة طرق التحسين مثل ال indexing , sharding , partioning , التعرف على انواع قواعد البينات القابلة للكتابة بسرعة مثل nosql databases , wide column databases , التعرف على عالم ال streaming processing وادواته مثل apache Kafka > التعمق في علم انظمة البيانات ومعرفة الفروق بينها ودراستها بعمق يسهل من تخطي عقبة عنق الزجاجة في قواعد البيانات. **6- قواعد البيانات العلائقية جيدة :** عند التفكير في الانظمة القابلة للتوسع تجد كثير من المطورين ينظر لقواعد البينات العلائقية **RDBMS** على انها اداة لا تنفع في مثل هذه الانظمة ويجب استبدالها فورآ بال **no sql database** او اطر العمل للبيانات الضخمة مثل apache spark , Hadoop وهذا المفهوم خاطئ تمامآ وغير صحيح وهو مجرد حالة شعورية صنعتها بعض المقالات التي تروج لل no sql databases على انها الرصاصة الفضية silver bullet والحل لكل مشاكل البيانات الكبيرة . قواعد البينات العلائقية RDBMS جيدة وتحل اغلب المشاكل , مثلا في قواعد البيانات PostgreSQL تستطيع تخزين ملاين السجلات ومئات التيرا بايتس من البينات اذا قمنا بتنفيذ بعض التقنيات الذكية مثل ال indexing , partioing , replicating nodes > في غالب الاحيان تستطيع تخزين بياناتك في قواعد بيانات علائقية والاستفادة من خصايص ال acids بدلا من اعادة اختراعها داخل الكود. **7- الكل ينسى فهرسة قواعد البيانات :** الجزء الاكبر من المطورين دائما ما ينسى تفعيل الفهرسة , فهرسة قواعد البينات **database indexing** هي عملية مشابهة تمامآ للفهرسة اليدوية في الكتب الكبيرة او حتى في المصحف الشريف , عملية الفهرسة تغير مجرى البرنامج الكلي وتؤثر بشكل فعال في سرعة الاستجابة للنتائج فبدلا من البحث في كل الجداول full tables scanning يتم البحث في جزئية صغيرة مما يعني توفير العتاد , الوقت > حاول دائمآ فهرسة الحقول التي تبحث بها كثيرآ لتحسين عملية البحث وسرعة الإستجابة . **8- قلل الاتصال بين الخدمات عن طريق الشبكات IO/ networking :** يلجئ الكثير من المبرمجين اثناء بناء معمارية "قابلية التوسع" إلي فصل الخدمات (حتى وان لم يكن مؤثرآ او ضروريآ) عن بعضها البعض ومن ثم وضع وسيلة تواصل بينها (RPC , HTTP , Websocket) , مما يضيف الكثير من التعقيد لان التواصل ما بين الخدمات عن طريق ال network يعني الدخول في تفاصيل مثل , ال bandwidth , timeouts , load balancing , routing , congestion ويعني ذلك ضمنيآ زيادة المشاكل والاخطاء ووقوف الخدمات servers downs. > لا تقم بفصل الخدمات ان لم يكن ذلك ضروريآ وضروريآ جدآ , حاول جعل الخدمات قريبة من بعضها monolithic way , سريعة التواصل بشكل مباشر دون وجود طبقة network حتى تحتاج لذلك لاحقآ **9- ثبات سرعة الإستجابة هي المقايس :** قياس سرعة استجابة الخدمات latency metric هو بالتأكيد اهم عناصر التأكد من ان النظام او الخدمة قابلة للتوسع فنظام ينفذ الطلب في ثانية واحدة والنسخة من هذه الخدمة تستطيع تنفيذ 1000 طلب في الثانية الواحدة هذا يعني عمليا اذا اردنا ان نستوعب اكثر من 200000 طلب ان نقوم بإنشاء 20 نسخة من هذه الخدمة 20 instance of service ولكن هل يكفي القياس وحده؟ بالتأكيد لا ! ثبات سرعة استجابة الخدمات latency consistency هو المعيار الذي يخطئ كثير من المطورين في حسابه ولا يلقي له بالآ ويتم تعريف ذلك بان سرعة استجابة الخدمة ثابتة لا تتاثر مع الوقت او الظروف ويعتبر هذا التحدي الاصعب في تصميم معماريات الانظمة القابلة للتوسع , ونجد ان كثيرآ من خدمات الكلاود cloud services providers في تروجيهم لخدمات يكتبون عبارات مثلا we guarantee single digit response time وفيما معناه (اننا نضمن رد واستجابة الخدمة فيما لا يزيد عن خانة واحدة من الارقام اي من 1 الي 9 جزء من الثانية مثلا ولا يمكن ان تصبح 10 او 11 او اي رقم مكون من خانتين). > قياس سرعة استجابة الخدمات جيد , لكن لضمان المحافظة على سرعة الاستجابة لا بد من التأكد من ثبات هذه القياسات على المدى الطويل latency consistency. **10- الـ caching هو الرصاصة الفضية :** في كثير من الأحيان تعد الاستعلامات من قواعد البيانات مكلفة جدآ very expensive & resource intensive , وايضا عمليات الحسابات المعقدة computinonal algorithms والقراءة من الشبكات والعتاد io& networking request كل هذه الاشياء تستهلك وقتآ طويلآ في المعالجة وبالتالي تأخير سرعة الإستجابة. هنا يأتي دور ال caching في توفير الوقت والجهد المبذول واستهلاك العتاد. > يعتبر ال caching هو رصاصة الفضية في معظم الاحيان , قم بعمل caching للاستعلامات المعقدة والتي تاخذ وقت طويل , للبيانات التي تقرأها كثيرآ من ال hardDisk , قم بعمل cache لكل شئ يستدعيه المستخدم بشكل متكرر وسريع . **11- قلل الضغط على السرفرات وال IO:** يلعب القرص الصلب hardDisk دورآ اساسيآ في سرعة الاستجابة حيث يتم منه قراءة معظم البيانات (مثلا قواعد البيانات تنفذ الاستعلامات على القرص الصلب) , تقليل الضغط على القرص الصلب يتم بإسناد مهام قراءة الملفات (ايآ كان نوعها css , js , png , vidoes , ziped files , documents ) الي خدمات خارجية او سرفرات مخصصة , حاول ان تفصل الملفات عن سرفرات الخدمات العادية التي تستعلم من قواعد البيانات او تقوم بتنفيذ خوازرميات رياضية لتوفير جهد القرص الصلب . يمكن استخدام خدمات جاهزة من مقدمي الخدمات السحابية cloud providers مثل AWS S3 من امزون , و GCP Buckets من قوقل , ويمكنك ايضآ تحميل ادوات مفتوحة المصدر على السرفر الخاص بك مثلا blobTFS و azureMOck > كفاءة القرص الصلب وعدم الضغط عليه مهم في معماريات الانظمة القابلة للتوسع , حاول بقدر الإمكان تخفيف الضغط على القرص الصلب بإبعاد قراءة وكتابة الملفات من سرفرات الخدمات المستخدمة لديك **12- التحويل لصيغة ال JSON مكلف جدآ :** كثير من المطورين لا يعلم هذه الحقيقة ان ال json serialization مكلف جدآ لل cpu وال hardDesk reading & writing وكلما زاد حجم البينات المحولة **payload** كلما زاد الاستهلاك والكلفة واصبحت العملية مكلفة اكثر وقد تصل في كثير من الأحيان ان تتحول هذه المشكلة إلي bottleneck في النظام خصوصا في اللغات التي تستخدم ال garbage collection لتنظيف الذاكرة بعد الاستخدام , وقد يؤدي ذلك لتوقف الخدمة نفسها service down. > حاول بقدر الإمكان تقليل عمليات التحويل الي json وحاول تقليل حجم البيانات المحولة json payloads **13- Allocation is expensive :** هذا العنوان الوحيد الذي كتبته باللغة الانجليزية لصعوبة ترجمته , ال allocation تعني عملية تفريغ الذاكرة ونظافتها من المتغيرات والبيانات الموجودة بها وهي ميزة في كثير من لغات البرمجة المعتمدة على JVM مثل java , csharp. الحقيقة أن هذه العملية مكلفة جدآ وتاخذ وقتآ كبيرآ وجهد ومصادر خصوصا حينما نتكلم في خدمات تأتيها ملاين الطلبات اليومية. > لغات ال jvm جيدة لكن حاول ان تستخدم لغات اخرى لا تعتمد على ال memory allocating في الخدمات التي عليها ضغط عالي وطلبات كثيرة استخدم لغات اخرى تعطي تحكما اكبر مثل c++ , go , خصوصا ان كنت تعمل على معمارية microservices يمكنك كتابة ال services التي تتطلب سرعة استجابة عالية وفيها كثير من عمل ال memory بلغات اخرى غير لغات ال JVM **14- ال threading & concurrency مهمة ولكن صعبة :** قدرة تحمل السرفرات على اكبر كمية من الطلبات ومعالجتها بشكل سريع يعتمد على **thread modeling** المستخدم فتارة نجد ان ال **parallelism** جيد (حيث يقوم بتنفيذ اكثر من مهمة في نفس الوقت بعدة cpu cors) وتارة نجد ان ال **async programing** افضل (حيث يرسل الطلب الي الجهة المراد سحب البيانات منها سواء شبكة الانترنت او قاعدة البيانات , وبعدها يتحرر ال thread ليخدم طلبآ اخر وعند اكتمال النتيجة يعود ليرسلها للمستخدم) , وتارة نجد ال **sync programing** هي الافضل (حيث يقوم بوضع المهام في صف انتظار queue وينتظر حتى اكتمال المهمة ثم ينفذ التي تليها) . > ان اختيار ال thread modeling المناسب للعميلة المطلوب او الخدمة هو تحدي كبير ويحتاج الاجابة على اسئلة كثيرة مثل استهلاك الموارد , سرعة الرد , عدم تعارض البيانات او تداخلها ومشاكل ال race conditions & dead lock. **15-بعض التقنيات ضعيفة بطبيعتها واصلها :** بعض التقنيات ولغات البرمجة واطر العمل هي بطيئة بطبيعة تصميمها **genuinely slow** , مثلا في python لا تستطيع ان تبني web socket يستقبل ملاين الطلبات وقابل للتوسع بشكل كبير لان التصميم الغة نفسه لا يسمح بذلك , كذلك نجد ان ruby بطيئة بعض الشئ حينما نتحدث عن الاف وملاين الطلبات في جزء من الثانية , فمهما تقوم بتحسينات مستمرة وتحسن من جودة الكود يزال تصميم اللغة عائق اساسي امام الكود ليتوسع > حاول ان تختبر اللغة , اطار العمل بشكل جيد قبل البدء وتاكد من انه يلبي احتياجاتك المستقبلية بشكل جيد , اتذكر في مرة ان احد الفرق في شركة linked in تحدث عن بناء خدمة بلغة ruby وبعد الجهد الطويل من كل التحسينات وال best practices لم يستطيعو جعل الخدمة تقبل اكثر من عدد معين الطلبات , فقامو بإعادة كتابة الخدمة بلغة Go فاعطتهم تحكم اكبر في ال memory , threads فانتجت خدمة سريعة قابلة للتوسع لمليارات الطلبات . **16- توسع عموديآ بقدر الإمكان :** كثير من مطوري الخدمات القابلة للتوسع يفكر ابتداء بالتوسع بشكل عمودي **horizontally** وهذا جيد لكن لو استخدمنا مفهوم ال trade - offs ف يمكن ان نتوسع افقيا **vertically** (ما دامت الخدمة جيدة وسريعة ولا يوجد مشاكل) وندفع المزيد من المال (لانك غالبا سترفع امكانيات العتاد) مقابل التخلص من التعقيد في التوسع العمودي (لانك تحتاج ان تجعل الكود وقواعد البيانات تقبل هذا النوع من التوسع وهذا يضيف طبقة تعقيد كبيرة جدآ ) > دائما ابدأ التفكير بالتوسع الافقي vertically scaling ولا بأس بدفع المزيد من المال مقابل التخلص من التعقيد المصاحب لل horizontally scaling **17- العائق البشري :** يوجد مقالة قديمة راسخة ابان تطوير الذكاء الصناعي تقول if human found , problems comes (اي اذا وجد الانسان داخل عملية معينة , هذا يعني بالتأكيد وجود المشكلة) , يعتبر البشر احد العوائق عند بناء الأنظمة القابلة للتوسع فتجد ان الشركة لتوفير المال توظف مطورين بأجر بسيط وبخبرة ضعيفة تتسبب في بناء خدمات تسقط من اول 1000 طلب لها , وكذلك العكس قد يوجد خبراء ولكن يحل المشكلة بشكل اكثر كلفة ووقتآ (قد يكون حل المشكلة ملف sqlite او حتى ملف نصي text file ولكن المطور استخدم خدمات من aws مثلا full text search و redis و nosql database) قام بوضع تكلفة وقت ومال لمشكلة قد تحل فقط بملف نصي او حتى قاعدة بيانات علائقية عادية مفتوحة المصدر وبسيطة. > ان اختيار المبرمج الخبير والحكيم بنفس الوقت يوفر عليك الكثير من المال والوقت , وان الاعتماد على مبرمج سئ بخبرة ضعيفة قد يؤدي لفشل الخدمة ويصبح هو ال bottleneck للمعمارية. بهذا نكون قد سردنا اغلب النقاط العملية بعيدآ عن الكتب النظرية والمقالات التي تتكلم بشكل نظري بحت , تعلمنا فيها بعض الممارسات الجيدة والنقاط التي قد تغيب عنا جميعآ اثناء بناء هذا النوع من الانظمة. ### خريطة لتعلم الـ Vuejs مع بعض النصائح!! URL: https://informatic-ar.com/vuejs-learning-roadmap/ Last updated: 2026-10-03T22:36:29.000Z هذه المادة قابلة للتحديث من وقت لآخر... Vuejs عبارة عن مكتبة Front-End تستخدم في برمجة واجهات برمجيات الانترنت, لها مثيلاتها كـ React و Angular في هذه النصوص أود عرض صورة مبسطة وسريعة لتعلم Vuejs. > الشئ الأول والذي يتوجب فهمه على أي مطور برميجات انترنت هو أن يجد سبيل او طريقة مناسبة لتعلم لغة JavaScript أولا. وبما أن هذه المقالة لن تخوض في ماهية كيفية تعلم الـ JavaScript لكن سأشير لبعض النقاط للشئ المطلوب منك والذي يساعدك أيضا في تعلم Vuejs سريعا. ستحتاج أولا كيفية التعامل مع المتغيرات في JavaScript ايضا ستحتاج كيفية التعامل مع المصفوفات واللغة كائنية التوجه Arrays and OO ثانيا ستحتاج لتعلم كيفية التعامل مع الأساليب Functionsوهذه مهمة جدا لأنك ستحتاجها في الـ Vuejs ثالثا ستحتاج للتعامل مع الـ Promises والتعامل مع HTTP رابعا: من النادر جدا ان تتعامل الـ DOM في Vuejs لأن طبيعة عمل الـ Vuejs هوالتخلص من التعامل بصورة مباشرة مع ال DOM. دعنا الآن ننتقل لخريطة تعلم Vuejs: أولا: قم بإنشاء صفحة HTMLعادية وأنشئ ملف js داخل نفس المجلد الذي قمت بإنشاء صفحة الـ HTML بداخله صفحة الـ HTML ستكون كالتالي: ```markup
The Result: {{ name }} - {{ secondName() }}
``` داخل ملف الـ js قم بكتابة التالي ```javascript const Name= { data() { return { name: 'My name is, '; secondname: 'Mohammed' } } , method: { secondName() { retuen this.secondname } } } Vue.createApp(Name).mount('#name') ``` قم بتجرية الصفحة على المتصفح لديك.. ليس بالضرورة ان تفهم ماذا يفعل هذا الكود.. فقط قم بتجربته على متصفحك... هذه هي الـ Vuejs السطر الأخير في الشفرة السابقة هو عبارة عن تنفيذ للـ Vuejs وما تراه في الوسط عبارة عن أسطر JavaScript وما تراه في المقدمة عبارة عن أسطر HTML حسنا خريطتنا الآن أصبحت واضحة.. الآن لقد قمت بتجربة الـ Vuejs بنفسك ماذا بعدها 1. قم بالدخول للموقع الرسمي [https://v3.vuejs.org/guide/introduction.html#introduction](https://v3.vuejs.org/guide/introduction.html?ref=informatic-ar.com#introduction) وأبدا في قراءة الصفحة الموضحة في الرابط أعلاه. 2. الشئ الثاني ستحتاج لمعرفة التعامل مع الـ Data Binding 3. ستحتاج لمعرفة التعامل مع الـ Directive 4. كيفية التعامل مع الجمل الشرطية في الـ Vuejs وهي تسمى الـ Repeat Directive 5. معرفة التعامل مع الـ Events 6. كيفية التعامل مع الـ Forms 7. كيفية التعامل مع الـ Components هذه هي النقطة الأهم والتي تعني أنك الآن أصبحت جاهز للتعامل مع مشروع من النوع الوسط 8. كيفية التعامل مع الـ HTTP وهناك عدة خيارات سواء بإستخدام الأساليب المضمنة في لغة الـ JavaScript او بإستخدام مكتبات خارجية ك Axios وهي اشرهم (تستخدم مع React أيضا) 9. كيفية التعامل مع الـ State Management وهو ما يعني حفظ بعض البيانات في الجلسة Session لجلسات أخرى وحمل تلك البيانات لصفحات أخرى. 10. النقطة قبل الأخيرة، ستحاج للتعامل مع مفهوم الـ Routing للتنقل بين الصفحات والصفحات هنا تعني للتنقل بين الـ Components 11. أخيرا ستحتاج لمعرفة كيفية نشر التطبيق الذي قمت بإنشاءه بالتالي أنصحك بالتعامل مع Docker وكيفية تهيئة ملف الـ DockerFile هذه هي الخريطة ببساطة، لا تقم بالقفز فوق النقاط، إبدأ من النقطة رقم واحد وتحرك بصورة متتالية واحدة تلو الأخرى، ولا ضير إن لم تستوعب أي نقطة بسرعة، قم بإعادة قراءة الدروس مرارا وتكرارا حتى تستوعب الدرس جيدا وبعدها انتقل للنقطة التالية. هنالك كورس على موقع Udemy ويعتبر أفضل كورس لتعلم الـ Vuejs بعد الـ Documentation الرسمية.. بإمكانك شراء الكورس من هنا [https://www.udemy.com/course/vuejs-2-the-complete-guide](https://www.udemy.com/course/vuejs-2-the-complete-guide?ref=informatic-ar.com) تحياتي - محمد ### استخدام ال API Gateway في بناء Microservices URL: https://informatic-ar.com/api-gateway-microservices/ Last updated: 2026-10-03T22:31:19.000Z في هذه المقالة سوف نتحدث عن بوابة وواجهة العبور الى ال Backends، ففي التطبيقات الضخمة التقليدية Monolithic يكون هناك رابط واحد Endpoint يمثل بوابة الدخول الى ذلك النظام. بينما في ال Microservices فكل خدمة مصغرة لديها ال Endpoints الخاصة بها، وسوف نتحدث تأثير هذا الأمر على التطبيقات Clients وكيف يفيد استخدام ال API Gateway بها. محتويات السلسلة: - [مقدمة حول ال Microservices](https://informatic-ar.com/microservices/) - استخدام ال API Gateway في بناء Microservices (المقالة الحالية) - التواصل Communications بين الخدمات المصغرة # مقدمة عن ال API Geteway لنفرض أنك تود تطوير تطبيق موبايل Native Mobile عبارة عن سلة تسوق Shopping Application، بكل تأكيد سوف تحتاج لعمل صفحة للمنتجات، وصفحة لتفاصيل المنتج والتي تعرض كل التفاصيل الخاصة بذلك المنتج. الصورة التالية توضح الخطوات بدءاً من صفحة تفاصيل المنتج الى وضعه في السلة في تطبيق أمازون. ![](https://informatic-ar.com/wp-content/uploads/2020/10/microservice1.png) في الصورة الأولى (من اليسار) سوف تجد أن صفحة التفاصيل بها العديد من المعلومات وهي: - المعلومات الأساسية حول المنتج مثل الاسم، الوصف، السعر - عدد الأصناف الحالية في سلة التسوق - تاريخ الطلبات Order History - تعليقات العملاء Customer Reviews - معلومة من المستودع بأنه قارب على النفاذ Inventory Warning - خصائص الشحن Shipping - مقترحات Recommendations عدة لشراء منتجات شائعة، منتجات اشتروها العملاء بعد شرائهم لهذا المنتج، منتجات شاهدوها العملاء بعد شراءهم لهذا المنتج في التطبيقات التقليدية Monolithic سوف يقوم التطبيق بجلب بيانات المنتج من خلال REST API مشابه لما يلي: GET api.company.com/productdetails/productId وبعدها يقوم ال backend بعمل عدة استعلامات من قاعدة البيانات وارجاع النتيجة للعميل، وفي حالة كان هناك Load Balancer فسوف يقوم بتوجيه الطلب لأي Instance لكي تخدم هذا الطلب وترجع النتيجة المطلوبة. لكن في حالة الخدمات المصغرة Microservices فهذه البيانات التي تُعرض في صفحة التفاصيل لا تتواجد في خدمة واحدة، وانما متواجدة في عدة خدمات، على سبيل المثال: - **خدمة السلة Shopping Cart Service**: وهي تحتوي على العناصر في السلة - **خدمة الطلبات Order Service**: وهي تحتوي على تواريخ الطلبات - **خدمة المنتجات Catalog Service**: وهي تحتوي على معلومات المنتجات الأساسية - **خدمة التقييمات Review Service**: وهي تحتوي على اراء المستخدمين - **خدمة المستودع Inventory Service**: وهي التي تظهر معلومات او تحذيرات بخصوص الأصناف - **خدمة الشحن Shipping Service**: وهي التي تعرض تفاصيل الشحن، التكاليف، وتتواصل مع مقدمي خدمات الشحن Shipping Providers من خلال ال APIs فيما بينهم. - **خدمة المقترحات Recommendation Service**: وهي التي تقترح الأصناف والمنتجات الصورة التالية توضح هذه الخدمات، وسوف يكون السؤال كيف يمكن أن يتواصل التطبيق Mobile Client مع هذه الخدمات: ![](https://informatic-ar.com/wp-content/uploads/2020/10/microservice2.png) ## التواصل المباشر بين التطبيق مع الخدمات المصغرة Direct Communications نظرياً يمكن للتطبيق بأن يقوم باستدعاء كل هذه الخدمات المصغرة بشكل مباشر، فسوف يكون لكل خدمة مصغرة Public Endpoint لها مثلاً [https://service.api.company.name](https://service.api.company.name/?ref=informatic-ar.com) وداخلياً قد يكون هناك load Balancer لها يقوم بتوجيه الطلب الى أحد ال Instances التي تعمل من هذه ال Microservices. لسوء الحظ هناك عدة مشاكل في هذا النوع: - **أولاً**: التطبيق سوف يحتاج لمعرفة كل ال Endpoints لكل الخدمات المصغرة والتي قد تتجاوز المئات، وهذا يجعله مليء بالروابط، والمشكلة انه سوف يتواصل معها بشكل مباشر (لفتح صفحة تفاصيل المنتجات فيحتاج عمل 7 استدعاءات منفصلة) وهذا يعني استهلاك كبير للبيانات او الانترنت، ولذلك هذا الأمر غير عملي. - **ثانياً**: المشكلة الأخرى ان بعض الخدمات المصغرة قد تستخدم بروتوكولات ليس مناسبة مع ال Web، مثلاً أحد الخدمات قد تستخدم RPC وأخرى تستخدم AMQP لل Messaging، وهذه البروتوكولات ليست مناسبة للمتصفحات Not Web Friendly، ولا حتى عند مرورها على الجدر النارية Firewalls، وانما هي مناسبة للتواصل الداخلي Internally. - **ثالثاً**: وجود هذه الروابط في التطبيق قد يجعل عملية تحسين Refactoring الخدمات المصغرة أمراً صعباً، فقد يحتاج المطورين الى دمج خدمتين أو تقسيم خدمة الى أكثر من خدمة مع مرور الوقت، وبالتالي يجب أن يتم تحديث التطبيق بهذا الأمر، وهذا ما يعد عملية صعبة أيضاً. - **رابعاً**: التواصل المباشر بين التطبيق والخدمات المصغرة سوف يفرض وجود آليات الحماية Security والتحقق على مستوى جميع هذه الخدمات، وهذا ما يضيف عبئ إضافي لها. وبالتالي نستطيع القول بأن هذا الأسلوب (التواصل المباشر) لا يستخدم ويندر من يقوم باستخدامه، سوف نشاهد الحل الأنسب فيما يلي. ## استخدام ال API Gateway ال API Gateway هو سيرفر أو خدمة (تقدم RESTful API) تمثل بوابة العبور الى النظام ككل، وهو يشبه Façade Design Pattern، حيث يخفي Encapsulate كل تفاصيل النظام الداخلية ويقدم API مخصصة Tailored لكل Client، وقد يكون لديه مسؤوليات أخرى مثل التحقق Authentication، المراقبة Monitoring، توزيع الضغط Load Balancing، الحفظ المؤقت Caching، التحكم في الطلبات القادمة Traffic Control وغيرها. الصورة التالية توضح كيف أن استخدام API Gateway في المعمارية للمشروع: ![](https://informatic-ar.com/wp-content/uploads/2020/10/microservice3.png) ال API Gateway سوف يكون مسؤولاً عن توجيه الطلبات، التحويل بين البروتوكولات Protocol Translations وغيرها، فكل الطلبات سوف تأتي عبر هذه البوابة ويقوم بتوجيهها الى الخدمة المناسبة. وقد يقوم ال API Getaway بالاتصال بعدة خدمات وجلب النتيجة وارجاعها في ال Response للتطبيق. أيضاً قد يزود ال API Gateway أي Client ب API له، مثلاً سوف يقوم بعمل Coarse-grained API لل Mobile Apps. فيقوم التطبيق باستدعاء الرابط /productdetails?productId=xxx ومن ثم يقوم ال API Gateway داخلياً بجلب البيانات من كل الخدمات المصغرة الأخرى وارجاع النتيجة، وهكذا حصل التطبيق على النتيجة في طلب واحد Single Request. أحد الأمثلة على أهمية تخصيص ال API بحسب الاستعمالات هو Netflix API Gateway حيث كانت في البدء هي API لعمل streaming service لكل أنواع الأجهزة (جوالات، تلفزيونات، العاب الخ) ولكن لم تكن مناسبة one-size-fit-all وقرروا بناء tailored API لكل نوع من الأجهزة، وال API Gateway يستقبل Billions الطلبات في اليوم الواحد. ## فوائد وعيوب ال API Gateway أحد أهم الفوائد في ال API Gateway هي أنها تخفي كل التفاصيل الداخلية وتقدم API بسيطة للتطبيقات، وهذا يقلل من عدد الطلبات Round Trips بين النظام والتطبيق ويسهل أيضاً أكواد التطبيقات Client Apps. من الفوائد الأخرى: - **ال Authentication:** حيث يقوم ال API Gateway بكل التحقق من ال Tokens وبعدها يرسل الطلبات الى الخدمات المصغرة الداخلية Downstream Services التي لن تحتاج الى اجراء أي تحقق من هذه ال Tokens. - **الحماية Security:** هذه البوابة عادة تتعامل مع الاتصال الآمن Transport Security بمعنى انه ال Clients سوف يتصلوا بها HTTPS وهي بعد أن يصل الطلب الآمن تقوم بعمل SSL Termination وترسل الطلب الداخلي للخدمات الداخلية بدون SSL. - **التحكم في ال Traffic:** فيمكن عمل IP Filtering والسماح أو إيقاف الوصول لمجموعة من ال IPs. - **تحويل شكل البيانات Transformation:** مثلاً قد ترجع الخدمة الداخلية النتيجة ب XML format ويقوم ال API Gateway بتحويلها الى JSON Format. - **ال Caching:** فمثلاً إذا كانت هناك بيانات لا تتغير أو تأخذ وقتاً لحسابها، فيمكن وضعها على الكاش ويقوم ال API Gateway باستخدامها من الذاكرة المؤقتة. ![](https://informatic-ar.com/wp-content/uploads/2020/10/microservice4.png) لكن هناك بعض المشاكل في ال API Gateway، فلأنها نقطة العبور لكل الطلبات فيجب أن تعمل بشكل دائم Highly Available ويجب أن يتم تطويرها وإدارة والعمل عليها أيضاً كما في بقية الخدمات المصغرة. ومن المخاطر أن تكون هذه البوابة عائق للمطورين، لأنهم يحتاجوا لتحديثها لكي تستطيع معرفة ال Endpoints الجديدة لكل خدمة مصغرة، عموماً هذه ليست مشاكل مقلقة، فكل الاستخدامات السليمة Real-World Applications تستخدم ال API Gateway. وهناك حلول أصبحت تستكشف الروابط تلقائياً بدون أي تدخل يدوي. ## تطبيق ال API Gateway حتى هذه اللحظة تحدثنا عن الدوافع Motivations وراء ال API Gateway وأيضاً النقاط الإيجابية والسلبية Trade-Offs بها، فسوف نتحدث عن بعض المشاكل التصميمية التي تحتاج للنظر لها عن تطبيقك لل API Gateway. ### الأداء Performance والتوسعة Scalability لأن ال API Gateway هي بوابة العبور للنظام فيجب أن يكون أدائها عالياً وتستحمل الضغط، وهذا يستوجب دعم مفاهيم ال asynchronous وال nonblocking I/O ويمكن بنائها من خلال التقنيات التي تدعمها اللغات المختلفة. ### استخدام أسلوب ال Reactive Programming Model يقوم ال API Gateway بمعالجة الطلبات عن طريق توجيهها الى أحد الخدمات المصغرة، أو استدعاء عدة خدمات مصغرة وتجميع النتيجة Aggregate Result وارجاعها في ال Response. وفي حالة استدعاء عدة خدمات مصغرة، فقد لا تكون هناك اعتمادية بينهم، وهنا قد يكون من المستحسن لتقليل الوقت استدعاء هذه الخدمات في آن واحد Concurrently، لكن بعض الأحيان قد تكون هناك اعتماديات وخطوات معينة، مثلاً في البدء يتم التحقق من الطلب من خلال ال Authentication Service، وبعدها جلب بيانات العميل من الخدمة المخصصة لذلك، وبعدها مثلاً ارجاع تفاصيل المنتج وهكذا. كتابة هذه ال API Composition باستخدام ال asynchronous callback سوف يؤدي لتعقيد ال Callback hell، ويصبح الكود معقداً وصعباً للفهم وعرضة للأخطاء والمشاكل. ولذلك عادة ما يتم كتابة ال API Gateway بطريقة Declarative Style من خلال Reactive Model. وأغلب اللغات تدعم هذا الأسلوب مثلاً Future في سكالا، CompltableFuture في جافا 8، promise في جافا سكربت، كما أن هناك عدة مكتبات Rxjava وغيرهم. هذا الأسلوب يسهل كتابة أكواد وتكون Efficient في ال API Gateway. ### التواصل بين الخدمات Service Invocation الخدمات المصغرة هي عبارة عن أنظمة موزعة Distributed System وللتواصل فيما بينهم يجب ان تستخدم آلية مناسبة للتواصل Inter-Process Communication، وهناك أسلوبين للتواصل، الأول Asynchronous باستخدام ال Messaging وهناك الكثير من الخدمات أو المكتبات لها، النوع الثاني وهو Synchronous باستخدام ال HTTP. وعادة ما تستخدم الأنظمة النوعين بحسب أنواع الخدمات المصغرة فيها. ### اكتشاف الخدمات Service Discovery ال API Gateway يحتاج لمعرفة ال Endpoint لكل من الخدمات المصغرة لكي يتواصل معها (عنوان ال IP ورقم المنفذ Port)، ومن الممكن أن يتم كتابتها يدوياً لكنها عملية صعبة وقد تكون مستحيلة لأن بعض الخدمات تتغير أماكنها بشكل تلقائي مثلاً بسبب ال Auto scaling او ال Upgrades، وبعض الخدمات قد تكون ثابتة في موقع معين مثلاً Messaging، لكن ال Application Microservices غالباً متغيرة، ولذلك يجب استخدام آلية لاكتشاف هذه الخدمات بشكل تلقائي، وهناك طريقة Server-Side Discovery أو Client-Side Discovery، وفي مقالات تالية سوف نوضح هذه الجزئيات بتفاصيل أكبر. ## التعامل مع الفشل الجزئي Handling Partial Failures من التحديات التي تحتاج لحلول عند تطبيق ال API Gateway هي التعامل مع الفشل الجزئي لأحد الخدمات، وهذه المشكلة تحدث في جميع الأنظمة الموزعة عندما تنادي خدمة مصغرة خدمة أخرى ولكن الثانية لم تكن متواجدة أو تأخرت في الرد. وهنا يجب على ال API Gateway ألا ينتظر Blocking الى الأبد لأي من هذه الخدمات، ويتعامل مع المشكلة بما يتناسب مع طبيعة الخدمة. مثلاً إذا كانت الخدمة هي Recommendation Service وتم استدعائها اثناء مرحلة جلب تفاصيل المنتج ولم تستجيب، فال API Gateway يجب أن يرجع تفاصيل المنتج لأنه ما زال مفيداً للمستخدم. وجزئية ال Recommendations يمكن أن تكون فارغة او تبدل بقائمة افتراضية بأكثر 10 خدمات طلباً. لكن بعض الخدمات قد يضطر ال API Gateway ارجاع المشكلة لل Client مثلاً إذا كانت ال Product Information Service لا تستجيب فهنا قد يرجع الخطأ للعميل بتلك المشكلة. ومن الممكن أن تقوم ال API Gateway بإرجاع نتائج محسوبة مسبقاً Cached Data إذا كانت متوفرة، مثلاً إذا كانت تفاصيل المنتج متواجدة في ال Cache (سواءً بداخل ال API Gateway أو من خدمة منفصلة لها) فيمكن أن يرجعها، وبالتالي يضمن النظام أن الفشل الداخلي لأحد الخدمات لا يؤثر على تجربة المستخدم. وكثير من المكاتب المخصصة لاستدعاء الخدمات Service Invocation قد تقوم بعمل Timeout إذا تجاوز الطلب مدة معنية Threshold، وتطبق مفهوم ال Circuit Breaker وبالتالي لن يحتاج ال Client الى الانتظار طويلاً في حال لم تستجيب الخدمة إذا تجاوزت المدة المحددة، وقد تسمح ايضاً بتعريف Fallback Action تقوم من خلالها بالقراءة من ال Cache او ارجاع أي بيانات افتراضية حينها. ## خلاصة لأغلب الأنظمة التي تستخدم مفهوم ال Microservices فاستخدام ال API Gateway كبوابة عبور للنظام يعد أمراً مهماً، حيث تستقبل هذه البوابة كل الطلبات وتقوم بعمل ما يلزم تجاهها، وتوفر Custom API لكل نوع مختلف من ال Clients وهذا ما يجعله أكثر مناسبة لذلك ال Client. ايضاً تستطيع إخفاء الفشل Mask Failures في أحد الخدمات المصغرة وترجع نتيجة محسوبة مسبقاً Cached Data أو أي بيانات افتراضية إذا أمكن. المقالة التالية سوف نتحدث عن طرق التواصل بين هذه الخدمات المصغرة. ## المصادر مقالة[ Building Microservices:Using an API Gateway](https://www.nginx.com/blog/building-microservices-using-an-api-gateway/?ref=informatic-ar.com) في موقع nginx ### مقدمة حول ال Microservices URL: https://informatic-ar.com/microservices/ Last updated: 2026-10-03T22:31:21.000Z انتشرت في الآونة الأخيرة مفهوم ال Microservices بشكل كبير، عشرات المقالات والدروس التي تتحدث عنها، العديد من المحاضرات والمؤتمرات حولها وفوائدها وعيوبها. وانقسم مجتمع المطورين لفريقين، فريق يرى أنها ليست شيئاً جديداً وأنها مجرد إعادة تسمية Rebranding لمفهوم ال SOA. وفريق يرى عكس ذلك. وبالرغم من كل تلك الأراء المتفاوتة، فال Microservices لها فوائد كبيرة خصوصاً في ال Agile Development ([كل ما تود معرفته عن ال Agile](https://informatic-ar.com/agile%5Fin%5Fnatual/)) وتطوير التطبيقات المعقدة Complex Enterprise Applications. في هذه السلسلة سوف نتحدث عن عدة مواضيع في ال Microservices، سواءً مرحلة تصميمها Designing، بنائها Building، ونشرها Deploying. سوف نتعلم هذه المعمارية ونقارنها بالمعمارية القديمة Monolith Architecture Pattern. وسنتحدث أيضاً عن فوائد وعيوب ال Microservices، وهل ستكون مناسبة لمشروعك أم لا، وكيف يمكنك تطبيقها. محتويات السلسلة: - مقدمة حول ال Microservices (المقالة الحالية) - [استخدام ال API Gateway في بناء Microservices](https://informatic-ar.com/api-gateway-microservices/) - التواصل Communications بين الخدمات المصغرة سنبدأ أول فقرة ولنلقى الضوء على ال Microservices فوق 30 ألف قدم، وقبل المضي قدماً في حال قرأت هذه المرادفات "**تطبيق**، **نظام**، **برنامج**" فغالباً نعني نفس الشيء، وفي حالة كنا نتحدث عن شيء معين سوف نوضحه حينها. ## بناء المشروع في شكل برنامج واحد Building Monolithic Application لنفترض أنك تريد بناء تطبيق توصيل تاكسي Taxi Application وتريد منافسة Uber وغيرها من الشركات، بعد عدة اجتماعات Meetings وجلب للمتطلبات Requirement Gathering قمت بالبدء في تطوير النظام وكتابة الأجزاء الداخلية فيه. كطبيعة هذا النوع من المشاريع، فهي تحتوي على عدة أمور: - **واجهات ربط APIs** لكي تتواصل مع التطبيقات (تطبيق العميل، وتطبيق السائق) - **واجهات رسومية Web UI** لمدراء النظام لمشاهدة التقارير والإحصاءات وسير العمليات - **الدفع الالكتروني** واستخدام أي خدمة خارجية لإتمام الدفع الالكتروني - **التنبيهات** واستخدام أي خدمة خارجية لإرسال التنبيهات Emails, SMS - **خصائص النظام الأساسية:** الرحلات Trips، السائقين Drivers، العملاء Passengers، الفواتير Billings والطلبات Orders. - **قاعدة البيانات:** لتخزين كل هذه البيانات غالباً في النسخة الأولى سوف تكون كل هذه الأمور في نفس النظام كما في الشكل التالي: ![](https://informatic-ar.com/wp-content/uploads/2020/09/microservices1.png) داخلياً إذا كان المطور يتبع طريقة سليمة في الكتابة وتقسيم الدوال والبناء فسوف يكون Modular Architecture (مثلاً يستخدم [مفاهيم التصميم الكائني SOLID](https://informatic-ar.com/solid/)، عمل التسمية بشكل جيد الخ) ويمكن ألا يكون كذلك أيضاً. الآن بعد تجهيزه للنشر Packaging سوف ينشر ككتلة واحدة Deployed as Monolith وقد يختلف ال Format بحسب اللغة وال Framework المستخدم، مثلاً في تطبيقات الجافا فهي تكون WAR Files وتنشر على مخدم الويب مثلاً Tomcat، وبعض مشاريع الجافا تكون ايضاً JAR. مثلاً مشاريع ال Rails وال Node.js تكون على شكل مجلد من الملفات. كتابة المشاريع بهذه الطريقة Monolithic Apps منتشرة جداً، فهي: - **سهلة للتطوير** حيث أغلب أدوات التطوير IDEs تركز على واحد بناء نظام واحد - **سهلة الاختبار** فيمكن عمل end-to-end testing عن تطريق تشغيل النظام واختبار الواجهات باستخدام Selenium. - **سهلة النشر** فبمجرد عمل ال Packaging تستطيع نشره في السيرفر، - **سهلة التوسعة** Scaling بواسطة تشغيل عدة نسخ من هذا المشروع خلف Load Balancer. اذاً في المراحل الأولية للمشروع Early Stage، هذه المعمارية تعمل بشكل جيد. ## الزحف الى جحيم ال Monolithic لسوء الحظ فهذه الطريقة لديها حدود كثيرة Limitations، فالتطبيقات الناجحة تنمو بشكل سريع مع الوقت وتصبح ضخمة في مرحلة ما، ففي كل دورة تطوير Sprint/Iteration يقوم المطورين بتطوير عدة خصائص Stories والتي بكل تأكيد تضيف المزيد من الأسطر في المشروع. بعد عدة أشهر أو سنوات سوف يكون مشروع ضخم للغاية monstrous monolith. عندما يصبح المشروع معقداً Complex Monolith فسوف يبدأ فريق التطوير بالشعور بالألم، فأي محاولات للتطوير السريع Agile Development أو النشر السريع سوف تفشل. أحد المشكلات الرئيسية أن المشروع أصبح معقداً للغاية وهو أكبر من أن يفهمه مطور واحد بشكل جيد. ونتيجة لذلك، اصلاح المشاكل، تطوير الخصائص الجديدة سوف تصبح صعبة وتأخذ وقتاً كبيراً. إضافة الى ذلك سوف تأتي مرحلة الهبوط Downwards Spiral حيث أن المشروع صعب الفهم، فأي تعديلات لا تتم بشكل سليم سوف تأثر على المشروع من ناحية المشاكل Bugs وغيرها، سوف ينتهي الأمر بك مع كرة وحشية كبيرة من الطين big ball of mud. الحجم الكبير للمشروع سوف يؤدي الى ابطاء عملية التطوير Slow Down Development، فكلما أصبح المشروع أكبر، كلما زاد وقت التشغيل Startup Time، في أحد الاستبيانات القديمة وجد بعض المطورين أن وقت التشغيل لديهم قرابة 12 دقيقة، وبعض الحالات الشاذة وصلت الى 40 دقيقة فقط لتشغيل المشروع. المطورين عموماً يقوموا بإعادة التشغيل بشكل مكثف اثناء التطوير، فاذا كان جزء كبير من يومهم هو الانتظار فهذا بكل تأكيد سوف يؤثر على الإنتاجية Productivity. مشكلة أخرى في هذه التطبيقات الكبيرة المقعدة Complex Monolith App وهي أنها عقبة لل Continuous Deployment، فاليوم من الطبيعي لمشاريع SaaS أن يتم نشر التحديثات فيها بشكل يومي لل Production. وهذا الأمر صعب للغاية مع المشاريع ال Monolith لأنك تحتاج لإعادة نشر المشروع بالكامل ولو لتغيير جزء بسيط منه. ضع في اعتبارك مشكلة وقت التشغيل حينها لحظة النشر، وأيضاً الاختبار اليدوي المكثف للمشروع. لذلك ال CI/CD هي أقرب للمستحيل في هذه المشاريع. أيضاً ال Monolith Applications لتحمل الضغط Scaling من خلال ال Load Balancers، لا يأخذ في الاعتبار مسألة أن هناك أجزاء في المشروع قد تكون لديها متطلبات مختلفة عن الأجزاء الأخرى Conflicting Resources Requirements، مثلاً أحد الجزئيات Module تقوم بعمل عمليات CPU-Intensive لمعالجة الصور وكان من الأفضل نشرها على سيرفرات لديها Computing عالية لها. وجزئية أخرى Module تحتوي على قاعدة بيانات في الذاكرة in-memory database فأيضا لهذه الجزئية الأفضل أن تعمل على سيرفر لها ذاكرة عالية. لكن بسبب أن كل الجزئيات تنشر ككتلة واحدة فسوف تقع في هذه المشكلة، وتضطر إما لدفع تكاليف عالية لكي تكون كل السيرفرات بنفس المواصفات، او التنازل عن الأداء الأفضل. مشكلة أخرى في هذه المشاريع وهي Reliability، فبسبب أن كل الجزئيات تعمل في نفس البرنامج Process، فأي مشكلة في أي جزئية مثلاً Memory Leak سوف تؤثر على كل ال Process وبالتالي قد تتعطل ويوقف المشروع بالكامل. وحتى لو كان هناك Load Balancer خلف أكثر من Instance، فلأنها كلها نفس النسخة من هذا البرنامج فهذه ال Bug ستتواجد بهم كلهم وبالتالي ستقف كل ال Instances من العمل. أخيراً وليس أخراً، هذه المشاريع صعبة جداً لكي تتغير وتعتمد على لغة او إطار عمل جديد Difficult To Adaptation، مثلاً لدينا مشروع فيه 2 مليون سطر باستخدام إطار عمل XYZ، فسوف يكون مكلفاً للغاية من ناحية الوقت والجهد إعادة كتابة كل البرنامج باستخدام إطار عمل جديد ABC وحتى إذا كان أفضل من كل النواحي. كنتيجة لذلك سوف يكون هناك عائق أمام تبني التقنيات الجديد وستكون عالقاً مع التقنية التي اخترتها عند بداية المشروع. **كملخص**: لديك تطبيق مهم وناجح Successful Business-Critical Application نما بشكل كبير الى معمارية ضخمة قبيحة، ولا يفهمها الا عدد قليل جداً من المطورين (إذا وجد مطورين يستطيعوا فهمها). وتم كتابتها باستخدام تقنية قديمة Obsolete، غير منتجة Unproductive سوف تجعل عملية توظيف المطورين الموهوبين Talents صعبة ايضاً، والتطبيق يصعب توسيعه Scaling، وغير موثوق به Unreliable، والنتيجة أن ال Agile Development and Delivery هي مستحيلة! اذاً، ماذا تستطيع أن تفعل حيال هذه المشاكل؟ ## الخدمات المصغرة Microservices – معالجة التعقيدات Tackling Complexity الكثير من الشركات مثل Amazon, eBay, Netflix حلت هذه المشاكل بتبني ما يعرف الآن ب Microservices Architecture Pattern، فبدلاً من بناء المشروع كبرنامج واحد Monolith Application، فسيتم تقسيم المشروع الى عدة جزئيات صغيرة Interconnected Services. الخدمة Service هي مجموعة من الخصائص المهام المتعلقة ببعضها، مثلاً إدارة الطلبات Order Management، إدارة العملاء Customer Management، وهكذا. وكل خدمة مصغرة Microservices هي تعتبر مشروع كامل صغير ولديها داخلياً معمارية لها. بعض من هذه الخدمات المصغرة Microservices يمكن أن تقدم Expose API سوءاً للتطبيقات أو لخدمات مصغرة أخرى. وبعض هذه ال Microservices قد تكون هي Web UI. وفي وقت التشغيل كل من هذه الخدمات سوف تكون على VM لوحدها او Docker Container. الصورة التالية تبين شكل المشروع بعد تبني هذه الطريقة في العمل: ![](https://informatic-ar.com/wp-content/uploads/2020/09/microservices2.png) كل المهام الوظيفية المتعلقة ببعضها سوف تجد أنها بداخل أحد هذه الخدمات المصغرة، حتى ال Web App هناك جزئين، الأولى هي المتعلقة بالركاب، والثانية بالسائقين. هذا يجعل من الأسهل نشر خصائص جديدة مثلاً للسائقين بدلاً من نشر المشروع ككل كما كان في الطريقة السابقة Monolith. بعض هذه الخدمات تقدم REST API وبعضها يقوم بالاستفادة Consume من ال APIs، مثلاً ال Driver Management يستخدم خدمة Notification لكي خبر السائق المتواجد عن الرحلة المحتملة. مثلاً UI service قد تُستخدم بواسطة خدمات أخرى لعرض صفحة ويب وهكذا. وهذه الخدمات قد تكون غير متزامنة asynchronous وmessage-based communication، سوف نتحدث عن التواصل بين الخدمات inter-service communication في مقالة منفصلة لاحقاً. وبعض هذه الخدمات تقدم REST API يتم استخدامها بواسطة التطبيقات Mobile Apps مثلاً لتطبيق السائق او الراكب. والتطبيق لا يستطيع الوصول لكل الخدمات Microservices وانما تكون هناك طبقة اتصال وسيطة تعرف بال API Getaway، وال API Getaway هي المسؤولة عن المهام مثلاً Load Balancing والحفظ المؤقت Caching، وحماية الوصول Access Control، ومراقبة الاتصالات Monitoring، وايضاً وضع القيود على استخدام الدوال API Metering. سوف نتحدث ايضاً عن ال API Getaway لاحقاً في مقالة منفصلة. في كتاب ال Art of Scalability قام المؤلف بطرح مكعب ثلاثي الأبعاد [Scale Cube](https://microservices.io/articles/scalecube.html?ref=informatic-ar.com) وهو نموذج لتمثيل فكرة ال Scalability ([ما هي ال Scalability وكيف يتحمل الموقع ملايين الزوار](https://informatic-ar.com/web-scalability/)؟) وسوف تلاحظ المكعب في الصورة بالأسفل، حيث ذكر أن هناك 3 أبعاد ال Scaling: - **البعد الأول X-axis** وهو عن طريق نشر عدة نسخ من نفس البرنامج خلف Load Balancer. - **البعد الثاني Y-axis** عن طريق تقسيم المهام الى خدمات مصغرة باستخدام Microservices. - **البعد الثالث Z-axis** عن طريق تقسيم البيانات، وهو هجين بين النوع الأول والثاني، حيث يتم نشر نفس النسخة في عدة سيرفرات، ولكن لكل منها مجموعة من البيانات، ويكون هناك جزء في المشروع يحدد أي يذهب الطلب الى أي سيرفر تتواجد فيه البيانات وذلك بالاستفادة من بيانات الطلب مثلاً المفتاح Primary Key أو رقم العميل. ![](https://informatic-ar.com/wp-content/uploads/2020/09/microservices3.png) التطبيقات قد تستخدم هذه الابعاد الثلاثة معاً، فيستخدم ال Y-axis ويتم عمل ال scaling عن طريق تقسيم المشروع الى Microservices، وفي وقت التشغيل runtime يتم عمل X-axis عن طريق نشر كل خدمة أكثر من مرة خلف Load Balancer وذلك لمزيد من ال Throughput and Availability، وأيضاً قد تستخدم بعض التطبيقات Z-axis في بعض الحالات (لل Database Scaling). الصورة التالية توضح Trip Management Service والتي تم نشرها خلف Docker في Amazon EC2 وبالتالي تم تطبيق Y-axis وال X-axis. ![](https://informatic-ar.com/wp-content/uploads/2020/09/microservices4.png) في وقت التشغيل فال Trip Management Service تم نشرها على عدة Instances، وكل منها بداخل Docker Container ([ما هي تقنية Docker؟ دليلك الكامل لتعلم Docker](https://informatic-ar.com/getting-started-docker/)) . وللحصول على تواجديه أعلى فال Containers تعمل على عدة Cloud VMs، وفي الواجهة هناك Load Balancer يقوم بتوزيع الطلبات على هذه ال Instances. ويؤدي المهام الأخرى كالحفظ المؤقت، وحماية الوصول وغيرها. استخدام ال Microservices يؤثر بشكل كبير على طريقة العمل مع قواعد البيانات، بدلاً من أن تكون هناك قاعدة بيانات واحدة تشترك بها كل الخدمات، فهنا سوف تكون لكل خدمة قاعدة البيانات الخاصة بها. هذه الفكرة تبدوا غريبة خصوصاً لمن تعود على one data model، وايضاً قد تسمح بتكرار بعض البيانات في الخدمات المختلفة. لكن وجود قاعدة بيانات لكل خدمة هو شرط أساسي إذا كنت تريد الحصول على فوائد ال Microservices والتي من أهمها تقليل الترابط Loose Coupling بين الخدمات. المخطط التالي يوضح معمارية قواعد البيانات على المشروع وكيف تكون: ![](https://informatic-ar.com/wp-content/uploads/2020/09/microservices5.png) لكل خدمة مصغرة قاعدة البيانات الخاصة بها، وكل خدمة تستطيع استخدام قاعدة البيانات المناسبة بها، وهذا يعرف ب Polyglot Persistence Architecture. مثلاً ال Driver Management والتي تبحث عن السائقين الأقرب للراكب، قد تستخدم قواعد بيانات تدعم ال Goe-Queries باستخدام ال GPS Location. على السطح قد تبدوا ال Microservices مشابه لمفهوم ال SOA، فالاثنان يقسموا المشروع على شكل خدمات، لكن ال SOA لديها التعقيدات والبرتوكولات الخاصة بها Web Services Specification WS-\* وال ESB، ولذلك ال Microservices هي أبسط وأخف وتعتمد على ال REST بدلاً من تلك البروتوكولات. ## فوائد ال Microservices هناك العديد من الفوائد في ال Microservices Architecture: - **أولاً**: أنها تعالج مشكلة التعقيد Complexity حيث يتم تقسيم النظام الضخم Monolithic Application الى مجموعات من الخدمات يسهل العمل عليها وكل منها لديها حدود Boundaries وتواصل فيما بينهم بواسطة API (سواءً REST or RPC or Messaging API). واستخدامها سوف ينظم المشروع ويجعله Modular بشكل أفضل والذي يصعب تطبيقه في ال Monolithic. وبالرغم من أن المهام Functions هي نفسها، الا انه وصلنا لمجموعة من الخدمات التي يسهل تطويرها بسرعة وأسهل في الفهم والصيانة. - **ثانياً**: هذه المعمارية تجعل تطوير كل خدمة مفصولة ومستقلة عن الأخرى، وبالتالي كل فريق أو مطور مسؤول عن الخدمة له الحرية في استخدام التقنيات المناسبة ويقوم بوضع ال Contract APIs لها. وبالطبع قد لا يكون الأمر متاحاً للمطورين لاختيار أي تقنية بشكل عشوائي ولكن ستكون لديهم مساحة من الحرية وغير مقيدين بأي تقنيات قديمة استخدمت سابقاً في خدمة أخرى. وأيضاً لأن الخدمات أصبحت صغيرة فيمكن فعلياً إعادة كتابتها من جديد بتقنية جديدة. - **ثالثاً:** هذه المعمارية تسمح بأن يتم نشر Deploy الخدمات بشكل مستقل عن الأخرى، ولا يحتاجوا المطورين الى تنسيق بين كل الخدمات للتعديلات الداخلية في الخدمة والتي لا تؤثر على ال Exposed APIs. وهكذا يمكن نشرها مباشرة بمجرد الانتهاء من الاختبارات Testing وبالتالي ال Continuous Deployment أصبح ممكناً بسهولة هنا. - **رابعاً**: هذه المعمارية تسمح لكل خدمة بأن يتم توسعتها Scaling بشكل مستقل وفعال، وكل خدمة قد يكون لديها متطلبات معينة، او حتى سيرفرات بمواصفات معينة لها، على سبيل المثال يمكن نشر الخدمات التي تقوم بعمل معالجة للصور والتي تأخذ وقتاً من المعالج CPU-Intensive على سيرفرات لديها أكثر من CPU Cores، والخدمات التي تتطلب ذاكرة أعلى في سيرفرات بذاكرة عالية. ## عيوب ال Microservices كما كتب فريد بروكس Fred Brooks منذ 30 عاماً "لا توجد رصاصة فضية There are no silver bullets"، وسوف نجد أن ال Microservices كما في غيرها من التقنيات بها بعض العيوب والمشاكل: - **أولاً**: قد يكون اسمها Microservices يركز على الحجم وعدد الأسطر البرمجية يجب أن تكون قليلة، وهذا الأمر جيد، ولكن هي ليست الهدف النهائي، فالفكرة من الخدمات المصغرة هو تقسيم النظام الى أجزاء Decompose حتى يسهل العمل عليه ونشرها بشكل مرن Agile Application Development & Deployment. - **ثانياً**: التعقيد الذي ينشئ منها بسبب أنها عدة موزعة ومستقلة، فيجب على المطورين اختيار طريقة التواصل Interprocess Communication سواءً باستخدام Messaging or RPC، وأيضاً معالجة المشاكل والفشل في حالة لم تعمل الخدمة الداخلية أو تأخرت التي استدعتها خدمة أخرى. هذه المشكلة ليست موجودة في ال Monolithic فهناك عملية الاستدعاء هي مجرد استدعاء دالة أخرى في نفس النظام Process. - **ثالثاً**: من التحديات في الخدمات المصغرة هي أن بها أكثر من قواعد بيانات موزعة بداخل هذه الخدمات Partitioned Databases، وفي الوضع الطبيعي هناك العديد من العمليات Business Transactions تحتاج أن تقوم بالتعديل على عدة جداول في آن واحد، وهذا الأمر يسهل تطبيقه في ال Monolithic لأن هناك قاعدة بيانات واحدة، ولكن ال Microservices-Based Application فسوف تحتاج أن تقوم بالتعديل على عدة قواعد بيانات في عدة خدمات في آن واحد وهذا ما يضفي مزيد من التحديات للمطورين. - **رابعاً**: اختبار هذه الخدمات المصغرة يحتاج المزيد من العمل، ففي ال Monolithic يمكن كتابة Test Class يقوم بتشغيل كامل التطبيق ومن ثم يقوم باختبار وظائفه وكل ال REST APIs فيه، أما في الخدمات المصغرة فسوف تحتاج أن تقوم بكتابة ال Test Class وتقوم بتشغيل الخدمة وكل الخدمات التي تحتاجها لكي تؤدي الخدمة المطلوبة (أو عمل Stubs/Mocks لها) وهذا يحتاج المزيد من العمل أيضاً. - **خامساً**: من التحديات في الخدمات المصغرة وهو التعديلات التي تكون في أكثر من خدمة في آن واحد، مثلاً تريد تطبيق User Story وهي تتطلب تغير في الخدمة الأولى والثانية والثالثة، والخدمة الأولى تعتمد على الثانية، والثانية تعتمد على الثالثة. في ال Monolithic فهذا التعديل سهل للغاية فسوف تقوم بتغيير هذه الدوال في نفس المشروع ومن ثم رفعها جميعاً ونشرها على السيرفر. أما في ال Microservices فسوف تحتاج للتنسيق لكيفيه إتمام هذا التعديل ونشره، مثلاً تقوم بتعديل الخدمة الثالثة، وبعدها الثانية، وأخيراً الأولى. لحسن الحظ فطبيعة الخدمات المصغرة عادة لا يكون فيها هذا النوع من التعديلات، وغالباً معظم التعديلات تكون بداخل الخدمة نفسها فقط. - **سادساً**: نشر الخدمات المصغرة أيضاً به المزيد من التعقيدات، فمثلاً في ال Monolithic فيتم نشر المشروع نفسه على عدة سيرفرات وأمامها يكون Load Balancer لتوزيع الطلبات عليها. أما هنا في ال Microservices فهناك العديد من الخدمات المصغرة (مثلاً Netflix لديها أكثر من 600 خدمة مصغرة)، وكل خدمة مصغرة سوف يكون لديها أكثر من نسخة Instances تعمل. وهذا يعني المزيد من النسخ التي تحتاج للإعداد Configure، النشر Deploy، التوسعة Scaling، المراقبة Monitoring. أيضاً قد تحتاج لعمل مستكشف للخدمات Service Discovery بحيث تستخدمها الخدمات لمعرفة تفاصيل الخدمات الأخرى (الروابط والمنافذ Hosts and Ports) التي تريد أن تتواصل معها، ومع هذا الحجم من التعقيدات فإدخال هذه البيانات يدوياً Hardcoded لن يجدي ويحتاج أيضاً Automations لها. ## خلاصة بناء الأنظمة والتطبيقات المعقدة أمر صعب ولا يستهان به، المعمارية الضخمة Monolithic هي مناسبة للتطبيقات البسيطة والصغيرة أو التجريبية MVPs. ولكنها ليست مناسبة للتطبيقات الكبيرة والتي تنمو سريعاً وستجد أنك أمام العديد من المشاكل حينها، وفي المقابل ال Microservices Architecture هي خيار أنسب للتطبيقات الكبيرة والتي تنمو سريعاً، الا أنها بها أيضاً تعقيدات وتحديات في تطبيقها. في مقالات قادمة بإذن الله، سوف نتحدث عن مزيد من التفاصيل في عالم ال Microservices مثلاً إيجاد الخدمات Services Discovery، نشر الخدمات Service Deployment، وأيضاً طرق إعادة هيكلة Refactoring تطبيق موحد ضخم Monolithic الى Services. ## المصادر المصدر الأساسي هو مقالة [Introduction to Microservices](https://www.nginx.com/blog/introduction-to-microservices/?ref=informatic-ar.com) في مدونة NGINX وتم حذف بعض الجزئيات غير الضرورية والإعلانية مثلاً للمنتج NGINX Plus واضافة ما يلزم بحسب ما رأيته مناسباً. ### نظرة على DotNet Core من خلال تصميم نظام حجوزات URL: https://informatic-ar.com/dotnet-core-booking-system/ Last updated: 2026-10-04T04:35:38.000Z > تحديث 29-05-2020 سأقوم برفع الشفرة المصدرية لنظام الحجوزات على القيت هب [هنا](https://github.com/Shoogn?ref=informatic-ar.com) دوت نت كور تقنية جديدة من مايكروسوفت تم إصدارها في 2015 وتم كتابة شفرتها من الصفر بحيث أنها لا تقوم بالإعتماد على الـ DotNet Framwork. مميزات الدوت نت كور عديدة جدا ومن أهمها أنها تعتبر Cross platform بحيث يمكن تشغيلها على معظم أنظمة التشغيل ومن أهمها نظام التشغيل لينكس والماك. لا أريد أن أطيل في الشرح عن ماهي الدوت نت كور... بقدر ما سنقوم بعرض أمثلة عديدة تساعدنا على طريقة عمل التقنية. سنقوم بإنشاء تطبيق حجوزات بسيط يوضح لنا مميزات التقنية... الأدوات التي ستحتاجها **Visual Studio 2019** **SQL Server 2016** **MogoDB** > بالنسبة لـ mongoDB سأقوم بعمل صورة عملية متكاملة عنها وسأقوم بإنشاء نمط التصميم المستودعي Repository Pattern والذي سيتم معها.. لكن لن أقوم بإستخدامها في عملية حفظ وحجز التذاكر... لأنني سأقوم بإستخدام الـ Sql Provider لهذه العملية... لكن بإمكانك إستخدام الـ MongoDB إن أردت لأن نمط التصميم سيكون جاهز > لماذا MongoDB؟ المشروع الذي ستعمل عليه (أنت كقارئ / قارئة) هو نموذج مبسط من مشروع ضخم آخرونسبة لأن تدفق المعلومات في مثل هذه المشاريع دائما مايكون كبيرا بالتالي ستحتاج للسرعة في نقل وإرجاع البيانات و قواعد البيانات المفهرسة تعتبر أفضل من هذه الناحية. سأقوم بالتركيز في هذه المقالة على فئتين تعتبران محور عمل الدوت نت كور > Program.cs > Statup.cs حسنا؛ لقد قمنا بتوضيح ما سنقوم به.. الآن قم بفتح الفيجوال ستدويو قم بإنشاء Blank solution جديد وقم بإعطاءه الاسم ***Reservation\_Sys*** قم بإنشاء **4** مكتبات للفئات بالاسماء التالية...ولا تنسى بأن تقوم بإختيار الدوت نت كور كـقاعدة عمل **Reservation\_Sys.Infrastructure** **Reservation\_Sys.Model** **Reservation\_Sys.Service** **Reservation\_Sys.Repository** قم بإنشاء تطبيق **ASP.NET Web Application** من النوع **Empty** ولا تنسى ان يكون الدوت نت كور كقاعدة عمل... وقم بإعطاءه الاسم التالي: Reservation\_Sys.Web.UI.MVC وقم بربطه مع الـ Reservation\_Sys.Model و Reservation\_Sys.Service و Reservation\_Sys.Repository قم بعملية ربط للـ Reservation\_Sys.Service مع الـ Reservation\_Sys.Model و Reservation\_Sys.Repository قم بعملية ربط للـ Reservation\_Sys.Repository مع الـ Reservation\_Sys.Service و Reservation\_Sys.Model ![](https://informatic-ar.com/wp-content/uploads/2020/03/1.jpg) اشكل المشروع بعد إنشاء مكتبات الفئات قم بإنشاء مجلدين داخل مشروع الـ Reservation\_Sys.Model الأول بإسم **Entities** والآخر بإسم **SqlServerData** > لا تقلق عزيزي القارئ؛ سأقوم بشرح مفصل لكل شئ قمت بإنشاءه سابقا.. فقط نريد أن نقوم بتهيئة المشروع في البداية، وبعدها سنقوم بالشرح بالتفصيل مع التحليل قبل إنشاء الفئات التي سنعمل عليها. > بالنسبة لمشروع الـ MongoDB سيكون على الـ Github وذلك نسبة لحجم المساحة هنا. > ملاحظة: مرة أخرى سأقوم بشرح وتحليل المشروع بعد الانتهاء من تهيئة جميع أجزاء الـ Model سأقوم بإنشاء الفئات وربط الفئات مع بعضها البعض Relationship وسأقوم بإستخدام الـ EF Code First والتي ستمكنني من إنشاء قاعة البيانات عن طيقة خاصية الـ Migration داخل المجلد Entities قم بإنشاء الفئات التالية واحدا تلو الآخر ```csharp namespace Reservation_Sys.Model.Entities { public class Bus { public int BusID { get; set; } public string DriverName { get; set; } public int No_OfSeat { get; set; } public DateTime AddedDate { get; set; } public string AddedBy { get; set; } public virtual ICollection ScheduleTrips { get; set; } } } ``` ```csharp namespace Reservation_Sys.Model.Entities { public class City { public int CityID { get; set; } public string Name { get; set; } public DateTime AddedDate { get; set; } public string AddedBy { get; set; } } } ``` ```csharp namespace Reservation_Sys.Model.Entities { public class ScheduleTrip { public int ScheduleTripID { get; set; } public int BusID { get; set; } public string FromCity { get; set; } public string ToCity { get; set; } // We can build any schema fro generate tripcode, // but instead i will use hard type public string TripCode { get; set; } public string FromLandPort { get; set; } public string ToLandPort { get; set; } public decimal TripAmount { get; set; } public DateTime DepartureTime { get; set; } public DateTime ArrivalTime { get; set; } public string AddedBy { get; set; } public DateTime AddedDate { get; set; } public virtual Bus Bus { get; set; } public virtual ICollection Reservations { get; set; } } } ``` ```csharp namespace Reservation_Sys.Model.Entities { public class Reservation { public int ReservationID { get; set; } public int ScheduleTripID { get; set; } public string ClientIdentityNo { get; set; } public string ClientName { get; set; } public string PayType { get; set; } public string CreditCardNo { get; set; } public DateTime ExpiryDate { get; set; } public int SeateNo { get; set; } public string AddedBy { get; set; } public DateTime AddedDate { get; set; } public virtual ScheduleTrip ScheduleTrip { get; set; } public virtual ICollection Bookings { get; set; } } } ``` ```csharp namespace Reservation_Sys.Model.Entities { public class Booking { public int BookingID { get; set; } public int ReservationID { get; set; } public bool Status { get; set; } public virtual Reservation Reservation { get; set; } } } ``` حسنا الآن قمنا بإنشاء جميع الفئات التي ستساعدنا في جميع مراحل عمل التطبيق... من الضروري تنزيل مكتبة الـ Entity Framework Core على مشروع الـ Model لأننا سنقوم بإستخدام الفئة DbContext في جميع العمليات التي ستتم على قاعدة البيانات. حسنا.. سننتقل لمرحلة أخرى وهي مرحلة العمل على إنشاء جداول قاعدة البيانات، سأقوم بإستخدام طريقة الـ Fluent API والتي تتيح لي إستخدام لغة الـ #C في إنشاء فئات وبها سأقوم بعمل جيمع أنواع البيانات بالنسبة لقاعدة البيانات SQL Server مع جميع أنواع الـ Constraint التي سأحتاجها في قاعدة البيانات... قم بإنشاء جميع الفئات التالية واحدة تلو الأخرى داخل المجلد SqlServerData. ```csharp using System; using System.Collections.Generic; using System.Text; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Reservation_Sys.Model.Entities; namespace Reservation_Sys.Model.SqlServerData { internal class BusSchemaMap : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("Buses", "dbo"); builder.HasKey(k => k.BusID); builder.Property(p => p.AddedBy).HasMaxLength(50).IsRequired(true); builder.Property(p => p.AddedDate).IsRequired(true).HasDefaultValue(DateTime.Now); builder.Property(p => p.DriverName).IsRequired(true).HasMaxLength(200); builder.Property(p => p.No_OfSeat).IsRequired(true).HasMaxLength(50); } } } ``` > لن أوقم مرة أخرى بإدارة فضاءات الأسماء في الفئات المتبقية، فقط سأقوم بإدراج اسم الفئة.. نسبة لضيق المساحة وكسبا للوقت. ```csharp internal class CitySchemaMap : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("Cities", "dbo"); builder.HasKey(k => k.CityID); builder.Property(p => p.AddedBy).IsRequired(true).HasMaxLength(200); builder.Property(p => p.AddedDate).IsRequired(true).HasDefaultValue(DateTime.Now); builder.Property(p => p.Name).IsRequired(true).HasMaxLength(40); } } ``` ```csharp internal class ScheduleTripSchemaMap : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("ScheduleTrips", "dbo"); builder.HasKey(k => k.ScheduleTripID); builder.Property(p => p.ArrivalTime).IsRequired(true); builder.Property(p => p.DepartureTime).IsRequired(true); builder.Property(p => p.FromCity).IsRequired(true); builder.Property(p => p.FromLandPort).IsRequired(true); builder.Property(p => p.ToCity).IsRequired(true); builder.Property(p => p.ToLandPort).IsRequired(true); builder.Property(p => p.TripAmount).IsRequired(true); builder.Property(p => p.TripCode).IsRequired(true); builder.Property(p => p.AddedBy).IsRequired(true); builder.Property(p => p.AddedDate).IsRequired(true).HasDefaultValue(DateTime.Now); builder.HasOne(p => p.Bus).WithMany(m => m.ScheduleTrips).HasForeignKey(f => f.BusID); } } ``` ```csharp internal class ReservationSchemaMap : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("Reservations", "dbo"); builder.HasKey(k => k.ReservationID); builder.Property(p => p.ClientIdentityNo).IsRequired(true).HasMaxLength(20); builder.Property(p => p.ClientName).IsRequired(true).HasMaxLength(200); builder.Property(p => p.CreditCardNo).IsRequired(false).HasMaxLength(20); builder.Property(p => p.ExpiryDate).IsRequired(true); builder.Property(p => p.PayType).IsRequired(false); builder.Property(p => p.SeateNo).IsRequired(true); builder.Property(p => p.AddedDate).IsRequired(true).HasDefaultValue(DateTime.Now); builder.Property(p => p.AddedBy).IsRequired(true); builder.HasOne(p => p.ScheduleTrip) .WithMany(m => m.Reservations) .HasForeignKey(f => f.ScheduleTripID); } } ``` ```csharp internal class BookingSchemaMap : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("Bookings", "dbo"); builder.HasKey(k => k.BookingID); builder.Property(p => p.Status).IsRequired(true); builder.HasOne(p => p.Reservation) .WithMany(m => m.Bookings) .HasForeignKey(f => f.ReservationID) .IsRequired(true); } } ``` لاحظ أن جميع أنواع الوصول لهذه الفئات هي internal وذلك لأنني لن أقوم بإستخدامها خارج مكتبة الفئات Model، فسأقوم بإستخدامها داخل الفئة ReservationContext وهي الفئة التي تقوم بعمل جميع الحركات التي ستتم على قاعدة البيانات وأيضا تقوم بإنشاء جميع الجداول في قاعدة البيانات... الآن قم بإنشاء فئة جديدة داخل المجلد Entities وقم بإعطاء هذه الفئة اسم ReservationContext كما مبين تاليا ```csharp public class ReservationContext : DbContext { public ReservationContext() { } public ReservationContext(DbContextOptions options) : base(options) { } public DbSet Bookings { get; set; } public DbSet Buses { get; set; } public DbSet Cities { get; set; } public DbSet Reservations { get; set; } public DbSet ScheduleTrips { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new BookingSchemaMap()); modelBuilder.ApplyConfiguration(new BusSchemaMap()); modelBuilder.ApplyConfiguration(new CitySchemaMap()); modelBuilder.ApplyConfiguration(new ReservationSchemaMap()); modelBuilder.ApplyConfiguration(new ScheduleTripSchemaMap()); base.OnModelCreating(modelBuilder); } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseLazyLoadingProxies(true); } } ``` قم بتنزيل المكتبة Microsoft.EntityFrameworkCore.Proxies من على الـ NeGet. وهي المكتبة التي ستساعدنا في عمل الإستعلام بالنسبة لجميع العمليات التي تقوم بجلب البيانات من أكثر من جدول Lazy Loading. داخل مشروع الـ Infrastructure قم بإنشاء الواجهة التالية: ```csharp public interface IDateTime { DateTime Now { get; } } ``` هذه الواجهة ستساعدنا في عمليه جلب الوقت من خلال استخدام الـ DI وذلك لان الفئة DateTime لديها خاصية Now وهي عبارة عن static بالتالي سأقوم بإستخدام الـ Adapter Design Pattern ... ستفيدك هذه الفئة إذا كنت ستقوم بإستخدام كتابة شفرات الإختبار Unit Testing وذلك لأن الـ DateTime classs لا نسطيع السيطرة عليه نسبة لأه يعمل أثناء الـ *Rum-time* وإستخدامنا للـ Adapter Design Pattern بإمكاننا عمل مثيل للفئة من خلال الواجهة *Interface* التي قمنا بأنشائها IDateTime. الآن قم بإستخدام فئة جديدة بإسم DateTimeAdapter وجعلها ترث من الـواجهة IDateTime. ```csharp public class DateTimeAdapter : IDateTime { public DateTime Now { get { return DateTime.Now; } } } ``` حسنا الآن جميع الأساسيات التي يحتاجها التطبيق من مشروع الـ Model جاهز للعمل في المقالة الثانية سنقوم بإلقاء نظرة على مشروع الـ Repository وأيضا في حال إحتجنا لعمل أي تعديل في مشروع الـ Model أو الـ Infrastructure سنقوم به. **وكما ذكرت في بداية هذه المقالة أن تركيزي سينصب نحو عمل الدوت نت كور وليس طريقة إنشاء الفئات وطريقة عمل أنماط التصميم. فبالتالي سأمضي سريعا في عملية إنشاء مكتبات الفئات الأخرى. لكن سأِرح بالتفصيل عملية عمل مشروع ال Web.** ### تطوير الأنظمة وهندسة البرمجيات - Jeff Dean أنموذجاً URL: https://informatic-ar.com/software-engineering-jeff-dean/ Last updated: 2026-10-03T22:31:22.000Z حسب ويكيبيديا فإن [مطور البرمجيات](https://ar.wikipedia.org/wiki/%D9%85%D8%B7%D9%88%D8%B1%5F%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA?ref=informatic-ar.com) \- Web Developer - يتم تعريفه بأنه شخص متخصص في برمجيات الكمبيوتر يقوم بتصميم النظم لإنجاز المتطلبات التي يحددها محلل النظم، كذلك فإن [مهندس البرمجيات](https://ar.wikipedia.org/wiki/%D9%85%D9%87%D9%86%D8%AF%D8%B3%5F%D8%A8%D8%B1%D9%85%D8%AC%D9%8A%D8%A7%D8%AA?ref=informatic-ar.com) \- Software Engineer - هو شخص ممارس يقوم بتطبيق النظريات العلمية و الخوارزميات لتصميم أو تنفيذ حلول تقنية لمختلف المجالات ضمن معايير المهنية و الجودة. التعاريف أعلاه من الوهلة الأولى تبدو واضحه، حتى للأشخاص من خارج المجال، لكن ما يهمنا في هذا المقال إيراد نموذج إطاري لشخص في بداية حياته التقنية أو في طريقة ليصبح مطور نظم أو مهندس برمجيات ليكوّن من خلاله صورة ذهنية عن ماهية تطوير الأنظمة أو هندس البرمجيات في الحياة العملية. سنتحدث في هذا المقال عن [جيف دين "Jeff Dean"](https://en.wikipedia.org/wiki/Jeff%5FDean%5F%28computer%5Fscientist%29?ref=informatic-ar.com)، فمن هو؟ وكيف بدأ؟، وماذا فعل ويفعل؟ ![](https://informatic-ar.com/wp-content/uploads/2019/10/IMG_6660-1024x768.jpg) حصل جيف على درجة البكالوريوس مع مرتبة الشرف في علوم الحاسوب والإقتصاد من جامعة منيسوتا في العام 1990م، ودرجة الدكتوراه في علوم الحاسوب من جامعة واشنطن في العام 1996م. في الفترة من 1990م إلى 1991م عمل جيف مع منظمة الصحة العالمية في برنامج الإيدز العالمي لتطوير منظومة برمجية للنمذجة الإحصائية والتنبؤ لوباء الإيدز. إنضم جيف لشركة قوقل في العام 1999م، وهو حالياً يعمل ككبير باحثين ونائب رئيس قسم الذكاء الإصنطاعي والمشرف على مشروع Google Brain Project. منذ نشأة خوارزمية قوقل للبحث والتصنيف التي تمثل روح محركها للبحث (Software) ظهر إحتياج ملح لتقوية هذا الروح بالجسد القوي (Hardware) اللازم لتشغيلها، فجهاز حاسوب واحد لا يقوى على توفير الإسناد اللازم لعمل خوارزمية البحث، فكان التحدي أمام شركة قوقل في إيجاد طريقة لربط عدة أجهزة معاً لتعمل بشكل متناغم كجهاز واحد كبير، لتمكين الخوارزمية من تقديم الأداء الأفضل، بصورة أخرى بناء نظام موزع [(Distributed System)](https://en.wikipedia.org/wiki/Distributed%5Fcomputing?ref=informatic-ar.com) يعمل كوحدة برمجية واحده لكن من خلال عدة أجهزة متصلة ببعضها البعض، تزايد عدد هذه الأجهزة من عدة أجهزة إلى آلاف الأجهزة. ![](https://informatic-ar.com/wp-content/uploads/2019/10/065764400_1562773599-googleai-04.jpg) لعب جيف دور أساسي في بناء أول نظام موزع لمحرك البحث قوقل لتمكين خوارزميته من العمل بشكل متناغم لإجراء عمليات حسابية بشكل متزامن على كمية كبيرة جداً من البيانات وتتم هذه العمليات على عدة مجموعات حواسيب تسمى بالعناقيد الحاسوبية. هنا تكمن مقدرة المطور أو مهندس البرمجيات على إكتشاف وصناعة الحل أو تطبيقه أو تطويره، ومن الجدير بالذكر أن الحاسوب والعلوم المرتبطه به تعتبر علوم حديثة النشأة نسبياً بالمقارنة مع العلوم الأخرى كالرياضيات والفيزياء والكيمياء، لذا من الوارد وجود مشاكل ليس لها حل مسبق أو مسلمة تستند عليها، وهنا على المطور أو مهندس البرمجيات إجتراح حل جديد غير مطروق. شارك جفري في بناء البنية الأساسية التي أستندت عليها شركة قوقل تلك البنية التي تمثل عصب أعمال شركة قوقل ومن ذلك : - شارك في تصميم إجزاء محرك البحث الأساسي لقوقل مثل زواحف البحث Crawling، والمفهرسات Indexers، وأنظمة الإستعلام Query Serving Systems. - أشرف على مجموعة من فرق العمل التي تعمل في مهام مثل : Speech Recognition, Computer vision, Language understanding، وغيرها من مهام الMachine Learning. - شارك في بناء النسخة الأولى من نظام إعلانات قوقل [Google Ad-sense](https://en.wikipedia.org/wiki/Google%5FAdSense?ref=informatic-ar.com) والذي يمثل مصدر تمويل قوقل الأساسي. - شارك بفعالية في بناء البنية الموزعة للحوسبة Distributed computing infrastructure، التي من خلالها تمكنت قوقل من توفير بنية الأجهزة اللازمة لعمل أنظمة محرك البحث، وتشمل هذه الأنظمة [MapReducre](https://en.wikipedia.org/wiki/MapReduce?ref=informatic-ar.com) وهي خوارزمية البحث الأساسية لقوقل، و [BigTable](https://en.wikipedia.org/wiki/Bigtable?ref=informatic-ar.com) وهي قواعد البيانات التي يستخدمها محرك البحث في تخزين البيانات. وفي معرض الفكاهة يتم تداول المقولة التالية عن جيف : " جفري دين يكتب في سيرته الأشياء التي لم يفعلها، لأن سيرته بهذه الطريقة تكون مختصرة أكثر "، طبعاً هذا من باب المزاح والمبالغة، إلا أن للرجل فعلاً مشاركات تقنية كثيرة جداً وأثر تقني واسع، بما في ذلك مجموعة عضويات مميزة مثل : - عضو الأكاديمية الوطنية للهندسة [National Academy of Engineering](https://www.nae.edu/?ref=informatic-ar.com) : وهي مؤسسة أمريكية غير حكومية وغير ربحية تأسست في عام 1964م تتكون من الأعضاء الذين لهم إنجازات متميزة ومتواصلة في مجال البحوث. - زميل جمعية مكائن الحوسبة [Association for Computing Machinery](https://www.acm.org/?ref=informatic-ar.com) : وهي أول رابطة علمية وتعليمية للحوسبة في العالم تأسست في عام 1947م. - عضو الرابطة الأمريكية للعلوم المتقدمة[ American Association for the Advancement of Sciences](https://www.aaas.org/?ref=informatic-ar.com) : وتعرف إختصاراً بAAAS وهي إحدى أقدم وأهم المنظمات العلمية في العالم والتي أنشئت في سنة 1848م وتمثل أكبر منظمة علمية في العالم ويتبعها 275 مؤسسة علمية موزعة في عشرات الدول ويعمل فيها حوالي المليون باحث. يحصر جفري إهتماماته في موضوعات : large-scale distributed systems, performance monitoring, compression techniques, information retrieval, application of machine learning to search and other related problems, microprocessor architecture, compiler optimizations, and development of new products that organize existing information in new and interesting ways خلال حياته العملية تنقل جفري في عدة دول ومدن منها: بوسطن، ماساتشوستس، الفلبين مانيلا، أوغندا، هاواي، الصومال مقديشو، جنيف، سويسرا، واشنطن، بالو ألتو كاليفورنيا. يعمل جفري في مشاريعه وأبحاثه التقنية بصورة أساسية بلغة C++ لكنه بصورة عامة يستخدم لغات Java, Python, Perl, Self, Cecil, Assembly and Pascal. بالعودة إلى تعريف مطور الأنظمة و مهندس البرمجيات فبجانب ما يمكن أن يقال من تعاريف عديدة، فحياة مطور الأنظمة أو مهندس البرمجيات يجب أن تتسم بالثراء المعرفي والتجارب الواسعة في مجالات علوم الحاسوب أو ما جاورها من علوم، وبالنظر إلى نموذج جيف فيمكن سوغ الخلاصات التالية : - الدراسة المنهجية لمجال تقنية المعلومات أمر ضروري، وهنا يجب التفرقه بين المتخصص في علوم الحاسوب أو تقنية المعلومات وبين رائد الأعمال. - ضرورة توسع مطور الأنظمة أو مهندس البرمجيات في الإهتمامات لتشمل مجالات خارج مجالات علوم الحاسوب مثل : الإقتصاد، الإدارة، علم النفس، الهندسة، الفيزيا، … الخ. - البحث المستمر أمر ضروري والمشاركات البحثة والعلمية والكتابات التقنية ومشاركة خلاصة التجارب. - لا تبحث عن لغة برمجة لتتعلمها، بل أبحث عن مشكلة وحلها بأي لغة برمجة. - الإنتقال الجغرافي من مكان لآخر، والعمل مع شعوب مختلفة وثقافات مختلفة ربما يعتبر من كمال المقدرة والخبرة التقنية. - الأعمال والمشاريع التقنية في المجالات الإنسانية كمحاربة الأمراض والفقر وأبحاث التقنية المتعلقة بالزراعة أو الصناعة ذات مردود كبير في الخبرة التقنية. خاتمة : من الواضح أن جيفري دين شخص عبقري نشط للغاية شاءت عناية الله أن يكون في بيئة تقنية بإمتياز دعمت مقدراته الإبداعية وأنعكس ذلك على أعماله ومشاريعه، وهذا لا يعني أن نقتصر حياة مطور الأنظمة أو مهندس البرمجيات في المنطقة العربية وشمال أفريقيا على الألقاب والمسميات الوظيفية البراقة دون أثر تطبيقي أو بحثي ملموس، فعلوم الحاسوب رغم حداثتها نسبياً إلا أنها تعتبر علوم تطبيقية. [أبوبكر الطائف](http://bakrialtaif.batinjan.com/?ref=informatic-ar.com)، مبرمج ورائد أعمال ### إجتياز إمتحان الـ #C من مايكروسوفت 70-483.. من الألف للياء URL: https://informatic-ar.com/csharp-certification-70-483/ Last updated: 2026-10-04T04:35:39.000Z جلست؛ وإجتزت قبل ستة أيّام، تحديدا يوم 19/9/2019، امتحان Programming in C# 70-438 من قبل مايكروسوفت Microsoft وهذا الإمتحان يتعلق بتحديد مستويات المبرمجين الذين يستخدمون تلك اللغة في برمجة تطبيقاتهم على منصة الدوت نت. في هذه المقالة سأتحدث عن طريقة إجتياز هذا الإمتحان وماهي الإدوات التي ستعينك في إجتياز الامتحان من خلال الكتب أو المحاضرات أو المحاضرات المرئية (فيديوهات) وكيفية التسجيل للإمتحان. **لغة الـ** **#** **C** لن أطيل الحديث في هذه الفقرة، نسبة لأنّ قارئ هذه المقالة حتما يعرف إمكانية هذه اللغة وبالتالي يريد فقط معرفة كيفية الإستعداد للإمتحان. لغةالـ #C هي لغة ذات إستخدامات متعددة سواء في إنشاء أنظمة سطح المكتب أو مجالات الإنترنت سواء من خلال الهواتف الذكية او متصفحات الإنترنت، كما أيضا تستخدم في مجال لغة الآلة (اللغة تدعم هذا المجال من قبل سنين بسيطة)، كما أيضا تدعم مجال الخدمات من خلال الإنترنت Web Services. لكل تلك المجالات أصبحت الـ #C من اللغات المستخدمة بصورة كبيرة من قبل ملايين المستخدمين، وهي مدعومة من مايكروسوفت عملاق البرمجيات في العالم، تم إطلاق الـ C# 8 قبل يومين من كتابة تلك المقالة. **إمتحان الـ 70-483** هذا هو رمز الإمتحان. أولا يتوجب عليك قبل الجلوس للإمتحان ان تكون لديك خبرة في التعامل مع لغة الـ #C لمدة أقلّاها سنة، حتى تفهم من خلالها تركيب اللغة والجمل المستخدمة فيها وجميع الكلمات المحجوزة. الإمتحان مدتهُ ساعتين إضافة لـ 15 دقيقة أخرى تُعرض فيها سياسة الاتفاقية من مايكروسوفت وعدد الأسئلة لا يتجاوز الخمسين سؤال - أذكر في الـ Dump الذي امتحنت عليه كان عدد الأسئلة 41 سؤال. والتسجيل يتم من موقع مايكروسوفت وبعدها ينقلك لإحدى المواع الخدمية التي تتيح لك جدولة الإمتحان وطريقة دفع الرسوم - رسم الامتحان 165$ غير شامل الضريبة. الإمتحان ليس بالشئ الصعب، فقط يتطلب منك تركيز دقيق وبعون الله تستطيع إجتيازه. قبل كل شئ عليك أن تقوم بالدخول لصفحة الإمتحان وقراءة مواضيع الإمتحان ومعرفتها من هذا الرابط [https://www.microsoft.com/en-us/learning/exam-70-483.aspx](https://www.microsoft.com/en-us/learning/exam-70-483.aspx?ref=informatic-ar.com) مع تنزيل وقراءة ورقة المواضيع الخاصة بالإمتحان بصيغة PDF والمكونة من ثلاثة صفحات بصورة جيدة وهذا **أول** متطلب وسيساعدك كثيرا في اجتياز الامتحان، لأنّ معرفة المواضيع بصورة دقيقة سيسهل عليك نقاط ضعفك وبالتالي التركيز على تقوية مهاراتك. هذا اولا **ثانيا**: عليك تنزيل الكتاب الرسمي للإمتحان وموجود على موقع أمازون من [هنا](https://www.amazon.com/Exam-Ref-70-483-Programming-Miles/dp/1509306986/ref=sr%5F1%5F1?keywords=Exam+Ref+70-483+Programming+in+C%23&qid=1569484625&s=gateway&sr=8-1&ref=informatic-ar.com). وحتى تكون على بيّنة، الكتاب يتكون من 400 صفحة فقط، وهو لن يعلّمك لغة الـ #C بل فقط يوضح لك مواضيع الإمتحان مع التركيز عليها بصورة كبيرة فأي شي خارج نطاق الإمتحان لن تجده في الكتاب الرسمي. **ثالثا**: يتوجب عليك الإعتماد كثيرا على الـ Documentation من مايكروسوفت حيث أنها المعتمدة الوحيدة والتي ستجد بها كل صغيرة وكبيرة خاصة بلغات البرمجة والتقنيات الخاصة بمايكروسوفت ومن دونها لن تتعرف على كل تفاصيل اللغة، ولأنه ببساطة لايوجد كتاب او فيديو يستطيع شرح كافة أجزاء اللغة نسبة لالآف العناصر والفئات الموجودة باللغة. **مايتوجب عليك معرفته!!** أي لغة برمجية يوجد بها أساسيات وجمل تعبيرية يتوجب عليك أولا معرفتها قبل الدخول والتعمّق في اللغة نفسها. في الـ #C ستحتاج لمعرفة التعامل مع المتغيرات. يوجد نوعان من المتغيرات في الـ #C - Value types و Reference types والأولى تتعلق بالأرقام اما الثانية تتعلق بالفئات وبعض الأشياء الأخرى وبصورة مرتبطة بهذا الموضوع لابد أن تتعرف على كيفية إدارة الذاكرة خصوصا في ما يتعلق بالـ Reference types والجميل في لغة الـ #C أنها تقوم بإدارة الذاكرة بصورة تلقائية. أيضا يتوجب عليك معرفة التعامل مع المؤشرات، في الـ #C يطلق عليها Delegate وهي التي تتيح لك استخدام الأساليب Methods كـ Argument ويرتبط معها مباشرة معرفة استخدام الـ Lambda Expression **الكتب التي ستفيدك في الإمتحان** هناك العديد من المصادر التي بإمكانك الإعتماد عليها. لكن كما ذكرت في الفقرة قبل السابقة يتوجب عليك اولا قراءة الكتاب الخاص بالإمتحان. لكن أيضا هناك بعض الكتب التي بإمكانك الإعتماد عليها ومنها: Pro C# 7 and with .Net and .Net Core C# 7 in a Nutshell #CLR via C > **في الحقيقة جميع تلك الكتب غاية في الروعة وقد تكون كافية لتعلّمك لغة C# بصورة كبيرة وليس فقط كي تساعدك للجلوس للإمتحان. وشخصيا هذه الكتب الثلاثة لا تفارق مكتبي نهائيا.** بالنسبة للكتاب الأول سيساعدك في إجتياز الإمتحان خصوصا في القسم **الأول والثاني والرابع** الخاص بالإمتحان. بالنسبة للقسم **الثالث** الخاص بالإمتحان ستحتاج لقراءة الكتاب الرسمي للإمتحان مع الإعتماد على كتاب C# 7 in a nutshell فيه ستتعرف على مواضيع الـ Security وطريقة التعامل مع خوارزميات الـSecurity. فرضا في القسم الخاص بعملية الـ Hashing وهي عمليه تقوم بتحويل الأرقام والحروف لرموز وبإمكانك إستخدام خوارزميات تساعدك على توليد هذه الرموز بأطوال محددة. لو إفترضنا أننا نريد أن نقوم بعمل أساليب Methods تساعدنا على تحويل الكلمات السرية التي نستخدمها في الدخول لمواقع عديدة لرموز يصعب فكّها، حينها ستحتاج لعملية الـ Hashing وتخزين تلك الرموز في قاعدة البيانات. دعني أنشي اسلوب يقوم بعمل توليد لأرقام مشفرة ```csharp static string ApplyHashing(string input) { HashAlgorithm hashAl = SHA256.Create(); byte[] hashData = hashAl.ComputeHash(Encoding.Default.GetBytes(input)); Console.WriteLine(hashAl.HashSize); return Convert.ToBase64String(hashData); } ``` في هذا الاسلوب قمت بإستخدام الفئة HashAlgorithm هذه الفئة تتيح لي إنشاء رموز يصعب تفكيكها من خلال استخدام الأسلوب ComputeHash. في حال كنت تريد عمل مطابقة لتلك الرموز مع الكلمة الرئيسية التي تم توليد تلك الرموز منها بإمكانك عمل اسلوب جديد كالتالي: ```csharp static bool VerifyHash(string input, string hashValue) { HashAlgorithm sha = SHA256.Create(); byte[] hashData = sha.ComputeHash(Encoding.Default.GetBytes(input)); return Convert.ToBase64String(hashData) == hashValue; } ``` > **أقوم بإستخدام هذا الإسلوب في تطبيقاتي بالنسبة لإنشاء حسابات للستخدمين فبعض تطبيقاتي أقوم بإستخدام الـ Hashing فيها لتخزين كلمات المرور في قاعدة بيانات الـ SQL Server بصورة رموز يصعب قراءتها و ليس بالضرورة استخدام نفس هذا الاسلوب الذي قمت بكتابته هنا لكن بإمكانك عمل اسلوب Method خاص بعملك.** > **لتجربته بإمكانك إستخدام Console App وتعرف متغيرات نصية والتأكد من عمل الإسلوبين بصورة جيدة.** أيضا في هذا الكتاب C# 7 in a nutshell ستجد العديد من المواضيع المرتبطة بالامتحان منها علي سبيل المثال التعامل مع الـ Garbage Collection وهو الذي يختص بالتعامل مع الذاكرة، ومن ميزات لغة الـ C# أنها تقوم بإدارة الذاكرة تلقائيا بدون التدخل من المبرمج، لكن بإمكان المبرمج التدخل وعمل تنظيف للذاكرة أينما رأى ذلك مفيدا في سرعة تطبيقه الذي عمل عليه. لو افترضنا لدينا فئة كالتالي: ```csharp class Customer { string Name { get; set; } int Age { get; set; } } ``` بإمكاننا إنشاء مثيل لتلك الفئة كالتالي: ```csharp Customer cus = new Customer(); cus.Name = "Mohammed"; cus.Age = 30; ``` بمجرد استخدام الكلمة المحجوزة **new** سيقوم المترجم بإنشاء المتغير cus في الـ Heap الخاص بالذاكرة بمعنى آخر بمجرد اردت التعامل مع الفئة Customer لن أقوم بإنشاء مثيل جديد في نفس الـ Thread بل سيقوم المترجم بإستدعاءه من الذاكرة، طبعا هذه العملية طويلة جدا وبإمكانها إضعاف اداء التطبيق في حال لم تكن هناك عملية GC حيث تتيح هذه الفئة مجموعة من الأساليب تساعد على عملية التنظيف وفقا لآلية معينة. **بإمكانك إستخدام الفئة** **WeakReference** **لمنع عملية الـ** **GC** **. وهذه الفئة من ضمن مواضيع** **الإمتحان – كان هناك سؤال عن هذه الفئة واجهني بالإمتحان,** بالتالي ستجد تلك الكتب مفيدة جدا.. وشخصيا كنت معتمد على كتاب Pro C# 6 and the .Net Framework 4.6 بصورة كبيرة جدا حيث كان كافي بالنسبة لي لتغطية جميع مواضيع الإمتحان ماعدا الجزء الثالث للإمتحان حيث كنت معتمد على كتاب C# 7 in a nutshell. **مواضيع الإمتحان بصورة سريعة** بإمكانك الدخول للرابط التالي وتنزيل وثيقة مواضيع الإمتحان... [https://www.microsoft.com/en-us/learning/exam-70-483.aspx](https://www.microsoft.com/en-us/learning/exam-70-483.aspx?ref=informatic-ar.com) **أولا**: الـ MultiThreading وهو يعنى بإنشاء برمجيات تعمل بصورة غير متزامنة وبإمكانك استخدام كلمتي Async و await لتنفيذ البرمجيات بصورة غير متزامنة. في بدايات الدوت نت كان معظم المبرمجين يستخدمون Delegate لأنه بها مجموعة من الفئات والواجهات تساعدة على تنفيذ البرمجيات بصورة غير متزامنة. **ثانيا**: طريقة سندلة الأخطاء والتعامل معها Exceptions ويتوجب عليك معرفة التعامل مع الاخطاء بالنسبة لفئات الشبكات وقواعد البيانات. **ثالثا**: يتوجب عليك بصورة كبيرة معرفة التعامل مع الـ Validation من خلال مكتبة الـ Json (مجموعة كبيرة من أسئلة الامتحان كانت في هذا الموضوع بالإضافة للـ Serialization) أيضا يتوجب عليك قراءة مواضيع الـ Security والتعامل مع الـ Encrypt **رابعا**: من المواضيع المهمة أيضا معرفة التعامل مع فئات الدوت نت من خلال الـ DLL وطريقة تثبيت المكتبات الخاصة بك كمبرمج داخل الـ GAC **خامسا**: يتوجب عليك التركيز على تنفيذ جمل الإستعلام بالنسبة لقواعد البيانات عن طريقة الـ LinQ ويتوجب عليك معرفة الواجهتين الـ IEnumerable والـ IQuerable سواء من النوعين الـ non-Generic او من النوع Generic نسبة لأن التنفيذ على قاعدة البيانات دائما مايكون بعد إستدعاء أساليب الـ Extension Methods فرضا ToList( ) لو فرضنا مثالا بسيطا كالتالي: ```csharp String[] data = {“Mohammed”, “Ahmed”, “Hussien”, “Babiker” }; ``` لو فرضنا لدي تلك المجموعة من البيانات من النوع النصي في صيغة مصفوفة، وأردت البحث عن كل فهرس من تلك المصوفة يحتوي على الحرف m لكن قبل عملية الـبحث. يتوجب علي إستدعاء جميع البيانات وبعد عملية البحث، فإذا قمت بإستدعاء ToList( ) قبل عملية البحث فلن أستطيع عمل بحث للفهارس المحتوية على حرف m. **سادسا**: أيضا يتوجب عليك معرفة التعامل وكتابة جمل الإستعلام بالنسبة لملفات الـ XML ، أيضا الإمتحان يحتوي على مجموعة كبيرة من الأسئلة في هذا السياق ويرتبط معهُ بصورة مباشرة إستخدام الـ XmlSerialization التي تتيح لنا تخزين البيانات بصورة مؤقتة على ملفات XML. **سابعا**: التعامل مع الأخطاء وتسجيلها في الـ Events Log الخاص بنظام التشغيل Windows من خلال الفئة EventLog فبإمكانك كتابة الشفرة التالية: ```csharp String yourLine = Console.ReadLind(); EventLog el = new EventLog(); el.Source = “My Data”; el.WriteEntry(yourLine); ``` في هذه الشفرة قمت بإعطاء اسم الـ EventLog – My Data وللتأكد من عدم وجودها بذلك الإسم يتوجب عليك إستخدام الـ Expression سواء من خلال if او Switch (بإمكانك النظر للصورة التي تظهر بعد الشفرة التالية التي توضح الإحدى عشر مصدر التي بإمكانك تخزين بياناتك عليها) ستكون الشفرة كالتالي: ```csharp if (!EventLog.SourceExists("My Data")) { EventLog.CreateEventSource("My Data", "Application"); } ``` ![](https://informatic-ar.com/wp-content/uploads/2019/09/sssssssss.jpg) هنا قمنا بإستخدام الاسلوب CreateEventSource ولديه معاملان Two Arguments **الأول** وهو الاسم الذي تود تعريف الـ Event عليه والمعامل **الثاني** هو المصدر الذي تود التخزين عليه (راجع الصورة بالأعلى لمعرفة جميع المصادر). من المهم جدا قراءة الجزء الخاص بالـ Implement diagnostic in an application نسبة لأهميته وأيضا توجد أسئلة متعددة تأتي في الامتحان. **سابعا**: يتوجب عليك إنشاء تطبيق متكامل يتعامل مع قواعد البيانات تقوم من خلاله بإستخدام مجموعة من الطرق والتجريب عليه بإستخدام الـ Reflection والتي تعينك على إنشاء أو استدعاء الفئات من غير استخدام كلمة new او من غير إستدعاء الـ DLL وحفظها في الـ manifest الخاص بالتطبيق. > لدي تطبيق حجوزات ضخم جدا سأقوم بعرض المكتبة الخاصة بالـ Entity Framework من خلاله بإستخدام ILdasm.exe لرؤية جميع بيانات الـ manifest ومن خلال شاشة الـ manifest بإمكانك الضغط على Ctrl+m لعرض جميع الـ Classes والـ Interfaces والـ Events ومجموعة كبيرة من البيانات داخل مكتبة الـ Entity Framework > **بضغطك على Ctrl+m داخل شاشة الـ manifest لمكتبة الـ Entity Framework سيأخد الـ ILdasm.exe فترة طويلة لعرض البيانات نسبة لكبر حجم مكتبة الـ Entity Framework** في الصورة التالية يظهر جميع تفاصيل الـ manifest من على أداة الـ ILdasm.exe ![](https://informatic-ar.com/wp-content/uploads/2019/09/dasm-1024x477.jpg) **سؤال غريب**: أيضا في الامتحان كان هنالك سؤال يطلب مني توضيح صحة اسلوب من عدمه Method من خلال الـ Unit Testing لو إفترضنا لدينا اسلوب لجمع عددين كالتالي: ```csharp puplic int Add(int a, int b) { return a + b; } ``` بالتأكيد هذا الإسلوب للمقاربة والتوضيح فقط للسؤال الذي واجهني في الامتحان لكن ليس هو السؤال نفسهُ.. فالسؤال كان يقوم بعرض Unit Test كالتالي: ```csharp [TestMethod] Puplic void IS_AddMethod_ReturnInteger() { // Implementations goes here…. } ``` فكان السؤال غريب نوعا ما نسبة لأنه من ضمن مواضيع الامتحان **لا** يوجد الـ Unit Test، بالتالي خبرة المطور او المبرمج العملية تأتي هنا. **أخيرا**: الامتحان ليس بالشئ الصعب فقط يجب عليك تحديد الأدوات التي تساعدك على تخطيه. هناك بعض المطورين الذين جلسوا للامتحان ونجحوا فيه أكدوا لي إكتفائهم بالكتاب الرسمي (Programming in C# 70-483) للإمتحان والإعتماد عليه مع الدخول للـ Documentation الخاصة بمايكروسوفت. تحياتي. ### مفهوم الـ Data Concurrency من خلال الـ Entity Framework URL: https://informatic-ar.com/entity-framework-concurrency/ Last updated: 2026-10-03T22:31:24.000Z في هذه المقالة سأتطرق لمفهوم الـ Data Concurrency وقد يكون فيه من الصعوبة في بداية التعامل معه بالنسبة لبعض المطورين، خصوصا أنه قد يتم تطبيقه في بعض التطبيقات خصوصا التطبيقات الحسابية أو تطبيبقات البنوك أو تطبيقات التسوق الإلكتروني. الـ Data Concurrency تنقسم لقسمين وهما: - Pessimistic - Optimistic نفترض لدينا برنامج مبيعات POS وبه جدول يُسمى Product في هذا الجدول يوجد ثلاثة حقول الاول رقم المنتج والثاني اسم المنتج والثالث سعر المنتج. وبرنامج المبيعات هذا يتم استخدامه من قبل عدة مستخدمين .. نفترض انه يتم استخدامه من مستخدمين اثنين؛ المستخدم الأول هو البائع والمستخدم الثاني هو مدير النظام... نفترض ان البائع قام بجلب المنتج والذي رقمه 1 لبيعه.. وفي نفس اللحظة قام مدير النظام بجلب نفس رقم المنتج لتعديله او لحذفه. فكر قليلا ما هي المشكلة هنا؟!! دعنا نأخذ مثالا آخر.. نفترض انك تقرأ في هذه المقالة الآن وتريد إضافة تعليق، لكن قبل أن تقوم بعملة حفظ التعليق تمّ حذف المقالة من قاعدة البيانات من قبل مدير المنصة! الآن أتوقع ان الصورة قد اكتملت في ذهنك، هذا هو مفهوم الـ Data Concurrency. دعنا الآن نبدأ في شرح ماهي الطريقة المناسبة لوضع بعض الإجراءات التي تساعدنا على تلافي تلك المشاكل. في هذه الحالة من تلك البرمجيات الكبيرة قد تختلف الأمور من برمجيات لأخرى.. الحلول هنا تعتمد على نوعية النظام نفسه وعلى الشئ الذي يريده صاحب النظام.. في حالة برنامج المبيعات الأول الذي أخذناه كمثال قد يقول لك صاحب المتجر ليس لدي مشكلة من ان يتم حذف المنتج او تعديله أثناء قيام البائع بجلبه في شاشة المبيعات. حسنا هنا أنت كمطور ستقوم بعمل الإجراءات المناسبة للتطبيق على تلك الحالة. > **عندما أخذت مثال برنامج المبيعات وقلت ان صاحب المتجر يريد التعديل والحذف أثناء جلب المنتج في شاشة المبيعات، فقط أردت توضيح الحالة.. لكن في برمجيات المبيعات والبنوك تلك الآلية خاطئة ولا يتم استخدامها.. فالأصح ان لا يتم التعديل او الحذف اثناء جلب المنتج في شاشة المبيعات..حتى الإنتهاء من عملية بيعه.** > **هذه المقالة قد تكون مختلفة نوعا من مفهوم كيفية التعامل مع البرمجيات الضخمة وفي تقديري إذا كنت مطور مبتدئ قد ترى تلك المقالة ليست في إطار اهتمامك.. أيضا بعض المطورين لا يهتمون لموضوع الـ** Data Concurrency **لإعتبارت كثيرة منها عدم تلقيهم أي ملاحظات من العملاء الذين يعملون على أنظمتهم أو ان معظم برمجياتهم لايوجد بها هذا الشئ من الـ** Data Concurrency **لكن ما أن تدلف على برمجيات ضخمة عدد المستخدمين فيها ضخم ستواجه تلك الإشكالية ويجب أن تضعها في حسبانك وأنت تقوم بعمل برمجياتك.** > **او فرضنا أنك ذهبت للسوبر ماركت وأخذت قطعة شوكولاته وسعرها 10 جنيهات وذهبت لطاولة البائع (الكاشير) وقام البائع بقراءة باركود تلك الشوكولاته وظهرت على الشاشة سعرها 10 جنيهات... وقبل ان يقوم البائع بحفظ وطباعة الفاتورة لك قام مدير النظام في تلك اللحظة بتعديل سعر تلك الشوكولاته من 10 لـ 20 جنيها. وبعده قام البائع بحفظ وطباعة الفاتورة لك... عندها سيصبح إجمالي الفاتورة 20 جنيها وحينها ستبدأ أنت الزبون في الصياح في البائع.. قد تشتمه حتى وقد تقول له: لماذا في الباركود مسجلة 10 وعند بيعها 20؟ حسنا هنا يأتي دور الـ** Data Concurrency. سأقوم ببناء تطبيق بسيط جدا يساعدنا في وضع الـ Data Concurrency في سياقها وبعد بناء التطبيق سأتناول الـ Data Concurrency في بعض النقاط وسأوضح أيضا بعض الحالات البديلة التي يمكن استخدامها. التطبيق يتكون من جدول وحيد في قاعدة بيانات SQL Sever واسمه Products وهذه هي الحقول الخاصة بالجدول كما موضحة بالصورة أدناه. ![](https://informatic-ar.com/wp-content/uploads/2019/03/1_db.jpg) جدول الـ Product > انتبه لحقل الـ ID قم بإعطاءه القيمة Identity وقم بجعلة مفتاح رئيسي PK أيضا قمت بإنشاء Console App وبه فئة Class اسمها Product وبها الخصائص التالية: ```csharp [Table("Products")] public class Product { [Key] public int ID { get; set; } [StringLength(200)] public string ProductName { get; set; } [StringLength(200)] public String ProductCategory { get; set; } public Decimal SalesPrice { get; set; } [Timestamp] public byte[] Timestamp { get; set; } } ``` لاحظ لأخر خاصية وهي Timestamp هذه الخاصية مهمتها عمل نسخة من الحقل تساعد في حال قام المستخدم بجلب ذلك الحقل أو حذفه، وفي نفس اللحظة تقوم بعمل موازنة عندما يريد مستخدم آخر تعديل هذا الحقل. الخاصية تلك تم بلورتها بإضافة صفة Attribute واسمها Timestamp و بعض المطورين يقومون بتسمية ذلك الحقل بـ RowVersion .. وكما لاحظت اخي المطور هذا الحقل يأخذ النوع Timestamp في الـ SQL Server ويأخذ النوع Byte في الـ #C وهذا الحقل يجب ان يأخذ قيمة دائما حيث لا يتوجب ان يكون فارغ وقيمته دائما تكون Binary وهي مجموعة ارقام يقوم الـ SQL Server بتوليده فبالتالي قمنا بتعريفها كمصفوفة في فئة الـ Product. > **لقد قمت بإستخدام** **Console App** **خصيصا لأنه يساعدني في التركيز في الشرح على موضوع المقالة فقط.. وبإمكانك أخذ ما سأكتبه هنا و إدراجه في برمجياتك سواء كانت** **ASP.NET MVC** **او** **WPF** **أو** **ASP.NET Web Form** قم بتثبيت مكتبة الـ Entity Framework داخل مشروعك كما موضح بالصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2019/03/2_db.jpg) مكتبة الـ Entity Framework من خلال الـ Nuget أيضا قمت بإنشاؤ فئة Class آخر بإسم ConcurrencyTestDvContext وهو الذي سيربط بين التطبيق وقاعد البيانات وهو كالتالي: ```csharp public class concurrenyTestDbContext : DbContext { public concurrenyTestDbContext() : base("name = concurrenyTestDbContext") { } public DbSet Products { get; set; } } ``` أيضا قمت بتعريف ملف الإتصال من خلال ملف الـ App.config كالتالي: ```markup ``` قم بلصق الشفرة التالية في الاسلوب Main داخل الفئة Program: لا تقم بمسح الـ **Comment** الموجود في جملة الـ **Catch** لأننا سنقوم بشرحه فيما بعد. ```csharp static void Main(string[] args) { using (var context = new concurrenyTestDbContext()) { var update = context.Products.FirstOrDefault(c => c.ProductName == "Milk"); update.ProductCategory = "Ahmed"; try { context.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { // ex.Entries.Single().Reload(); context.SaveChanges(); } } Console.ReadLine(); } ``` قبل ان تقوم بتنفيذ هذه الشفرة قم بإدخال حقل في قاعدة البيانات وقم بإسناد الحقل كالتالي: **ProductName = Milk** **Price = 12** **ProductCategory = Test** **ليس بالضرورة كتابة نفس قيم الحقول كما كتبتها هنا بإمكانك كتابة القيم التي نود كتابتها.. فقط كتبتها هنا لكي أستطيع التوضيح لك عند تنفيذ البرنامج.** الآن نريد تنفيذ عملية تحديث لقاعدة البيانات حيث نريد تغيير قيمة الحقل ProductCategory لـ Drinks في الحقل ProductName والذي يحمل القيمة Milk وهو ما كتبناه كالتالي: ```csharp var update = context.Products.FirstOrDefault(c => c.ProductName == "Milk"); update.ProductCategory = "Ahmed"; ``` قم بوضع **BreakPoint** في جملة الـ **()Context.SaveChanges** الـموجودة في جملة **try** وبعدها قم بالضغط على زر **F5** لتنفيذ البرنامج. ما ان يتوقف البرنامج قم بفتح الـ **SQL Server** وقم بتعديل قيمة الحقل **ProductCategory** لأي قيمة أخرى.. بعدها قم بالرجوع للفيجوال ستديو وقم بالضغط على زر **F10** حتى نستطيع الإستمرار في تنفيذ البرنامج. لاحظ الآن البرنامج سيقوم بعمل **Throw** وهذه الرسالة توضح لك ان هناك تعديل تم على قاعدة البيانات.. والبرنامج لن يستطيع التعديل. ![](https://informatic-ar.com/wp-content/uploads/2019/03/3_db.jpg) وهنا تفاصيل أكثر للخطأ ![](https://informatic-ar.com/wp-content/uploads/2019/03/4_db.jpg) **دعنا نشرح العملية بصورة أدق** في شفرة البرنامج قمنا بإستدعاء الحقل **ProductName** والذي قيمته *Milk* وذلك حتى نقوم بتغيير تلك القيمة لـ Drinks ، هنا مجرد استدعاءنا لهذا الحقل سيقوم الـ SQL Server بإستدعاء حقل الـ **Timestamp** أيضا والذي يحمل رقم معين. لقد قمنا بعمل **BreakPoint** في جملة الـ **()Context.SaveChanges** وقمنا بتغيير قيمة الحقل لقيمة أخري، مجرد تغييرنا لقيمة الحقل سيقوم الـ SQL Server بإنشاء قيمة أخرى لحقل الـ **Timestamp** وبرجوعنا للبرنامج لعملية استمرار التنفيذ لتعديل قيمة الحقل الذذي قمنا بإستدعاءه وهو الـ **ProductCategory** سيقوم البرنامج بعملية **Throw** لأن قيمة الـ **Timestamp** هنا مختلفة وبالتالي لن يستطيع التعديل. لو قمنا بفتح الـ **SQL Server Profiler** سنرى ان الـ **SQL Server** قام بتوليد الشفرة التالية: ```sql exec sp_executesql N'UPDATE [dbo].[Products] SET [ProductCategory] = @0 WHERE (([ID] = @1) AND ([Timestamp] = @2)) SELECT [Timestamp] FROM [dbo].[Products] WHERE @@ROWCOUNT > 0 AND [ID] = @1',N'@0 nvarchar(200),@1 int,@2 binary(8)',@0=N'Ahmed',@1=4,@2=0x00000000000007E8 ``` هنا لقد قمت بتغيير الحقل بالقيمة Ahmed .. إنظر للرقم الأخير **0x00000000000007E8** هذا االرقم قام بتوليده الـ **Timestamp** فبمجرد حصول اي عملية أخرى في نفس الحقل سيقوم الـ **SQL Server** بتغيير ولن يستطيع المستخدم الأول الذي بإستدعاء الحقل والذي به الـ Timestamp القديم لن يستطيع التعديل بإمكانك فتح الـ **SQL Server Profiler** من الـ **SQL Server Management Studio** من قائمة **Tools** كما مبين بالصورتين التاليتين. ![](https://informatic-ar.com/wp-content/uploads/2019/03/5_db.jpg) ![](https://informatic-ar.com/wp-content/uploads/2019/03/6_db.jpg) الآن قم بحذف الـ **Comment** الموجود بجملة الـ **Catch** وقم بتعديل القم لأخرى وقم بتنفيذ البرنامج مع وضع **BreakPoint** في جملة الـ ()Context.SaveChanges وقم بتغيير الحقل في قاعدة البيانات مرة أخرى. هنا لن يقوم البرنامج بعمل **Throw** لأنّ هذا السطر قام بعمل **Refresh** للـ **Context** وهنا تُسمى تلك العملية بـ Store Win وهي التي ستقوم بإرجاعنا لأنواع الـ Data Concurrency وتحديدا النوع **Optimistic**. لا تنسى ان تقوم بوضع الرسائل المناسبة التي سوف تظهر للمستخدم أثناء حصول تضارب في العمل على قاعدة البيانات Conflict وحتى تصبح برمجياتك أكثر تفاعل مع المستخدمين. في المقابل النوع الأول **Pessimistic** لا يُنصح بإستخدمه لأنه يقوم بعمل Lock للحقل الذي يستدعيه المستخدم وبالتالي إذا أراد مستخد آخر إستدعاء نفس الحقل لن يستيطع حتى يقوم المستخدم الأول الذي قام بإستدعاءه من إنهاء الإجراء الذي يريده. هناك بعض المطورين يقوم بإستخدام الحقل Datemodefied بدلا من التعامل مع الـ Data Concurrency لكن هذه العملية مكلفة جدا لأنها تتطلب منك إستخدام مجموعة من الـ Logic للتأكد من كل عملية تعديل تتم على قاعدة البيانات. تحياتي ### تثبيت نظام ويندوز للأجهزة عبر الشبكة Windows Deployment Services URL: https://informatic-ar.com/windows-deployment-services/ Last updated: 2026-10-03T22:31:24.000Z قبل إسبوع أو أكثر تم تكليفي بمهمة مشروع برمجي جديد لوزارة سيادية في دولة ما وقد تم تسليمي جهاز الخادم Server والأجهزة المساعدة الأخرى.. الخادم يعمل على نظام التشغيل ويندوز سيرفر 2012 Windows Server 2012 R2 ... في فترة من الفترات بعد تسليمي جهاز الخادم طلب مني كمساعدة من قسم آخر (خارج الإطار الرسمي) تثبيت نظام التشغيل ويندوز 10 على أكثر من عشرة أجهزة حواسيب (لديهم معمل متكامل) فطلب مني شخص إحضار عدد من إسطوانات الويندوز لبدء التثبيت (مبرمج سيقوم بتثبيت الويندوز!!!) ... تذكرت في حينها أنه قد تم تسليمي جهاز خادم يعمل على ويندوز سيرفر 2102 R2 ، فما المانع من إستخدام الـ Windows Deployment Services لتثبيت الويندوز على جميع أجهزة المعمل من خلال الشبكة وبإستخدام إسطوانة إقلاع واحدة (بالطبع ليس على الخادم الذي استلمته).... قمت بثبيت الويندوز سيرفر 2012 على أحد أجهزة المعمل لديهم وقمت بعمل مشاركة للويندوز 10 (هذا ما سنطبقه في هذه المقالة) وقمت بتوجيه الشباب الموجودين بالمعمل على تثبيت الويندوز 10 في أقل من 30 دقيقة على 10 أجهزة حواسيب. كيف؟ هذا ما سنراه هنا.. > قمت بتحرير هذه المقالة على عجالة لكي أشارككم بعض الجوانب العملية التي تواجهنا في العمل وأنا أعمل على مشروعي البرمجي الضخم من خلال الـ ASP.NET MVC حيث لدى بعض من الوقت لإستجماع بعض قواي التفكيرية. > > بعض الصور مأخوذة من السيرفر الخاص بي - والبعض الآخر من الشبكة العنكبوتية الإنترنت ما هو و كيفية إستخدام الـ Windows Deployment Services؟ عبارة عن خدمة تقنية من مايكروسوفت لتثبيت أنظمة التشغيل من خلال الشكبة وتستخدم بصورة تحكميّة من على البعد لأنظمة تشغيل عديدة كـ Vista , Win7,8,10...الـ WDS تستخدم الـ Windows Image format وهي مدمجة في ملفات أنظمة التشغيل حيث تعمل من خلال ملفين وهما: **Boot.wim وهو الملف الذي يتيح لنا الإقلاع من الإسطوانة. بإمكانك المعرفة أكثر على هذا الملف من موقع ويكيبيديا من هنا [https://en.wikipedia.org/wiki/Windows\_Imaging\_Format](https://en.wikipedia.org/wiki/Windows%5FImaging%5FFormat?ref=informatic-ar.com)** **Install.wim وهو الملف الذي توجد به نسخ الويندوز على اسطوانة الإقلاع.** أيضا تعمل مع أنوية الـ 32bit والـ64bit ومع مجموعة من توزيعات أنظمة التشغيل. والفوائد كثيرة من استخدام الـ WDS منها اتاحة التثبيت لأنظمة التشغيل من خلال الشبكة مما يقلل من التعقيد والتكلفة عند المقارنة بالطريقة التقليدية. - يدعم نشر الصورة للأنظمة المختلفة بما في ذلك الـ Win7 - وأيضا يقوم بنقل البيانات والصور بإستخدام وظيفة الإرسال المتعدد - كما يسمح بإنشاء Images لجهاز السيرفر عن طريق استخدام معالج إلتقاط الصور ويعتبر بديل لأداة الـ ImageX. - **أولا** نقوم بتثبيت الـ Active Domain Services من خلال الـ Server Manager - **ثانيا** نقوم بتثبيت الـ DHCP - **ثالثا** نقوم بتثبيت الـ WDS كما موضح بالصورة ادناه من خلال خاصية الـ Roles الموجودة بالويندوز سيرفر 2012 الآن سنرى بالصور التوضيحية كيف نقوم بذلك. أو سأقموم بفتح الـ Server Manger لإضافة مجموعة من الخدمات التي يعتمد عليها الـ WDS هنا سأقوم بإضافة Role جديدة لتثبيت الـ Active Domain Services ومن ثم نقوم بتثبيت خاصية الـ DHCP لأن خدمة الـ WDS تعتمد عليها عندما نبدأ في تثبيت نظام التشغيل لدينا سنقوم بإختيار الإقلاع من خلال الشبكة بدلا من الإسطوانة والخدمة تعتمد على توزيع عناوين الشبكة IP بصورة تلقائية DHCP. > حسنا نفترض أنك قمت بتثبيت الـ ADS والـ DHCP الآن سأشرح طريقة تثبيت الـ WDS ومشاركة الويندوز عليها ليصبح جاهزا للإستخدام > > ![](https://informatic-ar.com/wp-content/uploads/2018/11/1-300x225.jpg) ![](https://informatic-ar.com/wp-content/uploads/2018/11/2-300x211.jpg) الآن سأختار اسم السيرفر (اسم الجهاز تحديدا) ويجب عليك إعطاء الجهاز عنوان شبكة IP ![](https://informatic-ar.com/wp-content/uploads/2018/11/3-300x212.jpg) وكما ترون في الصورة التالية قمت بتحديد الـ Windows Deployment Services سأقوم بالضغط على Next ![](https://informatic-ar.com/wp-content/uploads/2018/11/5-300x212.jpg) قم بالضغط على Next وستظهر معك الشاشة التالية ![](https://informatic-ar.com/wp-content/uploads/2018/11/6-300x212.jpg) قم بالضغط على Next وسيبدأ التثبيت للخدمة كما مبين في الصورتين أدناه ![](https://informatic-ar.com/wp-content/uploads/2018/11/7-300x213.jpg) الآن الخدمة جاهزة للبدء فقط قم بعمل إعادة تشغيل للسيرفر Restart . وبعدها سنقوم بتهيئتها لمشاركة الويندوز. قم بالدخول على الـ Server Manager ومن أقصى يمين الشاشة قم بالضغط على Tools ومن ثم قم بإختيار Windows Deployment Services كما موضح بالصورة ادناه ![](https://informatic-ar.com/wp-content/uploads/2018/11/8-293x300.jpg) قم بالضغط بالزر الأيمن للمؤشر (ماوس) على اسم النطاق – الدومين لديك وقم بإختيار أول خيار Configure Server ![](https://informatic-ar.com/wp-content/uploads/2018/11/9-300x215.jpg) ستظهر معك الشاشة التالية قم بالضغط على Next ![](https://informatic-ar.com/wp-content/uploads/2018/11/10-300x235.jpg) وقم بتحديد أول خيار كما مبين في الشاة التالية وإضغط Next ![](https://informatic-ar.com/wp-content/uploads/2018/11/11-300x237.jpg) بعدها حدّد أي مسار على جهازك تريد ان تحفظ بداخله ملفات التهيئة التي سنختارها من الإسطوانة الخاصة بالويندوز وبعد التحديد إضغط على Next ![](https://informatic-ar.com/wp-content/uploads/2018/11/12-300x236.jpg) بعدها قم بإختيار الخيار الأخير ... وهو يتيح منح عنوان شبكة IP لجميع الأجهزة المتصلة بالسيرفر عن طريق DHCP. ![](https://informatic-ar.com/wp-content/uploads/2018/11/13-300x236.jpg) الآن سيبدأ في التهيئة وبعد الإنتهاء ستظهر معك الشاشة التالية والتي ستقوم فيها بإختيار الملفات الخاصة لثبيت الويندوز من خلال الشبكة من الإسطوانة الخاصة بالويندوز..كما مبين في الصورة أدناه ![](https://informatic-ar.com/wp-content/uploads/2018/11/15-300x236.jpg) بعد الضغط على Finish ستظهر معك شاشة والتي سنختار فيها ملف الـ install.wim الموجود في الملف source داخل اسطوانة الويندوز. (راجع المقدمة لمعرفة ماهو ملف install.wim) ضغط على Next ستظهر معك جميع نسخ الويندوز الموجود على الإسطوانة قم بإختيار النسخة التي تريد العمل عليها... وكما مبين في الصورة التالية إضغط Next وستظهر معك شاشة تأكيدية للنسخ التي اخترها إضغط Next أيضا وسيبدأ التثبيت الآن الخطوة الأخيرة سنقوم بإضافة الـ Boot Image قم بالضغط بالزر الأيمن للمؤشر (ماوس) على الـ Boot Image كما موضح أدناه ![](https://informatic-ar.com/wp-content/uploads/2018/11/16-300x275.jpg) إضغط Next وستظهر معك شاشة تأكيدية للنسخ التي اخترها إضغط Next أيضا وسيبدأ التثبيت الآن الخطوة الأخيرة سنقوم بإضافة الـ Boot Image قم بالضغط بالزر الأيمن للمؤشر (ماوس) على الـ Boot Image كما موضح أدناه ![](https://informatic-ar.com/wp-content/uploads/2018/11/17-300x217.jpg) ستظهر معك شاشة والتي نقوم منها بإختيار ملف الـ boot.wim من اسطوانة الويندوز والذي موجود داخل ملف Source أيضا.كما موضح أدناه (راجع المقدمة لمعرفة ماهو ملف boot.wim) ![](https://informatic-ar.com/wp-content/uploads/2018/11/18-300x260.jpg) ستظهر معنا شاشة أخرى وستقوم بجلب المعمارية الخاصة بالويندوز الذي قمنا بإختياره عندما قمنا بتثبيت الـ Install Image كما مبين أدناه ![](https://informatic-ar.com/wp-content/uploads/2018/11/19-300x260.jpg) قم بالضغط على التالي وستظهر معك الشاشة الأخرى .. أيضا قم بالضغط على Next وسيبدأ التثبيت.. الخطوة الأخيرة قم بالضغط بالزر الأيمن للنطاق – الدومين وتأكد من عمل Start للخدمة. وبإمكانك الآن الآن بدء الويندوز على الأجهزة الفرعية لكن لا تنسى ان تقوم بإختيار الإقلاع من الأجهزة الفرعية عن طريق الشبكة لاحظ في الصورة التالية المبينة أدناه بعد الإقلاع من الشبكة أن الويندوز سيرفر قام بإعطاء الجهاز الفرعي عنوان شبكة تلقائي وكما أيضا مبين اسم النطاق – الدومين ![](https://informatic-ar.com/wp-content/uploads/2018/11/20-300x168.jpg) وبعد الإقلاع ستظهر شاشة تحميل ملفات الويندوز كالتالي: ![](https://informatic-ar.com/wp-content/uploads/2018/11/21-300x228.jpg) تحياتي ### تشغيل البرامج باستخدام ال Docker Compose URL: https://informatic-ar.com/docker-compose/ Last updated: 2026-10-03T22:31:24.000Z تحدثنا سابقاً عن كيفية تشغيل البرامج وال Containers، وكيف يمكن ان تشغل أحد البرامج بدخل ال Containers التي تعمل، وكيف نقوم بتشغيل ال Web Servers، وايضاً كيف نتعامل مع قواعد البيانات Databases، في هذا الموضوع سوف نقوم بكل هذه الخطوات ونشغل أي خدمة بكامل ملحقاتها موقع او ويب سيرفس بالإضافة مع قاعدة البيانات وذلك عن طريق ال Docker Compose. بالإضافة الى التعامل مع الشبكات واضافة أي container لأي شبكة تريدها. **المواضيع في السلسلة:** 1. [مقدمة حول دوكر Docker](https://informatic-ar.com/docker-introduction/) 2. [البداية مع دوكر Docker](https://informatic-ar.com/getting-started-docker/) 3. [مشاركة المجلدات في دوكر Docker](https://informatic-ar.com/docker-volumes/) 4. [بناء ال Docker Images](https://informatic-ar.com/building-docker-images/) 5. [تشغيل قواعد البيانات في دوكر Database in Docker](https://informatic-ar.com/docker-databases/) 6. تشغيل البرامج باستخدام ال Docker Compose (الموضوع الحالي) ## أهمية ال Docker Compose عندما تبرمج المواقع العادية او الصغيرة فهي تكون مكونة من قسمين، الموقع نفسه Web Application بالإضافة الى قاعدة البيانات Database، وإذا أردنا تشغيلهم في دوكر فيجب ان نضع كل جزء في Container خاص به، بمعنى Container للموقع، والأخر لقاعدة البيانات، بالطبع يمكن وضعهم بداخل Container واحد ولكن الأفضل تقسميهم بكل الحالات. لكي نقوم بهذه الخطوات بالأوامر التي قمنا سابقاً، فيجب أولاً تشغيل قاعدة البيانات ونحدد Volume لها حتى يتم تخزين البيانات، ومن ثم معرفة ال IP الخاص بال Container لأنه نحتاجه لكي نضعه في الموقع ويستطيع التخاطب مع قاعدة البيانات، وبعد ذلك تشغيل الموقع وارسال ال IP الخاص بقاعدة البيانات حتى يستطيع التواصل معها، بالإضافة الى Volume Mapping لكي نشارك ملفات الموقع الذي نريد تشغيله (أو وضعها في ال image مباشرة) الصورة التالية توضح تشغيل قاعدة البيانات: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-1.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-1.png) تم تقسيم الأمر من خلال علامة ' في ال PowerShell لعدة اسطر للمقروئية فقط، ويمكن كتابة كامل الأمر في سطر واحد بدون الحاجة للعلامات ' : ```bash docker run --name database -p 3333:3306 -v db:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=P@ssw0rd -d mysql:5.7 ``` بعد تشغيل قاعدة البيانات قمنا بتشغيل الأمر التالي docker inspect database حتى نحصل على ال IP الخاص بذلك ال Container، ومن ثم تمريره للموقع الذي يستقبل البورت وعنوان قاعدة البيانات حتى يستطيع التواصل معها، ويجب أن تبرمج موقعك بحيث يقرأ القيم المرسلة في ال environment variable ويتصل على أساسها، وإذا لم تكن موجودة فيمكن وضع قيم افتراضية حينها. [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-2.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-2.png) بعد ذلك سنقوم بتشغيل الموقع، وسوف نقوم بتشغيل موقع WordPress يتصل بهذه القاعدة، وسوف نحتاج ان نمرر عنوان قاعدة البيانات حتى يستطيع الاتصال بها، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-3.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-3.png) الان بهذه الخطوات الثلاثة اشتغل الموقع كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-4-1024x309.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-4.png) ويمكن ان نضع هذه الأوامر في ملف سكربت مثلاً Bach Script وتشغيله مرة واحدة بدلاً من إعادة هذه الخطوات عند تشغيل الموقع على أي سيرفر، ولكن لا نحتاج لذلك لأن دوكر يوفر Docker-Compose لهذه المهمة وهو تشغيل العديد من ال Containers بدلاً من إعادة كتابتها كل مرة، وال Docker-Compose يتعامل مع ملفات بها الأوامر التي سوف يقوم بتشغيلها بصيغة YML. سوف نقوم الان بحذف الموقع وقاعدة البيانات، وايضاً ستجد في الحذف والايقاف أنك بحاجة لإصدار الأمر لكل منهم على حدة كما في التشغيل: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-5-1024x127.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-5.png) الصورة التالية تبين إيقاف وحذف البرامج اولاً، ومن ثم حذف قاعدة البيانات: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-6.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-6.png) ## استخدام ال Docker Compose بدلاً من القيام بهذه الخطوات الثلاثة لتشغيل الموقع ومعرفة عنوانه الداخلي ومن ثم تشغيل قاعدة البيانات بذلك العنوان، فيمكن أن نستخدم ال Compose File ونضعها في ال yml format وهي نفس البيانات لكن بصيغة Yml، ومن ثم نحفظ الملف docker-compose.yml ونقوم بتشغيل Docker-Compose ونمرر هذا الملف حتى يقوم بتشغيل كل ما فيه. [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-7.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-7.png) ويتم تسمية الملف بالاسم docker-compose.yml ، وبعد ذلك يمكن تشغيل الملف بالذهاب لموقع الملف ومن سطر الأوامر يتم تنفيذ الأمر docker-compose up وسيتم التشغيل لهذين ال Containers. [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-8.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-8.png) بهذه الطريقة اختصرنا بدل تشغيل ال container الأولى ومن ثم اخذ ال IP لها، وتشغيل الثانية وارسال ال IP للأولى، فنقوم بوضع التعليمات داخل ال docker-compose ولاحظ ان ال container الثانية اخذت اسم ال container الأولى وسيتم عمل resolve لها داخلياً لكي يحصل على ال IP. حيث يتم انشاء شبكة خاصة لهذه البرامج، ويقوم ال DNS Server المدمج بمعرفة ال IP من خلال الاسم، وبالتالي فغالباً عندما تعمل على دوكر وتريد تشغيل أي مشروع مكون من عدة أجزاء فسوف تتعامل مباشرة مع ال docker-compose file ويتم تشغيل هذه البرامج مباشرة. الشبكة الخاصة التي تم انشائها هي بالاسم data\_default وهي تكون باسم المجلد الذي تعمل به، وبعدها يأتي \_default. وهذا الاسم سوف يسبق كل من ال volumes وال containers لكي تدل على انها بهذا المشروع. وميزة الشبكة الخاصة ان كل ال containers بها تستطيع التواصل مع الأخرى من خلال الاسم، ولا تستطيع أي containers أخرى من شبكة أخرى التواصل مع هذه ال containers، الا فقط من تكون بداخل الشبكة. الأن الموقع أصبح يعمل الان، وسوف نقوم بالتعامل مع هذه الشبكة، وسوف نقوم بطباعة كافة الشبكات المتواجدة: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-9.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-9.png) سوف نقوم بفحص هذه الشبكة من خلال الأمر inspect كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-10.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-10.png) وسوف تجد قائمة البرامج بالإضافة الى عنوان كل منهم. وتستطيع في دوكر ان تقوم بانشاء شبكة، إضافة container لهذه الشبكة، أو إزالة container من هذه الشبكة، او مسح الشبكة ككل. من المهم ملاحظة عندما قمنا بتشغيل البرامج يدوياً فهي انضمت تلقائياً للشبكة bridge، بينما في حالة ال docker compose فهو يقوم بانشاء شبكة خاصة لكل البرامج المتواجدة فيه، وهذا ايضاً من الفروقات المهمة بين التشغيل اليدوي او استخدام ال compose. سوف نقوم الان بالدخول الى أحد ال containers ولاحظ في الصورة التالية استخدمنا الطريقة التقليدية اولاً، وبعدها قمنا بالخروج والدخول بطريقة أخرى باستخدام docker-compose ولن نعد نحتاج الى استخدام ال –it فهي تلقائية في هذه الحالة: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-11.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-11.png) الان نريد ان نتأكد هناك اتصال بين ال containers وانه يمكن ان تقوم أي منهم بعمل ping للأخرى، وسوف نقوم اولاً بتحميل برنامج ال ping لأنه لا يتواجد في نسخ دوكر: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-12.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-12.png) بعد ذلك سوف نقوم بعمل ال ping ونتصل بال database container: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-13.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-13.png) ونفس الأمر لو قمت بذلك من ال container الأخرى سوف تستطيع التخاطب مع الأولى. سوف نقوم الأن بتشغيل container جديدة منفصلة ومن ثم نربطها مع هذه الشبكة التي تمت انشاءها مع المشروع، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-14.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-14.png) وهكذا كل من ال containers أصبحت بداخل الشبكة (بالرغم من أن الأخيرة لم تكن ضمن بال compose file). سوف نقوم الان بعرض كافة ال containers باستخدام docker ps ومن ثم عرض ال containers التي تعمل في هذه ال compose كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-15.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-15.png) ويمكن ان نقوم بتشغيل أي من ال container بداخل ال compose او ايقافها باستخدام docker-compose stop name أو docker-compose start name وتحدد ال container الذي تريده إيقافه او تشغيله. سنقوم الان بإيقاف كل ال containers وذلك من خلال docker-compose stop بدون تحديد أي من الأسماء، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-16.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-16.png) ويمكن تشغيلها مجدداً بالأمر start. سنقوم الان بحذف هذه ال containers مع ال volumes التي لم تسمى (لم يتم تحديد أي اسم لها) بها كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-17.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-17.png) ويمكن ان نقوم بعمل down وسوف يتم حذف الشبكة كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-18.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-18.png) الان يمكنك حذف ال volume يدوياً ان اردت، وإذا اردت أن تقوم باسترجاع كل شيء مع بياناتك، فسوف تقوم بعمل Up: [![](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-19.png)](https://informatic-ar.com/wp-content/uploads/2018/11/docker-compose-19.png) لاحظ لم نقم بتشغيله بالوضع –d ولذلك ظهرت المخرجات. إذا اردت ان تقوم بحذف ال named volumes فيمكنك استخدام docker-compose down –v وسوف يقوم بإيقاف كل شيء وحذف كل ال volumes المرتبطة بها. ## **خلاصة** ال Dockerfile يستخدم لتسهيل Automation لبناء ال Images بدلاً من تشغيل Container ومن ثم نسخ الملفات ومن ثم commit، وال Docker-Compose هو تسهيل Automation لإنشاء ال Containers بدلاً من ادخال الأوامر واحداً تلو الأخر. ### التعامل مع الإجراءات المخزنة من خلال Dapper في تطبيقات الدوت نت (2) URL: https://informatic-ar.com/dapper-stored-procedures/ Last updated: 2026-10-03T22:31:25.000Z في المقالة السابقة تحدثت عن مجموعة من الاساسيات التي توفرها Dapper وهي الـ CRUD وقد شرحت أن Dapper تعتمد على الواجهة IDbConnection وبالتالي في جميع حالات التنفيذ على قاعدة البيانات نقوم بإستخدام الاسلوب Execute أما في حالة الإستعلام نقوم بإستخدام الاسلوب Query مصحوبا بالنوع او الكائن المراد الإستعلام عنه Query. بإمكانك قراءة المقالة الأولى من الرابط التالي: [مقدمة عن Dapper من خلال بيئة الـ DotNet](https://informatic-ar.com/dapper-introduction/) ويتوجب عليك قراءتها (المقالة الأولى) إذا كنت تريد التطبيق في هذه المقالة الجديدة. في هذه المقالة الثانية سنتطرق لطريقة استخدام الإجراءات المخزنة Stored Procedures وسأقوم بإستخدام نفس التطبيق الذي استخدمته في المقالة الأولى. سأقوم بإستخدام قاعدة البيانات NORTHWIND التي قمت بإستخدامها سابقا. في قاعدة البيانات تلك توجد 7 إجراءت مخزنة..ولقد قمت بإضافة إجراء مخزن لقاعدة البيانات تلك يقوم بإرجاع جميع التصنيفات Categories وهذا الإجراء لايوجد به أي معامل بل يقوم بإرجاع حميع المعلومات الموجودة بالجدول، والإجراء الذي أنشأته كالتالي: ```sql CREATE PROCEDURE [dbo].[GetAllCategories] AS BEGIN SELECT CategoryName,Description FROM Categories END GO ``` وفي حال أردت تنفيذ الإجراء في صفحة إستعلام من داخل الفيجوال ستديو أو على الـ SQL Management Studio قم بكتابة السطر التالي: ```sql EXEC dbo.GetAllCategories ``` حسنا لتنفيذ ذلك الإجراء داخل مشروعنا (DemoDapper) لقد قمت بإنشاء فئة جديدة بإسم Categories وبها الخصائص التالية (نفس الحقول الموجودة بالجدول داخل قاعدة البيانات): ```csharp public class Categories { public Int32 CategoryID { get; set; } public string CategoryName { get; set; } public string Description { get; set; } public byte[] Image { get; set; } } ``` الآن سنقوم بكتابة الأسلوب الذي يتيح لنا إستدعاء الإجراء المخزن ..قم بفتح الفئة DapperRespository.cs وقم بلصق الشفرة التالية داخل تلك الفئة: ```csharp public IEnumerable FindAllCat() { var spGetAllCategories = "GetAllCategories"; using (IDbConnection db = new SqlConnection(con)) { var affectedRows = db.Query(spGetAllCategories, CommandType.StoredProcedure); return affectedRows; } } ``` بكل بساطة هذه هي طريقة التعامل مع الإجراءات المخزنة من خلال تقنية Dapper ... طالما سنقوم بعمل إستدعاء لمجموعة بيانات سنقوم بإستخدام الإسلوب Query وسنقوم بإسناء Categories كنوع البيانات T التي نريد إرجاعها وهذا الإسلوب يقبل العديد من المعاملات Parameters وفي حال قمت بالضغط على زر F12 أثناء تحديد المؤشر على الأسلوب نفسه سينقلك للصفحة التي بها جميع الطرق التي يمكن ان ينفذ بها هذا الاسلوب .. وقد قمنا بإستخدام تلك الطريقة: ```csharp public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null); ``` حيث في أول معامل قمنا بإسناد الاستعلام الذي نود إرجاعه وهو من النوع النصي String وفي المعامل الثاني قمنا بتحديد نوع الإستعلام والذي هو عبارة عن إجراء مخزن Stored Procedure. في حال أردت رؤية المجموعات على صفحة WebForm قم بعمل صفحة WebForm جديدة وقم بتسميتها SiteCategory.aspx وفي جزء الـ Source قم بلصق الشفرة التالية داخل الـ Form ```markup

All Category

|
``` في صفحة الـ cs لصفحة الـ SiteCateory قم بلصق الشفرة التالية: وتلك الشفرة تقوم بإسناد جميع المجموعات التي قمنا بالإستعلام عنها داخل الأسلوب FindallCat في الفئة DapperRepository.cs ؛ تقوم بإسناد جميع المجموعات في أداة الـ ListView ```csharp using DemoDapper.Models; private DapperRepository dr = new DapperRepository(); protected void Page_Load(object sender, EventArgs e) { var query = dr.FindAllCat(); listview.DataSource = query.ToList(); listview.DataBind(); } ``` والنتيجة ستكون كما في الصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/11/res-267x300.png) كما موضح عرض جميع المجموعات من الوصف لكل مجموعة على صفحة الويب حسنا الآن لقد عرفنا كيفية التعامل مع الإجراء المخزن الذي لا يحتوي على أي معامل Parameter ؛ لكن ماذا لو أردت أستدعاء إجراء مخزن يوجد به معامل Parameter دعنا نقوم بإنشاء إجراء مخزني جديد داخل قاعدة البيانات NORTHWIND .. قم بلص الشفرة التالية على صفحة إستعلام جديدة وقم بتنفيذها ```sql CREATE PROCEDURE [dbo].[GetCategoryByID] @Id int AS SELECT CategoryName, Description from Categories where CategoryID = @Id RETURN 0 ``` وللتأكد من عملها بصورة صحيحة بإمكانك الإستعلام عنها بإستخدام الشفرة التالية في صفحة استعلام جديدة ```sql EXEC GetCategoryByID 1; ``` وستكون النتيجة كالتالي كما موضحة بالصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/11/res2-300x132.png) نتيجة الاستعلام من خلال الاجراء المخزني GetCategoryByID حسنا الآن قم بفتح الفئة DapperRepository.cs وقم بكتابة الإسلوب التالي: ```csharp public Categories GetCategory(int id) { var spGetCategoryByID = "GetCategoryByID"; using (IDbConnection db = new SqlConnection(con)) { return db .Query(spGetCategoryByID, new { @Id = id }, CommandType.StoredProcedure) .SingleOrDefault(); } } ``` كل ما قمنا به هو إسناد المعامل الموجود بالإجراء المخزني بصورة مجهولة Anonymous بنفس الاسم الموجود بالإجراء المخزني. كمطور برمجيات في حال اردت التعامل بسهولة مع قواعد البيانات بإمكانك استخدام Dapper لعمل جميع الإستعلامات في تطبيقاتك فهي لا ترهق قاعدة البيانات بعكس الـ Entity Framewrok بالتالي كل ما يتعيّن عليك أن هو ان تكون قادرا على كتابة جمل الـ SQL بصورة صحيحة. سأقوم بتحديث هذه المقالة حتى نتعرف على مميزات جديدة ومتقدمة في طريقة تعامل Dapper مع قواعد البيانات. وبعد الإنتهاء من السلسلة سأشير لذلك في عنوان المقالة نفسها تحياتي. ### مقدمة عن Dapper من خلال بيئة الـ DotNet URL: https://informatic-ar.com/dapper-introduction/ Last updated: 2026-10-03T22:31:26.000Z Dapper عبارة عن Micro-ORM وتختص بعملية الوصول لقاعدة البيانات المرتبطة بالمشروع الذي نعمل عليه ،وهي خفيفة الحجم وسهلة التعامل وعالية في الأداء. معظم التطبيقات التي ننشئها تتعامل مع قواعد البيانات؛ بالتالي دائما ما نبحث عن طرق الإتصال بين التطبيق وبين قاعدة البيانات... من خلال الجمل الإستعلامية التي تتم كتابتها. ففي فترة من الفترات كنا نستخدم الـ ADO.NET للتعامل مع قواعد البيانات من خلال كتابة الجمل الإستعلامية (SQL) داخل الفيجوال ستديو، وبعدها ظهرت تقنية الـ Entity Framework - EF وهي متضَمنة داخل الفيجوال ستديو ووتيح لنا التعامل مع قواعد البيانات العلائقية سواء SQL Server أو MySQL أو Oracle أو Sybase وغيرها والـ EF تحتوي على ثلاثة أنماط - Code First - Model First - Database First وبالإمكان استخدام تقنية الـ Language Integrated Query - Linq لكتابة الجمل الإستعلامية وتعتبر من النوع الـ Strongly Type أي تحتوي على خواص التكملة عند الكتابة وإظهار الأخطاء عن تنفيذ التطبيق عكسا من الـ ADO.NET مسبقا. حسنا...الـ Dapper أيضا تعتبر من أنواع الـ Object-Relational Mapper - ORM وبإختصار عن عملية ربط بين جداول قواعد البيانات مع الفئات الموجودة بالتطبيق. لكن الـ ORM تعتبر مرهقة نوعا ما في التعامل مع قواعد البيانات نسبة للعدد الكبير من الإجراءات التي تقوم بعملها أثناء تحويل الشفرة لجملة SQL وتنفيذها ومن ثم إرجاعها للـ Caller بالتالي الـ Micro-ORM (و Dapper أحد أنواعها) تعتبر خفيفة وغير مرهقة لقاعدة البيانات نسبة لختفها لأنها في الأصل عبارة عن جمل SQL وتقوم بتحويل الصفوف المطلوبة من قاعدة البيانات لمجموعة من العناصر بالتالي الفرق الوحيد والأهم بين الـ ORM والـ Micro-ORM هو السرعة بالنسبة للأخيرة في جلب والتعامل مع البيانات. في المثال العملي الذي سنتطرق إليه في هذه المقالة والذي أنشئته داخل الفيجوال ستديو 2013 من خلال مشروع Web Form سأوضح طريقة التعامل مع قاعدة البيانات من خلال الجمل الإستعلامية الأربعة الرئيسية CRUD من خلال مكتبة الـ Dapper. أول ما قمت به تثبيت مكتبة الـ Dapper من خلال الـ Nuget Package بكتابة السطر التالي من خلال الـ Console: ```csharp PM > Install - Package dapper ``` في هذا المشروع سأتعامل مع قاعدة البيانات NOTHWIND والتي بإمكانك تنزيلها من الإنترنت بالبحث عنها من خلال أي محرك بحث من متصفحك. قمت بإنشاء مشروع Web Form من النوع Empty حيث قمت بتسمية المشروع DemoDapper ، وأول إجراء قمت به هو الربط بين التطبيق وقاعدة البيانات NOTHWIND من خلال ملف الـ Web.Confg كما موضح بالكود التالي: ```markup ``` > يجب عليك تغيير نوع الإتصال على جهاز إذا كان مختلفا. قاعدة البيانات NOTHWIND بها مجموعة من الجداول، لكن سأتعامل مع جدول واحد داخلها وهو جدول الـ Region حيث يوجد به حقلين كما مبين بالصورة أدناه: ![](https://informatic-ar.com/wp-content/uploads/2018/10/1234-300x286.jpg) بالتالي سأقوم بإنشاء فئة Class بنفس اسم الجدول الذي سأتعامل معه داخل المشروع الذي أعمل عليه داخل ملف الـ Models وستكون كالتالي: ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace DemoDapper.Models { public class Region { public int RegionID { get; set; } public string RegionDescription { get; set; } } } ``` الآن لنفترض أننا نريد أن نقوم بإرجاع جميع البيانات الموجودة داخل جدول الـ Region والموضحة في الصورة أدناه: ![](https://informatic-ar.com/wp-content/uploads/2018/10/345-300x150.jpg) حسنا سأنشئ فئة Class جديدة لتكون عبارة عن مستودع Repository سأقوم بكتابة جميع الإستعلامات التي سأحتاجها داخل تلك الفئة وهي بالإسم DapperRepository.cs حسنا لإستخدام Dapper في استدعاء جميع بيانات حقول الجدول Region سأقوم بكتابة الإستعلام التالي: ```csharp string con = ConfigurationManager.ConnectionStrings["DapperConnection"].ConnectionString; public List FindAll() { using (IDbConnection db = new SqlConnection(con)) { return db.Query("Select * From Region").ToList(); } } ``` حسنا سأقوم بشرح الإستعلام.. أولا قمت بتعريف Field بإسم con من النوع String وهو عبارة عن حلقة وصل بين التطبيق وملف الـ Web.config والذي مهتمه ربط التطبيق مع قاعدة البيانات. الـ Dapper تقوم بعمل Extend للواجهة IDbConnection وهي واجهة رئيسة للتعامل مع قواعد البيانات حيث الـ BaseClass بالنسبة لتلك الواجهة هو الـ DbConnection ، أيضا الـ Query عبارة عن Extension Method تتيح لنا جلب البيانات من قاعدة البيانات ومن ثم العمل عليها وتخزينها داخل العنصر الذي نتعامل معه (Object-Model) والذي في تلك الحالة هو الفئة Region والـ Constructor الذي تقبله له ثلاثة أنواع وما يهمنا في هذه المقالة هو النوع الأول String وهي الجملة التننفيذية التي نريد أن نقوم بتنفيذها على قاعدة البيانات وهي إستجلاب جميع البيانات من الجدول المحدد. ```csharp return db.Query("Select * From Region").ToList(); ``` > بالإمكان تعريف Field من النوع String وكتابة الجملة الإستعلامية :إسناد للـ Field ومن ثم إستدعاء داخل الـ Constructor بالنسبة للـ Query<>(string ***command***). لعرض البيانات على المتصفح قم بإنشاء صفحة Web From وقم بتسميتها Default.aspx وقم بإستخدام Grid View لعرض البيانات ومن خلال الـ Code Behind للصفحة قم بكتابة الشفرة التالية والتي ستستدعي الأسلوب FindAll من الفئة DapperRepository.cs كما مبين تاليا: ```csharp private DapperRepository dr = new DapperRepository(); protected void Page_Load(object sender, EventArgs e) { var query = dr.FindAll(); GridView1.DataSource = query.ToList(); GridView1.DataBind(); } ``` عند تنفيذ المشروع ستظهر معنا البيانات التي عرضتها من داخل الفيجوال ستديو على شاشة المتصفح: ![](https://informatic-ar.com/wp-content/uploads/2018/10/12-300x209.jpg) الآن لنفترض أننا نريد أن نقوم بكتابة جملة إستعلامية تقوم بإرجاع قيمة واحدة من قاعدة البيانات ستكون الشفرة كالتالي: ```csharp public Region Find(int id) { using (IDbConnection db = new SqlConnection(con)) { return db.Query("Select * From Region" + "Where RegionID = @id", new { id }) .SingleOrDefault(); } } ``` توجد أيضا Extension Method أخرى وهي Execute ويمكن إستدعائها لأي Object من النوع IDbConnection وهي تستخدم لـ Insert أو Update أو الـ Delete والـ Stored Procedure الآن فرضا لو أردنا عمل تحديث لحقل ما داخل قاعدة البيانات في الجدول Region سنقوم بكتابة الجملة الإستعلامية التالية: ```csharp public int Update(Region region) { using (IDbConnection db = new SqlConnection(con)) { string updateQuery = "UPDATE Region SET RegionDescription = @RegionDescription " + "WHERE RegionID = @RegionID"; int rowsAffected = db.Execute(updateQuery, region); return rowsAffected; } } ``` أيضا بالنسبة لإدخال حقل جديد ستكون الشفرة كالتالي: ```csharp public void InsertTo(Region region) { using (IDbConnection db = new SqlConnection(con)) { string insertQuery = "INSERT INTO Region (RegionDescription) VALUE (@RegionDescription);"; int rowsAffected = db.Execute(insertQuery, region); } } ``` الملاحظ أننا لا نقوم بأكثر من كتابة جمل الـ SQL الإستعلامية...بالنسبة للحذف سيكون كالتالي: ```csharp public void Delete(int id) { using (IDbConnection db = new SqlConnection(con)) { int rowAffectd = db.Execute("DELETE * From Region" + "Where RegionID = @id", new { id }); } } ``` بهذا أكون قد قدمت مقدمة بسيطة عن الـ Dapper في المقالات القادمة سأتطرق لمجموعة من الإمكانيات العديدة لهذه التقنية خصوصا كيفية التعامل مع الـ Stored Procedure. تحياتي. ### إستدعاء الـ ASP.NET Web API في الـ ASP.NET MVC URL: https://informatic-ar.com/consuming-aspnet-web-api/ Last updated: 2026-10-03T22:31:26.000Z ... تحدثتُ في المقالة السابقة لي، عن كيفية تهيئة الـ Service من خلال استخدام منصة الـ ASP.NET Api وعرضت مشروع مبسط يختص بتجديد واصدار جوازات المواطنين، كما عرضت كيفية اختبار الشفرات بإستخدام منصة مايكروسوفت Unit Test لجميع الأساليب التي أنشأناها في فئة التحكم PersonsController وكما اسلفت في تلك المقالة ان فئات التحكم في مشروع الـ Web Api ترث من فئة الـ ApiController ولا تقوم بإرجاع صفحات Html بل تقوم بإرجاع عناوين نقل المعلومات والتي يتولاها برتوكول الـ HTTP؛ بمعنى آخر يستقبل العميل صيغتان وهما – Xml و json ويستطيع العميل تهيتة تلك الصيغتان في بيئته البرمجية...(وهذا ما سنراه في هذه المقالة)! بإمكانك مراجعة المقالة السابقة من هنا [مفهوم الـ WEB API … تهيئتهُ … إستدعاءهُ … إختبار الشفرات عن طريق الـ Unit Testing](https://informatic-ar.com/aspnet-web-api-introduction/) .... ويجب عليك مراجعتها (المقالة) ان أردت ان تواصل في هذه المقالة الجديدة لأنها تعتمد على المقالة الأولى. وكما موضح بالصورة ادناه جميع البيانات التي يظهرها متصفح الكروم بصيغة XML والتي تم عرضها من المقالة السابقة ![](https://informatic-ar.com/wp-content/uploads/2018/08/5-300x146.png) إرجاع جميع المواطنين بصيغة XML من مشروع الـ Web Service في هذه المقالة سنقوم ببناء تطبيق بسيط لبنك يختص بفتح حسابات جديدة للمواطنين، وفتح الحساب يعتمد على ان يكون جواز المواطن ساري المفعول وهو يعتمد على الأسلوب ValidToOpenAccountForCustomer الذي أنشأناه في المشروع السابق (مشروع الـ Api) فإذا كان جواز المواطن ساري المفعول سيتم فتح حساب جديد...اما اذا كان غير ذلك فلن يستطيع موظف البنك فتح حساب لأن الخدمة عبارة عن Readonly يتم قراءتها عبر رابط من الـ Services داخل شفرة تطبيق العميل. البيئة البرمجية التي استخدمها في هذه المقالة هي Visual Studio 2017 بالنسبة لتطبيق العميل Client او لنقل الـ Consume وبالنسبة للخدمة نفسها (Api) كما في المقالة السابقة موضحة، قمت بإستخدام Visual Studio 2015 .. لكن أثناء كتابتي وتحرير لشفرة تطبيق العميل استخدمت Visual Studio Express 2013 For Web لفتح تطبيق الـ Web API. > ملاحظة – يمكنك استخدام Visual Studio 2013 اذا اردت تنفيذ هذا المشروع، سواء إعادة بناء الخدمة Web API وبناء شفرة العميل. في المقالة الأولى التي أشرت لرابطها آنفا توجد لدينا قاعدة يبانات باسم WebAPI\_DB وبها جدول واحد بإسم AccountDetails وداخل هذا الجدول يوجد حقل بإسم IsProcessFininshing ويُعني بإنتهاء عملية تجديد الجواز ففي حالة True – تم تجديد الجواز. وفي حالة False – الجواز لم يتم تجديده بعد. مشروع العميل Client/Consume سيعتمد على هذا الحقل لفتح حساب بنكي لمواطن ما عن طريق رقمه الوطني وفتح الحساب البنكي يعتمد على القيمة True في الحقل المعني. ... سأقوم بعمل التطبيق خطوة بخطوة وسأقوم بشرح ما سنكتبه من شفرات بعد الإنتهاء من كل خطوة. > في المشروع السابق الذي أشرت لرابطه أعلاه قم بعمل تعديل بسيط على أحد حقول البيانات للحقل IsProcessFininshing ليصبح False. قم بفتح الفيجوال ستديو 2017 أو 2015 أو 2013 لديك وقم بعمل تطبيق ويب جديد من النوع الفارغ (Empty) وقم بتسمة المشروع **ASPNETConsumeWebApi** كما مبين في الصورة ادناه. ![](https://informatic-ar.com/wp-content/uploads/2018/08/1-300x183.png) > بيئة العمل لدى تعمل على فيجوال ستديو 2017 واقوم بإستخدام الـ Dot Net Frameworm 4.7.1 من الصفحة التي ستظهر بعد انشاء المشروع قم بإختيار النوع Empty ولا تنسى تحديد نوع المشروع ليكون MVC وكما موضح بالصورة أدناه. ![](https://informatic-ar.com/wp-content/uploads/2018/08/2-300x194.png) قم بتثبيت الحزم التالية على من على الـ Nuget داخل المشروع: ```csharp System.Net.Http Microsoft.Net.Http System.Net.Http.Formatting ``` ![](https://informatic-ar.com/wp-content/uploads/2018/08/3-300x159.png) هذه المكتبات ستساعدنا على في مشروعنا والتي مرتبطة بالعمل مع الـ Web API ... قم بإنشاء فئة بإسم AccountDetail داخل ملف الـ Models وألصق بها الشفرة التالية: ```csharp [Table("AccountDetails")] public class AccountDetail { [Key] public Int32 PersonID { get; set; } public String Name { get; set; } public String NID { get; set; } public Boolean? Received { get; set; } public Nullable ReceivedDate { get; set; } public Boolean IsProcessFinishing { get; set; } } ``` لاحظ ان تلك الفئة هي نفسها الفئة الموجودة بمشروع الـ Web service والتي ستقوم بإرجاع العناصر الموجودة داخل قاعدة البيانات...(حيث لاتوجد قاعدة بيانات داخل مشروع العميل Client / Consume). قم بإنشاء فئة تحكم جديدة بإسم BankAccountController وتلك الفئة بها اسلوبين *Two Action Result* الاول يقوم بإرجاع جميع المواطنين من خدمة الـ Web API والثاني يقوم بإرجاع مواطن واحد لفتح حساب بنكي لهُ ويعتمد فتح الحساب البنكي للمواطن على الإسلوب ValidToOpenAccountForCustomer والموجود بفئة التحكم PersonsController والتي موجودة بمشروع الـ Web API Service ، الآن قم بلصق الشفرة التالية داخل الفئة التي اشئتها اخيرا: ```csharp using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Http; using System.Net.Http; using System.Net.Http.Headers; using System.Web.Mvc; using ASPNETConsumeWebApi.Models; String myBaseAddress = "http://localhost:3570/"; public async Task GetAllPersons() { IEnumerable acc = null; HttpClient client = new HttpClient(); client.BaseAddress = new Uri(myBaseAddress); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage response = await client.GetAsync("api/persons/getpersons"); if (response.IsSuccessStatusCode) { var persons = response.Content.ReadAsAsync>().Result; acc = persons; } return View(acc); } public async Task OpenAccountForPersonHas(int id) { AccountDetail acc = null; HttpClient client = new HttpClient(); client.BaseAddress = new Uri(myBaseAddress); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); HttpResponseMessage response = await client.GetAsync($"api/persons/ValidToOpenAccountForCustomer/{id}"); if (response.IsSuccessStatusCode) { acc = await response.Content.ReadAsAsync(); return View(acc); } else { return View("InvalidDocument", response.StatusCode); } } ``` هل تدري ان هذا كل ما سنحتاجه للقيام بفتح حساب بنكي جديد للمواطن يعتمد على تنفيذه على خدمة خارجية عن طريق رابط يأتي من Web Server آخر غير الذي موجود به تطبيق العميل *Hosted*.. فقط سنقوم ببناء صفحات العرض Html views بالنسبة لتطبيق العميل Client/Consume حسنا في الإسلوب الأول GetAllPerson قمنا بعمل مثيل من الواجهة IEnumerable والتي تقوم بإرجاع البيانات في شكل مصفوفة هذا المثيل *instance* قمنا بإعطاءه القيمة null والمثيل هذا (acc) سنقوم بإسناد جميع البيانات فيه لنقوم بعرضها على صفحة الـ view الخاصة بإرجاع جميع المواطنين. في السطر الثاني في ذلك الاسلوب قمنا بعمل مثيل *instance* من الفئة **HttpClient**: ```csharp HttpClient client = new HttpClient(); ``` فئة الـ **HttpClient** هي محور استدعاء البيانات من مشروع الـ Web Service ، هذه الفئة بها خاصية تساعدنا على تحديد الرابط الذي سنستخدمه في جلب البيانات..او قل بمعنى آخر الرابط الذي سيوفره لنا المسؤول من تطبيق الـWeb API. وإذا لاحظت قمنا بتعريف الرابط في أعلى الفئة وتأخذ النوع النصي **String** وتوضح رقم المنفذ لدىّ **3570** والذي يعمل على بيئة التشغيل لدىّ *work station* من خلال الـ **IIS Express** ```csharp String myBaseAddress = "http://localhost:3570/"; ``` وهذه الخاصية من النوع الذي يمكن تغييره وجلبه: ```csharp public Uri BaseAddress { get; set; } ``` في السطر الرابع قمنا بإستدعاء (*Property)* الخاصية **DefaultRequestHeaders** والتي من خلالها سنوضح أي صيغة من صيغ البيانات نود ارجاعهُ من خلال مشروع الـ Web API حيث قمت بعمل تنظيف لجميع الصيغ اولاً وفي السطر الذي يليه قمت بتحديد نوع الصيغة التي أود إرجاعها وهي من النوع **json** ، والخاصية **DefaultRequestHeaders** عبارة عن قارئ فقط اي **ReadOnly** وهي كالتالي: ```csharp public HttpRequestHeaders DefaultRequestHeaders { get; } ``` في السطر السادس في الاسلوب GetAllPersons قمنا بإستخدام فئة **HttpRsponseMessage** والتي من خلاها سنستدعي الأسلوب الموجود بمشروع الـ Web API نفسه من خلال الرابط الذي قمنا بتعريفه في المثيل **client** من خلال الفئة **HttpClient** ولو لاحظت هنا قمت بإستخدام كلمة *await* والتي ستقوم بعمل **قفل** حتى يتم الانتهاء من استدعاء جميع المواطنين للطلب **Request** ومن خلال المثيل **response** سنختبر نجاح العملية عن طريق الخاصية **IsSuccessStatusCode** ومن ثم سنقوم بإرجاع الصفحة التي سيتم عليها عرض جميع المواطنين...لأن جميع البيانات ستضمّن في المثيل **response** والذي بدوره سينقل البيانات للمثيل الذي أنشأناه أولا **acc**. حسنا قم بالضغط بالزر الأيمن للمؤشر (ماوس) على الاسلوب GetAllPersons وقم بإختيار Add View وقم بالضغط على Add مباشرة..سيقوم الفيجوال ستديو بإنشاء صفحة Html داخل الملف Views داخل الملف BankAccount لذلك الإسلوب... وقم بلصق الشفرة التالية داخل الصفحة: ```csharp @model IEnumerable @ { ViewBag.Title = "GetAllPersons"; } @foreach (var person in Model) { }
جميع المواطنين
@Html.ActionLink("قم بفتح حساب جديد", "OpenAccountForPersonHas", new { id = person.PersonID })
@person.Name
``` هذه صفحة Html عادية جدا..بها جدول يوجد به اسماء المواطنين وامام كل مواطن يوجد رابط سنقلنا للإسلوب **OpenAccountForPersonHas** والذي مهمته فتح الحساب البنكي > ملاحظة: دائما ما اكتب فتح حساب بنكي؛ فهذه طريقة إجرائية للشرح...مهمتي هنا ليست لإنشاء قاعدة بيانات لفتح الحسابات لبنك ما؛ بل لشرح إستدعاء مشروع الـ Web API من خلال روابط محددة تأخذ جميع الإجراءات من التطبيق...فإن أردت ان تقوم بعمل قاعدة بيانات داخل مشروع العميل Client/Consume فبإمكانك عمل هذا بإتمام عملية فتح الحساب البنكي...لكن الإجراء هنا اذا كان جواز المواطن ساري قم بفتح حساب واذا غير ذلك لن يتم فتح حساب. الإسلوب الآخر **OpenAccountForPersonHas** لا يختلف كثيرا عن الإسلوب الاول فقط يكمن الفرق في هذا الاسلوب يأخذ معامل *Parameter* ليقوم بإرجاع مواطن واحد من الاسلوب **ValidToOpenAccountForCustomer** والذي يوجد بمشروع الـ Web API ```csharp HttpResponseMessage response = await client.GetAsync($"api/persons/ValidToOpenAccountForCustomer/{id}"); ``` لقد قمت بإستخدام الـ *String Interpolation* بإستخدام علامة الدولار **$** قبل فتح علامتي تنصيص **" "** والتي ستساعدني في القوائم المنسدلة التي يقوم الفيجوال بإستدعائها اثناء كتابة الجملة النصية نفسها..لاحظ الصورة ادناه. ![](https://informatic-ar.com/wp-content/uploads/2018/08/4-300x100.png) يقوم ذلك الاسلوب بإرجاع صفحة لفتح حساب المواطن في حال كان الجواز ساري المفعول...وإلا سيقوم بعرض رسالة توضح عدم سريان الجواز للتمكن من فتح حساب بنكي جديد في صفحة **InvalidDocument** الآن قم بإنشاء صفحة Html View لهذا الاسلوب وقم بلصق الشفرة التالية داخلها ```csharp @model ASPNETConsumeWebApi.Models.AccountDetail @ { ViewBag.Title = "OpenAccountForPersonHas"; }

فتح حساب لعميل

@Model.Name

``` أيضا قم بعمل صفحة Html View وأعطها الاسم InvalidDocument وألصق بها الشفرة التالية: ```csharp @ { Layout = null; }

عفوا لا تستطيع فتح حساب لهذا المواطن نسبة لعدم صلاحية وثيقته الرسمية

``` الآن قم بتشغيل التطبيقين تطبيق الـ **Web API** والتطبيق الذي تعمل عليه...تطبيق Web API سيتيح لنا قراءة المنفذ الذي موضحّ في السطر الأول من فئة **BackAccountController** الذي يأخذ النوع String وسيكون نشط دائما وإلا لن نستطيع إستدعاء البيانات من خلاله داخل مشروع العميل Client/Consume قم بكتابة الرابط التالي داخل متصفحك ```csharp http: //localhost:1788/BankAccount/GetAllPersons ``` > لا تنسى بأن المنفذ سيكون مختلفا لديك..وذلك لأنني أستخدم الـ IIS Express والذي يتم تثبيته أثناء تثبيت الفيجوال ستديو...لذلك قم بتشغيل التطبيق أولا ومن ثم؛ قم بلصق اسم فئة التحكم والاسلوب المعني. هذا الرابط مهمته إرجاع جميع المواطنين...وسيقوم بإظهارهم لنا في صفحة الـمتصفح في صورة منسقة عكسا من عرضها على صيغة XML أو json التي تظهر من خلال مشروع الـ Web API service ستظهر معك تلك البيانات كما موضح أدناه بالصورة: ![](https://informatic-ar.com/wp-content/uploads/2018/08/6-300x169.png) في حال تمريرك للمؤشر (ماوس) فوق الرابط **قم بفتح حساب جديد** للمواطن الذي رقمه **1** أنظر لأسفل الشاشة كما موضح بالصورة التالية ![](https://informatic-ar.com/wp-content/uploads/2018/08/7-300x169.png) ستوضح لك ماهو الأسلوب الذي سياخذه الرابط ... عند ضغطك على الرابط ستظهر معك بيانات المواطن والذي اسمه Person one ويوجد زر سينقلك لصفحة فتح حساب بنكي جديد. ![](https://informatic-ar.com/wp-content/uploads/2018/08/8-300x169.png) قم بالضغط على **الرابط الثالث** للمواطن **Person Three** سيعرض لك الصفحة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/08/9-300x169.png) ولأن ذلك المواطن جوازه غير ساري المفعول ستظهر معنا الرسالة الموضحة في الصورة اعلاها والتي تقرأ من صفحة **InvalidDocument**. لأن الحقل **IsProcessFininshing** لهذا المواطن *False* كما قمنا بتعديله في بداية تلك المقالة. في حال أردت ان تقوم بإتمام عملية فتح الحساب البنكي بإمكانك إنشاء قاعدة بيانات داخل مشروع **ASPNETConsumeWebApi** وداخل قاعدة البيانات قم بإنشاء جدول به تفاصيل رئيسية لمعلومات فتح الحساب...بعدها قم بعمل أسلوب (*Action Result*) داخل فئة **BankAccountController** مهمتها عمل تنفيذ لفتح الحساب وقم بتعديل طفيف داخل صفحة **OpenAccountForPersonHas** لتنفيذ الجملة المنطقية Logic لزر فتح الحساب لينقلك لصفحة فتح الحساب. أيضا إذا أردت ان تقوم بإختبار الشفرات فمن السهولة بمكان عمل الاختبار فقط قم بعمل مثيل instance لفئة الـ **HttpClient** من خلال المكتبة **Moq** لتستطيع العمل بصورة غير حقيقية virtual داخل بيئة إختبار الأكواد من خلال الفيجوال ستديو. تحياتي - محمد ### تشغيل قواعد البيانات في دوكر Database in Docker URL: https://informatic-ar.com/docker-databases/ Last updated: 2026-10-03T22:31:27.000Z عملية تنصيب وتشغيل قواعد البيانات بالطريقة التقليدية ليست سهلة وتحتاج للعديد من الخطوات والاعدادات والتأكد من عدم استخدام ال port بواسطة برامج اخرى، في هذا الموضوع سوف نقوم بتشغيل قواعد البيانات في دوكر مثل Microsoft SQL Server وايضاً MySQL ونقوم بتشغيلهم بداخل ال Containers، وسوف ترى كيف أصبحت العملية بسيطة وسهلة، وسوف نتحدث عن كيفية الاحتفاظ بالبيانات حتى لو تم حذف ال Container وذلك من خلال ال Volumes. وأخيراً سوف نتحدث عن أوامر حذف ال Images وال Containers وال Volumes. **المواضيع في السلسلة:** 1. [مقدمة حول دوكر Docker](https://informatic-ar.com/docker-introduction/) 2. [البداية مع دوكر Docker](https://informatic-ar.com/getting-started-docker/) 3. [مشاركة المجلدات في دوكر Docker](https://informatic-ar.com/docker-volumes/) 4. [بناء ال Docker Images](https://informatic-ar.com/building-docker-images/) 5. تشغيل قواعد البيانات في دوكر Database in Docker (الموضوع الحالي) 6. [تشغيل البرامج باستخدام ال Docker Compose](https://informatic-ar.com/docker-compose/) ## تشغيل MS-SQL Server بداخل ال Container حتى وقت قريب كانت كل ال MS-SQL Server تعمل بداخل ويندوز، ولكن بدءاً من الإصدارات الحديثة قامت مايكروسوفت بدعم نظام لينوكس، وأصبح بالإمكان تشغيلها على لينوكس. وفي دوكر سوف تجد هناك بعض ال Images لل SQL Server التي تعمل على ال Linux Container وبعضها على ال Windows Container. وسوف نستخدم النسخة التي تعمل على ال Linux Container بداخل ويندوز وهي نسخة رسمية من مايكروسوفت: [![](https://informatic-ar.com/wp-content/uploads/2018/08/sql-server-linux-1024x494.png)](https://informatic-ar.com/wp-content/uploads/2018/08/sql-server-linux.png) سوف نقوم اولاً بتحميل ال Image المطلوبة: [![](https://informatic-ar.com/wp-content/uploads/2018/08/pull-sql-server-linux.png)](https://informatic-ar.com/wp-content/uploads/2018/08/pull-sql-server-linux.png) بعد ذلك سوف نقوم بتشغيل ال SQL Server، وعادة يجب الرجوع للتوثيق في ال Docker Hub لتعرف ما هي القيم المرسلة التي يجب ارسالها سواءً عند التشغيل أو لتغيير الاعدادات الافتراضية، ومثلاً في قواعد البيانات يجب ان تضع كلمة مرور للمستخدم الأساسي، وتحدد ال Mapping Port في النظام الأساسي والبورت الذي يعمل عليه ال SQL Server بداخل ال Container حتى تستطيع الوصول لها من النظام الأساسي، الصورة التالية تبين تشغيل ال SQL Server في ثواني معدودة وليس كما لو تستخدم الطريقة التقليدية فتثبيته يحتاج وقتاً واعدادات. [![](https://informatic-ar.com/wp-content/uploads/2018/08/run-sql-server-linux-1024x97.png)](https://informatic-ar.com/wp-content/uploads/2018/08/run-sql-server-linux.png) في الأمر أعلاه قمنا بتمرير عدة متغيرات نظام Environment Variable وهي الطريقة لكي ترسل أي متغيرات واعدادات للبرامج، حيث وافقنا على الشروط، ووضعنا كلمة مرور ويجب ان تكون معقدة Complex والا سوف تحصل على خطأ، وأيضاً حددنا أنه نريد ال Express Edition وتم تشغيل ال Container وحصلنا على ال ID ```bash docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=P@ssw0rd" -e "MSSQL_PID=Express" -p 1434:1433 -d microsoft/mssql-server-linux ``` سوف نقوم بمشاهدة السجلات logs لل Container وهي قد تحتاجها في حال لم تعمل أي Container فعليك استخدام الأمر docker logs id حيث تشاهد كل السجلات وتعرف الخطأ الذي صدر: [![](https://informatic-ar.com/wp-content/uploads/2018/08/sql-server-logs-1024x326.png)](https://informatic-ar.com/wp-content/uploads/2018/08/sql-server-logs.png) الأن سوف نقوم بالاتصال مع ال SQL Server من خلال ال SQL Server Management Studio وسوف أقوم في النظام الأساسي Host بفتحه والاتصال من خلال اسم المستخدم وعنوان IP الجهاز الحالي لأن ال Container تعمل عليه وقمت بعمل Mapping للبورت وبالتالي أستطيع الوصول له من خلال ذلك البورت. من المهم ان تستخدم نسخة حديثة من ال SQL Server Management Studio مثلاً 2017 حيث هناك مشاكل تحدث عندما تحاول الوصول للسيرفر من كلاينت يعمل بنسخة قديمة، سواءً كان يعمل بداخل دوكر او حتى بشكل عادي، لذلك قم بتحميل ال Microsoft SQL Server Management Studio 2017. [![](https://informatic-ar.com/wp-content/uploads/2018/08/connect-sql-server-client.png)](https://informatic-ar.com/wp-content/uploads/2018/08/connect-sql-server-client.png) لاحظ طريقة الاتصال وهو برقم ال IP للجهاز ويمكن الحصول عليه من خلال ipconfig ومن ثم فاصلة ورقم البورت الذي قمت بعمل Mapping ولأنه يتواجد لدى في جهازي الأساسي SQL Server يعمل على البورت 1433 الافتراضي فقمت بتغيير البورت لبورت اخر. هكذا تم الاتصال بين ال SQL Server Client المتواجد على النظام الأساسي في ويندوز، وتستطيع انشاء أي قاعدة بيانات تريدها بشكل طبيعي. [![](https://informatic-ar.com/wp-content/uploads/2018/08/show-database-sql-server.png)](https://informatic-ar.com/wp-content/uploads/2018/08/show-database-sql-server.png) ## تشغيل MySQL بداخل ال Container يمكنك تحميل النسخة الرسمية ل MySQL من خلال Docker Hub: [![](https://informatic-ar.com/wp-content/uploads/2018/08/mysql-docker-hubn-1024x482.png)](https://informatic-ar.com/wp-content/uploads/2018/08/mysql-docker-hubn.png) وسوف تجد في هذه الصفحة تفاصيل تشغيل الداتابيس وما هي البورت التي يجب عمل Mapping لها، وسوف نقوم بتشغيلها كالتالي: [![](https://informatic-ar.com/wp-content/uploads/2018/08/run-mysql-docker-1024x371.png)](https://informatic-ar.com/wp-content/uploads/2018/08/run-mysql-docker.png) لاحظ أننا لم نقوم بعمل Mapping لل container وبالتالي لن نستطيع الوصول لها من النظام الأساسي، ويمكن القيام بذلك أو يمكن ان نستخدم ال MySQL Client المتواجد في هذه ال Container، لذلك سوف نقوم بالدخول عليها من خلال الأمر docker exec ونقوم بالاتصال مع الكلاينت لكي نستعرض قاعدة البيانات التي تعمل الآن. [![](https://informatic-ar.com/wp-content/uploads/2018/08/mysql-client-docker.png)](https://informatic-ar.com/wp-content/uploads/2018/08/mysql-client-docker.png) ## حفظ البيانات في ال Volumes تحدثنا سابقاً عن مفهوم ال Volume وأنه يمكن عمل Mounting لمجلد من النظام الأساسي الى مسار بداخل ال Container، ولقد ذكرنا ان هناك استخدام آخر لل Volumes الا وهو في حفظ البيانات حتى لو تم إيقاف او حذف ال Container سوف تبقى البيانات محفوظة في حال تم استخدام ال Volume. لنقوم بكتابة الأمر التالي لعرض كل ال Volume الموجودة في الجهاز docker volume ls: [![](https://informatic-ar.com/wp-content/uploads/2018/08/volumes.png)](https://informatic-ar.com/wp-content/uploads/2018/08/volumes.png) لنجرب الان إيقاف ال mysql وذلك من خلال الأمر docker stop some-mysql وقم بتنفيذ الأمر docker volume ls مجدداً وسوف تجد ال volume متواجد ايضاً، وهذه ال Volume الموجودة تم انشائها عند تشغيل ال mysql أول مرة. [![](https://informatic-ar.com/wp-content/uploads/2018/08/show-volums.png)](https://informatic-ar.com/wp-content/uploads/2018/08/show-volums.png) وهذا الأمر جيد فحتى لو حذفت ال database container فسوف تكون البيانات موجودة، والبيانات سوف تكون موجودة على النظام الأساسي في مجلد باسم غير محدد (لكن بالإمكان تحديده كما سيلي) مع مسار تخزين البيانات بداخل ال container وهذه يتم تحديدها بواسطة ال image ومن قام ببنائها، وبالتالي لن يتم تخزين أي بيانات في ال Container وانما ستكون بنفس فكرة ال Volume التي تحدثنا عنها سابقاً وأنها مشاركة للمجلد المتواجد على النظام الأساسي. بالتالي في حال قمت بتشغيل new MySQL container فسوف تجد انه يعمل بنفس البيانات بالضبط، وهذه ميزة جيدة حيث بالإمكان ترقية ال database version وتشغيله على نفس البيانات بالضبط. سوف نقوم بتشغيل MySQL instance جديدة وسوف نحدد named volume لكي يستخدمها، وبعد الانشاء كما تلاحظ سوف يتم انشاء volume بالاسم الذي تم تحديده بدلاً من رقم عشوائي [![](https://informatic-ar.com/wp-content/uploads/2018/08/reuse-volume.png)](https://informatic-ar.com/wp-content/uploads/2018/08/reuse-volume.png) سوف نقوم الان بالدخول لل MySQL وننشئ قاعدة بيانات جديدة بالاسم firstDb كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/08/create-database-mysql.png)](https://informatic-ar.com/wp-content/uploads/2018/08/create-database-mysql.png) بعد ذلك سنقوم بحذف ال MySQL container ونتأكد من أنه لا يوجد أي MySQL Container تعمل: [![](https://informatic-ar.com/wp-content/uploads/2018/08/remove-container-mysql-1024x230.png)](https://informatic-ar.com/wp-content/uploads/2018/08/remove-container-mysql.png) بعد حذف ال container فلن يتم حذف ال Volume، وفي حال قم بتشغيل new MySQL container بدون تحديد ال volume فسوف يتم انشاء volume جديد، ولكن في حال قمت بتحديد volume موجود لديك فسوف يتم استخدامه، الصورة التالية تبين اولاً عرض ال volume ومن ثم قمنا بتشغيل ال MySQL container بال volume المحدد، وبعد ذلك عرضنا ال volume للتأكد من عدم انشاء volume جديدة، واخيراً قمنا بالدخول على ال container وعرضنا الداتابيس وفعلاً وجدتا قاعدة البيانات التي أنشأناها مسبقاً. [![](https://informatic-ar.com/wp-content/uploads/2018/08/named-volume-mysql.png)](https://informatic-ar.com/wp-content/uploads/2018/08/named-volume-mysql.png) وهكذا تكون قواعد البيانات محفوظة بغض النظر عن ال container الذي قام بأنشائها. ## الحذف - إيقاف ال Containers عند تعاملك مع ال containers سوف تجد أنك تقوم بتحميل العديد من ال images وقد تكون كبيرة ولذلك قد تود حذف البيانات التي لا تريدها. نريد أولاُ إيقاف ال Containers التي تعمل، وذلك من خلال الأمر docker stop id ولكن في حال لديك عدة containers تعمل وتريد ايقافهم جميعاً فيمكنك استخدام الأمر docker ps –q والذي يعرض ال Id لل containers، الصورة التالية توضح الأمر docker ps –q حيث ظهر كل ال containers id، والأمر الثاني هو دمج docker stop مع docker ps –q وبالتالي إيقاف كل ال containers التي تعمل. [![](https://informatic-ar.com/wp-content/uploads/2018/08/remove-container.png)](https://informatic-ar.com/wp-content/uploads/2018/08/remove-container.png) لاحظ قمنا بالدخول أولاً على ال PowerShell حتى يسمح لنا باستخدام معامل $ وهكذا كما هو واضح تم إيقاف كل ال containers التي تعمل. ## الحذف - حذف ال Containers عند ايقافك لل containers فسوف تتوقف من العمل ولكنها تكون موجودة ويمكن تشغيلها مجددأً، وإذا اردت حذفها فهذا عن طريق الأمر docker rm id وسوف نقوم الان بعرض كافة ال containers (التي تعمل والمتوقفة) وهم جميعهم الآن Stopped ونقوم بحذفهم جميعاً وذلك من خلال دمج الأمر docker ps –a مع الأمر docker rm مع إضافة q الى docker ps –aq حتى نحصل على ال ids لهم فقط، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/08/remove-container2.png)](https://informatic-ar.com/wp-content/uploads/2018/08/remove-container2.png) وايضاً يمكن استخدام الأمر docker container prune والذي سوف يقوم بحذف كل ال containers المتوقفة. إذا لم تكن تريد حذف كل شيء، وذلك لأن الأمر docker ps –a سوف يظهر جميع ال containers التي تعمل والتي لا تعمل. ## الحذف - حذف ال Volumes كما ذكرنا يتم انشاء ال Volume بواسطة ال Database Containers غالباً حيث نريد الاحتفاظ بالبيانات حتى بعد حذف ال containers، وفيما يلي سوف نستخدم أمر الحذف docker volume rm ونمرر لها اسم ال volume، أو نقوم بحذفهم جميعاً عن طريق الدمج docker volume ls –q مع أمر الحذف كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/08/remove-volume.png)](https://informatic-ar.com/wp-content/uploads/2018/08/remove-volume.png) ويمكن ايضاً استخدام الأمر docker volume prune لحذف ال volume غير المستخدمة بواسطة containers. وايضاً يمكن استخدام الأمر docker rm –fv id عند إيقاف وحذف ال container وتحديد v لحذف ال volume المرتبطة به، ولكن يجب ان تكون بدون اسم، وسيتم حذفها معهم. عندما يتم حذف ال container المرتبط بال volume سوف تعتبر dangling volume لأنها غير مستخدمة وال container تم حذفها ايضاً، فيمكن استخدام امر لحذف ال dangling volume كما يلي، وسيتم أولاً انشاء 2 containers وبعدها سنقوم بحذف واحد منهم، وبالتالي أحد ال volume سوف يكون dangling، وسوف نقوم بعرضه اولاً ومن ثم حذفه: [![](https://informatic-ar.com/wp-content/uploads/2018/08/remove-volume2.png)](https://informatic-ar.com/wp-content/uploads/2018/08/remove-volume2.png) الآن ما زالت هناك container تعمل وبها ايضاً volume موجودة، فيمكن حذفهم بالطرق السابقة، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/08/remove-volume3.png)](https://informatic-ar.com/wp-content/uploads/2018/08/remove-volume3.png) ## الحذف - حذف ال Images لحذف ال image يتم استخدام docker rmi image\_name ويتم تمرير اسم أو معرف ال Image الذي تريد حذفها: [![](https://informatic-ar.com/wp-content/uploads/2018/08/remove-image.png)](https://informatic-ar.com/wp-content/uploads/2018/08/remove-image.png) ولن تستطيع حذف Image مستخدمة بواسطة container ويجب إيقافه اولاً وحذفه قبل حذف ال image المستخدمة. ولحذف جميع ال images يمكن تطبيق الأمر docker rmi $(docker images –q) وسيتم حذفها جميعاً. ويمكن استخدام الأمر docker image prune لحذف كل ال dangling image، وهي ال Image التي لا Tag واسم لها، وهي تحدث عندما تقوم ببناء الImages في جهازك بشكل متكرر بنفس ال tag، فقد تجد النسخ القديمة أصبحت بدون تاق. ويمكن حذفها بهذا الأمر. [![](https://informatic-ar.com/wp-content/uploads/2018/08/remove-image2.png)](https://informatic-ar.com/wp-content/uploads/2018/08/remove-image2.png) ## خلاصة هناك نوعين من ال Volumes: - الأول لمشاركة الملفات عن طريق Mounting لملفات من النظام الأساسي لل Container - النوع الثاني Managed Volume وهي التي يتم انشائها بواسطة الأمر docker volume سواءً للإنشاء او الحذف وغيرها، وهي كأنه يتم انشاء قرص صلب hard disk ومن ثم عمل mount له الى ال container. ال Managed Volume تكون غير متعلقة بال Container فهي تعتبر قرص صلب إضافي ولكن تم عمل mount له لل Container، فاذا تم حذف تلك ال Container فتكون البيانات متواجدة، ويمكن عمل mount لها مجدداً في Container جديد يتم انشاءه. وايضاً البيانات في ال Volume ليست موجودة في ال Image، فمثلاً إذا قمت بإنشاء container ونسخت بيانات الموقع الى ال volume فعند اخذ صورة commit له فلن تحصل على البيانات لأنها لا تعتبر جزءاً من ال image. لذلك دائماً ضع البيانات الخاصة بالقراءة heavy read-only data في image، وضع البيانات التي ستكتب في ال Volumes مثل قواعد البيانات وغيرها. ### مقدمة حول ال ASP.NET Web API URL: https://informatic-ar.com/aspnet-web-api-introduction/ Last updated: 2026-10-03T22:31:27.000Z **مقدمة** **– برتوكول الـ** **HTTP** **– عمل صفحة الـ** **ASP.NET**: جميعنا نستخدم الشبكة العنكبوتية (الإنترنت) في جُل معاملاتنا اليومية؛ الرسمية أو الترفيهية، نقوم بالدخول على المواقع الإلكترونية وننتظر نتائج ذلك الدخول، نقوم بحفظ المعلومات واستجلابها عن طريق عناوين الوصول URL التي نقوم بكتابتها في مربع بحث الروابط في المتصفحات... كل تلك العمليات تتم عن طريق بروتوكول يُدعى HTTP وهو اختصار لـ Hypertext Transfer Protocol ومهمة هذا البروتوكول جميع ماذكرته آنفا من استدعاء للبيانات وتوزيعها حسب النطاق المطلوبة فيه تلك البيانات. برتوكول الـ HTTP يعمل من خلال خاصيتين وهما الطلب والإستجابة Request – Response وعمليتا الطلب والإستجابة دائما ما تتم بين جهاز المستخدم وجهاز الخادم (المزوّد – سيرفر) حيث يقوم المستخدم من خلال المتصفح الذي مثبّت على جهازه بطلب صفحة معينة – هنا تسمى العملية تلك عملية الطلب Request ، عملية الطلب ترسل لجهاز الخادم (مزوّد – سيرفر) يقوم جهاز الخادم بعدها بالإستجابة للطلب وإرساله Response للمستخدم. أيضا البرتوكول HTTP يستخدم في خدمات الويب او ما تعرف بالإنكليزية Web Services. > حتى أكون أكثر وضوحا معك عزيزي القارئ (المطور)، في هذا العرض لن أتطرق لكيفية عمل الويب من خلال برتوكول الـ HTTP وكما ترى لقد قمت بشرح سريع جدا وموجز في المقدمة لكيفية عمل هذا البرتوكول، ولأكون أكثر وضوحا معك موضوع المقالة هذا (WEB API) يحتاج منك ان تكون ملمّاً بمعرفة هذا البرتوكول HTTP، لكن لا تقلق سأقوم بإرفاق رابط لك هنا يشرح مفهوم برتوكول الـ HTTP بالتفصيل مصحوبا بعدد من المواضيع المرتبطة بهذا البرتوكول والشرح باللغة العربية ... سيقوم الرابط بتوجيهك لموقعنا هذا (انوفورماتيك) حيث من قام بكتابة المقال وجدي عصام، بإمكانك قراءة المقال من هنا > > [مقدمة عن ال HTTP](https://informatic-ar.com/http-introduction/) **طريقة عمل صفحة الـ** **ASP.NET** ![](https://informatic-ar.com/wp-content/uploads/2018/07/2-300x78.png) تقنية الـ WEB API تعمل مع تقنية الـ ASP.NET حيث قامت مايكروسوفت بإصدارها مع نسخة الفيجوال ستديو 2012 وجميع النسخ التي تلت تلك النسخة وحتى يتسنى لنا وضع الـ WEB API في سياقه لابد لنا أن نشرح طريقة عمل صفحة الـ ASP.NET. عندما تقوم بكتابة الرابط www. informatic-ar.com في متصفحك يقوم المتصفح بعمل طلب لتلك الصفحة من الخادم (مزوّد – سيرفر) وتتم تلك العملية عن طريق برتوكول الـ HTTP حيث ينشأ إتصال بين جهاز المستخدم Web-Client وجهاز الخادم Web-Server وفي حال كان الخادم يعمل، سيقوم بعمل ارسال Response للصفحة المطلوبة للمتصفح الذي تمت من خلاله عملية الطلب، وإرجاع تلك البيانات دائما ما يكون على صيغتي الـ JSON أو الـ XML. وكما واضح ان موقعنا informatic-ar.com يعمل على خادم إستضافة Web-Server لتتاح عملية الوصول إليه من جميع إنحاء العالم. > مشروع الـ ASP.NET دائما ما يتكون من ملفات الموقع نفسه وصور ومستندات وملفات ساكنة Static file تلك الملفات الساكنة دائما تكون موجودة في جهاز الخادم عند طلبها يقوم الخادم بإرسالها مباشرة للمستخدم (Request) أما إذا كانت الملفات ديناميكية سيقوم خادم الويب بعمل بعض العمليات عليها وخادم الويب هذا يعمل مع الـ ASP.NET Run-time والذي يتم تثبيته من خلال تثبيت مكتبة الـ .NET Framework وخادم الويب الذي اقصده هنا هو الـ IIS وهو الذي يتم عليه نشر جميع ملفات مشروع الـ ASP.NET. الآن بعد ان عرفنا كيفية عمل صفحة الـ ASP.NET سننتقل لمشروعنا الذي سنقوم ببناءه وتهيئته واستدعاءه من خلال الـ WEB API وعمل اختبار لشفراته التي سنكتبها. > دائما ما أقوم بعمل إختبار شفرات مشاريعي التي أكون مسؤول عنها سواء صغيرة أو كبيرة ومن خلال خبرتنا عزيزي القارئ لقد عملنا على مشاريع كبيرة دائما تُظهر مشاكل أثناء عمل نشر المشروع عند المستخدم فبالتالي كلما قمت بإختبار شفرات برمجياتك ستسهل عليك معرفة هيكلية برنامجك ودورة حياة تلك البرمجيات.. > > أيضا اختبار البرمجيات أثناء كتابة الشفرات يجنُبك تشغيل مشروعك على المتصفح كثيرا للتأكد من عمل الشفرة التي قمت بكتابتها هل هي تعمل بصورة كما الصورة المتوقعة ام لا؟ بالتالي كتابة شفرات الإختبار تختصر لك الزمن..وعند استدعاء مشروعك من خلال المتصفح لديك ستكون متأكدا من عمله بصورة صحيحة من مرة واحدة. > > سنقوم ببناء مشروع من الصفر خطوة بخطوة لتسليم المواطنين جوازاتهم أو تجديدها حيث سيقوم المواطن بعمل طلب لتجديد أو أصدار جواز جديد...سيقوم المسؤول بحفظ بيانات المواطن...يقوم المواطن بالمراجعة للتأكد من إنتهاء إجراء الجواز ام لا..في حال تم الإنتهاء من الإجراء المطلوب سيقوم المسؤول بتحديث بسيط يوضح ان المواطن قام بإستلام جوازه. > > هذا المشروع لقد قمت ببرمجته من قبل بإستخدام الـ ASP.NET MVC وتم نشره على موقعنا informatic-ar.com وبإمكانك مطالعته من الرابط التالي ان إردت > > [بناء تطبيق متكامل لتسليم وتسلم جوازات المواطنين يوضح تقنية الـ ASP.NET MVC واستخدام الـ Unit Tests لإختبار الشفرات](https://informatic-ar.com/aspnet-mvc-passports-unit-tests/) **(وسيكون جميلا الإطلاع عليه لتفهم طريقة عمل الـ MVC لأن الـ WEB API شبيهة في عملها للـ MVC فقط سيكون الفرق في عملية الإستجابة Response من خلال السيرفر بالنسبة للـ WEB API حيث يقوم بإرجاع للبيانات بصيغتي الـ XML أو الـ JSON)** **وفي مشروع الـ WEB API هذا لقد قمت بإضافة حقل جديد على قاعدة البيانات يوضح هل انتهى الجواز من الاصدار او التجديد من عدمه (IsProcessFinishing) عكس مشروع الـ ASP.NET MVC الموضح في الرابط السابق والذي كان لا توجد به تلك الخاصية، فقط كانت عملية التأكد تتم بإستخدام جملة منطقية في فئة التحكم PersonController بإستخدام جملة If المنطقية توضح هل المواطن استلم جوازه ام لا؟ وفي حالة الإستلام يقوم الـ ASP.NET Run-time بتوجيه المستخدمم لرابط التفاصيل (ستجد هذا التحديث في اول تعليق على المقالة المشار لرابطها في الأعلى).لكن هنا في مشروع الـ WEB API سنقوم بتحديد تلك العملية من قاعدة البيانات من خلال الحقل (IsProcessFinishing).** **لماذا نستخدم الـ** **WEB API** إذا كنت من عشاق موقع امازون وتستخدم وسيط في الشراء والطلب من هذا الموقع ستسأل نفسك كيف قام الموقع الوسيط هذا بالشراء بدلا مني؟ امازون لديها خدمة تسمى AWS وهي اختصار لـ Amazon Web Services تقوم بإرجاع البيانات في عدة صيغ تستخدمها مواقع عديدة مثلا كموقع (sa.edfa3ly.co) والذي يتيح لك نسخ رابط المنتج من موقع اماوزن ووضعه في مربع نصي Textbox في موقعهم وبوضعك لرابط المنتج من امازون سيقوم موقع sa.edfa3ly.co بقراءة تفاصيل المنتج كاملة (بإمكانك تجربته). الـ WEB API هي منصة تساعدك على إنشاء برمجيات بهذا الشكل ... خذ مثالا آخر لدينا الرقم الوطني في السودان هذا الرقم قامت بإنشاءه وزارة الداخلية وهي المسؤولة عنه وإذا كان لديك انت المطور برنامج (برنامج لمؤسسة حكومية) يعتمد على الرقم الوطني ستقوم بمخاطبة المسؤولين بوزاة الداخلية لطلب رابط يتيح لك تزويدك بالرقم الوطني للمواطن واسمه وعمره وصورته ان أردت (كما في التقديم لحج هذا العام 2018) ويعتبر هذا الرابط كقناة Channel تقوم أنت بإدراجه في تطبيقك كتنفيذ Implementation لأي جزء في تطبيقك يحتاج التعامل مع بيانات الرقم الوطني. خذ مثالا أيضا البنوك لدينا تقوم بإستخدام الـ Web Services في حال أردت فتح حساب لا بد أن يكون لديك رقم وطني أو لابد أن يكون لديك جواز ساري المفعول فتطبيقات البنوك أصبحت مربوطة بقاعدة بيانات الرقم الوطني. الآن نحن سنقوم بعمل تطبيق بإمكانه أن يكون كقناة Channel يمكن إستدعائه من تطبيقات أخرى حيث سنقوم بعمل استجابة Response للتطبيقات الأخرى ودعمهم بصيغة للبيانات المطلوبة في شكل JSON أو XML المشروع هنا بسيط جدا..(أي ليس بإمكانك بيعه بتلك الطريقة ! لكن سأحاول بقدر الإمكان أن أضع تلك التقينة في سياقها الصحيح من خلال هذا المشروع..لكن أن كنت تعمل على نطاق رسمي فستحتاج للكثير لتقوم بتطويره من حيث عدة أجزاء خصوصا من ناحيتي الأمان Security والأداء). **بناء مشروع** **ASP.NET WEB API** لقد قمت بإستخدام الفيجوال ستديو 2015 لكن بإمكانك العمل على الفيجوال ستديو 2013 أو فيجوال اكسبريس 2013 للويب...كما قمت بإستخدام الـ SQL Server Express LocalDB والتي تتيح لي العمل داخل بيئة الفيجوال ستديو نفسه... الآن بإمكاننا أن نبدأ.. قم بفتح الفيجوال ستديو 2015 قم بإنشاء مشروع جديد من على يسار الشاشة التي ستفتح لك قم بالضغط على Other Project Solutions قم بإختيار Blank Solution وقم بتسميته DiscoverWebApi وحدد مسار حفظ المشروع على أي قرص صلب على جهازك كما موضح بالصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/07/3-300x184.png) بعد ان يقوم الفيجوال ستديو بإنشاء الـ Solution سنقوم بإنشاء مكتبتين للفئات Class Library وسنستخدم لغة الـ C# .. قم بإنشاء مكتبة الفئات الأولى وأعطها الاسم DiscoverWebApi.Models وقم بإنشاء مكتبة الفئات الأخرى وأعطها الاسم DiscoverWebApi.Repository. سيقوم الفيجوال ستديو بإنشاء فئة بإسم Class1 قم بحذفها في المكتبتين. انظر للصورة في الاسفل لمعرفة كيفية انشاء مكتبة الفئات: ![](https://informatic-ar.com/wp-content/uploads/2018/07/4-300x184.png) \*\*\* انتبه من ان تقوم بإختيار Class Library Portable فتلك مكتبة تستخدم لإنشاء الـ DLL الخاصة بك أولا وبإمكانك إستدعائها لأي نوع مشروع سواء Javascript – WCF – Win APP – ASP.NET لكن لا نريد تلك المكتبة فقط قم بإختيار الـ Class Library.. وإستخدامنا لتلك الطريقة في إنشاء المشروع هذا تعني اننا سنقوم بإستخدام أنماط التصميم Design patterns والنمط الذي سنستخدمه سيكون النمط المستودعي Repository Pattern. بعد إنشائنا لمكتبات الفئات تلك سنقوم بإنشاء قاعدة البيانات والتي سنقوم بحفظ بيانات المواطنين عليها... قم بفتح الـ Server Explorer لديك من خلال الفيجوال ستديو 2105 أو 2013 وقم بالضغط على شعار موصل الكهرباء والذي توجد به علامة الجمع (+) في فيجوال ستديو 2013 اما الفيجوال ستديو 2015 سيكون بالشكل التالي كما موضح بالصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/07/5.png) قم بإختيار Microsoft SQL Server من شاشة Choose Data Source وقم بالضغط على Continue كما موضح بالصورة (إذا كنت تستخدم الـ SQL Server دائما قم بالتعليم على المربع الصغير اسفل الشاشة..وذلك سيكون إختيار افتراضي للفيجوال ستديو عند انشاء مشاريعك بدلا من فتح تل الشاشة كل مرة لعمل اتصال بين المشروع وقاعدة البيانات): ![](https://informatic-ar.com/wp-content/uploads/2018/07/6-300x204.png) قم بكتابةlocaldb)\\v11.0) في خانة اسم الخادم Server Name (إذا كنت تعمل على خادم مختلف قم بكتابته بدلا من الخادم الذي استخدمه) وقم بكتابة WebAPI\_DB في خانة Select or enter a database name وقم بالضغط على OK سيقوم الفيجوال ستديو بتنبيهك بإنه لا توجد قاعد بيانات بإسم WebAPI\_DB هل تريد إنشائها قل له نعم...سيقوم الفيجوال بإنشاء قاعد البيانات. قم بالضغط بالزر الأيمن للمؤشر (ماوس) على قاعدة البيانات التي أنشئتها قبل قليل ومن القائمة المنسدلة التي ستظهر لك قم بإختيار New Query ستفتح لك صفحة خالية وقم بلصق الإستعلام التالي عليها: ```sql CREATE TABLE [dbo].[AccountDetails] (     [PersonID]           INT           IDENTITY (1, 1) NOT NULL,     [Name]               NVARCHAR (50) NOT NULL,     [NID]                NVARCHAR (50) NOT NULL,     [Received ]          BIT           NULL,     [ReceivedDate]       DATETIME      NULL,     [IsProcessFinishing] BIT           DEFAULT ((0)) NOT NULL,     PRIMARY KEY CLUSTERED ([PersonID] ASC) ); ``` وكما موضح بالصورة التالية؛ قم بتحديد قاعدة البيانات التي أنشئتها قبل قليل وقم بالضغط على زر البدء...وتلك الجملة الإستعلامية تقوم بإنشاء جدول بإسم AccountDetails وعدد 6 حقول في هذا الجدول (رقم الحقل – اسم المواطن – الرقم الوطني للمواطن – تحديد استلام الجواز من عدمه – تاريخ استلام الجواز – وهل انتهى الجواز من الاصدار او التجديد من عدمه). ![](https://informatic-ar.com/wp-content/uploads/2018/07/7-300x80.png) في نفس الصفحة التي قمت بفتحها في الخطوة السابقة قم بلصق تلك الجملة الإستعلامية الأخرى، وهي جملة تقوم بإنشاء بيانات داخل قاعدة البيانات التي أنشأناها: ```sql SET IDENTITY_INSERT [dbo].[AccountDetails] ON INSERT INTO [dbo].[AccountDetails] ([PersonID], [Name], [NID], [Received ], [ReceivedDate], [IsProcessFinishing]) VALUES (1, N'Mohammed', N'123456', NULL, NULL, 0) INSERT INTO [dbo].[AccountDetails] ([PersonID], [Name], [NID], [Received ], [ReceivedDate], [IsProcessFinishing]) VALUES (2, N'Ahmed', N'1234567', NULL, NULL, 0) INSERT INTO [dbo].[AccountDetails] ([PersonID], [Name], [NID], [Received ], [ReceivedDate], [IsProcessFinishing]) VALUES (3, N'Omram', N'12345678', NULL, NULL, 0) INSERT INTO [dbo].[AccountDetails] ([PersonID], [Name], [NID], [Received ], [ReceivedDate], [IsProcessFinishing]) VALUES (4, N'Ghassan', N'123456789', NULL, NULL, 0) SET IDENTITY_INSERT [dbo].[AccountDetails] OFF ``` لقد أنشئت في البداية المشروع الآن قم بالضغط بالزر الأيمن للمؤشر (ماوس) على اسم الـ Solution لديك DiscoverWebApi وقم بإختيار Add ومن ثم New Project ستفتح معك نافذة قم بإختيار Web من على يسار الشاشة وقم بإختيار الـ ASP.NET WEB Application وقم بإعطاء المشروع إسم DiscoverWebApi.Web.API واضغط على OK ستفتح معك نافذة أخرى قم بإختيار الخيار Empty منها وأسفل الشاشة قم بالتعليم على منصة Web API ولا تنسى ان تحذف العلامة التي من على يمين الشاشة من الخيار Host in the cloud وقم بالضغط على OK كما موضح بالصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/07/8-300x237.png) الأن سيكون شكل المشروع بالصورة التالية حيث لدينا مساحة عمل (Solution) بها مكتبتي فئات ومشروع WEB API (انظر للصورة التالية): ![](https://informatic-ar.com/wp-content/uploads/2018/07/9.png) قم بالضغط بالزر الأيمن للمؤشر (ماوس) على مشروع DiscoverWebApi.Web.API ومن القائمة المنسدلة قم بإختيار Set as Startup Project وذلك يعني عند تشغيل المشروع على المتصفح سيكون هذا هو المشروع الرئيسي... الآن سنقوم بربط الفئات ببعضها..قم بالضغط على مشروع DiscoverWebApi.Web.API بالزر الأيمن للمؤشر (ماوس) ومن القائمة المنسدلة قم بإختيار ADD ومنها ADD Reference ستفتح معك شاشة على يسارها مجموعة خيارات، يوجد خيار بإسم Solution قم بالضغط عليه، ستظهر معك مكتبات الفئات الموجودة بمساحة العمل، قم بالتعليم عليهم الأثنين واضغط OK. قم أيضا بعمل تلك العملية لمكتبة الفئات DiscoverWebApi.Repository وقم بربطها مع مشروع DiscoverWebApi.Models فقط. > لاحقا سنقوم بإنشاء مشروع Unit Testing لا تقلق..الآ تركيزنا في إنشاء أجزاء المشروع. قم بفتح مشروع الـ DiscoverWebApi.Models وقم بإنشاء فئة (Class) وأعطها الإسم AccountDetail.cs وقم بلصق الشفرة التالية عليها وأحفظ المشروع : ```csharp using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations;    [Table("AccountDetails")]    public class AccountDetail     {         [Key]         public Int32 PersonID { get; set; }         public String Name { get; set; }         public String NID { get; set; }         public Boolean ? Received { get; set; }         public Nullable ReceivedDate { get; set; }         public Boolean IsProcessFinishing { get; set; }     } ``` هذه الفئة ستكون محرك العمل حيث سنقوم بتحديدها كأنها ستمثل الجدول الذي أنشأناه سابقا في قاعدة البيانات وإذا لاحظت أن خواص *Properties* هذه الفئة هي مثلها حقول الجدول AccountDetails الذي أنشأناه سابقا. إذا لا حظت في جدول AccountDetails الذي لدينا في قاعدة البيانات توجد حقول تأخذ القيمة *NULL* كحقلي الـ **Received** و **ReceivedDate** وحتى أقوم بالتبين للفئة Person.cs بفراغ هاذين الحقلين قمت بإستخدام العلامة ؟ بالنسبة للحقل Received واستخدام كلمة AccountDetails { get; set; }     } ``` هذه الفئة مهمتها أنشاء جدول وهمي *Virtual table* بإسم **AccountDetails** وهو نفس اسم الجدول الموجود بقاعدة البيانات و يساعدنا على تنفيذ الجمل الإستعلامية التي سنحتاجها للعمل مع قاعدة البيانات...حيث ترث هذه الفئة من فئة أخرى وهي **DbContext** وهذه الفئة بها جميع خصائص الحفظ والتعديل والمسح والقراءة التي نستخدمها في الـ SQL. قم بإنشاء واجهة Interface بإسم IPersonRepository.cs داخل مكتبة الفئات DiscoverWebApi.Models وقم بلصق الشفرة التالية عليها: ```csharp public interface IPersonRepository     {        IQueryable GetAllPersons { get; }        AccountDetail GetPerson(int id);        void SavePerson(AccountDetail person);        AccountDetail DeletePerson(int id);     } ``` وبما أننا سنسخدم الـ Dependency Injection بالتالي قمت بإستخدام الواجهات والتي ستحقن في فئة أخرى ستقوم بإنشاءها في الخطوة التالية. قم بإنشاء فئة جديدة بإسم PersonRepository.cs داخل مشروع الـ DiscoverWebApi.Repository وقم بلصق الشفرة التالية داخلها: ```csharp using System.Data.Entity; using DiscoverWebApi.Models; public class PersonRepository : IPersonRepository     {        private EFDbContext _context = new EFDbContext();        public IQueryable GetAllPersons        {            get { return _context.AccountDetails; }        }        public AccountDetail GetPerson(int id)        {            return _context.AccountDetails.FirstOrDefault(p => p.PersonID == id);        }        public void SavePerson(AccountDetail person)        {            if (person.PersonID == 0)            {                person.Received = false;                person.ReceivedDate = null;                person.IsProcessFinishing = false;                _context.AccountDetails.Add(person);                          }            else            {                AccountDetail entry = _context.AccountDetails.Find(person.PersonID);                if (entry != null)                {                    entry.Name = person.Name;                    entry.NID = person.NID;                    entry.Received = person.Received;                    entry.ReceivedDate = person.ReceivedDate;                    entry.IsProcessFinishing = person.IsProcessFinishing;                }                         }            _context.SaveChanges();        }        public AccountDetail DeletePerson(int id)        {            AccountDetail person = _context.AccountDetails.Find(id);            if (person != null)            {                _context.AccountDetails.Remove(person);                _context.SaveChanges();            }            return person;        }     } ``` في هذا المشروع سنستخدم الـ Dependency Injection وهي تقينة تتيح لمشروع الـ WEB API بإنشاء مثيل Instance لفئة الـ PersonRepository وذلك بدلا من إنشائها في كل طلب أثناء عمل التطبيق (Services) لأنها ستقلل من كفاءة الذاكرة وستحتاج الذاكرة لعملية الـ Dispose من خلال الـ GC والذي يقوم الـ CLR run-time بتنفيذه...بالتالي استخدام مكتبات الـ DI ستقلل من تلك العملية الطويلة وتحافظ على كفاءة النظام...في هذا المشروع سأقوم بإستخدام مكتبة الـ Unity.بإمكانك مراجعة هذا مقالي الذي كتبته في موقع انفورماتيك والذي يوضح مفهموم الـ DI بإستخدام مكتبة الـ Ninject من على الرابط التالي: > [كيفية تهيئة مكتبة الـ Ninject للعمل مع مشروع الـ ASP.NET MVC](https://informatic-ar.com/ninject-aspnet-mvc/) قم بتثبيت مكتبة الـ Unity.Web.Api من خلال الـ Nuget package Manager. كما موضح بالأسفل في الصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/07/1-300x197.png) سيقوم الفيجوال ستديو بإنشاء فئة بإسم UnityConfig.cs داخل ملف App\_Start قم بفتح تلك الفئة وقم بلصق الشفرة التالية عليها داخل الإسلوب RegisterComponents() : ```csharp using DiscoverWebApi.Models; using DiscoverWebApi.Repository; container.RegisterType(); ``` قم بفتح ملف الـ Gloabal.asax وقم بلص السطر التالي داخل الإسلوب Application\_Start() كالتالي: ```csharp using DiscoverWebApi.Web.API; UnityConfig.RegisterComponents(); ``` الآن قم بفتح ملف الـ web.Config وسنقوم بتعريف جذع الإتصال مع قاعدة البيانات..قم بكتابة الشفرة التالية في سطر واحد: ```markup       ``` > لاحظ إذا كان اسم الخادم لديك يختلف فقم بإدخاله صحيحا. الخطوة المهمة الآن هي إنشاء ملف فئة التحكم **Controller** ..قم بالضغط بالزر الأيمن للمؤشر (ماوس) على ملف الـ Controller وقم بإختيار Add ومن ثم Controller ستفتح معك شاشة قم بإختيار Web API2 Controller – Empty وقم بالضغط على OK ستفتح معك شاشة صغيرة أخرى قم بإعطاء تلك الفئة اسم PersonsController واضغط Ok...ثم قم بنسخ الشفرة التالية داخل تلك الفئة (سنشرح هذه الفئة بالتفصيل): ![](https://informatic-ar.com/wp-content/uploads/2018/07/10-300x208.png) ```csharp using System.Web.Http.Description; using DiscoverWebApi.Models; using DiscoverWebApi.Repository; public class PersonsController : ApiController     {         private IPersonRepository _repository;         public PersonsController(IPersonRepository repository)         {             _repository = repository;         }         // GET: // http://localhost:3570/api/persons/getpersons         public IEnumerable GetPersons()         {             return _repository.GetAllPersons.AsEnumerable();         }         [ResponseType(typeof(Person))]         public IHttpActionResult GetPerson(int id = 0)         {             AccountDetail person = _repository.GetPerson(id);             if (person == null)             {                 return NotFound();             }             return Ok(person);         }         [ResponseType(typeof(AccountDetail))]         public IHttpActionResult PostPerson(AccountDetail person)         {             if (!ModelState.IsValid)             {                 // you can accept ModelState itself as message parameter.                 return BadRequest("Sorry There is somthing ERROR");             }             _repository.SavePerson(person);             return CreatedAtRoute("DefaultApi", new { id = person.PersonID }, person);         }         [ResponseType(typeof(AccountDetail))]         public IHttpActionResult DeletePerson(int id)         {             AccountDetail person = _repository.DeletePerson(id);             if (person == null)             {                 return BadRequest();             }             return Ok(person);         }     } ``` كل العمل يتركز على تلك الفئة..فما قمنا بفعله سابقا لا يعدو كونه إجراءات روتينية لإنشاء ملفات المشروع وربطها بقاعدة البيانات...أما الآن فتلك الفئة هي التي ستتحكم في المشروع بصورة كلية...أولا لاحظ أن تلك الفئة ترث من فئة اسمها APIController عكسا من الـ MVC التي ترث من الـ Controller. في الـ WEB API لا توجد صفحات HTML فقط يتم إرجاع البيانات للمستخدم Client في صيغتي *JSON* و *XML* حيث يستخدم الـ IE صيغة الـ JSON والـ Google Chrome صيغة الـ XML ... إذا نظر لأول اسلوب وهو GetPerson حيث يقوم بإرجاع البيانات في شكل مصفوفة بدون عرضها على صفحة HTML أيضا في الإسلوب الثاني GetPerson يأخذ معامل واحد من النوع Int32 ويقوم ذلك الإسلوب بإرجاع **HTTP 404** إذا لم يجد أي بيانات وإذا وجد بيانات يقوم بإرجاع الإسلوب **OK** ويترجم للـ **HTTP 200** أيضا في الإسلوبين PostPerson و DeletePerson إذا كان هناك أي خطأ سيقوم الـ WEB API بإرجاع اسلوب يُسمى **BadRequest** وهو عبارة عن **HTTP 400** ويحتوي على معامل أختياري **Optional paramete**r يأخذ إذا أردت رسالة نصية توضح نوع الخطأ وهو يعتمد على ***ModelStateDictionary*** وقد قمت بتوضيح ذلك في comment في الإسلوب PostPerson. أيضا لاحظ ان جميع الأساليب تأخذ النوع **IHttpActionResult** وهي تقوم بتسهيل مجموعة من العمليات التي تقوم بإرجاع البيانات للمستخدم أيضا كما تقوم بتسهيل عملية إختبار الشفرات كما سنرى في الجزء الأخير من هذا المشروع. أخيرا وليس آخرا قم بفتح ملف الـ WebApiConfig.cs وقم بتحديث عنوان الوصول ليصبح كالتالي: ```csharp config.Routes.MapHttpRoute(                 name: "DefaultApi",                 routeTemplate: "api/{controller}/{action}/{id}",                 defaults: new { controller = "Persons", action = "GetPersons", id = RouteParameter.Optional }             ); ``` > هنا قمت بإستخدام عنوان الوصول الإفتراضي لكن بالتأكيد في المشاريع الكبيرة والمتوسطة لن تقوم بفعل هذا .. فيتوجب عليك إنشاء عناوين وصول لجميع الأساليب التي تقوم الـ *IHttpActionResult* بإرجاعها الآن قم بتشغيل المشروع على أي متصفح لديك وقم بلصق الرابط التالي على صندوق عناوين الوصول: [http://localhost:3570/api/persons/getpersons](http://localhost:3570/api/persons/getpersons?ref=informatic-ar.com) لا تنسى سيختلف المنفذ لديك (لأنن استخدم الـ *IIS* *Express*) ستظهر معك تلك البيانات كما في الصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/07/11-300x166.png) الآن الحمدلله لقد أنتهينا من المشروع كليا...**ومهمتي** الأخيرة هل عمل اختبار للشفرات التي كتبناها في فئة التحكم PersonsController **ومهمتك** **أنت** أن تقوم بإنشاء مشروع الـ WEB Application الذي يستدعي مشروع الـ WEB API ولو فرضنا اننا نريد أن نقوم بتشغيل الـ WEB API هذا لدى إحدى البنوك بحيث لن يستطيع أي عميل فتح حساب مالم يكون جوازه غير منتهي الصلاحية...عندها بالإمكان إضافة إسلوب للـ PersonsController كالتالي: ```csharp public IHttpActionResult ValidToOpenAccountForCustomer(int id)         {             var customer = _repository.GetPerson(id);             // var account = customer.IsProcessFinishing == true;             if (customer.IsProcessFinishing == true)             {                 return Ok();             }             else             {                 return BadRequest("Sorry This person his Passport NOT valid");             }                  } ``` الآن بإمكان البنك أن يستخدم ذلك الإسلوب لفتح حسابات للعملاء عن طريق جوازاتهم..فاذا كان الجواز صالح قم بإنشاء حساب *OK - IHttpActionResult* وان كان غير ذلك لا تقم بإنشاء حساب. أرايت كيف تعمل تلك العملية بسهولة...بالتأكيد لن تذهب للبنوك وتقول لهم لقد قمت بإنشاء Services لفتح الحسابات بتلك الصورة...هذه صورة تقديرية فقط..لكنها تعمل - فط تحتاج لكثير من العمل..وكلي ثقة بأنك تستطيع تطويرها..فقط أردت توضيح الفكرة لديك. **عمل إختبار للشفرات** قم بإنشاء مشروع جديد داخل الـ Solution وقم بإعطاءه الإسم DiscoverWebApi.UnitTests وقم بربط المشروع بالمشاريع الثلاثة التي أنشأتها داخل الـ Solution وقم بتثبيت مكتبة الـ Microsoft.AspNet.webapi.core من خلال Nuget package Manager وستقوم تلك المكتبة بتثبيت مكتبات أخرى معها.أيضا قم بتثبيت مكتبة الـ Moq. ![](https://informatic-ar.com/wp-content/uploads/2018/07/12-300x157.png) بعد إنشائك لمشروع اختبار الأكواد يقوم الفيجوال ستديو بإنشاء فئة بإسم UnitTest1.csقم بفتحها وقم بلصق الشفرة التالية داخلها: ```csharp using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; using System.Web.Http.Results; using DiscoverWebApi.Web.API.Controllers; using DiscoverWebApi.Models; using DiscoverWebApi.Repository; using Moq; using System.Linq; using System.Web.Http; [TestClass]     public class UnitTest1     {         [TestMethod]         public void GetAllPersons_Should_ReturnAllPersons()         {             Mock mock = new Mock();             mock.Setup(m => m.GetAllPersons).Returns(new AccountDetail[]             {                 new AccountDetail { IsProcessFinishing = true, Name = "Mohammed", NID = "123456", PersonID = 1, Received = true, ReceivedDate = DateTime.Now },                 new AccountDetail { IsProcessFinishing = true, Name = "Ahmed", NID = "1234", PersonID = 2, Received = true, ReceivedDate = DateTime.Now },                 new AccountDetail { IsProcessFinishing = false, Name = "Hussien", NID = "126", PersonID = 3, Received = true, ReceivedDate = DateTime.Now },                 new AccountDetail { IsProcessFinishing = true, Name = "Babiker", NID = "12", PersonID = 4, Received = false, ReceivedDate = DateTime.Now },                 new AccountDetail { IsProcessFinishing = false, Name = "weeeeeYaaaaa", NID = "12345698", PersonID = 5, Received = false, ReceivedDate = DateTime.Now }             }.AsQueryable());             PersonsController controller = new PersonsController(mock.Object);             var result = controller.GetPersons();             Assert.AreEqual(5, result.Count());         }         [TestMethod]         public void GetPerson_Should_ReturnOnePerson()         {             var mock = new Mock();             mock.Setup(x => x.GetPerson(40)).Returns(new AccountDetail { PersonID = 40 });             PersonsController controller = new PersonsController(mock.Object);             IHttpActionResult actionResult = controller.GetPerson(40);             var contentResult = actionResult as OkNegotiatedContentResult;             Assert.IsNotNull(contentResult);             Assert.IsNotNull(contentResult.Content);             Assert.AreEqual(40, contentResult.Content.PersonID);         }                [TestMethod]         public void WhenGiveWrongId_ShouldReturn_NotFound()         {             var mock = new Mock();             PersonsController controller = new PersonsController(mock.Object);             IHttpActionResult actionResult = controller.GetPerson(400);             Assert.IsInstanceOfType(actionResult, typeof(NotFoundResult));         }         [TestMethod]         public void DeletePerson_ShouldReturn_Ok()      {             var mock = new Mock();             mock.Setup(x => x.GetPerson(40)).Returns(new AccountDetail { PersonID = 40 });             PersonsController controller = new PersonsController(mock.Object);             IHttpActionResult actionResult = controller.DeletePerson(40);             Assert.IsInstanceOfType(actionResult, typeof(BadRequestResult));         }         [TestMethod]         public void PostPerosn_ShuoldReturn_DefaultUrl()         {             var mock = new Mock();             var controller = new PersonsController(mock.Object);             IHttpActionResult actionResult = controller.PostPerson(new AccountDetail { PersonID = 1, Name = "Mohammed" });             var createResult = actionResult as CreatedAtRouteNegotiatedContentResult;             Assert.IsNotNull(createResult);             Assert.AreEqual("DefaultApi", createResult.RouteName);             Assert.AreEqual(1, createResult.RouteValues["id"]);         }     } ``` لدينا جميع الإحتمالات الواردة والتي يمكننا عمل إختبار لها وبعد تنفيذك لهذا الإختبار ستظهر معك الصورة التالية والتي يوجد بها العلامة الخضراء والتي تبين لك أن مشروع جاهز للنشر بعد فحص جميع أجزاءه وهي تعمل بصورة صحيحة. ![](https://informatic-ar.com/wp-content/uploads/2018/07/13-300x132.png) الآن مهمتك هي عمل اختبار للأسلوب الأخير الذي قمنا بإضافته للتأكد من أن جواز المواطن صالح حتى يتسنى له فتح حساب بنكي...اختبرها الآن..بإمكانك. مهلا ... ما رأيك نقوم بتنفيذه مع بعضنا البعض الأسلوب لدينا يقوم بإرجاع الـ *IHttpActionResult* من النوع *OK* وبالتالي سنوقم بالتأكد من صحة نوع البيانات التي سيقوم الخادم بإرجاع **Response** من النوع *OK.*.. قم بلصق تلك الشفرة على فئة UnitTest1.cs ```csharp [TestMethod]         public void ValidToOpenAccountForCustomer_ShouldReturnOk_WhenThePassportIsValid()         {             var mock = new Mock();             mock.Setup(x => x.GetPerson(40)) .Returns(new AccountDetail { PersonID = 40, IsProcessFinishing = true });             PersonsController controller = new PersonsController(mock.Object);             IHttpActionResult actionresult = controller.ValidToOpenAccountForCustomer(40);             Assert.IsInstanceOfType(actionresult, typeof(OkResult));         } ``` الآن انظر للصورة التالية...لقد قمنا بعمل فحص للأسلوب ValidToOpenAccountForCustomer وقد نجح الفحص...(الآن هل يمكنك عرض المشروع لأحد البنوك...بالتأكيد يحتاج للكثير!!). ![](https://informatic-ar.com/wp-content/uploads/2018/07/14-300x250.png) تحياتي - محمد ### التقدير الخطي Linear Regression خوارزميات تعلم الآلة URL: https://informatic-ar.com/linear-regression/ Last updated: 2026-10-03T22:31:28.000Z سميت بهذا الإسم لأنها تفترض أن النتائج الغير معروفة يمكن تقديرها بالإعتماد على معادلة الخط المستقيم y=mx+b لنفترض أن لدينا مجموعة بيانات ممثلة بهذا الجدول وهذا المخطط لأطوال ساق نبتة في عدة أيام مختلفة | **(طول ساق النبتة (سم** | **اليوم** | | ----------------------- | --------- | | 0.5 | الأول | | 0.9 | الثاني | | 1.5 | الثالث | | 2.3 | الخامس | | 3 | السادس | ![](https://4.bp.blogspot.com/-nosF1rtF7oo/WWf42V-VhVI/AAAAAAAABSM/l-N73UdW0nkGk3VagiyQoG5IavFRJqNGQCLcBGAs/s1600/plot1.jpeg.jpg) مخطط يوضح العلاقة بين الزمن و طول ساق النبتة إذا أردنا مثلا أن نعرف طول ساق النبتة في اليوم الأول سنرى ما يقابلها في الجدول وهو 0.5 سنتيمتر، في اليوم الثاني كانت بطول 0.9 سنتمتر إذا العملية بسيطة جدا إذا توفر لدينا جدول لكل القيم التي نحتاجها وهذا لن نجده دائما فمثلا إذا أردنا أن نقدر طول ساق النبتة في اليوم الرابع لن نستطيع معرفة ذلك من الجدول مباشرة لكن باستخدام معادلة الخط المستقيم يمكننا تقدير ذلك إذا قمنا برسم خط مستقيم وحاولنا أن نجعله يمر بأكبر عدد ممكن من النقاط في عملية تسمى ملائمة خطية (linear fitting) هكذا ![](https://1.bp.blogspot.com/-fDmeVomOoCE/WWgJCplrRgI/AAAAAAAABSg/_b1NEnRaxZ4wO2acahgTVc1CML4AERh8wCLcBGAs/s1600/plot3.jpg) مخطط يوضح ملائمة الخط المستقيم للنقاط المبعثرة على المستوى ## الآن نستطيع تقدير طول ساق النبتة في اليوم الرابع وذلك بالتعويض في معادلة هذا الخط المستقيم عن قيمة اليوم الرابع (الزمن) وحساب طول ساق النبتة أو من خلال الرسمة مباشرة سنرى بأنه يساوي 1.9 سنتيمتر تقريبا وهكذا أصبح بإمكاننا تقدير طول ساق النبتة في أي ساعة وفي أي لحظة باستمرار . لاحظ أن ميل الخط المستقيم m ونقطة تقاطع الخط المستقيم مع المحور الرأسي b وكلما كانت قيمة m وb مضبوطتان بشكل صحيح كلما كانت القيم المقدرة أقرب إلى القيمة الحقيقية لكن كيف سنقوم بملائمة الخط المستقيم على مجموعة من البيانات وتحديد الزاوية الأنسب للميل عن طريق برنامج يعمل على الحاسوب ؟ يلزمنا أولا أن نكتب دالة تقوم بحساب قيمة الخطأ للقيم المقدرة وفي كل مرة نقوم بتعديل زاوية الميل والإزاحة الرأسية حتى نصل إلى أقل قيمة خطأ ممكنة الجواب بسيط لكن كيف سنحسب قيمة الخطأ إذا كانت النتائج الحقيقية مجهولة ؟! نحن لدينا مجموعة بيانات معروفة مسبقا سنستخدمها للتدريب، في البداية ستكون زاوية الميل والإزاحة الرأسية قيم عشوائية ثم سنقوم بحساب النتائج في كل مرة بتقديرها عن طريق معادلة الخط المستقيم وسنقارنها بالنتائج المعروفة لدينا مسبقا من الجدول لنقوم بتصحيح الخطأ . دالة حساب قيمة الخطأ لكل نقطة= القيمة الحقيقية - القيمة المقدرة ![](https://4.bp.blogspot.com/-YH7-6lpgbUs/WWlqPxbTAhI/AAAAAAAABTI/ILXAE7t4eGk8blPrudd8LtnRLk1H-zsgACLcBGAs/s1600/45647486.png) ## | a(x) | طول ساق النبتة الحقيقي في اليوم x | | ---- | ---------------------------------------------------- | | y(x) | طول ساق النبتة الذي تم تقديره بواسطة المعادلة y=mx+b | لكن هذه الدالة ستقوم بحساب قيمة الخطأ لنقطة واحدة فقط يعني في كل يوم على حدة ونحن نريد أن تحسب قيمة الخطأ الكلي لجميع النقاط وهذا ممكن عن طريق هذه الدالة التي ستقوم بحساب قيمة الخطأ الكلي وهذه الدالة عادة يطلق عليها دالة الكلفة أو دالة الخطأ ## **![](https://4.bp.blogspot.com/-Bcdf14i-9ZQ/WWlpw7hTYGI/AAAAAAAABTA/5vzBxZ-i-EktL-R3x0-mh2WSxYuii0CuACLcBGAs/s1600/3.png)** من الواضح أن الدالة تقوم بحساب قيمة الخطأ لكل نقطة ثم تجمعها مع قيمة الخطأ للنقطة التي تليها ثم ستقسم الناتج على عدد النقاط لتحسب متوسط الخطأ ، يمكننا تفسير التربيع أيضا بأننا لا نريد قيم سالبة لكن لماذا ضربنا الناتج في ½ ? هذا الثابث سيساعدنا على تبسيط المعادلة عندما نحتاج إلى تفاضل ما بين الأقواس المرفوعة للأس التربيعي سنستخدم قاعدة السلسلة ونضرب الناتج في الأس وهو في حالتنا هذه 2 ثم نختصر الإثنين في المقام والبسط لنتخلص من الثابت وهكذا تم تبسيط المعادلة هذه الدالة ستمكننا من حساب قيمة الإنحراف الكلي للنقاط . الآن بعد أن كتبنا معادلة نستطيع من خلالها تقدير قيمة الخطأ الكلي لتموضع الخط المستقيم على البيانات المبعثرة في المستوى (x,y) سنحتاج طريقة ما لتصحيح ميل المستقيم وقيمة إزاحته الرأسية بناءا على قيمة الخطأ لهذا الغرض سنستخدم إحدى تقنيات التحسين (Optimization Techniques) وهي خوارزمية الإزاحة المتدحرجة أو gradient descent شائعة الإستخدام هذه التقنية تعتمد على مبدأ بسيط وهو السير بخطوات تكرارية في الإتجاه المعاكس لتدرج الدالة ولأن تدرج الدالة أو بمعنى آخر تفاضلها الجزئي دائما يكون في إتجاه أكبر معدل زيادة لها فإن الخطوات التي تسير عكس هذا الإتجاه ستقلص قيمة الدالة مع الوقت ، لا تقلق إذا لم تتضح الصورة جيدا تابع هذا الشرح ## الإزاحة المتدحرجة أو gradient descent التدرج أو gradient هو تعميم التفاضل على الدوال متعددة المتغيرات، إذا كان بإمكانك تفاضل دالة في متغير واحد كذلك يمكنك حساب تدرج دالة في متغيرات متعددة وذلك باستخدام التفاضل الجزئي بالنسبة لمتغير واحد في كل مرة مثلا إذا كان لدينا دالة في متغيرات (x,y) موضحة باللون الأزرق في المخطط بالأسفل فنتيجة التدرج هي المتجهات الموضحة باللون الأحمر لاحظ أن اتجاه المتجهات يشير إلى أكبر معدل زيادة للدالة في كل نقطة وأن مقدارها يمثل الإنحناء slope هذا يعني أن المتجهات أكبر في الأماكن التي تزداد فيها قيمة الدالة بشكل أكبر ولذلك يعرف التدرج بأنه يشير دائما إلى أكبر معدل زيادة للدالة هذه الخاصية يمكننا الإستفادة منها في حالة أردنا العكس وهي القيم التي تنقص فيها قيمة الدالة وذلك بعكس الإتجاه! ![](https://1.bp.blogspot.com/-CMISo2kF96c/WWltoxY-1WI/AAAAAAAABTU/eCxwwrm3eeUoyuliNou1AIOfXPzb_G66QCLcBGAs/s1600/45457768.png) وهذا هو التعبير الرياضي لخوارزمية الإزاحة المتدحرجة ![](https://3.bp.blogspot.com/-92oGABXpRaA/WWlpelBzMbI/AAAAAAAABS0/VKm9I1PsfqkPIhPlmjFdfvG39oKk-EcnQCLcBGAs/s1600/45457.png) الدالة F هي دالة الكلفة أو دالة الخطأ الرمز نبلا هو مؤثر التدرج على الدالة F يعني التفاضل الجزئي للدالة F بالنسبة للمتغير a المتغير الذي نريد أن تتقلص قيمة الدالة عنده في الخطوة التالية a إذا قمنا برسم مخطط ثلاثي الأبعاد يبين قيمة الخطأ لكل قيم الميل والإزاحة المختلفة،دالة الخطأ ستشكل شكل مشابه لهذا الموجود بالأسفل، قيمة الخطأ الكلي على المحور z مقابل قيمة الميل m وقيمة الإزاحة الرأسية b وطبقنا خوارزمية الإزاحة المتدحرجة على المتغيرات m,b في كل مرة ستقل قيمة الخطأ وعندما تصل إلى أدنى مستوى نكون قد حققنا المطلوب، النقاط الظاهرة باللون البنفسجي تمثل تدحرج الدالة نحو القاع ## **![](https://2.bp.blogspot.com/-oPk3YNkHK-g/WWl4GwaLxCI/AAAAAAAABTo/2Bk28oDNSQYaiJ9Ben136ZmuLWB5WYZfgCLcBGAs/s1600/345assa345.png)** هذه الصورة المتحركة توضح الفرق بين كفاءة عدة تقنيات تستخدم لتقليص قيمة الخطأ وهي مثالية لتوضيح طريقة عملها الكرة الحمراء تمثل الطريقة التي نتحدث عنها وهي تقنية [الإزاحة المتدحرجة Gradient Descent](https://en.wikipedia.org/wiki/Stochastic%5Fgradient%5Fdescent?ref=informatic-ar.com) ![](https://1.bp.blogspot.com/-iyWAfBcb_As/WWqGElRJWfI/AAAAAAAABUA/_NNPhqRqjCcQYOIRh9f-G_0rut2vo6HTACLcBGAs/s1600/Long%2BValley%2B-%2BImgur.gif) ### للمزيد من التوضيح إذا قمنا بتطبيق تقنية الإزاحة المتدحرجة على دالة الخطأ الكلي أو دالة الكلفة ستقوم الخوارزمية بالبحث عن أقل قيمة خطأ في كل خطوة وتنزاح إليها وذلك بتغيير القيم الإبتدائية لميل المستقيم وإزاحته على المحور الرأسي حتى تصل إلى أقل قيمة خطأ ممكنة كما هو موضح بالمسار البنفسجي على المخطط . ## نعلم مما سبق الآن أن الإنحدار أو التدرج (gradient) يعني التفاضل الجزئي للدالة الذي سيعطينا ميل المماس هذا الميل يقع في اتجاه أكبر زيادة للدالة عند طرح هذه القيمة من قيمة المتغير الذي فاضلنا الدالة بالنسبة له سنحصل على قيمة متغير جديدة تتقلص قيمة الدالة عندها ، يمكننا التحكم في مقدار التدحرج عن طريق ثابت لمدا يجب اختياره بشكل مناسب حتى لا تبتعد الدالة كثيرا .بعد تقليص قيمة الخطأ لدالة الكلفة ، يمكننا استخدام القيم الجديدة الناتجة من خوارزمية الإزاحة المتدحرجة ونعوض في معادلة الخط المستقيم ولأن لدينا متغيران m و b سنطبق خوارزمية الإنحدار المتدرج مرتان مرة للمتغير m الذي يمثل ميل الخط المستقيم ![](https://4.bp.blogspot.com/-DvEizg9EaFg/WWlodcNpD-I/AAAAAAAABSw/mUX0yTMoZNYHgiWBMgNYQnBUIDI6lKnHQCLcBGAs/s1600/45.png) ومرة أخرى للمتغير b الذي يمثل نقطة تقاطع المستقيم مع المحور الرأسي ![](https://2.bp.blogspot.com/-cCm15SOerdc/WWlpqsF6ycI/AAAAAAAABS8/v2IQNxZk5o4jNT9GpMVPow1tU7X2uVx3wCLcBGAs/s1600/456.png) عند تكرار هذه الخطوات أكثر من مرة سنحصل على قيم m و b عند اقل قيمة خطأ ممكنة . بعد تحديد أفضل قيمة ميل m وأفضل إزاحة رأسية b أثناء عملية التدريب على البيانات التي لدينا يمكننا كتابة معادلة الخط المستقيم بالقيم الجديدة وتقدير إرتفاع النبتة في أي لحظة بالتعويض في المتغير x وحساب y ## **![](https://2.bp.blogspot.com/--elbKmPongY/WWl7D7mkk3I/AAAAAAAABTw/DWYc3Aa_YEMGgz2t0miUlORVu_860O-1QCLcBGAs/s1600/34543.png)** **ملخص الخطوات**أولا قمنا بتحديد معادلة رياضية لنستخدمها في تقدير النتائج هذه المعادلة في هذا المثال كانت معادلة الخط المستقيم يطلق على هذه المعادلة غالبا hypothisis أو افتراض هذا يعني أنه يمكننا كتابة hypothisis = mx+b or h(x)=mx+b ثانيا قمنا بكتابة معادلة الكلفة أو معادلة الخطأ التي استخدمناها في حساب قيمة الخطأ الكلي للنتائج المقدرة بواسطة معادلة الخط المستقيم رابعا قمنا بتدريب نموذجنا المكون من معادلة تقدير نتائج ومعادلة حساب قيمة الخطأ حتى نحصل على أفضل نتائج للثوابت m و b وهما عادة يرمز لهم بالرمز 𝜽n… 2𝜽 1𝜽 خامسا استخدمنا نموذجنا الذي تم تدريبه في تقدير أطوال ساق النبتة في أوقات غير مدونة في الجدول .هذه النقاط الأساسية سنكررها دائما لكن بطرق مختلفة مع كل خوارزميات التدريب المختلفة .**ملاحظات**ربما تلاحظ أن معادلة الخط المستقيم ستعطي قيم غير معقولة إذا قدرنا طول ساق النبتة بعد زمن طويل فقد تتوقف عن النمو بعد مدة معينة ، في الواقع هي ليست المشكلة الوحيدة التي ستواجهنا فطول ساق النبتة لا ينمو بشكل خطي أصلا وقد تختلف سرعة النمو من نبتة إلى أخرى و هذه المشاكل ستواجهها في كل تطبيقات وخوارزميات تعلم الآلة لذلك لا يجب أن تتوقع أن نماذجنا مثالية فهي يمكنها تقدير نتائج قريبة جدا وعملية لكنها قد تخطيء وتعطي نتائج غير صحيحة لذلك يوجد عدة اجراءات لتقليل نسبة الخطأ قدر الإمكانالإعتماد على مجموعة كبيرة من بيانات التدريب في حالتنا هذه لا يجب الإعتماد على قراءة واحدة لنبتة واحدة ربما نحتاج إلى العشرات منها أيضا لا تتوقع بأنه كلما زادت بيانات التدريب ستزيد الدقة لأنك ستصل إلى مرحلة ستسقر عندها قيمة الخطأ!وضع قيود على البيانات فمثلا في مثالنا هذا ربما نضع شرط لنتأكد من أن الزمن لا يتعدى عشرة أيام أو حتى أسبوع التحجيم عادة يوجد تباين أو اختلاف كبير بين عينات أو بيانات التدريب مع أنها أخذت من حقل واحد ، مثلا كانت لدينا أطوال ساق نبتة في طرف المزرعة وأخرى قريبة من مصدر مياه سيكون هناك اختلاف في سرعة النمو عادة تستخدم علاقات تناسب للتعامل مع شريحة كبيرة من البيانات استخدام فرضية مختلفة (hypothesis) ببساطة إذا كانت هذه الطريقة غير دقيقة استخدم طرق أخرى أكثر دقة فمثلا يمكنك استخدام معادلات غير خطية متعددة الحدود من الدرجة الثانية أو الثالثة أو الرابعة لتجعل المنحنى يتموضع على النقاط بشكل أفضل ### بناء نظام جوازات المواطنين باستخدام الـ ASP.NET MVC والـ Unit Tests URL: https://informatic-ar.com/aspnet-mvc-passports-unit-tests/ Last updated: 2026-10-03T22:31:29.000Z هل أنت مستعد للعمل هنا؟...ستكتب 1000 سطر برمجي اليوم... لنبدأ يتكون هذا التطبيق من ثلاثة أجزاء..وجميعها سنعمل عليها هنا..يتكون من إنشاء قاعدة البيانات والتطبيق...ويتكون من إنشاء إدارة التطبيق..ويتكون من عمل إختبار الشفرات للتطبيق. مقدمة هذا التطبيق به العديد من الخصائص التي قمت بحذفها منه وذلك تسهيلا للشرح والتطبيق الفعلي الذي أنشأته يختلف اختلافا تاما عن ما سأقدمه لك هنا عزيزي المطور ولكن هذا التطبيق الذي ستنشئه هنا سيكون مكتملا من ناحية تعليمية وليس من ناحية عملية..أي لا تستطيع تقديمه لإدارة الجوازات لنشره، اللهم إلا اذا قمت بتطويره بصورة محترفين..وفي الحقيقة سنستخدم في قاعدة البيانات جدول واحد Persons وهذا غير منطقي في حال اردت أن تقوم بتقديمه لإدارة الجوازات ..ففكرة التطبيق هنا (وليس الحقيقي) بسيطة، المواطن سيقدم جوازه للتجديد وسيأتي لإستلامه..والجواز الخاص به سيكون جاهزا فهنا لم أقم بإنشاء جدول يوضح هل تم تجديد الجواز ام لا ولم أقم بإنشاء جدول يوضح في حال تجديد الجواز أين يوجد الجواز الآن وفي أي رفٍ يوجد بمكتب التسليم. > *ملاحظة: هل هذا يعني بأنني لا أريد مشاركة الشفرات التي أكتبها معك عزيزي المطور؟! الإجابة بلا..لا أعني هذا ابدا.. لكن حتى تكون على علم...المشروع هذا به العديد من الخصائص التي قمت بحذفها حتى يصبح بسيطا في الشرح هنا فالمشروع هذا قد قمت بتقديمه في ورشة صغيرة في إحدى الجامعات بالسوان لقسم تقانة المعلومات بتلك الجامعة...وقد قمت بإكماله بعد سفري من السودان بصورة تامة حيث من حيث الـ* *Security* *والـ* *Performance* *وتعديل جميع الشفرات التي ستراها انت هنا جميعها قمت بتغييرها وقد قمت بإستخدام انماط التصميم* *Design patterns* *والـ* *Instrumentation* *والـ* *Profiler* *والـ* *CLR Profiler* *و مكتبات الـ* *Jquery* *و مكتبة الـ* *Caching* *والكثير من الخصائص..كل تلك الخصائص قمت بحذفها من هنا حتى لا يصبح الشرح عسيرا بالنسبة لي أو لانه سيأخذ أيام عديدة ومع ظروف عملي قد يبدو صعبا كتابة شرح لجميع الأكواد والخصائص..وأيضا لتكون على علم عزيزي المطور لقد قمت بعرض المشروع على المسؤولين (...) هناك ولكن لم أجد أي استجابة (...)..فعندما أقول بأنني لن أقوم بعرض جميع شفرات المشروع الحقيقي وشفرات الإختبار Unit Tests فهذا يعني تقليلا للعمل هنا فقط...وسأحاول في مرات قادمات نشر مقالات توضح كيفية اختبار الأكواد مع شرحها ولأنني هنا قمت بنشر أكواد اختبار الشفرات بدون شرح و التي تساعدنا في اختبار برمجياتنا.* **الجزء الأول – إنشاء قاعدة البيانات والتطبيق** بسم الله والصلاة والسلام على رسول الله...في هذا التطبيق سنقوم بمراجعة تامة للـ ASP.NET MVC التطبيق بسيط جدا..وقد كنت قمت ببنائه لغرض مــا..كنت في السودان قبل فترة وعندما أردت تجديد الجواز الخاص بي وعند استلام الجوازكان الإستلام بالبحث عن اسم صاحب الجواز على سبورة وبالتالي العملية كانت صعبة جدا..فقررت بناء تطبيق متكامل لتسليم الجوازات عن طريق جهاز الحاسوب..وقمت ببناء التطبيق وللحقيقة التطبيق الذي ستبنيه اليوم هو تطبيق مصغر جدا من التطبيق الحقيقي، حيث يحتوي التطبيق الحقيقي على العديد من الإمكانيات المتقدمة جدا والتي قمت بحذفها من هنا لكي نتعلم بسهولة تامة كيفية إستخدام الـ ASP.NET MVC في بناء التطبيقات، هذا من ناحية، من الناحية الأخرى ستتعلم كيفية استخدام مختبر الشفرات Unit Tests وعمل اختبار للشفرات التي ستكتبها هنا...وكل هذا سيتم داخل بيئة الفيجوال ستديو...وكالعادة البيئة التي اعمل عليها هو نظام التشغيل Windows بالإضافة للـ Visual Studio 2013 زائداً لغة قواعد البيانات SQL Server من داخل بيئة الـ SQL Server Management Studio، لكن يمكنك استخدام بيئة الفيجوال ستديو نفسها لإنشاء قواعد البيانات والجداول لقواعد البيانات تلك والإجراءات المخزنة Stored Procedures إذا أردت أيضا عن طريق استخدام الـ LocalDB والتي يتم تثبيتها مع الفيجوال ستديو 2013، أيضا بإمكانك استخدام فيجوال ستديو 2012 أو 2015 أو 2017 لعمل هذا التطبيق. سأحاول بقدر الإمكان وضع التطبيق في سياقه الصحيح مع شرح الأكواد التي سنكتبها. **إنشاء المشروع** قم بفتح الفيجوال ستديو وقم بإنشاء تطبيق جديد قم بإختيار الفريم وورك 4.5 ومن على يسار الشاشة قم بالضغط على Web مرتين Double-Click وقم بإختيار Visual Studio 2012 وقم بإختيار المشروع ASP.NET MVC 4 Application قم بتسمية المشروع MvcExplain (بإمكانك تسميته بما يعجبك) وقم بتحديد مسار حفظ التطبيق على أي قرص صلب على جهازك وقم بالضغط على OK. (مرفق الصورة)، بعدها ستظهر شاشة قم بإختيار Basic لهيكلية المشروع ولا تنسى ان تختار No Authentication للمشروع قم أيضا بإختيار محرك نصوص Razor لصفحات الـ Html وقم بالضغط على OK. ![](https://informatic-ar.com/wp-content/uploads/2018/06/1-300x181.png) **إنشاء قاعدة البيانات** بعد أن يقوم الفيجوال ستديو بإنشاء التطبيق قم بفتح الـ Server Explorer، من تلك الشاشة قم بالضغط على العلامة التي بها شعار موصل الكهرباء والتي ملصق عليها علامة الجمع (إنظر للصورة في الأسفل) بالضغط عليها ستفتح معك شاشة لإدخال المزود الخاص بك قم بإدخال LocalDb)\\v11.0) على خانة الـ Server name وفي خانة Select or enter a database name قم بإدخال اسم قاعدة البيانات التالية PassportRecive واضغط على Ok سيسألك الفيجوال ستديو بأنه لاتوجد قاعدة بيانات بهذا الإسم في المزود الخاص الذي قمت بإدخاله، هل تريد إنشاء قاعدة بيانات جديدة بهذا الإسم قل له نعم بالضغط على OK. ![](https://informatic-ar.com/wp-content/uploads/2018/06/2-300x228.png) الآن لقد أنهيت الخطوة الأولى التي تعنى بإنشاء قاعدة البيانات، الآن سنقوم بإنشاء الجدول والحقول التي ستعيننا على تكملة هذا المشروع بصورة صحيحة. قم بالضغط مرتين Double-Click بالزر الأيسر للمؤشر (ماوس) على قاعدة البيانات التي أنشئتها ستفتح معك عدة خيارات داخل قاعدة البيانات تلك قم بالضغط بالزر الأيمن للمؤشر (ماوس) على Table ومن الشاشة التي ستظهر لك قم بإختيار Add New Table قم بلص الشفرة التالية على أسفل الشاشة او شاشة الإستعلامات التي سيفتحها الفيجوال ستديو تلقائيا: ```sql CREATE TABLE Persons ( [PersonID] INT NOT NULL PRIMARY KEY IDENTITY, [Name] NVARCHAR(50) NOT NULL, [NID] NVARCHAR(50) NOT NULL, [Recived] BIT NULL, [RecivedDate] DATETIME NULL ) ``` أعلى الشاشة في نفس الصفحة توجد كلمة Update قم بالضغط عليها حتى يقوم الفيجوال ستديو بإنشاء الجدول وحقول ذلك الجدول. ستفتح لك صفحة تأكيدية بها خاصيتان واحدة لعمل تحديث والأخرى لتوليد نص لقاعدة البيانات، قم بالضغط على Update Database. في هذا الجدول لدينا رقم المواطن الذي ستتولى قاعدة البيانات توليده، ولدينا اسم المواطن ورقمه الوطني NID ولدينا حقل من النوع BIT ويعنى بإستلام الجواز من عدمه وحقل أخير يُعنى بتاريخ استلام الجواز. قم بتنفيذ الجملة الإستعلامية تلك على قاعدة البيانات..وستقوم تلك الجملة بإضافة حقلين لقاعدة البيانات: ```sql SET IDENTITY_INSERT [dbo].[Persons] ON INSERT INTO [dbo].[Persons] ([PersonID], [Name], [NID], [Recived], [RecivedDate]) VALUES (1, N'Mohammed', N'123-87847-888', NULL, NULL) INSERT INTO [dbo].[Persons] ([PersonID], [Name], [NID], [Recived], [RecivedDate]) VALUES (2, N'Ahmed', N'554-78478', NULL, NULL) SET IDENTITY_INSERT [dbo].[Persons] OFF ``` *\*\*\* ملاحظة: في المشاريع الكبيرة دائما ما نستخدم شاشة الإستعلام النصي لإنشاء قواعد البيانات وحفظها على الأقراص الصلبة وعمل أرشفة لقاعدة البيانات بتقنية الـ JSON أو الـ XML أو الـ BSON..ومن خلال خبرتنا فهي مهمة جدا...لكن لأن المشروع هذا بسيط جدا مقارنة بالمشاريع الكبيرة فلن تحتاج لعمل كهذا...لكن إذا قامت إدارة الجوازات بطلب تنفيذ هذا المشروع فبالتالي لن يكون بتلك الصورة كما ذكرت لكم في المقدمة بأنني حذفت العديد من الخواص والخصائص حتى يكون الشرح هنا مبسطا.* **العمل على المشروع الآن** بعد إنشاء قاعدة البيانات سنقوم بعمل ربط بين قاعدة البيانات والتطبيق من خلال ملف الـ Web.Config عن طريق فرع الإتصال connectionString ولتقوم بالربط قم بفتح ملف الـ Web.config وقم بلصق الشفرة التالية عليه: ```markup       ``` لا تنسى كتابة شفرة الإتصال تلك في سطر واحد..وان لم تفعل فلن يتم الإتصال بين التطبيق وقاعدة البيانات. في نص الإتصال لقد قمت بإدخال المزود الخاص بي وهو LocalDb\\v11.0 وإذا كنت تعمل على هذا المزود قم بإدخاله كما مبين سابقا وإن كنت تعمل على مزوّد آخر قم بإدخال المزوّد الذي تعمل عليه. قم بالضغط بالزر الأيمن على المؤشر (ماوس) على ملف الـ Models وقم بإنشاء فئة جديدة بإسم Person وقم بلصق الشفرة التالية في صفحة تلك الفئة: ```csharp    [Table("Persons")]     public class Person     {         [HiddenInput(DisplayValue = false)]         public int PersonID { get; set; }         [Required(ErrorMessage = "Please Enter The Name")]         public string Name { get; set; }         [Required(ErrorMessage = "Please Enter The NID")]         public string NID { get; set; }         [HiddenInput(DisplayValue = false)]         public bool ? Recived { get; set; }         [HiddenInput(DisplayValue = false)]         public Nullable RecivedDate { get; set; }     } ``` قم بإضافة فضاء الأسماء التالية أعلى الفئة: ```csharp using System.Web.Mvc; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; ``` الفئة Person توجد بها نفس الحقول الموجود على الجدول الذي أنشأناه بقاعدة البيانات مع بعض الخواص لكل Property والخواص تلك فرضا تظهر في اسم المواطن توجد خاصية Attribute بإسم Required وستفيدنا عند إستلام الجواز من المواطن حيث ستفرض على المستخدم العادي ادخال الاسم وإلا ستظهر له رسالة خطأ كما موضّحة في المعامل Parameter الذي تأخذه تلك الخاصية..الخاصية الأخرى HiddenInput وقد قمنا ببلورتها لبعض الحقول أيضا كتاريخ استلام الجواز وتعني هنا عدم إظهار الحقل أثناء إنشاء حقل جديد لمواطن ما..لا تقلق سترى كيف تعمل تلك الخواص عندما ننشئ صفحات الـ Html. بالإمكان استخدام الـ Regular Experssion إذا أردت لعمل تحقق يجبر المستخدم على إدخال ارقام فقط بالنسبة للرقم الوطني. لو لاحظت أن اسم المزوّد في ملف الـ web.Config هو PersonDbContext ولكي نقوم بربط التطبيق بقاعدة البيانات سنُنشئ فئة Class بهذا الإسم داخل الملف Models وسنقوم بكتابة الشفرة التالية عليها: ```csharp public class PersonDbContext : DbContext     {         public PersonDbContext() : base("name=PersonDbContext")         {         }         public DbSet Persons { get; set; }     } ``` لا تنسى ان تقوم بإستدعاء فضاء الأسماء التالي في أعلى الفئة: ```csharp using System.Data.Entity; ``` في هذه الفئة قمنا بإستخدام تقنية الـ Entity Framework وهي تقنية من مايكروسوفت تقوم بإنشاء جداول وهمية على شكل فئات تمكننا من العمل عليها بدلا من العمل مباشرة على الجداول التي موجودة بقاعدة البيانات وتلك الجداول الوهمية تعتبر كـ Strongly type أي بمعنى أنه سيقوم الفيجوال ستديو بمساعدتنا للعمل عليها من خلال التكملة عند الكتابة في بيئته أو إذا كان هناك خطأ سيقوم الفيجوال ستديو بإخطارنا بالخطأ Compile time error ومساعدتنا على الأخطاء التي تظهر..وتلك التقنية أتت بديلا لتقنية الـ ADO.NET التي نذكرها جميعا والتي تعمل على تنفيذ الجمل الإستعلامية بصورة نصية داخل بيئة الفيجوال ستديو..وكما هو معروف العمل على الإستعلام النصي سيكون به أخطاء مؤكدة في كتابة الكلمات..ولن يستطيع الفيجوال ستديو اكتشاف الخطأ لأنه من النوع String وسنستخدم تقنية الـ LinQ لتنفيذ الجمل الإستعلامية في التطبيق نسبة لسهولتها وأيضا هي Strongly type. الفئة التي أنشأناها سابقا تحتوي على خاصية Property واحدة وهي Persons وهذه الخاصية مطابقة لإسم الجدول الذي أنشأناه في قاعدة البيانات PassportRecive سابقا، أيضا الفئة ترث من فئة أخرى وهي الـ DbContext وهي تمكننا من عمل الإستعلامات في قاعدة البيانات والجداول الموجود في قاعدة البيانات تلك. أيضا إذا لاحظت قمنا بتعريف اسم الإتصال بصورة صريحة Explicitly من خلال مُنشئ الفئة Constructor. وهذا يقوم بإخبار التطبيق بإستخدام اسم الإتصال PersonDbContext هذا لعمل إتصال مع قاعدة البيانات الموضحة في ملف الـ web.Config.من خلال الجزع Catalog. > *ليس بالضرورة استدعاء منشئ الفئة* *Constructor* *... لكن هذا للتوضيح..لكن ستحتاجه إذا كان اسم الفئة* *Class* *مغاير لإسم الإتصال في ملف الـ* *web.Config* *.* قم بالضغط بالزر الأيمن للمؤشر (ماوس) على اسم المشروع في الفيجوال ستديو وقم بإنشاء المجلدات التالية: ```csharp Infrastructure Repo ``` في نفس ملف الـ Models قم بإنشاء واجهة Interface جديدة بالإسم IPerson وقم بلصق الشفرة التالية داخلها: ```csharp public interface IPerson     {         IQueryable GetAllPersons { get; }         Person GetPersonByID(int personID);         Person GetPersonByHisNID(string personNID);         Person RecivedHisPassport(int personID);         void SavePassportInfo(Person person);            } ``` سنقوم يإستخدام فصل الأشياء عن بعضها DI وسنستخدم تقنية الـ Entity Framework لإنشاء مستودع لجلب وتنفيذ العمليات التي نريدها من قاعدة البيانات عن طريق تلك التقنية EF وقد قمنا بإستخدام الـ Code First بإنشائنا للفئة Person يدويا وعدم توليدها من قاعدة البيانات..ولفصل الأشياء من بعضها سنحتاج لإحدى المكتبات التي تساعدنا على ذلك وسنستخدم مكتبة الـ Ninject، إذا كنت تريد فهم ماذا نعني بـ Dependency Injection قم بمراجعة المقال الموضح في الرابط أدناه والمنشور على نفس منصة **انفورماتيك** والتي شرحت فيها طريقة فصل الأشياء DI عن طريق استخدام مكتبة الـ Ninject، وسترى كيفية تهيئة تلك المكتبة للعمل مع تطبيقات الـ ASP.NET MVC. [كيفية تهيئة مكتبة الـ Ninject للعمل مع مشروع الـ ASP.NET MVC](https://informatic-ar.com/ninject-aspnet-mvc/) اضغط على TOOLS في اعلى الشريط المساعد الخاص بالفيجوال واختار الـ Library Package Manager والتي ستظهر لك خيارات عدة إضغط على اول خيار Package Manager Console واكتب السطر التالي: ```csharp PM > Install - Package Ninject - Version 3.0.1.10 ``` في هذه الخطوة قمنا بتنزيل مكتبة الـ Ninject وهي المكتبة التي تساعدنا على فصل الفئات عن بعضها البعض بإستخدام الواجهات Interfaces. في الواجهة التي أنشأناها سابقا لدينا خمسة أساليب Methods الأسلوب الأول يُعني بإرجاع جميع المواطنين من قاعدة البيانات والآخر يقوم بإرجاع مواطن واحد بالرقم الذي تنشئه قاعدة البيانات Identity والأسلوب الثالث يقوم بإرجاع بيانات المواطن عن طريق رقمهُ الوطني والرابع يقوم بتنفيذ جملة Query على قاعدة البيانات في حقل المواطن بعمل تسليم لجواز المواطن والأسلوب الأخير يُعني بحفظ بيانات مواطن جديد يريد عمل إصدار أو تجديد جواز جديد. بعد إنشاء تلك الواجهة سنقوم بإنشاء فئة ستقوم بعمل تنفيذ Implementation للأساليب الخمسة التي ذكرناها آنفا. وستتولى مكتبة الـ Ninject مهمة حقن تلك الفئة عندما نقوم بطلبها من بيئة الـ MVC Framework من خلال عمل طلب Request او إرسال Response من خلال فئة التحكم Controller من المتصفح لديك. قم بإنشاء فئة Class في ملف الـ Repo الذي أنشأته سابقا وقم بتسمية تلك الفئة بـ PersonRepository وتلك الفئة عبارة عن تنفيذ للأساليب الموجودة بالواجهة IPerson لذلك سترث منها، الآن قم بنسخ الشفرة التالية داخل تلك الفئة: ```csharp public class PersonRepository : IPerson     {         private PersonDbContext _dbContext = new PersonDbContext();         public IQueryable GetAllPersons { get { return _dbContext.Persons; } }         public Person GetPersonByID(int personID)         {             return _dbContext.Persons.FirstOrDefault(p => p.PersonID == personID);         }         public Person GetPersonByHisNID(string personNID)         {             return _dbContext.Persons.FirstOrDefault(p => p.NID == personNID);         }         public Person RecivedHisPassport(int personID)         {             // First             Person p = (from x in _dbContext.Persons                         where x.PersonID == personID                         select x).First();             // Second             p.Recived = true;             p.RecivedDate = DateTime.Now;             _dbContext.SaveChanges();             return p;         }         public void SavePassportInfo(Person person)         {             if (person.PersonID == 0) // Save new one             {                 person.RecivedDate = DateTime.Now;                 person.Recived = false;                 _dbContext.Persons.Add(person);                             }             else // Edit exciting one             {                 Person dbEntry = _dbContext.Persons.Find(person.PersonID);                 if (dbEntry != null)                 {                     dbEntry.Name = person.Name;                     dbEntry.NID = person.NID;                     dbEntry.Recived = false;                  }             }             _dbContext.SaveChanges();         }                } ``` لا تنسى إستدعاء فضاء الأسماء التالي: ```csharp using MvcExplain.Models; using System.Data.Entity; ``` ماذا لدينا في تلك الفئة التي أنشأناها سابقا؟ في لغة الـ C# هناك متحكم شامل بتلك اللغة يدعى CLR وهو الذي يتولى ترجمة الأكواد التي نكتبها بتلك اللغة..في حالة الواجهات Interfaces لو لاحظت لقد قمنا بإنشاء واجهة اسمها IPerson تلك الواجهة تأخذ النوع public للوصول إليها وجميع الأساليب التي بها يجب ان لا تأخذ أي نوع وصول Member accessibility ولذلك سيجبرك المترجم على تعريفها داخل الفئات التي سترث من تلك الواجهة IPerson وفي حالتنا تلك ستكون الفئة هي الـ PersonRepository فجميع الأساليب أخذت أمكانية الوصول لجميع عناصرها النوع public. أيضا الـ CLR سيقوم ببلورة تلك العناصر بالكلمة virtual أثناء عمل التطبيق وإن لم تقوم بكتابة تلك الكلمة بصورة صريحة للعنصر سيقوم المترجم بكتابتها بدلا منك وسيضيف الكلمة sealed إليها وذلك يعني لن تستطيع الوراثة من هذا الإسلوب Method. والعكس إن قمت بكتابتها بصورة صريحة سيقوم المترجم بإتاحة الفرصة لك بالوراثة منها والكتابة فوقها override مع الأخذ في الإعتبار أن المترجم سيضيف كلمة virtual في كلا الحالتين.(لا تشغل نفسك بتلك المعلومات ان كنت لا تملك خبرة عن كيفية ترجمة أكواد لغة الـ C# وهي ليست ضرورية لفهم عمل وآليه التطبيق الذي نبنيه الآن...فقط أردت أن أشرح لك كيفية عمل الوراثة من الواجهات Interfaces). الآن الفئة PersonRepository ترث من الواجهة IPerson وتقوم بتنفيذ جميع الأساليب الموجودة بالواجهة IPerson. ولأننا سنعمل مع قاعدة لبيانات قمنا بتعريف مثيل للفئة التي تربط التطبيق بقاعدة البيانات وهي الفئة PersonDbContext من النوع private وهذا يعني عدم الوصول إليها من خارج تلك الفئة. الإسلوب الأول GetAllPersons يتولى مهمة جلب جميع المواطنين من قاعدة البيانات وقمنا بإستخدام الـ Generic من النوع IQuerable والتي بدورها ترث من الـ IEnumarable والتي تقوم بتنفيذ عملية Foreach بالنسبة لقاعدة البيانات . الإسلوب الثاني GetPersonByHisID يقوم بجلب مواطن واحد من قاعدة البيانات وقمنا بإستخدام تقنية الـ LinQ بإستخدام الـ Element Operator والتي تُدعى FirstOrDefault والتي تقوم بعمل تجميع للمواطنين ثم تقوم بإستدعاء المواطن المطلوب الذي تمت إضافته في الـ constructor هذه العملية تقوم بإرجاع القيمة null إذا لم تجد في التجميع الذي تقوم بعمله الرقم المطابق للرقم الذي أسندناه في الـ constructor. ويمكنا كتابتها بتلك الصورة فرضا لو كنا نريد أرجاع المواطن الذي رقمه 1 ```csharp var person = (from c in _dbContext.Persons                           where c.PersonID == 1                           select c).FirstOrDefault(); ``` بإمكانك بدلا من الرقم واحد إسناد المعاملpersonID. الإسلوب الثالث شبيه بالعنصر الثاني..الفرق اننا سنقوم بإسترجاع قيمة من النوع String. الإسلوب الرابع RecivedHisPassport يقوم بعمل تنفيذ لجملة في قاعدة البيانات لمواطن مــا، تقوم بتغير الحقل Recived في قاعدة البيانات لـ True والتي تعني أن المواطن قد استلم جوازه..في هذا الإستعلام يتم عمل تجميع للمواطن اولا..هنا: ```csharp Person p = (from x in _dbContext.Persons                         where x.PersonID == personID                         select x).First(); ``` بعدها يتم إختيار المواطن الذي رقمه مطابق للمعامل Parameter الذي تم اسناده للأسلوب هذا.. بعدها سنقوم بعمل عمليتين على هذا المواطن وهي تغيير الحقل Recived للقيمة True وأيضا سيتم إضافة تاريخ الإستلام للحقل RecivedDate ونقوم بالحفظ وإرجاع القيمة للمواطن داخل قاعدة البيانات والذي يتم في تلك الجملة: ```csharp p.Recived = true;             p.RecivedDate = DateTime.Now;             _dbContext.SaveChanges();             return p; ``` الإسلوب الأخير يُعني بحفظ مواطن جديد وتعديل مواطن موجود مسبقا على قاعدة البيانات..وتعمل بصورة مبسطة إذا كان الرقم الذي تم إرساله لقاعدة البيانات موجود إذاً قم بالتعديل الذي تستلمه من الـ Caller وإذا كان غير موجود قم بإنشاء حقل جديد. الآن انتهينا من تهيئة قاعدة البيانات والجمل التي سنستخدمها لتعيينعا على العمل على قاعدة البيانات...الآن سنقوم بالتوجه لعمل صفحات الـ Html وسنرى كيفية استخدام الجمل التي أنشأناها مسبقا. في المجلد Infrastructure قم بإنشاء فئة Class بالإسم التالي NinjectControllerFactory وقم بلصق الكود التالي بداخلها: ```csharp public class NinjectControllerFactory : DefaultControllerFactory     {         private IKernel _kernel;         public NinjectControllerFactory()         {             _kernel = new StandardKernel();             AddDefaultBingings();         }         protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)         {             return controllerType == null ? null : (IController)_kernel.Get(controllerType);         }         private void AddDefaultBingings()          {             _kernel.Bind().To();         }     } ``` لا تنسى أن تقوم بإستدعاء فضاء الأسماء التالي: ```csharp using System.Web; using System.Web.Routing; using System.Web.Mvc; using Ninject; using MvcExplain.Models; using MvcExplain.Repo; ``` قم بلص الشفرة التالية على ملف الـ Global.asax داخل الإسلوب Application\_Start ```csharp ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); ``` iقم بإنشاء Controller جديدة وأعطها اسم PersonController بالضغط على ملف Controllers وأختيار Add لا تقم بتغيير أي شئ عدا الاسم وقم بالضغط على OK. كما موضح بالصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/06/3-300x200.png) قم بلصق الكودة التالي بتلك الفئة: ```csharp public class PersonController : Controller     {         private IPerson repository;         public PersonController(IPerson repo)         {           this.repository = repo;         }         //         // GET: /Person/         public ActionResult Index()         {             return View(repository.GetAllPersons);         }         public ActionResult SerachForPerson(string q)         {             var persons = repository.GetPersonByHisNID(q);             if (persons != null)             {                 return PartialView("_PersonSearch", persons);             }             else             {                 return View("NotFound");             }         }         [HttpGet]         public ViewResult PersonRecivedHisPassport(int passID)         {             Person person = repository.GetPersonByID(passID);             if (person == null)            //                 return View("HasRecived");             else if (person.Recived == true)                     return View("HasRecived");             else             return View();         }         [HttpPost]         public ActionResult PersonRecivedHisPassport(int passID, FormCollection form)         {             Person person = repository.RecivedHisPassport(passID);             if (person != null)             {                 TempData["message"] = string.Format("{0} OK", person.Name);                 return RedirectToAction("Index");             }             else             {                 return View("NotFound");             }         }     } ``` لا تنسى استدعاء فضاء الاسماء التالي: ```csharp using MvcExplain.Models; using MvcExplain.Repo; ``` لدينا ثلاثة أساليب واحد يُعني بإرجاع جميع المواطنين والثاني يُعني بالبحث عن مواطن نريد أن نقوم بتسليمه لجوازه والثالث بقوم بتنفيذ الإسلوب RecivedHisPassport الذي أنشأناه في فئة الـ PersonDbContect والذي مهمته تغيير حقلي الـ Recived والـ RecivedDate في قاعدة البيانات. قم بالضغط على مرتين Double-Click بالمؤشر (ماوس) على ملف الـ Content ستجد بداخله ملف لتنسيق صفحات الـ Html واسمه Site.css قم بفتحه ولصق الشفرة التالية بداخله: ```css TABLE.Grid TD, TABLE.Grid TH { border-bottom: 1px dotted gray; text-align:left; } TABLE.Grid {border-collapse: collapse;width:50%; margin:30px 0 0 100px;} TABLE.Grid TH.NumericCol, Table.Grid TD.NumericCol {text-align: right; padding-right: 1em; } #mainform {} #searchform { margin:120px 0 0 400px;} DIV.searchresults  {margin:120px 0 0 400px; font-size:x-large;} input.text {width:50%;padding:16px 20px;box-sizing:border-box;border:2px solid red;border-radius:5px;font-size:x-large;} input.button {width:50%; padding:8px 32px; box-sizing:border-box;font-size:x-large;} ``` > *ملاحظة: دائما إذا كنت تريد أداء أفضل لتطبيقك قم بكتابة خصائص ملف الـ* *Css* *في سطر واحد بالنسة للـ Element.* قم بفتح ملف الـ \_Layout.chtml وستجده في ملف الـ Views داخل ملف الـ Shared وقم بلصق الكود التالي داخل الـ Element التي تُسمى Body: ```markup
        @if (TempData["message"] != null)         {             
            @TempData["message"]        
        }            
                @if (TempData["savemessage"] != null)         {             
            @TempData["savemessage"]            
        }    
           
``` في الشفرة أعلاه قمنا بإستخدام الخاصية TempData لحالتين لدينا كإشعار في حال تم تسليم المواطن لجوازه في حال تم استلام جواز لمواطن ما..وتلك الخاصية في بيئة الـ MVC ترث من TempDataDictionary وكما معروف عنها تحميل قيمتان على هذا المنوال Key-Vlaue وصفحة الـ \_Layout.chtml تعتبر مثل صفحة الـ master page في الـ ASP.NET Web Form وستقرأ هذه الرسالة من الأسلوبين الموجودين في فئتي التحكم HomeController والـ AdminControler والتي سنضيفها لاحقا. قم بفتح ملف الـ RouteConfig.cs والذي ستجده في ملف الـ App\_Start وقم بلصق الكود الشفرة التالية داخل الأسلوب RegisterRoutes: ```csharp      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");             routes.MapRoute(                 name: "PassRecive",                 url: "person/recivehispassport/{passID}",                 defaults: new { controller = "Person", action = "PersonRecivedHisPassport", passID = (int?)null }                 );             routes.MapRoute(                 name: "EditExcitePersonInfo",                 url: "person/passport/Edit/{personId}",                 defaults: new { controller = "Admin", action = "EditPersonInfo", personId = (int?)null }                 );             routes.MapRoute(                 name: "CreateNewPersonInfo",                 url: "person/passport",                 defaults: new { controller = "Admin", action = "CreatePersonInfo" }                 );             routes.MapRoute(                 name: "Default",                 url: "{controller}/{action}/{id}",                 defaults: new { controller = "Person", action = "Index", id = UrlParameter.Optional }             ); ``` هنا قمنا بإضافة أربعة عناوين وصول..الأول لصفحة تسليم الجواز والثاني لتعديل بيانات مواطن والثالث لإنشاء بيانات جديدة لمواطن ما..والأخير للصفحة الرئيسية التي لدينا. > *ملاحظة: الكثير من المبرمجين لا يقومون بإستخدام عناوين وصول مشابهة للأساليب التي ينشؤونها في فئات التحكم بل يقومون بإستخدام عنوان الوصول الإفتراضي الذي ينشئه الفيجوال ستديو...من خلال خبرتنا العملية أود أن تأكيد أن إنشاء عناوين وصول خاصة بك سيسهل عملية البحث في موقع بالنسبة للزوار الذين يستخدمون محركات البحث كـ* *Google* *و* *Bing* *وهذا ما يعرف بالـ* *SEO* *وهي إختصار لـ* ***S*** *earch *E*ngine *O*ptimization* *وتعريف بتحسين البحث على محركات البحث والتي تتم عن طريق إنشاء عناوين الوصول الخاصة بك على مشروعك.* الآن سننشئ صقحة الـ Html الأولى وهي صفحة الـ Index قم بفح الـ HomeContoller التي أنشأناها سابقا وقم بالضغط بالزر الأيمن للمؤشر (ماوس) على الإسلوب Index وقم بالضغط على OK سيقوم الفيجوال ستديو بإنشاء صفحة بإسم Index داخل الملف Person والذي بدروه أنشئ داخل الملف Views > *ملاحظة: هذه طريقة متبعة* *Convention* *في تقنية الـ* *MVC* *وهي كالتراتيبية للملفات والحرف الأوسط لتلك التقنية حرف الـ* *V* *والذي يعني* *View* *الخاص بالصفحات التي تم إنشاءها في الحرف الأخير* *C* *والتي تعني الـ* *Controller* *والتي بدورها تقرأ جميع بيناتها من الحرف الأول* *M* *التي تُعنى بالفئات داخل الـ* *Models* *هذا ببساطة شديدة.* قم بلصق الشفرة التالية داخل صفحة الـ Index: ```csharp @model IEnumerable @ {     ViewBag.Title = "Index";     Layout = "~/Views/Shared/_Layout.cshtml"; }
@using (Ajax.BeginForm("SerachForPerson", "Person",     new AjaxOptions {      InsertionMode=InsertionMode.Replace,      HttpMethod="GET",      UpdateTargetId="searchresults",               })) {   
 

    
   }
``` هذه الصفحة يوجد بها مربع نص وزر إدخال يقوم ومهمته البحث عن مواطن عن طريق رقمه الوطني..قم بإنشاء صفحة Html أخرى بنفس الطريقة السابقة وقم بتسميتها NotFound وقم بلصق الشفرة التالية داخلها: ```csharp @ {     ViewBag.Title = "NotFound";     Layout = "~/Views/Shared/_Layout.cshtml"; }

NotFound

   

Sorry There is somthing ERROR

``` وتلك صفحة تعنى بإظهار أي خطأ يحدث في حال قام المستخدم بإدخال رقم وطني خاطئ لمواطن ما. قم بالضغط بالزر الأيمن للمؤشر (ماوس) فوق ملف الـ Person الموجود داخل الملف Views وقم بإختيار Add وإختيار View كما موضح بالصورة التالية وقم بكتابة اسم الصفحة HasRecived وقم بالضغط على OK. ![](https://informatic-ar.com/wp-content/uploads/2018/06/4-300x218.png) > *ملاحظة: هذه طريقة أخرى أيضا لإنشاء صفحات الـ* *Html* *إذا كنت لا تريد إنشائها بالطريقة الأولى..الآن لقد وضحت لديك طريقة إنشاء صفحات الـ* *Html* *في المرات القادمة لن أشير لكيفية طريقة إنشائها..فقط سأقول لك أنشئ صفحة* *Html* *وأعطها الإسم (...).* قم بنسخ الشفرة التالية داخل ملف الـ HasRecived.cshtml: ```csharp @ {     ViewBag.Title = "HasRecived";     Layout = "~/Views/Shared/_Layout.cshtml"; }

HasRecived


Sorry This person has Recived his passport!

``` وهذه الصفحة تُعني في حال تم إدخال رقم وطني لموطن وقد قام بإستلام جواز مسبقا ستقوم بتوضيح أن هذا المواطن قد استلم جوازه وفي التاريخ الفلاني. قم بالضغط على ملف Shared بالزر الأيمن للمؤشر (ماوس) وقم بإختيار Add à View وقم بتسميتها \_PersonShearch وقم بالضغط على المربع Create as a partial view وقم بالضغط على Ok ![](https://informatic-ar.com/wp-content/uploads/2018/06/5-300x295.png) وقم بلصق الشفرة التالية بداخلها: ```markup @model MvcExplain.Models.Person @{     ViewBag.Title = "Result";     Layout = "~/Views/Shared/_Layout.cshtml";     }
   
           
  • @Model.Name
  •        
  • @Model.NID
  •        
  • @Model.Recived
  • |        
  •  @Html.ActionLink("please Perss here","PersonRecivedHisPassport",new {passID=Model.PersonID})        
  •        
  • @Html.RouteLink("Go", "PassRecive", new { passID = Model.PersonID }) | @Model.Recived
  •    
``` وهذه الصفحة تُعنى بنتيجة البحث عن مواطن لم يستلم جوازه..وهي من النوع Partial أي هي ليست صفحة Html متكاملة بمعني ستظهر كجزء من صفحة أخرى...وهي شبيهة بصفحات الـ User Control في الـ ASP.NET Web Form. الصفحة الأخيرة والتنفيذية والتي بها خاصية تسليم المواطن لجوازه والتي اسمها PersonRecivedHisPassport.chtml ..قم بإنشائها وقم بلصق الشفرة التالية عليها: ```csharp @model MvcExplain.Models.Person @ {     ViewBag.Title = "PersonRecivedHisPassport";     Layout = "~/Views/Shared/_Layout.cshtml"; }

PersonRecivedHisPassport


    @using (Html.BeginForm())     {    

        |         @Html.ActionLink("Back to Search Page", "Index")    

    }

``` هنيئا لك...الآن لقد أنتهيت من تنفيذ 80% من التطبيق..لدينا خطوة أخيرة وهي تثبيت أداة تُسمى Glimpse وهي أداة جميلة ومهمة جدا دائما ما نستخدمها في تطبيقاتنا وسنستفيد من تلك الأداة في رؤية عناوين الوصول الخاصة بنا التي ستسخدمها الصفحات من خلال المتصفح ومنها سنتأكد هل ستقوم الـ MVC Framework بإختيار روابط الوصول من ملف الـ RouteConfig.cs بصورة صحيحة كما قمنا بتهيئتها. قم بالضغط بالزر الأيمن للمؤشر (ماوس) وقم بإختيار Manage Nuget Packages ستفتح معك شاشة..قم بإدخال كلمة Glimpse على يمين الشاشة في مربع البحث واضغط Enter ستجد مكتبة Glimpse.mvc4 قم بتثبيت تلك المكتبة... بعد الإنتهاء من تثبيتها قم تشغيل التطبيق لتعمل عليه الآن. بعد أن تقوم بتشغيل التطبيق قم بكتابة السطر التالي على رابط التطبيق..لتقوم بإعطاء مكتبة Glimpse خاصية العمل مع تطبيق...(عليك بتوقيف عمل تلك المكتبة عندما تقرر نشر تطبيقك على موقع الإستضافة الذي ستستخدمه Web server) ```csharp http: //localhost:58419/glimpse.axd ``` بعد كتابة هذا السطر ستفتح معك صفحة تستطيع من خلالها بدء وتوقيف عمل تلك المكتبة. قم بالضغط على Turn Glimpse on كما موضح بالصورة. ![](https://informatic-ar.com/wp-content/uploads/2018/06/6-300x188.png) والآن بإمكانك كتابة الرابط التالي على رابط الوصول في متصفحة لفتح الصفحة الرئيسية للتطبيق: ```csharp http: //localhost:58419/Person/Index ``` لا تنسى سيكون عنوان المنفذ مختلفا..لأنني استخدم الـ IIS Express والذي يقوم بتغيير عنوان المنفذ في كل مرة او في كل تطبيق مختلف. سيكون التطبيق على تلك الشاكلة كما مبين في تلك الصورة ![](https://informatic-ar.com/wp-content/uploads/2018/06/7-300x188.png) ..قم بإدخال الرقم 123-87847-888 في مربع النص وقم بالضغط على Search ستظهر معك تفاصيل المواطن الذي اسمه Mohammed قم بالضغط على please press here أو بالضغط على Go ستنقلك لصفحة تنفيذ استعلام تسليم الجواز..بالمناسبة الرابطين الموضحين هنا هما يؤديان نفس المهمة فقط قمت بكتابتهما مرتين لأوضح لك الـ Html Helper الخاصة بعناوين الوصول ففي الأول استخدمت Html.RouteLink والذي يقرأ اسم العنوان من ملف الـ RouteConfig.cs والثاني استخدمت فيه Html.ActionLink والذي يقرأ من فئة التحكم الـ HomeController. لو لاحظت أيضا في الصورة السابقة شكل مكتبة Glimpse وبتمريرك المؤشر (ماوس) على تبويب الـ Views سيظهر لك الوقت الذي أخذته الصفحة في عملية التنفيذ وستظهر لك عنوان الوصول الذي قام الـ MVC Framework بإختياره. الآن بإمكانك تجريب التطبيق بالبحث عن المواطنين وتسليمهم الجوازات. > *ملاحظة لا تنسى لقد تبقى لدينا مهمتين مهة إدارة قاعدة البيانات وعمل إختبار* *Unit Tests* *للشفرات..!!* **الجزء الثاني – إدارة التطبيق** قم بإنشاء فئة تحكم Controller جديدة وقم بإعطاءها الإسم AdminController وقم بلصق الشفرة تلك بداخلها: ```c public class AdminController : Controller     {         private IPerson _repository;         public AdminController(IPerson repo)         {             _repository = repo;         }         //         // GET: /Admin/         public ViewResult Index()         {             return View(_repository.GetAllPersons.Where(p = > p.Recived == false));         }         [HttpGet]         public ViewResult EditPersonInfo(int personId)         {             Person person = _repository.GetPersonByID(personId);             return View(person);         }         [HttpPost]         public ActionResult EditPersonInfo(Person person)         {             if (ModelState.IsValid)             {                 _repository.SavePassportInfo(person);                 TempData["savemessage"] = string.Format("{0} has been saved ", person.Name);                 return RedirectToAction("Index", "Person");             }             else             {                 return View(person);             }         }         public ViewResult CreatePersonInfo()         {             return View("EditPersonInfo", new Person());         }     } ``` لا تنسى استدعاء فضاء الاسماء التالي: ```csharp using MvcExplain.Models; using MvcExplain.Repo; ``` في هذه الفئة لدينا ثلاثة أساليب الاولى لعرض جميع المواطنين الذين لم يستلموا جوازاتهم والثانية لتعديل بيانات مواطن موجود لدينا..والثالثة لحفظ بيانات مواطن جديد. > *لاحظ في الإسلوب الأول* *Index* *لقد قمت بإستخدام الجملة التنفيذية داخل الإسلوب نفسه وهذه طريقة خاطئة لانه من المفترض علىّ ان انقل هذا التنفيد* *Logic* *داخل الواجهة* *IPerson* *واقوم بعمل التنفيذ داخل ملف الـ* *PersonRepository.cs* *والذي يرث من الـ* *IPerson* *وأقوم بإستدعاء داخل فئة التحكم* *AdminController* *مثيل الواحهة* *\_repository* *بإمكانك عمل تلك العملية للتأكيد على قدرة إستيعابك لما قمنها بعمله على هذا التطبيق. وتلك الجملة بسيطة تستخدم الـ* *Restriction operator* *وهي جملة* *where* *في تنقية* *LinQ* *وهي شبيهة بجملة* *where* *في قواعد البيانات* *SQL* *وتقوم بعمل تنقيب في مجموعة بيانات محتملة وتقوم بإرجاع قيمة مطابقة للشرط الذي تأخذه في المعامل الخاص بها* *parameter* *.* قم بإنشاء صفحة Html للإسلوب Index وقم بلصق الشفرة التالية داخله: ```csharp @model IEnumerable @ {     ViewBag.Title = "Index";     Layout = "~/Views/Shared/_Layout.cshtml"; }

Admin Page @Html.RouteLink("Go To Add New Info", "CreateNewPersonInfo")
    < table class = "Grid" >                 Person         NID         Status                    @ foreach (var p in Model)         {                     @Html .RouteLink(p.Name, "EditExcitePersonInfo", new { personId = p.PersonID })                        @p.NID              @p.Recived                      }     ``` وصفحة أخري لـ EditPersonInfo وقم بلصق الشفرة التالية داخلها: ```csharp @model MvcExplain.Models.Person @ {     ViewBag.Title = "EditPersonInfo";     Layout = "~/Views/Shared/_Layout.cshtml"; }

EditPersonInfo @using(Html.BeginForm("EditPersonInfo", "Admin")) { @Html.EditorForModel();         @Html.ActionLink("Cancel", "Index") } ``` الإسلوب CreatePersonInfo لن يحتاج لصفحة Html لأنه سيقوم بإستدعاء صفحة الـ EditPersonInfo وإذا رأيت في في الإسلوب بأننا قمنا بإسناد كائن الـ Person لتقوم الصفحة بإرجاعه من خلال صفحة التعديل كالتالي: ```csharp return View("EditPersonInfo", new Person()); ``` الـ MVC Framewrok تقوم بإستخدام الـ Command pattern لإرجاع صفحات الـ Hmlt. أخيرا قم بفتح ملف الـ Global.asax وقم بلصق الشفرة التالية داخل الإسلوب Application\_Start: ```csharp ViewEngines.Engines.Clear();             ViewEngines.Engines.Add(new RazorViewEngine()); ``` تلك الشفرة ستحسن من عمل التطبيق ولأن منصة الـ MVC لديها محركين للعمل على صفحات الـ HTML وهما الـ Razor والـ Web Form وعند تشغيلك للتطبيق ستقوم المنصة بالبحث بالمحركين عن صفحة الـ Index وهذه ستأخذ وقت إضافي..لكن بكتبابتنا لتلك الشفرة السابقة قمنا بإخبار منصة الـ MVC بإن تقوم بعمل تنظيف لجميع محركات إنشاء الـ HTML وثانيا أخبرناها بإنشاء المحرك Razor فقط ليعمل مع التطبيق..وإذا كنت تذكر في بدابة هذا التطبيق عندما أنشأناه لقد إخترنا الـ View Engine الخاص بتطبيقنا هو الـ Razor. الآن بإمكانك تشغيل التطبيق وكتابة الرابط التالي: ```csharp http: //localhost:58419/admin/index ``` لا تنسى سيكون المنفذ مختلفا..هذه الصفحة ستعرض جميع بيانات المواطنين الذي لم يستلموا جوازاتهم..وبإمكانك من تلك الصفحة إضافة بيانات مواطن جديد...والتعديل على بيانات موجودة مسبقا. **الجزء الثالث – عمل أختبار للشفرات** **Unit tests** كثير من المبرمجين لا يقومون بالقيام بإختبار شفراتهم؛ وهذا خطأ كبير ولأن اختبار الشفرات مهم جدا وكما متعارف عليه في اختبار شفرات بيئة الـ MVC Framework يُقال لك أقلّاها عليك اختبار روابط عناوين الوصول لتطبيقك لأنه إن لا كانت لا تعمل لن يعمل تطبيق أبدا. قم بالضغط على الـ Solution وليس اسم التطبيق وقم بإنشاء مشروع جديد كما موضح بالصورة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/06/8-300x197.png) قم بإختيار Test من على يسار الشاشة وإختيار Unit Test Project كما موضح بالصورة وقم بتسمية المشروع PersonData.UnitTests واضغط Ok. ![](https://informatic-ar.com/wp-content/uploads/2018/06/9-300x185.png) قم بالضغط على خيار References من داخل المشروع الذي أنشأته مؤخرا وقم بالضغط على Add References من يسار الشاشة إختار Solution وستجد مشروع الـ MvcExplain قم بإختياره ثم قم بضغط OK. في تلك الخطوة قمنا بربط مشروع إختبار الشفرات بمشروعنا الفعلي وذلك حت نقوم بعمل إختبار لأجزاءه بالطريقة التي نريدها اضغط على TOOLS في اعلى الشريط المساعد الخاص بالفيجوال واختار الـ Library Package Manager والتي ستظهر لك خيارات عدة إضغط على اول خيار Package Manager Console واكتب السطر التالي: ```csharp PM > Install - Package Moq - version 4.0.10827 ``` هنا قمنا بتنزيل مكتبة الـ Moq وهي مكتبة تتيح لنا إنشاء بيانات وهمية في حال أردنا إختبار شفراتنا بدلا من اغختبارها ببيانات حقيقية من داخل قاعدة البيانات وذلك لأنها ستزيد من بطء النظام لذلك نقوم بإستخدام تلك المكتبة. في شفرات الإختبار تلك لقد قمت بعمل إختبار لفئة التحكم PersonController وفئة الـ RouteConfige.cs فقط ولم أقم بعمل إختبار لجميع الأكواد. عند إنشائك لمشروع اختبار البرمجيات سيقوم الفيجوال ستديو بإنشاء فئة Class بإسم UnitTest1 سنقوم بكتابة شفرتنا عليها...لكن لا تفعل ذلك في برمجياتك الرسمية والكبيرة...فمثلا إذا أردت إختبار فئة التحكم AdminController قم بإنشاء فئة اسمها AdminControllerUnitTest حتى يكون عملك محترفا ومقبولا..ولا تنسى بأنه قد تترك العمل في الشركة التي تعمل بها وبالتالي هذا يعني تسهيلك للمطورين والمبرمجين الذي سيأتون من بعدك سيكون عليهم سهلا معرفة الأكواد التي كتبتها وهذا ما يعرف بأخلاقيات العمل (قم بالإطلاع على مواضيع **هندسة البرمجيات** على الإنترنت). في تلك الفئة UnitTest1 قم بإستدعا فضاء الإسماء التالي: ```csharp using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Linq; using Moq; using System.Collections; using System.Collections.Generic; using System.Web.Mvc; using MvcExplain.Models; using MvcExplain.Repo; using MvcExplain.Controllers; using System.Web.Routing; ``` وقم بكتابة الشفرة التالية داخل تلك الفئة: ```csharp [TestClass]     public class UnitTest1     {         [TestMethod]         public void Can_List_AllPersonInMainPage()         {             Mock mock = new Mock();             mock.Setup(m => m.GetAllPersons).Returns(new Person[] {                 new Person { PersonID = 1, Name = "Mohammed", NID = "23-1234", Recived = false },                 new Person { PersonID = 2, Name = "Ahmed", NID = "23-1235", Recived = true },                 new Person { PersonID = 3, Name = "Hussien", NID = "23-1236", Recived = true },                 new Person { PersonID = 4, Name = "babiker", NID = "23-1237", Recived = true },                 new Person { PersonID = 5, Name = "Ali", NID = "23-1238", Recived = false },                 new Person { PersonID = 6, Name = "Khalid", NID = "23-1239", Recived = false }         }.AsQueryable());             PersonController target = new PersonController(mock.Object);             //ViewResult result = target.Index() as ViewResult;             IEnumerable result = (IEnumerable)target.Index().Model;                         Person[] personData = result.ToArray();             // Assert             Assert.IsNotNull(result);             Assert.IsTrue(personData.Length == 6);             Assert.AreEqual(personData[0].Name, "Mohammed");                             }         [TestMethod]         public void Can_SearchForSpesificPerson_ByHidsNID()         {             // Arrange             Mock mock = new Mock();             // Arrange - Initil obj            Person n = new Person { NID = "23-1234" };             // Action            PersonController target = new PersonController(mock.Object);             // Action            ActionResult result = target.SerachForPerson("23-1234") as ViewResult;            Assert.IsNotNull(result);            Assert.AreEqual("23-1234", n.NID);         }         [TestMethod]         public void WhenYouWantToRecivePassPort_Return_HasRecived_View()         {             Mock mock = new Mock();             PersonController target = new PersonController(mock.Object);             Person person = new Person { Recived = false };             ViewResult result = target.PersonRecivedHisPassport(person.PersonID);             Assert.AreEqual("HasRecived", result.ViewName);         }         [TestMethod]         public void PersonHasBeenRecived_ShouldReturnTrue_WhenNotRecive()          {             Mock mock = new Mock();             // Arrange             PersonController target = new PersonController(mock.Object);             Person rec = new Person { Recived = false };             //int p = 1;             ActionResult result = target.PersonRecivedHisPassport(rec.PersonID, null);             mock.Verify(m => m.RecivedHisPassport(rec.PersonID));             Assert.IsInstanceOfType(result, typeof(ViewResult));         }         [TestMethod]         public void RouteToHomePage()         {             // Arrange             var mockContext = new Mock();             mockContext.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath) .Returns("~/");             // Arrange             var routes = new RouteCollection();             MvcExplain.RouteConfig.RegisterRoutes(routes);             RouteData routeData = routes.GetRouteData(mockContext.Object);             // Assert             Assert.IsNotNull(routeData);             Assert.AreEqual("Person", routeData.Values["controller"]);             Assert.AreEqual("Index", routeData.Values["action"]);             Assert.AreEqual(UrlParameter.Optional, routeData.Values["id"]);         }         [TestMethod]         public void RouteToPassRecivePage()         {             var mockContext = new Mock();             mockContext.Setup(c => c.Request.AppRelativeCurrentExecutionFilePath) .Returns("~/person/recivehispassport");             var routes = new RouteCollection();             MvcExplain.RouteConfig.RegisterRoutes(routes);             RouteData routeData = routes.GetRouteData(mockContext.Object);             Assert.IsNotNull(routeData);             Assert.AreEqual("Person", routeData.Values["controller"]);             Assert.AreEqual("PersonRecivedHisPassport", routeData.Values["action"]);         }     } ``` لإختبار تلك الشفرة ورؤيتها تعمل بصورة صحيحة قم بالضغط على Testعلى شريط الأدوات في فيجوال ستديو وقم بإختيار Windows ومنها إختار Test Explorer ستفتح معك شاشة على يسار الفيجوال ستديو...قم بالضغط على Run All سيقوم الفيجوال ستديو بعمل بناء للتطبيق Build وسيقوم بالتأكد من عمل الأكواد التي قمت بكتابتها وستعطيك إشارة خضراء إذا كانت الأكواد تعمل بصورة صحيحة ... وإشارة حمراء إن كان هناك خطأ. وكما موضح بالصورة التالية أن جميع الأكواد التي إختبرناها تعمل بشكل جيّد. ![](https://informatic-ar.com/wp-content/uploads/2018/06/10-248x300.png) في هذه الفئة قمنا بعمل اختبار للصفحة الرئيسية وصفحة تسليم الجوازات...أيضا قمنا بالتأكد من من صفحة الـ Index تقوم بإرجاع جميع البيانات الموجودة بقاعدة البيانات..أيضا اختبرنا اسلوب نتيجة البحث...وأيضا في حالة كنا نريد تسليم مواطن لجواز اختبرنا ان ذلك الاسلوب سيقوم بإرجاع الصفحة المخصصة لذلك واخيرا اختبرنا تنفيذ الـ Logic الخاص بتسليم الجواز للمواطن من تغيير False الي True في حقل قاعدة البيانات. توجد في فضاء الاسماء التالي العديد من الخواص على سبيل المثال منها TestClass والتي تبين ان الفئة تلك تحت الإختبار وسيكون بها العديد من الأساليب التي سيتم إختبارها..وأي اسلوب سيتم أختباره لابد أن يبلور بالخاصية TestMethod, ```csharp using Microsoft.VisualStudio.TestTools.UnitTesting; ``` تحياتي. ### تهيئة محرر النصوص TinyMCE للعمل مع الـ ASP.NET MVC بلغة الـ C# URL: https://informatic-ar.com/tinymce-aspnet-mvc/ Last updated: 2026-10-03T22:31:29.000Z كثير من المواقع الإلكترونية دائما ما يكون لديها صفحة لإدراة المحتويات الخاصة بالموقع وخصوصا تلك التي تنشر المقالات والأخبار...خذ فرضا مثل موقعنا هذا (انفورماتيك) فأنت الآن تقرأ هذه النصوص وبها العديد من الخصائص والتنسيقات كحجم الخطوط وعرضها وخواصها بصورة عامة. الصندق النصي TextBox او منطقة النصوص TextArea جميعهم لا يملكون تلك الخصائص الخاصة بتنسيق النصوص ووضعها في شكل محتوى منسق...فقط تقوم بعرض النص كما تمّ إدخاله. هنالك العديد من محررات النصوص التي تتيح لمدير إدارة المحتوى ان يتحكم في شكل ونسق المقالات التي تنشر على الموقع ومن تلك المحررات ما سنقوم بعرضه في هذه المقالة المحرر TinyMCE وهو محرر مفتوح المصدر؛ ما يعني بإمكانك التعديل عليه والإضافة للمتطلبات التي تريدها أنت كمبرمج...لتسهيل عمل مدير إدارة المحتوى للموقع الذي قمت بتطويره. \*\*\* لكي تكمل هذا المحتوى أو هذا المشروع ستحتاج لفيجوال ستديو 2013 أو فيجوال ستديو 2012 او أي نسخة أخرى بعده كـ 2015 وبمجرد تثبيتك للفيجوال ستديو 2013 على جهازك سيقوم بتثبيت قاعدة البيانات المحلية للغة الإستعلامات المهيكلة SQL Server Express LocalDB وهي خفيفة وتتيح لك العمل على قواعد البيانات داخل بيئة الفيجوال ستديو نفسه...وان كان لديك النسخة الكاملة SQL Server Management Studio أيضا بإمكانك استخدامها اذا كنت لا تريد استخدام LocalDB. بيئة العمل الخاصة بي دائما ما تتكون في VS 2013 و SQL Server Management Studio 2012 حسنا لنبدأ: سنقوم بعمل مشروع لنشر مقالات حيث تتكون المقالة من رقم وكاتب المقالة وجسم المقالة وسنقوم بربطها بقاعدة البيانات SQL Server. قم بفتح الفيجوال ستديو وقم بإنشاء مشروع ويب جديد...من النافذة التي تظهر لك قم بإختيار الفريم وورك 4.5 وقم بإختيار مشروع الـ ASP.NET MVC 4 Web Application وقم بتسمية المشروع بـ TinyEditorWithMVCApp وأضغط Ok كما موضح بالصورة أدناه. ![](https://informatic-ar.com/wp-content/uploads/2018/06/1-300x184.jpg) ستظهر لك نافذة أخرى قم بإختيار هيكل المشروع Basic واللغة التي سنستخدمها في صفحات الـ HTML ستكون الـ Razor Page وقم بضغط OK كما موضع بالصورة أدناه. ![](https://informatic-ar.com/wp-content/uploads/2018/06/2-300x255.jpg) قم بإنشاء قاعدة بيانات جديدة بإسم TestDB وقم بتنفيذ الإستعلام التالي في صفحة استعلام جديدة على نفس قاعدة البيانات: ```sql CREATE TABLE [dbo].[Posts] ( [PostID] INT IDENTITY (1, 1) NOT NULL, [AuthorName] NVARCHAR (200) NOT NULL, PRIMARY KEY CLUSTERED ([PostID] ASC) [Body] NVARCHAR (MAX) NOT NULL, ) ``` الإستعلام السابق يقوم بإنشاء جدول بإسم Post به مجموعة من الأعمدة او الحقول (ثلاثة حقول) وهي كالتالي – رقم المقال – اسم كاتب المقال – جسم المقال. حيث رقم المقال سيكون مفتاح رئيسي - دلالي ومن النوع الفريد بحيث لا يتكرر وستقوم قاعدة البيانات بتوليده تلقائيا. افتح ملف التهيئة web.config وقم بلصق شفرة الإتصال التالي لقاعدة البيانات التي أنشئتها في الفقرة السابقة لتعمل مع التطبيق: ```markup ``` بالنسبة للجزع الخاص بنص الإتصال كما ترى؛ اسم السيرفر الخاص بقاعدة البيانات هو الذي تم تثبيته مع الفيجوال على جهازي Work station كالتالي (localdb)\\v11.0 و في حال كنت تعمل على اسم سيرفر أخر قم بتهيئته بدلا من المكتوب في الجزع الخاص بالإتصال مع قاعدة البيانات ولا تنسى أيضا ان تقوم بكتابة شفرة الإتصال تلك في سطر واحد؛ وان لم تفعل فلن يتم الإتصال بين تطبيقك وقاعدة البيانات.أيضا ستلاحظ ان اسم الإتصال هو PostEFDbContext وهو سيكون اسم الفئة Class التي سننشئها (لاحقا) والتي ستتم عبر تقنية الـ Entity Framework Code First وتكون تلك الفئة مهمتها ربط التطبيق والفئات التي ستكون كجداول مع قاعدة البيانات في الخطوة التالية.وبإمكانك تعريف الإتصال مع قاعدة البيانات بصورة صريحة أيضا إذا أردت من خلال تعريف الـ Constructor لإسم الفئة تلك. قم بإنشاء فئة Class جديدة بالضغط على المحرك (ماوس) بالزر الأيمن وقم بإختيار Class كما موضع بالصورة التالية ... وقم بتسمية الفئة بـ Post. ![](https://informatic-ar.com/wp-content/uploads/2018/06/6-300x208.jpg) قم بكتابة الشفرة التالية داخل الفئة Post التي أنشئتها : ```csharp public class Post { public int PostID { get; set; } public string AuthorName { get; set; } public string Body { get; set; } } ``` قم بإنشاء فئة Class أخرى باسم PostEFDbContext وقم بكتابة الشفرة التالية داخلها ولا تنسى استدعاء فضاء الاسماء الخاص بالـ Entity FrameWork: ```csharp public class PostEFDbContext : DbContext { public DbSet Posts { get; set; } } ``` حسنا ... ماذا تعني تلك الفئات Classes التي أنشأناها سابقا؟ الفئة الأولى Post في الحقيقة تحتوي على نفس حقول الجدول الذي أنشأناه في قاعدة البيانات TestDB وهي الفئة المحركة للتطبيق ضمنيا للعمل كأنها الجدول الموجود بقاعدة البيانات خاصتنا. بالنسبة للفئة Class الأخرى PostEFDbContext فهي بمثابة الرابط بين التطبيق وقاعدة اليانات وهنا يأتي دور الـ EF Code First والتي تخبر قاعدة البيانات عن طريق الفرع الخاص بتهيئة الإتصال بملف الـ web.config بأن الفئة PostEFDbContext هي المسؤولة في التطبيق بإعتبارها كجدول وهمي Virtual table يربطنا بالجدول الحقيقي بقاعدة البيانات. وإذا لاحظت الخاصية Property الوحيدة في تلك الفئة اسمها Posts شبيهة بإسم الجدول الذي أنشأناه في قاعدة البيانات. وهذه الفئة ترث من فئة اخرى وهي DbContext ومهمتها تفعيل الجميل الاربعة في اللغة المهيكلة CRUD . \*\*\* الآن قمنا بتهيئة التطبيق للعمل مع قاعدة البيانات...الأن سنقوم بالعمل على كيفية استخدام محرر النصوص TinyMCE للعمل مع الحقل Body ليتيح لنا التنسيق لمحتويات هذا الحقل. أولا قم بالضغط بالزر الأيمن على المحرك (ماوس) على اسم التطبيق ستظهر لك قائمة قم بإختيار Manage NuGet Packages في محرر نص البحث قم بإدخال TinyMCE كما موضح بالصورة التالية... ستظهر لك المكتبة قم بالضغط على Install لتثبيتها، ستظهر لك شاشة أخرى للتأكيد قم بالضغط على Accept كما موضع بالصورة التالية أيضا. ![](https://informatic-ar.com/wp-content/uploads/2018/06/3-300x193.jpg) ![](https://informatic-ar.com/wp-content/uploads/2018/06/4-267x300.jpg) بإمكانك استخدام الـ Console أيضا اذا لتثبيت المكتبة بكتابة السطر التالي: ```csharp PM > Install - packages TinyMCE –Version 4.7.13 ``` في ملف الـScript ستقوم المكتبة بتثبيت ملف جديد باسم tinymce به جميع الملفات الجاهزة للإستخدام للإستفادة من تلك المكتبة. قم بإنشاء فئة تحكم Controller وقم بإعطائها الإسم HomeController وقم بإختيار الهيكلية الفارغة Empty MVC controller وقم بالضغط على Ok. قم بلصق الكود التالي على الـ Controller ولا تنسى استدعاء فضاء اسماء ملف الـ Models: ```csharp public HomeController() { } private PostEFDbContext _db = new PostEFDbContext(); // // GET: /Home/ [HttpGet] public ActionResult Index() { return View(_db.Posts.ToList()); } [HttpGet] public ActionResult AddPost() { return View(); } [HttpPost] public ActionResult AddPost(Post post) { if (ModelState.IsValid) { Post newpost = _db.Posts.Add(post); _db.SaveChanges(); return RedirectToAction("Index", newpost); } return View(); } ``` في فئة التحكم تلك لدينا اسلوبين Methods الأولى Index تقوم بعرض جميع الحقول الموجودة بقاعدة البيانات. والثانية AddPost مهمتها إضافة حقل جديد لقاعدة البيانات. وفي اول الفئة قمنا بإنشاء مثيل Instance لفئة تهيئة الاتصال بقاعدة البيانات لتتيح لنا العمل مع الجدول Post الموجود في قاعدة البيانات. أيضا لم نقم بإستخدام الـ Dependency Injection لذلك قمنا بتعريف صريح للـ Constructor الإفتراضي لفئة التحكم نفسها. ولتبسيط العمل لم أقم بإنشاء فئة Class تكون كمستوع لجميع الإستعلامات للتطبيق Repository بل قمت بكتابة الإستعلامات جميعها في فئة التحكم نفسها. في الإسلوب AddPost لدينا خاصيتين الاولى لطلب صفحة الـ HTML وتعرف كـ Request والأخرى لحفظ التفاصيل التي نريد اضافتها في الـ Request وتعرف بالـ Response وفي بيئة الـ MVC توجد خاصيتين تعرفان بـ HttpGet والـ HttpPost. قم بعمل Build للمشروع. قم بالضغط بالزر الأيمن للمحرك (ماوس) على الإسلوب Index وقم بإختيار Add View وحدد الخواص التالية كما واضحة بالصورة التالية واضغط Ok: ![](https://informatic-ar.com/wp-content/uploads/2018/06/7-300x295.jpg) هنا قمنا بإنشاء صفحة الـ Html والتي ستعرض لنا جميع الحقول الموجودة في قاعدة البيانات، قم بلصق الشفرة التالية في صفحة الـ Index: ```csharp @model IEnumerable @ { ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; }

All Post

@foreach (var p in Model) {

@p.AuthorName

@Html.Raw(p.Body)
}
``` كرر نفس الخطوة السابقة وأنشئ صفحة Html للإسلوب AddPost وألصق في صفحة الـ Html الشفرة التالية: ```csharp @model TinyEditorWithMvcApp.Models.Post @ { ViewBag.Title = "AddPost"; Layout = "~/Views/Shared/_Layout.cshtml"; }

Add new Post TintMCE App</ title><script src = "~/Scripts/tinymce/tinymce.min.js"></ script> <script type = "text/javascript"> tinyMCE.init( { mode : "textareas", theme : "modern", plugins : "advlist,code,bbcode,hr,link,paste,save", theme_advanced_toolbar_location : "top", theme_advanced_toolbar_align : "center", theme_advanced_statusbar_location : "bottom", forced_root_block : false, }); </script> </head> <body> <div> @using (Html.BeginForm("AddPost", "Home", FormMethod.Post)) { <div> @Html.TextBoxFor(model=>model.AuthorName) </div> <div> @Html.TextAreaFor(model=>model.Body) </div> <div> <input type="submit" value="Save Post"/> </div> } </div> </body> </html> ``` في صفحة الـ Html الخاصة بالصفحة AddPost قمنا بعمل مرجع لمكتبة الـ TinyMCE في الـ Element الخاصة بالـ Head من صفحة الـ Html كالتالي: ```csharp <script src="~/Scripts/tinymce/tinymce.min.js"></script> ``` أيضا في نفس الـ Element قمنا بتعريف خواص تلك المكتبة التي نود تهيئتها كالتالي: ```csharp <script type="text/javascript"> tinyMCE.init({ theme: "modern", mode: "textareas", plugins: "advlist,code,bbcode,hr,link,paste,save", theme_advanced_toolbar_location: "top", theme_advanced_statusbar_location: "bottom", theme_advanced_toolbar_align: "center", forced_root_block: false, }); </script> ``` ولو نظرت للصورة التالية بمجرد كتابتك للعنصر tinyMCE ستظهر معك قائمة منسدلة بجميع الأساليب التي يمكنك العمل عليها مع تلك المكتبة ![](https://informatic-ar.com/wp-content/uploads/2018/06/5-300x217.jpg) اذا لاحظت هنا قمنا بربط المكتبة مع حقل الـ Body من خلال تعريف الـ mode الخاصة في المكتبة للـ Element التالية Textareas وهي كالمعرّف العادي Id مثلما نفعل عندما نريد تعريف ملف Css عادي فرضا لو كان لدي العلامة Tag التالية: <div id=”test”> Hello </div> سأقوم في ملف الـ Css كتعريفها كـ Selector في حال أردت تنسيقها. فالـ mode في مكتبة الـ tinymce تعتبر كـ Selector. أخيرا قبل تشغيل التطبيق على متصفحك...هنالك شفرة دائما ما أقوم بكتابتها على جميع التطبيقات التي أنشئها بتقنية الـ MVC وهي شفرة تقوم بتسريع عرض صفحة الـ Html لتطبيقاتي...قم بفتح ملف الـ Global وقم بلصق الشفرة التالية في داخل الإسلوب Method التي تسمى Application\_Start() ```csharp ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new RazorViewEngine()); ``` لو تذكر بداية التطبيق اخترنا محرك عرض الصفحات Razor ولأن الـ MVC بها محركيين افتراضيين تستخدمها عند تشغيل التطبيق على المتصفح وتقوم بالبحث في الـ Root الخاص بالتطبيق لعرض الصفحة المطلوبة والمحرك الآخر هو WebForm ففي هذه الشفرة قمنا بتنظيف (وليس مسح) جميع المحركات في السطر الأول وفي السطر الثاني حددنا لمنصة الـ MVC ان تقوم بإستخدام الـ Razor فقط بدلا من ان تقوم بالبحث بالمحركين عند طلب صفحة مــا. الأن قم بتشغيل التطبيق واكتب الرابط التالي: ```csharp http: //yourport/Home/AddPost ``` ستظهر معك الصفحة التالية: ![](https://informatic-ar.com/wp-content/uploads/2018/06/8-300x291.jpg) بإمكانك أضافة اسم الكاتب وجسم المقالة لديك والضغط على زر الحفظ بعدها سيعرض التطبيق الصفحة الرئيسية Index والتي سيكون بها جميع الحقول التي ستضيفها لقاعدة البيانات. إذا كنت لا تريد عرض جميع المواضيع بإمكانك إضافة اسلوب Method داخل فئة التحكم Controller ولتكن اسمها على سبيل المثال GetPost وتأخذا معامل Parameter واحد وستكون كالتالي: ```csharp [HttpGet] public ActionResult GetPost(int id) { var p = _db.Posts.FirstOrDefault(o => o.PostID == id); return View(p); } ``` قم بإنشاء صفحة Html خاصة بها كما شرحنا كيفية ذلك واستخدم الـ Model الخاص بالفئة Post لعرض حقلي اسم الكاتب وجسم المقالة لمقال واحد فقط. وبإمكانك إستدعائها من على المتصفح كتالي إذا أردت استدعاء المقالة التي تحمل الPostId بالرقم 1: ```csharp http: //yourport/Home/GetPost/1 ``` الآن تعلمنا العديد من الأشياء: كيفية استخدام الـ EF Code First كيفية ربط قاعدة البيانات بالتطبيق. كيفية تضمين مكتبة محرر النصوص TinyMCE. كيفية تسريع التطبيق للبحث عن الصفحات في الـ Root الخاصة بالتطبيق. ### كيفية تهيئة مكتبة الـ Ninject للعمل مع مشروع الـ ASP.NET MVC URL: https://informatic-ar.com/ninject-aspnet-mvc/ Last updated: 2026-10-03T22:31:30.000Z ستحتاج لفيجوال ستديو 2012 او 2013 او 2015 او 2017 لتطبيق المثال الذي سنشرحه هنا. ولقد قمت بإستخدام Visual Studio 2013 Express For Web لتطبيق تلك المكتبة. لو فرضنا لدينا فئة class لحفظ تفاصيل درجة الحرارة C1 ولدى فئة أخري C2 لحساب درجات الحرارة وتعتمد على C1 وتلك الفئات قمت بإستدعائها من الـ Controller الخاصة بالمشروع الذي يوضح درجات الحرارة. الآن لو لاحظنا الـ controller تعتمد على فئتين وهذا يعني أي تعديل في تلك الفئات يعني ان اقوم بعمل تعديل على الـ Controller وأيضا أيّ تعديل على الفئة C1 يعني أن اقوم بعمل تعديل على الـ C2 هذا التداخل يطلق عليه الاعتمادية أو ما يعرف بالـ Dependency ولحل هذه المشكلة يوجد العديد من المكتبات التي تساعدنا على فصل الفئات عن بعضها. ويمكننا ايضا فصل الفئات من دون استخدام تلك المكتبات لكن المكتبات ستسهل علينا العمل بسرعة وستقوم بعمل اشياء كثيرة اثناء عمل التطبيق. ويطلق على تلك المكتبات DI container سنقوم بعمل تطبيق بسيط جدا يظهر لنا رسالة تهنئة بالعيد ويمكنك استخدام تلك الطريقة حتى على المشاريع الكبيرة والضخمة. حسنا لنبدأ قم بفتح الفيجوال ستديو..قم بعمل مشروع ويب جديد وقم باختيار ASP.NET Web Application حدد المسار الذي تريد ان تحفظ فيه المشروع وقم بإعطاء المشروع الاسم HappyEid واضغط OK ستفتح لك نافذة لإختيار نوع التطبيق قم إختيار Empty واسفل الشاشة اضغط على المربع الصغير لتحديد مشروع الـ MVC واضغط OK. بعد ان يقوم الفيجوال ستديو بالانتهاء من تهيئة التطبيق اضغط على TOOLS في اعلى الشريط المساعد الخاص بالفيجوال واختار الـ Library Package Manager والتي ستظهر لك خيارات عدة إضغط على اول خيار Package Manager Console واكتب السطر التالي: ```csharp PM > Install - Package ninject - version 3.0.1.10 ``` في هذه الخطوة قمنا يتنزيل مكتبة الـ ninject وهي المكتبة التي تساعدنا على فصل الفئات عن بعضها البعض بإستخدام الواجهات Interfaces في ملف الـ Models الذي انشئه الفيجوال ستديو قم بإنشاء واجهة Interface جديدة واعطها الإسم IHappyEid وأنشئ بها Method واحدة على هذا النحو: ```csharp public interface IHappyEid { string GetMessage(); } ``` قم بإنشاء فئة class باسم HappyEid ودعها ترث من الـ Interface التي انشأناها سابقا على هذا النحو: ```csharp public class HappyEid : IHappyEid { public string GetMessage() { return "Hi there I want to say for you EID MUBARK"; } } ``` قم بإنشاء ملف واعطه الاسم Infrastrucuture وقم بإنشاء فئة Class داخل هذا الملف بإسم NinjectControllerFactory هذه الفئة مهتمتها إنشاء Instance في حال كان هنالك معامل Parameter للـ Request والكود الخاص بهذه الفئة على هذا النحو: ```csharp public class NinjectControllerFactory : DefaultControllerFactory { private IKernel _kernel; public NinjectControllerFactory(IKernel kernel) { this._kernel = kernel; } protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) { return controllerType == null ? null : (IController)_kernel.Get(controllerType); } } ``` لا تنسي ان تستدعي فضاء الاسماء الخاص بالمكتبة كالتالي: ```csharp using Ninject; ``` والكود يعني اذا كان هناك أي طلب للـ Controller دع المهمة للـ Ninject بإنشاء الطلب من خلال النواة Kernel الخاصة بهذه المكتبة وسنقوم بتعريف استقبال الطلب لاحقا على ملف الـ Global. الآن سنقوم بتهيئة الفئة Class الخاص بالتهنئة التي اريدها ان تظهر على الشاشة من خلال استدعاء الـ Method التي توجد بالـ Controller. قم بإنشاء فئة جديدة Class داخل ملف الـ Infrastrucuture وقم بإعطائها الاسم NinjectDefaultBinding وهذه الفئة من اسمها توضح للـ Controller عندما اقوم بإستدعاء الإسلوب ( )GetMessage من الواجهة IHappyEid قم بعمل حقن Inject في الفئة HappyEid وإرجاع التنفيذ الخاص بهذا الإسلوب Method implementation. الان داخل الفئة NinjectDefaultBinding قم بكتابة الكود التالي: ```csharp public class NinjectDefaultBindings : NinjectModule { public override void Load() {   Bind<IHappyEid>().To<HappyEid>(); } } ``` :لا تنسى ان تقوم بإستدعاء فضاء الاسماء التالي في اعلى الفئة ```csharp using Ninject; using Ninject.Modules; using HappyEid.Models; ``` دعنا نشرح هذه الفئة ... الفئة ترث من فئة اخرى واسمها NinjectModule هذه الفئة بها العديد من الأساليب Load وهذه الفئة من النوع abstract وتعني اننا لا نستطيع ان ننشئ مثيل لها داخل الـ Manage heap من خلال المكدس Stack بإستخدام الكلمة new لذلك سنقوم بالكتابة فوقها override. حسنا الـ Method تعني قم بإستدعاء الفئة في حال تم طلبها من خلال النواة حسنا الخطوة الأخيرة في التهيئة سنقوم بتعريف الفئة NinjectDefaultBinding داخل النواة الخاصة بالـ ninject وهي الفئة Standardkernel والتي بدورها ترث من الـ kernelBase. وتقبل معامل Parameter من النوع INinjectModule وفي حالتنا هذه سيكون المعامل الخاص بنا هو NinjectDefaultBindings قم بإنشاء فئة Class داخل ملف الـ Infrastructure واعطها الاسم NinjectBootStraper وقم بكتابة الكود التالي داخلها: ```csharp public static class NinjectBootStrapper    {         public static IKernel Kernel { get; private set; }         public static void Initialize()         {             Kernel = new StandardKernel(new NinjectDefaultBindings());         } } ``` الآن سنقوم بإنشاء الـ Controller قم بإنشاء HomeController وقم بإختيار الخيار الفارغ Empty داخل هذا الفئة قم بكتابة التالي: ```csharp public class HomeController : Controller { private IHappyEid _provider;   public HomeController(IHappyEid provider) { this._provider = provider; } // GET: GetHappyEidMessage public string GetHappyEidMessage() { return _provider.GetMessage(); }       } ``` لا تنسى استدعاء فضاء الاسماء ```csharp using HappyEid.Models; ``` قم بكتابة الكود التالي داخل ملف الـ Global داخل الـ Method التالية Application\_Start() ```csharp NinjectBootStrapper.Initialize(); ControllerBuilder.Current.SetControllerFactory( new NinjectControllerFactory(NinjectBootStrapper.Kernel)); ``` أيضا هنا قم بإستدعاء فضاء الاسماء ```csharp using HappyEid.Infrastrucuture; ``` في الـ Controller قمنا بإستخدام الواجهات وكما شرحت في الفئة NinjectModule بأننا لا نحتاج بأن ننشئ مثيل للـ Controller من خلال الـ Default constructor بل سندع المهمة للـ Ninject. \*\* قد يكون في المشاريع الصغيرة لن تحس بصعوبة في التعديل عندما تحتاج لتعديل بعض الفئات... لكن من واقع خبرتنا العملية في مشاريع ضخمة قد تكون لم تعمل على مشاريع مثلها ستحتاج لتلك المكتبة..او ستحتاج لتطبيق مبدأ الـ IOC. الآن قم بتجربة التطبيق على المتصفح لديك بكتابة الرابط التالي على عنوان الوصول في متصفحك [http://localhost:1116/Home/GetHappyEidMessage](http://localhost:1116/Home/GetHappyEidMessage?ref=informatic-ar.com) البورت سيكون مختلفا... سترى الرسالة التالية على المتصفح ```csharp Hi there I want to say for you EID MUBARK ``` :هل تدري ان الثلاثة فئات تلك ```csharp NinjectControllerFactory NinjectDefaultBindings NinjectBootStrapper ``` هل تدري ان تلك الفئات يمكننا كتابتها في فئة واحدة؟ لكن لماذا قمت بكتابتها لكم بتلك الصورة؟ قم بتهيئتها بتلك الصورة لي يكتمل لديكم فهمها جيدا بحيث تعرف ماهي مهمة كل فئة... حسنا اذا كنت تريد كتابتها في فئة واحدة قم بعمل التالي داخل ملف الـ Infrastructure قم بإنشاء الفئة NinjectControllerFactory ودعها ترث من الفئة DefaultControllerFactory وقم بكتابة الكود التالي: ```csharp public class NinjectControllerFactory : DefaultControllerFactory { private IKernel _kernel;         public NinjectControllerFactory(IKernel kernel) { this._kernel = kernel; NinjectDefaultBindings(); } protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType) { return controllerType == null ? null : (IController)_kernel.Get(controllerType); /*if (controllerType == null) { return null; } else { return (IController)_kernel.Get(controllerType); }*/ }           private void NinjectDefaultBindings() { _kernel.Bind<IHappyEid>().To<HappyEid>(); } } ``` ولا تنسى ان تعدل ملف الـ Global ليصبح كالتالي: ```csharp ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactor()); ``` لاحظ لقد قمت بشرح طريقة انشاء مثيل من الـ Controller في نفس الكود بكتابة الكود نفسه بطريقة جملة if والشفرتين يمكنهما العمل ، لكن لقد قمت بعمل Comment على الجملة الشرطية...وفي حال ان اردت ان تعمل بالجملة الشرطية قم بعمل Comment على السطر المبسط الذي يستخدم تقنية الـ #C للتأكد من عدم فراغ العنصر. \*\*\* الطريقة الأولى التي تفصل الفئات عن بعضها (الثلاثة فئات) تعتبر أفضل لأنها تبيّن لك مهمة كل فئة...لكن يمكنك ان تعمل ايضا على الطريقة الثانية إن أردت. \*\*\* أيضا بإمكانك إضافة الفئات وحقن الواجهات Interfaces داخل الإسلوب Method التي قمنا بتعريفها على هذا النحو: ```csharp private void NinjectDefaultBindings() { _kernel.Bind<IHappyEid>().To<HappyEid>(); } ``` \*\*\* هنالك العديد من المكتبات الأخرى في حال لم ترد إستخدام مكتبة الـ Ninject فبإمكانك البحث عنها ومعرفة طريقة عملها. ### كيف يمكن عمل وبناء ال Docker Images URL: https://informatic-ar.com/building-docker-images/ Last updated: 2026-10-03T22:31:30.000Z في هذا الموضوع سوف نتحدث عن كيفية بناء ال Docker Image وكيف نقوم بوضع موقعنا ك Image ومن ثم نقوم بتشغيلها، وسوف نقوم بالتعامل مع Static Website مكون من ملفات جافا سكربت و HTML ونشغله ك docker container ومن ثم تصفحه بواسطة المتصفح. وأخيراً سوف نتحدث عن ال Dockerfile وكيف يسهل بناء ال Images بسهولة. **المواضيع في السلسلة:** 1. [مقدمة حول دوكر Docker](https://informatic-ar.com/docker-introduction/) 2. [البداية مع دوكر Docker](https://informatic-ar.com/getting-started-docker/) 3. [مشاركة المجلدات في دوكر Docker](https://informatic-ar.com/docker-volumes/) 4. بناء ال Docker Images (الموضوع الحالي) 5. [تشغيل قواعد البيانات في دوكر Database in Docker](https://informatic-ar.com/docker-databases/) 6. [تشغيل البرامج باستخدام ال Docker Compose](https://informatic-ar.com/docker-compose/) ولكي نقوم بتشغيل الموقع في دوكر، يجب أن يتم تطوير الموقع اولاً، وفي حالتنا هذه فالموقع جاهز وهو Angular Web Site، بعد ذلك هناك العديد من الخيارات: - تنزيل nginx image ومن ثم وضع ملفات الموقع في مجلد ما وعمل mount لهذا المجلد ومن ثم تشغيل ال container بهذا الملفات. - تنزيل nginx image ومن ثم نسخ ملفات الموقع الى داخل ال Container File system. - بناء Image بها الملفات الخاصة بالموقع ومن ثم تشغيل ال Container منها ولن تحتاج لنسخ او أي ملفات لأن كل شيء متواجد بداخلها الصورة التالية تبين الخطوات الثلاثة التي يمكن القيام بها لتشغيل الموقع بداخل دوكر: [![](https://informatic-ar.com/wp-content/uploads/2018/06/docker_options-1024x348.png)](https://informatic-ar.com/wp-content/uploads/2018/06/docker%5Foptions.png) قبل أن نبدأ في شرح الطرق الثلاثة هذه، [قم بتحميل الموقع من هذا الرابط](https://www.dropbox.com/s/r90jxwhugc6h9jc/solitaire.zip?dl=0&ref=informatic-ar.com) وبعد التحميل قم بفك الضغط وانسخ المجلد solitaire الى أي مسار تريده، وليكن ال C:\\ ، وبالتالي سوف يكون المجلد متواجد على القرص السي مباشرة. ## تشغيل الموقع عن طريق ال Mounting لقد تناولنا هذه الطريقة في الموضوع السابق وهي بسيطة، حيث يتم تشغيل البرنامج المطلوب وهو في حالتنا أي ويب سيرفر مثل nginx ويتم تحديد ال –v بمسار الملفات في النظام الأساسي ومن ثم : ومن ثم المسار بداخل ال container ويكون بصيغة ذلك النظام الذي يعمل عليه. الصورة التالية تبين الامر لتشغيل ال container وتم استخدام ال –p لكي يتم تحديد البورت في الجهاز الأساسي والبورت الذي يعمل به الموقع في ال container. [![](https://informatic-ar.com/wp-content/uploads/2018/06/mounting_files-1024x106.png)](https://informatic-ar.com/wp-content/uploads/2018/06/mounting%5Ffiles.png) لاحظ المسار بداخل ال container وغالباً لكل ويب سيرفر مسار خاص به، ويجب الرجوع للتوثيق الخاص بال nginx في ال docker hub لكي تعرف المسار المناسب لكي يعمل الموقع. الآن قم بفتح ال localhost:8081 وسوف تجد أن اللعبة تعمل لديك: [![](https://informatic-ar.com/wp-content/uploads/2018/06/running_website.png)](https://informatic-ar.com/wp-content/uploads/2018/06/running%5Fwebsite.png) ## تشغيل الموقع عن طريق نسخ الملفات لل Container كما ذكرنا أن الدوكر يعطي Isolation لنظام الملفات، فلن تستطيع ال Container الوصول لنظام ملفات النظام الأساسي، الا في حال ال Mounting/Volume فقط ويكون بحسب ما تم تحديده بها، والطريقة الثانية هي بنسخ الملفات من النظام الأساسي الى ال Container كما هو الحال عندما يتم نقل الملفات من جهاز لجهاز أخر. سوف نقوم اولاً بتشغيل ال nginx container في الخلفية –d وقمنا بوضع اسم لها –name، وبعدها قمنا بطباعة docker ps لكي تتأكد من أنها تعمل ولها الاسم الذي تم إعطائه لها، يمكنك الان الذهاب للمتصفح وسوف تجد ان الموقع الرئيسية بصفحة ال nginx الافتراضية تعمل. [![](https://informatic-ar.com/wp-content/uploads/2018/06/running_container_named-1024x185.png)](https://informatic-ar.com/wp-content/uploads/2018/06/running%5Fcontainer%5Fnamed.png) سوف نقوم الآن بالدخول لهذه ال container التي تعمل، وسوف نستخدم الأمر docker exec الذي تناولناه سابقاً وهو لتشغيل برنامج اخر بداخل نفس ال container، وهذا الأمر يأخذ اسم ال container أو ال ID ولذلك قمنا بتسمية ال container باي اسم (واخترنا nginx) وسوف نحدده الان عند الأمر exec. [![](https://informatic-ar.com/wp-content/uploads/2018/06/inside_container_bash.png)](https://informatic-ar.com/wp-content/uploads/2018/06/inside%5Fcontainer%5Fbash.png) بعد الدخول على الشل قمنا بعرض الملفات وكما هو واضح فنظام الملفات مختلف تماماً عن النظام الأساسي، ويمكن الدخول على المسار /usr/share/nginx/html وسوف تجد المسار فارغاً ونحن نريد تعبئته بالملفات من النظام الأساسي، بعد ذلك يمكنك الخروج من الشل باستخدام الأمر exit. اذاً هذه الخطوة فقط للمشاهدة والتأكد من ان المسار فارغاً، والخطوة الضرورية هي النسخ وذلك من خلال الأمر docker cp وهذا الأمر يأخذ معاملين الأول هو المسار من النظام الأساسي، ومن ثم اسم او معرف ال container التي تعمل ومن ثم : ومن ثم المسار الذي تريد النسخ عليه، الصورة التالية تبين كيفية النسخ بهذا الأمر: [![](https://informatic-ar.com/wp-content/uploads/2018/06/copy_files_inside_container.png)](https://informatic-ar.com/wp-content/uploads/2018/06/copy%5Ffiles%5Finside%5Fcontainer.png) الآن يمكنك الدخول بالشل مجدداً كما في الخطوة قبل السابقة والتأكد من وجود الملفات، أو يمكنك الذهاب للمتصفح والدخول ومشاهدة الموقع الآن. ## تشغيل الموقع عن طريق وضع كامل الملفات على شكل Image والطريقة لذلك هي نفس خطوات الطريقة الثانية، حيث نقوم أولاً بتشغيل ال container ومن ثم بنسخ الملفات بداخلها، وبعد الانتهاء وكل شيء نقوم بأخذ صورة Snapshot للحالة الحالية وهي سوف تعتبر Image ويمكن تشغيل ال Image مباشرة فيما بعد ويكون بداخلها كل الملفات. اذاّ يتم تشغيل ال Container من خلال ال Image، ويتم عمل ال Image من خلال صورة أو commit لل Container التي تعمل، وسوف نستخدم الأمر commit ويتم تحديد اسم او معرف ال Container ومن ثم ال Image الجديدة، وايضاً يمكن تحديد ال Tag الذي تريدها. [![](https://informatic-ar.com/wp-content/uploads/2018/06/docker_commit-1024x375.png)](https://informatic-ar.com/wp-content/uploads/2018/06/docker%5Fcommit.png) الآن سوف نقوم بتشغيل Container جديدة من ال Image الجديدة والتي بها كل شيء بدون ان نحتاج لنسخ أي ملفات أخرى، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/06/docker_run_container_from_image-1024x214.png)](https://informatic-ar.com/wp-content/uploads/2018/06/docker%5Frun%5Fcontainer%5Ffrom%5Fimage.png) ولاحظ أنه في هذه المرة حددنا اسم ال Image التي قمنا بعملها وليس ال nginx فقط، وتستطيع الان زيارة الموقع localhost:8090 وسوف تجده يعمل ايضاً. للتذكير، فقد بدئنا أولاً بأخذ ال nginx Image والتي تحتوي على ملفات نظام التشغيل الأساسية، وبها برنامج nginx نفسه، وبعد ذلك عندما نقوم بتشغيل ال Container فسوف يتم إضافة layer جديدة قابلة للقراءة والكتابة وهي التي نضع بها الملفات الخاصة بنا. [![](https://informatic-ar.com/wp-content/uploads/2018/06/image_container.png)](https://informatic-ar.com/wp-content/uploads/2018/06/image%5Fcontainer.png) ولأن كل Layer بها ملفات خاصة فسوف يتم عمل دمج بين كل هذه الملفات حتى تكون في نظام الملفات وذلك باستخدام عملية ال Union File System، مثلاً الصورة التالية تعرض ال Container مكون من عدة طبقات واخيراً الطبقة الأولى التي يمكن الكتابة عليها، ومن ثم حصل الدمج Union بين الملفات في تلك الطبقات لنحصل على نظام ملفات واحد. [![](https://informatic-ar.com/wp-content/uploads/2018/06/union_file_system.png)](https://informatic-ar.com/wp-content/uploads/2018/06/union%5Ffile%5Fsystem.png) هنا العديد من الأشياء التي تحدث أثناء دمج الملفات: - إذا كان هناك ملف اسمه txt في طبقة سفلية، ومن ثم حصل تغيير في ذلك الملف في الطبقة الأعلى فسوف يتم اجراء ما في الطبقة العليا، أو حتى في حال الحذف فسوف يتم الحذف ايضاً، لذلك الطبقة العليا تفوز دائماً. - الطبقة العليا تحتوي على التغييرات للطبقة السفلى فقط، سواءً إضافة ملفات، تحديث ملفات، أو حتى حذف ملفات. وهذا ما يجعلها صغيرة ولن تحتاج لوضع كل الملفات الموجودة في طبقة أسفل منها. وهكذا عندما قمنا بنسخ ملفات الموقع في ال container التي بدئت بال nginx image وقمنا بعمل Image جديدة فسوف تكون ال Image الجديدة مكونة من كل الطبقات في ال nginx image بالإضافة الى طبقة واحدة جديدة بها الملفات، وذلك من خلال الأمر docker commit. الصورة التالية توضح ذلك حيث يوضح حجم ال image الجديدة ذلك التغيير: [![](https://informatic-ar.com/wp-content/uploads/2018/06/image_sizes.png)](https://informatic-ar.com/wp-content/uploads/2018/06/image%5Fsizes.png) وبعد أن قمنا بتشغيل ال container من ال image الجديدة سوف يعمل وتضاف الطبقة الأولى الخاصة بالكتابة والقراءة لذلك ال container حتى يستطيع إضافة أي تعديلات يريدها ويمكن بعد ذلك حفظ image أخرى وهكذا. [![](https://informatic-ar.com/wp-content/uploads/2018/06/image_layer-1024x379.png)](https://informatic-ar.com/wp-content/uploads/2018/06/image%5Flayer.png) يمكنك أن تقوم بعمل export لل image وتقوم بفك ضغطها ومشاهدة الملفات الخاصة بالموقع في أحد المجلدات كما قمنا بذلك في الموضوع السابق. ## استخدام ال Dockerfile بدلاً من إعادة كتابة الأوامر لقد قمنا بالعديد من الخطوات حتى نقوم بعمل Image خاصة بنا: - تشغيل container يعتمد على nginx image من خلال الأمر docker run –d –p 8080:80 –name nginx وهذا الأمر سيقوم بتنزيل nginx image وتشغيل container في الخلفية ويعطيه اسم nginx - نسخ الملفات من النظام الأساسي الى مجلد بداخل الويب سيرفر في ال nginx container التي تعمل الأن من خلال الأمر docker cp .\\app\\. nginx:/usr/share/nginx/html - أخذ صورة من ال nginx container من خلال الأمر: docker commit nginx solitaire:nginx وتم تسميتها بالاسم solitaire ووضع تاق بها ايضاً nginx. - وقد تحتاج لأوامر نسخ أخرى او حذف لملفات معينة وسوف تكون موجود هنا، مثلاً تريد وضع اعدادات خاصة لل nginx او حذف الكاش او غيرها من الأمور، وكلها تكون قبل ال committing لل container حتى تخرج ال image الجديدة [![](https://informatic-ar.com/wp-content/uploads/2018/06/docker_steps_build_image.png)](https://informatic-ar.com/wp-content/uploads/2018/06/docker%5Fsteps%5Fbuild%5Fimage.png) كل هذه الخطوات حتى تنشئ ال image التي تريدها، وبدل من أن تكتبها في كل مرة فيمكنك وضعها في ملف سكربت اسمه Dockerfile وسوف يقوم دوكر بتشغيلها واحداً تلو الأخر، وسوف نقوم الآن بتحويل الأوامر أعلاه الى Dockerfile - بدلاً من تشغيل Container ونسخ ملفات فيه، فسوف نقوم بتحديد ال Image وذلك من خلال الأمر FROM nginx - نسخ الملفات وذلك من خلال الأمر COPY app /usr/share/nginx/html - انتهينا من الملف ونقوم بحفظه، وسوف نقوم ببناء ال image وذلك من خلال الأمر docker build ويتم تمرير ملف ال Dockerfile لها، ويتم تمرير اسم ال image الجديدة لها ايضاً، وذلك من خلال الأمر docker build –f Dockerfile –t solitaire:nginx . ويمكن اختصار الأمر الى docker build –t solitaire:nginx . في حال تم تسمية ال Dockerfile بالاسم الافتراضي. المعامل الأخير هو مسار التي يتواجد عليه الملف وهو . وتعني المسار الحالي. [![](https://informatic-ar.com/wp-content/uploads/2018/06/dockerfile.png)](https://informatic-ar.com/wp-content/uploads/2018/06/dockerfile.png) الصورة التالية توضح الخطوات فقمنا بإنشاء الملف في نفس المسار solitaire folder وقمنا بوضع محتوى ال Dockerfile وقمنا ببنائه كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/06/build_dockerfile.png)](https://informatic-ar.com/wp-content/uploads/2018/06/build%5Fdockerfile.png) لكل أمر سيتم تطبيقه على حدة، وأول أمر FROM nginx سوف يقوم فعلياً بتنزيل ال image إذا لم تكن موجودة، ومن ثم بتشغيلها، وبعد ذلك ينتقل للخطوة التالية وهي نسخ الملفات بداخلها، وبعد ذلك تم حذف ال container التي استخدمت اثناء عملية نقل الملفات، واخيراً تم حفظ ال image الجديدة، وظهر تحذير بأنه تم بناء Linux container في ويندوز ولكنه عادي أثناء التطوير، لكن في ال production فيفضل البناء على لينوكس إذا كنت تستخدم ال Linux Container. يمكنك الآن ان تستعرض ال images الموجودة وسوف ترى ال image الجديدة: [![](https://informatic-ar.com/wp-content/uploads/2018/06/docker_images-1.png)](https://informatic-ar.com/wp-content/uploads/2018/06/docker%5Fimages-1.png) وسوف تكون بنفس الحجم لل image التي قمنا بإنشائها يدوياً، وهذا يعني أن النتيجة هي واحدة سواءً بكتابة كافة الأوامر، أو من خلال الاستفادة من ال Dockerfile وبنائه وهو الأسهل بالتأكيد. يمكنك كتدريب أن تقوم بالتحويل الى Windows Container ومن ثم استضافة وتشغيل الموقع على ال iis بدلاً من ال nginx وذلك بتغيير ال nginx في Dockerfile الى iis:nanoserver وايضاً تغيير مسار الملفات على الويب سيرفر الى مسار ال iis وهو C:\\inetpub\\www وسيعمل الموقع بلا مشاكل على ال Windows Container. ## رفع ال Image على ال Docker hub بعد أن قمنا بعمل ال Images بعدة طرق فقد تريد مشاركتها سواءً لمطور اخر او لسيرفر اخر يقوم بتنزيلها وتشغيل النسخة مباشرة، وسوف نقوم برفعها على ال Docker Hub، وللقيام بذلك يجب تسجيل حساب في ال Docker Hub وبعد ذلك سوف ترفعها على اسم المستخدم الخاص بك، ويجب أن نقوم بتسمية ال image بما بتناسب مع الحساب حتى يتم رفعها بشكل صحيحاً. سوف نظهر الآن جميع ال images الموجودة: [![](https://informatic-ar.com/wp-content/uploads/2018/06/docker_images2.png)](https://informatic-ar.com/wp-content/uploads/2018/06/docker%5Fimages2.png) وبعد ذلك نقوم بتسمية ال Image وسوف نستخدم الأمر docker tag ويتم تمرير اسم ال image الموجودة، والمعامل الثاني هو الاسم الجديد التي نريد رفعها باسم الحساب، فمثلاً اسم حسابي هو wajdyessam فسوف يكون اسمها wajdyessam/new\_name، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/06/tagging_image.png)](https://informatic-ar.com/wp-content/uploads/2018/06/tagging%5Fimage.png) بعد ذلك سوف نستخدم الأمر docker push ويتم تمرير اسم ال Image التي نريد رفعها وسوف يتم رفعها مباشرة في الحساب: [![](https://informatic-ar.com/wp-content/uploads/2018/06/push_image.png)](https://informatic-ar.com/wp-content/uploads/2018/06/push%5Fimage.png) هذه الخطوة تستوجب أنك قمت من قبل بالدخول على حسابك في الدوكر Hub، فاذا حصلت على رسالة خطأ Authentication Required فيجب عليك الدخول لحسابك أولاً قبل رفعك لل Image وذلك من خلال الأمر docker login ومن ثم سوف يطلب منك اسم المستخدم وكلمة المرور وسوف تحصل على رسالة بأنه تم تسجيل الدخول بنجاح. الآن تستطيع الدخول لحسابك وسوف تجد ال Image مرفوعة، وكما يمكنك تشغيلها مباشرة بالأمر docker run –d wajdyessam/solitaire من أي جهاز تريده. [![](https://informatic-ar.com/wp-content/uploads/2018/06/docker_hub-1024x483.png)](https://informatic-ar.com/wp-content/uploads/2018/06/docker%5Fhub.png) وصلنا لنهاية الموضوع، وتعرفنا على ال Dockerfile وانه سهل الكثير من الخطوات لبناء ال Image والتي يتم اخذ صورة من ال Container الذي يعمل حتى يتم بنائها، الموضوع القادم سوف نتحدث عن تشغيل قواعد البيانات في ال Containers. ### مشاركة المجلدات في دوكر Docker URL: https://informatic-ar.com/docker-volumes/ Last updated: 2026-10-03T22:31:30.000Z أحد المميزات في دوكر Docker هي سهولة تشغيل البرامج بدون خطوات وتعقيدات كثيرة في التنزيل، وهذا الأمر سوف نستخدمه كثيراً لتحميل البرامج التي نحتاجها لاستضافة مواقعنا مثل nginx أو IIS وايضاً لتشغيل أي Command Line Application أخر. في هذا الموضوع سوف نقوم بتشغيل عدة برامج من سطر الأوامر Command Line ومنها نتعرف على بعض الأمور في دوكر وال Containers، خصوصاً ما يجري بداخل ال Container Image، وكيف يمكن مشاركة مجلدات او ملفات بين نظام التشغيل الأساسي مع ال Container ولماذا نحتاج لذلك. **المواضيع في السلسلة:** 1. [مقدمة حول دوكر Docker](https://informatic-ar.com/docker-introduction/) 2. [البداية مع دوكر Docker](https://informatic-ar.com/getting-started-docker/) 3. مشاركة المجلدات في دوكر Docker (الموضوع الحالي) 4. [بناء ال Docker Images](https://informatic-ar.com/building-docker-images/) 5. [تشغيل قواعد البيانات في دوكر Database in Docker](https://informatic-ar.com/docker-databases/) 6. [تشغيل البرامج باستخدام ال Docker Compose](https://informatic-ar.com/docker-compose/) ذكرنا في المواضيع السابقة أن كل ال User Space تكون بداخل ال Container Image سواءً كان نظام الملفات أو المكتبات الخاصة بنظام التشغيل وايضاً بعض البرامج الأخرى، كلها تكون بداخل وعاء ال Image، كما في الصورة التالية: [![](https://informatic-ar.com/wp-content/uploads/2018/06/user_space-1024x489.png)](https://informatic-ar.com/wp-content/uploads/2018/06/user%5Fspace.png) سوف نقوم الآن بالتأكد من ذلك وسحب image ومشاهدة محتواها، وسوف نقوم اولاً بمعرفة ال Images التي لدينا على الجهاز، وذلك من خلال الأمر docker images: [![](https://informatic-ar.com/wp-content/uploads/2018/06/docker_images.png)](https://informatic-ar.com/wp-content/uploads/2018/06/docker%5Fimages.png) وسوف نعمل على ال iis image فهي الأقل حجماً، واذا لم تكن لديك فيمكن تحميلها من خلال الأمر docker pull microsoft/iis:nanoserver وسيتم تحميلها. الآن سوف نستخدم أمر تصدير ال image الى ملف .tar مضغوط حتى نستطيع فكه ومشاهدة محتوياته، وذلك من خلال الأمر docker save image كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/06/save_image.png)](https://informatic-ar.com/wp-content/uploads/2018/06/save%5Fimage.png) بعد الانتهاء من التصدير سوف تجد الملف المضغوط الذي قمت بتصديره في المسار المحدد، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/06/exported_image.png)](https://informatic-ar.com/wp-content/uploads/2018/06/exported%5Fimage.png) سوف نحتاج الى فك الضغط من ذلك الملف، وسوف تحتاج لبرنامج لفك الضغط، ولأن نظام الويندوز لا يدعم ذلك الملف، فيمكنك تحميل أي برنامج للقيام بفك الملف مثلاً WinRAR أو 7zip أو أن تستفيد من دوكر وتقوم بتشغيل Linux Container وظيفته فك الملف من الضغط، وهذا ما سنفعله حيث أن فكرة تشغيل برامج نظام تشغيل آخر لكي تقوم ببعض العمليات مثل فك الضغط من خلال أمر واحد وكأن البرنامج يعمل على جهازك الحالي هي من الأفكار الجميلة في دوكر. كل أنظمة تشغيل لينوكس تأتي ب tar command line لضغط وفك الملفات وسوف نستخدم هذا البرنامج بداخل ال Linux container للقيام بهذا الأمر. لذلك سنقوم أولاً بالتحويل الى ال Linux Containers وذلك بالضغط على ايقونة دوكر بالأسفل ومن ثم اختيار Switch to Linux Containers، وبعد التحويل تستطيع تجربة مشاهدة ال docker images وسترى لديك ال images الخاصة باللينوكس. [![](https://informatic-ar.com/wp-content/uploads/2018/06/linux_containers_images.png)](https://informatic-ar.com/wp-content/uploads/2018/06/linux%5Fcontainers%5Fimages.png) سوف نحتاج الى image بها برامج لينوكس ويمكن تحميل أي من Linux images وسوف نختار ubuntu وسوف نقوم بتحميل ال image من المصدر الأصلي لها official حتى نضمن أنها من المصدر الأصلي ولا يوجد بها أي تعديلات. [![](https://informatic-ar.com/wp-content/uploads/2018/06/ubuntu_docker_hub.png)](https://informatic-ar.com/wp-content/uploads/2018/06/ubuntu%5Fdocker%5Fhub.png) سنقوم بتحميلها من خلال الأمر docker pull ubuntu ومن ثم التأكد من أن ال image أصبحت موجودة لدينا: [![](https://informatic-ar.com/wp-content/uploads/2018/06/pull-ubuntu.png)](https://informatic-ar.com/wp-content/uploads/2018/06/pull-ubuntu.png) الان سنقوم بتشغيل ال container من هذه ال image وسوف نقوم بفتح برنامج ال shell وهو سطر الأوامر في لينوكس، وسوف نقوم بكتابة امر فك الضغط من الملف حتى تخرج الملفات التي نريد فكها. ولكن كما ذكرنا سابقاً ان ال container يعطي انعزال isolation للعمليات ولنظام الملفات File System، فهذا يعني أنه من داخل ال container فلن تستطيع الوصول لأي ملف على ال File System لأنها معزولة عنها، ولذلك يبقى السؤال كيف يمكن الوصول للملف الذي تريد فك ضغطه اذاً؟ # استخدام ال Volumes لمشاركة جزء من نظام الملفات لحل هذه المشكلة سوف نقوم بعمل mount لجزء من نظام الملفات النظام الأساسي، سواءً كان مجلدا كاملاً بكل محتواه، أو حتى ملف واحد فقط، وهذا الجزء الذي تم عمل mount له سيكون متاح لل container للقراءة والكتابة فيه، يمكنك تخيلها كأنه أدخلت USB للجهاز فسوف تجد قرص جديد على جهازك، فبالنسبة لل container الملف الذي تم عمل mount له يكون بمثابة قرص او جزء جديد فيه. هذه العملية تسمى في دوكر بال Volumes. ولأننا الان في Linux Containers وهذا يعني كل ال Containers سوف تعمل على ال Moby VM بداخل ال Hyper-V، فيجب ان نقوم بعمل مشاركة من نظام ويندوز الى لينوكس اولاً، حتى بعد ذلك نستطيع تحديد الأجزاء التي نريدها ان نشاركها لل Containers. بالزر الأيمن من دوكر بالأسفل اختر اعدادات Settings ومن ثم Shared Drives واختر القرص الذي تريد مشاركته مع نظام التشغيل لينوكس واضغط على Apply وسوف يطلب منك إدخال كلمة مرور النظام للتأكيد. [![](https://informatic-ar.com/wp-content/uploads/2018/06/shared_drive.png)](https://informatic-ar.com/wp-content/uploads/2018/06/shared%5Fdrive.png) الأن أصبح القرص C متاحاً لنظام التشغيل لينوكس، ولكن ما زالت ال Containers لن تستطيع الوصول له، ويجب ان نستخدم ال volume الآن لتحديد الجزء من الملفات التي نرغب بالوصول لها. سوف نقوم بتجربة عدة أوامر لكي نفهم تلك الفكرة، ونبدأ بالأمر التالي وهو أمر تشغيل عادي docker run، ولكن ارسلنا له معامل –rm وهي تعني قم بحذف ال container بعد ان تنتهي وليس لها علاقة بال volumes، بعد ذلك يأتي المعامل –v وبها معاملين الأول هو جزء نظام الملفات الذي نريد مشاركته مع ال container، وبعدها : ومن ثم المسار الذي ستظهر فيه الملفات في ال containers، ولأن ال Container هي لينوكس فيجب ان تتعامل بمسارات اللينوكس حيث تبدأ ب / وهي الروت، بعكس الويندوز التي تبدأ بالقرص أولاً. بعد ذلك نحدد اسم ال image التي سنشغل ال container منها، وبعدها البرنامج بداخلها وهو ls الذي يعرض الملفات، وهو يستقبل معامل ايضاً وتم تمرير /data. [![](https://informatic-ar.com/wp-content/uploads/2018/06/listing_mounted_folder.png)](https://informatic-ar.com/wp-content/uploads/2018/06/listing%5Fmounted%5Ffolder.png) الان ستعمل ال containers وتم مشاركة ال C:/Users لها على المسار /data وبالتالي تستطيع مشاهدتها، وبالفعل تم تشغيل برنامج ls بذلك المسار وتم طباعة كل محتوى ذلك المجلد. وهو نفس الملفات إذا قمت بفتحها بال Windows Explorer أو من خلال ال dir وسوف تجد نفس الملفات بالضبط، وهذا يعني ان ال mounting للقرص يعمل والان ال container تتشارك بالملفات. الأن بدلاً من عمل mount لكل الملفات هذه، سوف نقوم بعملها للملف الواحد الذي نريده، ونقوم بتحديده وبالتالي ال container لن ترى الا هذا الملف فقط، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/06/file_mount.png)](https://informatic-ar.com/wp-content/uploads/2018/06/file%5Fmount.png) وهذا أفضل من ناحية الأمان، حيث نعطي ال Containers البيانات التي تحتاجها فقط في أداء عملها لا أكثر من ذلك. أصبحنا جاهزين لتشغيل ال sh بنفس الطريقة السابقة بدلاً من العرض، وسوف نستخدم وضع ال –it حتى نستطيع ادخال الأوامر، وأول امر هو tar –tf وهو لمشاهدة محتوى الملف المضغوط بدون فك: [![](https://informatic-ar.com/wp-content/uploads/2018/06/view_tar_files.png)](https://informatic-ar.com/wp-content/uploads/2018/06/view%5Ftar%5Ffiles.png) لاحظ المحتوى هو مجموعة من الطبقات المكونة منها هذه ال image. سنقوم الان بفك المحتوى من نفس الشل وذلك من خلال الأمر tar –xf كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/06/tar_extract-1024x141.png)](https://informatic-ar.com/wp-content/uploads/2018/06/tar%5Fextract.png) بعد فك الملف لمجلد جديد تم تحديده بالمعامل –C ظهرت رسالة بأن المجلد غير موجود، وبعدها قمنا بإنشائه ومن ثم تم فك الضغط مجدداً، وبعد ذلك قمنا بعرض محتوى المجلد الجديد وظهرت البيانات فيه. بهذا الشكل قمنا بعمل فك للملف المضغوط في ويندوز بداخل ال container، ولكن ماذا إذا اردت ان يتم الفك ايضاً الى ويندوز؟ فالتعديل بسيط، فبدلاً من ان نقوم بعمل mount ملف من ويندوز الى مجلد بداخل لينوكس، سوف نقوم بعمل mount مجلد من ويندوز الى مجلد داخل لينوكس، وبعدها نقوم بفك الملفات في ذلك المجلد او مجلد جديد بداخله، وسوف نرى ذلك التغيير في ويندوز. سوف نحتاج ان ننشئ مجلد جديد في الويندوز في داخل المجلد ال mounted حتى نضع الملفات فيه، ويمكن فعله من خلال الويندوز وانشاء مجلد جديد بالزر الأيمن وتسميته مثلاً iis، او حتى سوف نطبق نفس الفكرة وهي تشغيل امر لينوكس لإنشاء مجلد في ال mounted folder كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/06/container_mkdire.png)](https://informatic-ar.com/wp-content/uploads/2018/06/container%5Fmkdire.png) الأن قم بفتح المسار في الويندوز وستجد ان المجلد الجديد متواجد الآن: [![](https://informatic-ar.com/wp-content/uploads/2018/06/created_dir.png)](https://informatic-ar.com/wp-content/uploads/2018/06/created%5Fdir.png) الآن سنقوم بكتابة أمر فك الضغط الى ذلك المجلد، وبدلاً من فتح الشل ومن ثم كتابة أوامر الضغط يمكن الاختصار وتشغيلها مباشرة كما في الصورة التالية: [![](https://informatic-ar.com/wp-content/uploads/2018/06/extract_file_host-1024x136.png)](https://informatic-ar.com/wp-content/uploads/2018/06/extract%5Ffile%5Fhost.png) بهذا الأمر الأن سوف نجد انه تم فك الملف المضغوط في ويندوز، الى مجلد بداخل الويندوز وذلك من خلال برنامج يعمل بداخل نظام التشغيل لينوكس، أمر رائع! [![](https://informatic-ar.com/wp-content/uploads/2018/06/files_host.png)](https://informatic-ar.com/wp-content/uploads/2018/06/files%5Fhost.png) طبعاً مفهوم ال Volumes أتاح مشاركة بين مجلدات أو ملفات في النظام الأساسي وال container وبالتالي أي تغيير يتم عمله بداخل ال container في ذلك المجلد سوف يظهر في النظام الأساسي، وهذا ما تم تطبيقه في الأعلى. بدخل أي من المجلدات السابقة سوف نرى ايضاً ملف الطبقة مضغوط، وسوف نختار المجلد الذي يبدأ ب 49 ونقوم بفك الملف المضغوط بداخله، سوف نكرر نفس الخطوات، وهذه المرة سوف ندخل على الشل، ونقوم بفكها في مجلد نقوم بإنشائه ايضاً، وذلك من خلال أمر انشاء المجلد mkdir باي اسم ومن ثم فك ملف الطبقة المضغوط لهذا الملف بنفس الأمر السابق، وبعد الفك سوف نقوم بفتحه من داخل الويندوز الآن، وهو يبدوا كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/06/layer.png)](https://informatic-ar.com/wp-content/uploads/2018/06/layer.png) هل تلاحظ ما هو محتوى هذه الطبقة؟ هو نظام الملفات لل image، فكما ذكرنا أن ال image تحمل ال File System الخاصة بها لأنها معزولة عن الخاصة بنظام التشغيل. # تشغيل برنامج ffmpeg للتعامل مع ملفات الفيديو لنفرض اردت تحويل مقطع فيديو صغير الى صورة git، فسوف تحتاج الى تنزيل برنامج وتتأكد من انه يخلوا من المشاكل وغيرها، وأحد أشهر البرامج هو ffmpeg ويعمل على عدة أنظمة تشغيل، وسوف نقوم بتشغيله بنفس الفكرة ونستفيد من ال Linux Container لكي يحول لنا الفيديو الى صورة ويضعها لنا على نظام الملفات في مجلد نحدده، مثلاً الفيديو ملف mp4 المتواجد [في هذا الرابط](http://bit.ly/2fcrRK2?ref=informatic-ar.com) سوف نقوم الآن بتشغيل برنامج ffmepg وإعطاء أمر التحويل بداخل volume حتى نستطيع الوصول لها بسهولة، مثلاً اريد كتابته على الفولدر C:\\test.video سوف نقوم بانشائه ومن ثم ندخل على ال powershell من سطر الأوامر وننفذ أمر التشغيل: [![](https://informatic-ar.com/wp-content/uploads/2018/06/running_ffmpeg.png)](https://informatic-ar.com/wp-content/uploads/2018/06/running%5Fffmpeg.png) تم فتح ال powershell وذلك حتى نستفيد من خاصية المتغير pwd وهي تعني المسار المحلي، لأنه كما يتضح في الصورة التنفيذ سوف يتم من داخل المجلد الذي يتواجد عليه ال cmd وهو test.video ويمكن اذا أردت تشغيله من ال cmd بشكل عادي ان تقوم بكتابة المسار الذي تريد عمل mount له. ايضاً لاحظ استخدمنا –volume بدلاً من الاختصار –v وكلاهما في دوكر لنفس الأمر وهو استخدام ال volume لكي تحدد مجلد تقوم بعمل mounting له في مجلد بداخل ال container. بعد الانتهاء سوف تجد أن الصورة الآن لديك على المجلد: [![](https://informatic-ar.com/wp-content/uploads/2018/06/converted_video.png)](https://informatic-ar.com/wp-content/uploads/2018/06/converted%5Fvideo.png) # خلاصة فكرة ال Volume وال Mounting مشابه لفكرة تركيبك ل USB في نظام التشغيل، حيث ستجد قرص جديد لديك. أولاً يكون النظام الأساسي Host به نظام الملفات والأقراص مثلاً في نظام الويندوز، وبعد أن يتم تشغيل Container سوف يتم تشغيلها من Image موجودة، وكما رأينا سابقاً ال Image يكون بها نظام الملفات الخاص بها وبالتالي لديها File System Tree بداخلها معزولة عن نظام التشغيل الأساسي وكل ذلك يتم بمجرد تشغيل ال Container. الآن بعد إضافة ال Volume وعمل Mounting لمجلد من النظام الأساسي الى مجلد في ال Container فسوف تكون كأنها عبارة اختصار للوصول الى المجلد الأصلي في النظام الأساسي وأي عملية تجري سيتم مشاهدتها مباشرة من النظام الأساسي. [![](https://informatic-ar.com/wp-content/uploads/2018/06/docker_volume-1024x548.png)](https://informatic-ar.com/wp-content/uploads/2018/06/docker%5Fvolume.png) وسوف نرى لاحقاً أن لهذا الأمر فوائد عديدة مثلاً قمت بتطوير الموقع، فيتم وضعه على النظام الأساسي وبعدها يتم عمل mounting للمجلد الخاص بالموقع ويتم تشغيل ال nginx مثلاً، وبالتالي يتم قراءة الملفات والكتابة ايضاً بها، ولل Volumes استخدامات أخرى وأهمها الحفاظ على البيانات بعد ان يتم إيقاف او حذف ال container وسوف يتم التطرق لها عند الحديث عن تشغيل قواعد البيانات بداخل ال Containers في مواضيع قادمة. بهذا الشكل نكون قد تعرفنا على فكرة ال Sharing Host File الى ال Containers وكيف يتم كتابة الملفات من والى النظام الأساسي، وأيضاً وضحنا فوائد وسهولة تشغيل البرامج بداخل ال containers للقيام بأي عمليات مطلوبة وذلك من خلال أوامر دوكر البسيطة، الموضوع القادم بإذن الله سوف يكون حول بناء Image خاصة بنا. ### البداية مع دوكر Docker URL: https://informatic-ar.com/getting-started-docker/ Last updated: 2026-10-03T22:31:31.000Z سوف نتحدث في هذا الموضوع عن تنزيل دوكر في ويندوز، ولينوكس، ومن ثم نقوم بتشغيل بعض البرمجيات containers عليه مثل Nginx و IIS وسوف نرى كيف أصبحت عملية تنزيل البرمجيات سهلة للغاية وبدون أي اعدادات تذكر، وسوف نوضح الفرق بين ال Linux containers و windows containers وكيف يمكن تحويل دوكر ليعمل على أي منهم، وأخيراً سوف نرى العزل Isolation التي توفره ال Containers. **المواضيع في السلسلة:** 1. [مقدمة حول دوكر Docker](https://informatic-ar.com/docker-introduction/) 2. البداية مع دوكر Docker (الموضوع الحالي) 3. [مشاركة المجلدات في دوكر Docker](https://informatic-ar.com/docker-volumes/) 4. [بناء ال Docker Images](https://informatic-ar.com/building-docker-images/) 5. [تشغيل قواعد البيانات في دوكر Database in Docker](https://informatic-ar.com/docker-databases/) 6. [تشغيل البرامج باستخدام ال Docker Compose](https://informatic-ar.com/docker-compose/) # تجربة دوكر بدون تثبيت أي برامج في حال لم تريد تنصيب دوكر الآن، فيمكنك استخدام الموقع [Play with Docker](https://labs.play-with-docker.com/?ref=informatic-ar.com) وتستطيع إضافة جهاز او أكثر من جهاز ومن ثم يمكنك تجربة الأوامر التي سوف نأخذها في الفقرات التالية. وسوف يتم اعطائك Session لمدة 4 ساعات للتجربة في جهاز تخيلي. [![](https://informatic-ar.com/wp-content/uploads/2018/05/playwithdocker-1024x783.png)](https://informatic-ar.com/wp-content/uploads/2018/05/playwithdocker.png) # تحميل دوكر على نظام تشغيل ويندوز في نظام التشغيل ويندوز، قم بتحميل الدوكر من خلال الرابط [Docker Community Edition for Windows](https://store.docker.com/editions/community/docker-ce-desktop-windows?ref=informatic-ar.com) المتواجد على Docker Store: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_store-1024x708.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fstore.png) ودوكر للويندوز يحتاج أن تعمل على نظام التشغيل ويندوز 10 اما Pro, Enterprise, Education وغير ذلك فلن يعمل، بالإضافة الى تحديث نظام التشغيل بأخر التحديثات، وهذه من متطلبات دوكر للويندوز، فاذا لم يكن لديك هذه المتطلبات فيمكن تنزيل Docker Toolbox وهي تعتبر الأن قديمة Legacy ولكنها للأجهزة التي لا تتوافق مع متطلبات دوكر للويندوز، سواءً Windows 7 or Windows 8 ولكن يجب أن يكون 64 بت، ويمكن تنزيلها من هنا [Docker Toolbox](https://docs.docker.com/toolbox/overview/?ref=informatic-ar.com): [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_toolbox-1024x779.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Ftoolbox.png) ولتحميل دوكر على ويندوز سيرفر 2016 فلن نحتاج اليه وانما فقط يكفي ال Docker Engine. بعد تحميلك Docker for Windows.exe وقم بتنزيله بالخطوات الافتراضية التالي-التالي الى ان ينتهي، وبعد الانتهاء يمكنك تشغيله من خلال قائمة البرامج وفتح Docker for Windows. وهناك بعض النقاط والمتطلبات المهمة قبل تنصيب الدوكر، وهي: عند تنصيب الدوكر سوف يقوم بتفعيل ال Microsoft Hyper-V في حال لم يكن مفعلاً، ويجب عليك حينها إعادة تشغيل الجهاز. ولن يعمل أي VM اخر مثلاً Virtual Box حيث لا يمكن تشغيل اثنين في آن واحد، ولن تعمل بجانب ال Docker for Windows. أيضاً لن تستطيع تشغيل ال Android Studio Emulator لأنه يستخدم ال Hardware Accelerated Execution Manager (واختصاراً ال Intel HAXM) فهو يتعارض مع ال Hyper-V ولا يمكن تشغيلهم في آن واحد، ويجب عليك إعادة التشغيل في كل مرة اردت إيقاف أحدهم والتبديل للبرنامج الاخر. ايضاً يجب تفعيل خاصية ال Virtualization من ال BIOS وذلك حتى يعمل دوكر ويستطيع تشغيل ال Linux Containers بداخل الويندوز باستخدام ال Hyper-V، وغالباً ما تكون مفعلة في الوضع الافتراضي، يمكنك التحقق من ذلك من ال Task Manger والذهاب لل CPU ومشاهدة ال Virtualization Enabled: [![](https://informatic-ar.com/wp-content/uploads/2018/05/enable_virtualization.png)](https://informatic-ar.com/wp-content/uploads/2018/05/enable%5Fvirtualization.png) أثناء التنصيب سوف تجد الرسالة، وستجد أنه يمكن تفعيل ال Windows Container مباشرة بدلاً من الافتراضي Linux Container، ولكن على أي حالة يمكن تغييرها فيما بعد من خصائص دوكر. لذلك اتركها كما هي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/installing_docker.png)](https://informatic-ar.com/wp-content/uploads/2018/05/installing%5Fdocker.png) بعد التثبيت وفتحه: [![](https://informatic-ar.com/wp-content/uploads/2018/05/open_docker.png)](https://informatic-ar.com/wp-content/uploads/2018/05/open%5Fdocker.png) سوف تجد أيقونة دوكر بجانب الساعة ويمكنك فتح الخصائص والاعدادات والتحويل من ال Windows Container الى Linux Containers وغيرها من المهام، وذلك بالضغط عليها بالزر الأيمن، وسوف نتحدث عن ذلك فيما بعد. حالياً عند تثبيت الدوكر سوف يعمل على ال Linux Container mode. [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_status_bar.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fstatus%5Fbar.png) بعد تثبيت ال Docker for Windows سوف ينزل معه العديد من الأدوات مثل Docker Engine و Docker CLI Client و Docker Compose و Docker Machine. ولأن ال Linux Containers يعني أنه يمكن تشغيل برمجيات لينوكس على ويندوز، وهذا سيتم عن طريق Virtual Machine يتم انشائها مباشرة ويتم تشغيل كل ال Linux Container بها، فقم بفتح ال Hyper-V Manager من قائمة البرامج وسوف تلاحظ وجود VM من نظام MobyLinux تعمل وهي التي يستخدمها دوكر للتعامل مع ال Linux Containers. [![](https://informatic-ar.com/wp-content/uploads/2018/05/hyper_v-1024x498.png)](https://informatic-ar.com/wp-content/uploads/2018/05/hyper%5Fv.png) إذا ظهرت لك هذه الرسالة: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_error.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Ferror.png) فعليك بالتأكد من دعم ال BIOS وتفعيله لل Virtualization، كما تطرقنا سابقاً، وإذا لم تكن مفعلة فيجب تفعيلها من اعدادت ال BIOS. إذا كانت مفعلة فهذه المشكلة تحتاج الى ان تقوم بتعطيل ال Hyper-V يدوياً وإعادة التشغيل ومن ثم إعادة تفعيله مجدداً وإعادة التشغيل ومن ثم سوف يعمل دوكر معك، ولا أدرى لماذا هذه الخطوات ولكنها قد تحدث أحياناً. فقم بفتح خصائص الويندوز: [![](https://informatic-ar.com/wp-content/uploads/2018/05/windows_feature.png)](https://informatic-ar.com/wp-content/uploads/2018/05/windows%5Ffeature.png) ومن ثم فعل ال Hyper-V: [![](https://informatic-ar.com/wp-content/uploads/2018/05/enable_hyper_v.png)](https://informatic-ar.com/wp-content/uploads/2018/05/enable%5Fhyper%5Fv.png) أو قم بتعطيلها وأعد التشغيل، ومن ثم قم بتفعيلها واعد التشغيل مجدداً، وبعد إعادة التشغيل سوف يعمل معك بالشكل الصحيح. بعد أن يعمل الدوكر لأول مرة سوف تجد هذه النافذة، وقم بالتسجيل واخذ Docker ID لكي تقوم بتسجيل الدخول فيه: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_login.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Flogin.png) ولمعرفة النسخة التي تعمل عليها يمكن الضغط على ايقونة دوكر بالأسفل بالزر الأيمن والضغط على about وسوف تجد معلومات النسخة (وقد تتغير من النسخة هنا لأن دوكر تحدث البرنامج كل شهرين تقريباً)، لكن تظل المفاهيم واحدة: [![](https://informatic-ar.com/wp-content/uploads/2018/05/about_docker.png)](https://informatic-ar.com/wp-content/uploads/2018/05/about%5Fdocker.png) بهذا الشكل نكون قد أنهينا من تنزيل دوكر على الويندوز. **ملاحظة**: إذا كنت تعمل على نظام تشغيل Mac فيمكن ايضاً تحميل Docker for Mac ويمكن تطبيق كافة الجزئيات السابقة ما عدا جزئية ال Windows Container. وبالنسبة لل Windows Server 2016 فلن تحتاج لتحميل ال Docker for Windows وانما مباشرة تحتاج ال Engine فقط، وهناك نسخة مخصصة لل Windows Server 2016 وهي ال Enterprise Edition ويمكنك تحميلها بسهولة ايضاً في النظام. # تنصيب دوكر على لينوكس سيتم تنصيبه على نظام Ubuntu 18.4 LTS وطريقة التنصيب في لينوكس بسيطة، فنقوم أولاً بتحديث ال apt من خلال الأمر: ```bash $ sudo apt-get update ``` [![](https://informatic-ar.com/wp-content/uploads/2018/05/apt_update.png)](https://informatic-ar.com/wp-content/uploads/2018/05/apt%5Fupdate.png) بعد ذلك سنقوم بتحميل بعضاً من ال packages حتى يتم التحميل عبر https بواسطة apt: ```bash $ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common ``` [![](https://informatic-ar.com/wp-content/uploads/2018/05/enable_https_apt.png)](https://informatic-ar.com/wp-content/uploads/2018/05/enable%5Fhttps%5Fapt.png) ستقوم بتحميل الدوكر GPG Key: ```bash $ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - ``` [![](https://informatic-ar.com/wp-content/uploads/2018/05/adding_docker_gpg.png)](https://informatic-ar.com/wp-content/uploads/2018/05/adding%5Fdocker%5Fgpg.png) سنقوم الان بتحديد ال Stable Repository لتحميل دوكر: ```bash $ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu artful stable" ``` [![](https://informatic-ar.com/wp-content/uploads/2018/05/adding_docker_repository.png)](https://informatic-ar.com/wp-content/uploads/2018/05/adding%5Fdocker%5Frepository.png) نقوم بتحديث ال apt مجدداً: ```bash $ sudo apt-get update ``` الان نقوم بتحميل دوكر: ```bash $ sudo apt-get install docker-ce ``` [![](https://informatic-ar.com/wp-content/uploads/2018/05/installing_docker_linux.png)](https://informatic-ar.com/wp-content/uploads/2018/05/installing%5Fdocker%5Flinux.png) # البداية في الأوامر سوف ننتقل الآن الى التعامل مع دوكر، ولحسن الحظ كافة الأوامر التي سوف نتعامل معها سوف تعمل على دوكر بغض النظر عن نظام التشغيل المستخدم، سواء ويندوز أو ماك أو لينوكس. أول امر هو لمعرفة معلومات عن الدوكر وهو docker info وسوف يخرج معلومات عن ال docker engine: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_info.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Finfo.png) أيضاً يمكن استخدام docker version لمعرفة معلومات السيرفر والكلاينت كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_version.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fversion.png) سوف تلاحظ أن الكلاينت يعمل على ويندوز وذلك لأن نظام الشغيل هو ويندوز، بينما السيرفر يعمل على ال Linux (باستخدام VM)، وكما ذكرنا يمكن تغييره الى ويندوز كما سيأتي فيما بعد لتشغيل Windows Containers مباشرة على النظام. لننتقل الى أولى الأوامر: وهو docker ps لمعرفة حالة العمليات process status، وسوف يخرج كل ال containers التي تعمل الآن: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_ps-1024x257.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fps.png) سوف تلاحظ أنه لا يوجد أي Container يعمل الان، والقائمة فارغة. سوف نقوم بتشغيل اول container وهو يعتبر Hello-World واسمه كذلك ايضاً، من خلال الأمر: docker run hello-world، وكما ذكرنا سابقاً ان الأمر run سوف يقوم بعمل pull وبعدها يقوم بتشغيل ال container في آن واحد: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_hello_world.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fhello%5Fworld.png) بعد أن عملت ال container سوف تنتهي مباشرة، ويمكن ان نشغلها مرة ثانية بنفس الأمر، وسوف نلاحظ أنه لن يتم تحميلها مجدداً، والسبب ان Image أصبحت موجودة في الجهاز وسيتم تشغيل ال container منها مباشرة: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_hello_world2.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fhello%5Fworld2.png) سوف نقوم بتشغيل مثال أخر وليكن ويب سيرفر مثلاً Nginx من خلال الأمر docker run –p 80:80 nginx، وسوف نلاحظ الأن استخدام المعامل –p وهو لتحديد البورت فأول بورت 80 هو الخاص بالجهاز الحالي وهذا يعني اريد أي طلب بالبورت 80 ينتقل لهذه ال container، وبداخل ال container سوف يكون ال nginx يستمع للبورت 80 داخل ال container يتم التقاط الطلب بواسطتها، وهذا يعني كأنه يتم تشغيل ال nginx على بورت 80 في الجهاز مباشرة، ويمكن ان نغير البورت الأول مثلاً 8080 وبالتالي سوف يتم طلب 8080 ويتم تمريره لل nginx الذي يستمع عبر بورت 80. [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_nginx.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fnginx.png) سوف نلاحظ أن هناك 3 أشياء تم تحميلها وهي ال Layers لأنه غالباً ال Image يكون بها أكثر من طبقة وسوف نتحدث عن ذلك لاحقاً، وبعد التحميل سوف تجد انه لا مخرج من ال nginx لكنه يعمل الان، ويمكن فتح المتصفح مباشرة localhost وسوف تجد رسالة من الويب سيرفر: [![](https://informatic-ar.com/wp-content/uploads/2018/05/localhost_nginx-1024x307.png)](https://informatic-ar.com/wp-content/uploads/2018/05/localhost%5Fnginx.png) هكذا بكل بساطة عمل ال nginx من خلال امر واحد فقط، وستجد بعد فتحك الصفحة عندها مخرجات على الكونسول التي يعمل بها ال nginx وهي عبارة عن logs لأي طلب يأتي للويب سيرفر: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_nginx_logs-1024x248.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fnginx%5Flogs.png) فكرة ال containers مع ال Docker غيرت من مفاهيم تعلم الأدوات، فلن تحتاج الى أن تتعلم كيف يمكن تنصيب البرنامج قبل ان تستخدمه، فيمكنك الان ان تستخدمه مباشرة، وبعدها قرر هل تريد ان تتعلم طريقة التنصيب وكيف تم التنصيب. الان لكي اخرج من ال container التي تعمل عليك بعمل detach وذلك اما من خلال CTRL+C أو CTRL+PQ لكي تخرج منها، ولكن سوف تظل ال container قيد العمل، وانما هو خروج من مخرجات تلك ال container التي تعمل. ولكي تتأكد من ذلك قم بمشاهدة ال containers التي تعمل بالأمر docker ps، وسوف تجد ان ال nginx مازال يعمل: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_ps_nginx-1024x127.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fps%5Fnginx.png) سوف تجد ال Container ID وهو رقم ال container ويمكنك التعامل معه من خلال الرقم (أو جزء من الرقم فقط)، اسم ال Image، الأمر الذي تم تشغيله وهو يأتي مع ال image، وقت الانشاء، حالة ال container، البورت المفتوح، واسم عشوائي يعطي لل container (في حال لم تنسد اسم لها) ويمكنك التعامل معها ايضاً من خلال الاسم. ويمكنك ايضاً فتح ال localhost وسوف تجد الموقع ما زال يعمل. الان سوف نستخدم أمر إيقاف ال container وذلك من خلال docker stop Identifier ويمكن استخدام معرف ال container سواء كان ال CONTAINER\_ID في الصورة السابقة او ال NAMES، وبالتالي يمكن ان يتم استخدام docker stop e51 أو docker stop eager\_tereshkova وكلهم سوف يعطوا نفس النتيجة، ولاحظ لم نقم بكتابة كامل المرقم وانما اول جزء منه فقط، ويمكن وضع أي عدد من الأرقام، لكن في حال هناك أكثر من container كانوا لهم نفس البداية فسوف يحصل conflict وتخرج لك رسالة خطأ بأنه هناك أكثر من container بنفس البادئة وعليك بوضع عدد أطول من الأرقام حتى يتم معرفة ال container الذي تقصده. [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_stop_nginx-1024x186.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fstop%5Fnginx.png) إذا تم الإيقاف بشكل صحيح سوف تحصل على نفس الجزء من المعرف الذي قمت بإدخاله، ويمكنك التأكد من انه تم الإيقاف عن طريق docker ps ولن تجد ال container تعمل بعدها: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_ps_empty-1024x149.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fps%5Fempty.png) وايضاً إذا ذهبت للموقع localhost فلن تجده يعمل لأن الويب سيرفر لا يعمل. سوف نقوم الان بتشغيل نفس container السابقة، وذلك من خلال الأمر docker start identifier، وقبل ان نقوم بتشغيلها، سوف نعرض الأمر docker ps –a والذي يظهر كل ال containers سواء التي تعمل او التي تم ايقافها، وسوف تشاهد المخرج التالي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_ps_a-1024x141.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fps%5Fa.png) سوف تلاحظ 3 containers موجودة، اول واحدة هي ال nginx التي تم ايقافها قبل قليل، والأخريات هم ال hello-world حيث قمنا بتشغيلها مرتين docker run hello-world وهي تنتهي مباشرة بعد العمل، لذلك توجد نسختان هنا. يمكن تشبيه الفكرة بالطريقة التقليدية عندما تقوم بتشغيل برنامج ما وايقافه عن العمل فهذا لا يعني انه تم مسحه، وانما يكون موجوداً، ونفس الفكرة في ال containers فبعد ايقافها فهي تكون موجودة ويمكن حذفها او إعادة تشغيلها مجدداً. سنقوم الان بتشغيل ال nginx container باستخدام الامر start من خلال معرف ال container أو اسمه، وسوف يكون المخرج الرقم نفسه لدلالة على اكتمال العملية: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_start_container.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fstart%5Fcontainer.png) يمكن الان فتح الموقع مجدداً localhost وستجده يعمل بدون أي مشاكل. ويمكنك مشاهدة ال container يعمل عند تنفيذ الأمر docker ps. من الأوامر الشائعة ايضاً هو الأمر docker images وسوف يعرض كل ال Images التي تحميلها ومتواجدة على الجهاز الان كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_images.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fimages.png) وهذه ال images تعتبر هي القالب التي يتم منها انشاء ال container. الان لحذف ال container سوف نستخدم الأمر: docker rm identifier، وقبل ان نقوم بحذفه يجب ان يكون متوقفاً عن العمل، وسوف نقوم الان بحذف ال nginx container كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_remove_container-1024x306.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fremove%5Fcontainer.png) سوف تجد أعلاه قمنا أولاً بعرض كافة العمليات docker ps – a وسوف تلاحظ أن ال nginx تعمل، وقمنا بعدها بإيقافها من خلال docker stop e5، وبعد ذلك عرضنا العمليات مجدداً والان أصبحت متوقفة عن العمل، واخيراً قمنا بحذفها docker rm e5، الان في حال قمنا بعرض العمليات مجدداً فلن تكون موجودة: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_ps_a2-1024x166.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fps%5Fa2.png) سوف نقوم الان بحذف بقية ال containers وطالما هي متوقفة فيمكن اصدار امر الحذف مباشرة، ويمكن حذفهم في آن واحد من خلال كتابة المعرفات وبينهم مسافة، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_remove_container2-1024x216.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fremove%5Fcontainer2.png) سوف تجد أنه تم كتابة اسم ال container الأول خطأ، وتم حذف الثاني، ومن ثم قمنا بكتابته صحيحاً وتم الحذف في الأمر الذي يليه. الان عند كتابة docker ps –a فلن تجد أي containers تعمل أو متوقفة. لكن ال Images ما زالت متواجدة، فعند كتابة docker images فسوف ترى ال images ولحذفها عليك بكتابة الأمر docker rmi identifier وكتابة معرف ال image، وفيما يلي سيتم حذفهم في آن واحد: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_remove_container3.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fremove%5Fcontainer3.png) وسيتم حذف كافة ال Layers في ال images وبعدها عند كتابة docker images فلن تجد أي image متواجدة. # مثال ثاني سوف نأخذ مثال أخر، وليكن هذه المرة توثيق ال Docker حيث يمكن فتح التوثيق بأكمله اونلاين من خلال هذا الرابط [Docker Documentation](https://docs.docker.com/toolbox/overview/?ref=informatic-ar.com). ولكن يمكنك ايضاً تحميله offline ك docker images وتشغله محلياً على ويب سيرفر، وسوف نقوم بذلك، ويمكن استخدام الأمر docker search docs لكي ترسل أي كلمة بحث وارسلنا الآن docs لكي تخرج كل ال images المتواجد على ال docker hub بكامل الاسم لها، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_documentation_search-1024x501.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fdocumentation%5Fsearch.png) أو يمكنك فتح ال docker hub ومن ثم البحث عن docs وسوف تجد ال docker docs وتستطيع مشاهدة تفاصيلها كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_hub_docs-1024x387.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fhub%5Fdocs.png) وقبل التحميل سوف نلقى نظرة عن ال tags وهي مشابه لل git tags فهي نسخ او مراحل ما من هذا البرنامج، ويمكنك تحميل أي منها من خلال ال tag، وإذا لم يتم تحديد فسوف يتم تحميل ال latest (التاق الافتراضي). [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_docs_tags-1024x471.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fdocs%5Ftags.png) سوف تلاحظ أن حجم ال image هي 1 قيقا، ولذلك قد تأخذ وقتاً للتحميل بحسب سرعة الاتصال لديك، وسوف نقوم بتحميل وتشغيل ال image بالأمر run كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/run_docker_docs-1024x501.png)](https://informatic-ar.com/wp-content/uploads/2018/05/run%5Fdocker%5Fdocs.png) لاحظ أننا قمنا اولاً بتشغيل ال container بالبورت 8090 على ال host، أما البرنامج من داخل container فسوف يعمل على البورت 4000، وقمنا ايضاً بكتابة ال tag الذي نريده، ويمكنك تحديد أي نسخة تريد، وطالما حددنا latest فسوف يعطي اخر نسخة، وهذا الذي يحدث في حال لم يتم تحديد أي نسخة وهو تحميل ال latest. لاحظ ايضاً رابط ال image يبدأ بال docs/docker.github.io حيث ال docs هذه تعتبر اسم الشركة، وبداخل هذه الشركة سوف تجد العديد من ال images، نفس فكرة الحساب في github فبداخله العديد من ال repositories. أيضاً لاحظ أنه هناك العديد من ال Layers عند تحميل هذه ال Image فهي كبيرة من 1 قيقا بايت. واخيراً بعد انتهى التحميل اشتغلت مباشرة وظهر المخرج، ونستطيع فتح المتصفح الان localhost:8090 وسوف تجد كامل التوثيق متواجد لديك offline. [![](https://informatic-ar.com/wp-content/uploads/2018/05/view_docker_docs-1024x583.png)](https://informatic-ar.com/wp-content/uploads/2018/05/view%5Fdocker%5Fdocs.png) بعد فتحك للموقع وتصفحه سوف تجد في الكونسول العديد من المخرجات وهي logs من الويب سيرفر المضمن بداخل هذه ال container. الآن لكي تخرج من هذه ال container وتعود الى سطر الأوامر يمكنك الضغط على CTLR+C، وبالطبع كما ذكرنا سابقاً فهذا لن ينهي ال container وانما ستظل تعمل. وسنقوم الان بإيقاف هذه ال container من خلال الأمر stop كما ذكرنا سابقاً. [![](https://informatic-ar.com/wp-content/uploads/2018/05/stop_docker_docs-1024x239.png)](https://informatic-ar.com/wp-content/uploads/2018/05/stop%5Fdocker%5Fdocs.png) الآن سوف نأخذ معاملات جديدة لأي أمر وهو تشغيل ال container في وضع ال interactive mode بحيث عند الضغط على CTLR+C فسوف يتم ايقافها من العمل وذلك بتمرير المعامل –it للأمر، بالإضافة الى تسمية ال container بدلاً من الاسم العشوائي الذي يظهر تلقائياً من خلال المعامل –name كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/name_container.png)](https://informatic-ar.com/wp-content/uploads/2018/05/name%5Fcontainer.png) سوف تلاحظ أنه تسمية ال container بالاسم docs (ويمكنك كتابة الأمر docker ps أو docker ps –a وسوف تجد الاسم لهذا ال container)، بالإضافة الى تشغيلها في وضع ال it بحيث تستجيب لأي مدخل، وكما ترى في الصورة السابقة بعد تشغيل ال container والتأكد من عمل الموقع، قمنا بإدخال CTLR+C لإيقافها (وهي سوف توقف البرنامج الرئيسي الذي يعمل على ال container والذي يعني إيقاف ال container من العمل) ومباشرة توقفت عن العمل ولن تظهر في قائمة docker ps. إذا قمت بتشغيل الأمر السابق مجدداً فسوف تحصل على رسالة خطأ فلن تستطيع انشاء container اخر بنفس الاسم وعليك اما بحذف القديم، او تسمية الجديد باسم اخر. ولكي تخرج من ال interactive mode container يجب عليك أن تقوم بإدخال CTRL+PQ حتى تخرج بشكل صحيح بدون ان توقف ال container التي تعمل. # تجربة ال Windows Containers سوف نلقى نظرة الان عن تشغيل ال windows container، ولكي نقوم بالتحويل من ال Linux Container الى Windows Container فيجب الضغط في الأسفل على زر دوكر بالزر الأيمن واختيار Switch to Windows Container (وسوف يتم تفعيل ال Windows Container Feature) المتواجد على ويندوز 10. [![](https://informatic-ar.com/wp-content/uploads/2018/05/switch_container_mode.png)](https://informatic-ar.com/wp-content/uploads/2018/05/switch%5Fcontainer%5Fmode.png) بعد الانتقال عند الضغط مجدداً على نفس القائمة سوف تجدها الان أصبحت Switch to Linux Container وهذا يعني أنك تعمل على ال Windows Container ويمكنك تجربة الأوامر info, version الخاصة بدوكر لمعرفة تفاصيل ال client & server كما تطرقنا له في البدء. عند كتابتك docker ps أو docker images (أو حتى docker ps –a) فلن تجد ال containers أو ال images التي كانت في ال Linux container حيث تم الانتقال ال windows container وهو مختلف عن ال Linux Containers. وسوف نقوم بتشغيل IIS وهو الويب سيرفر المشهور في أنظمة الويندوز: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_hub_iis-1024x666.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fhub%5Fiis.png) وسوف نجد هناك نسختين الأولى تعتمد على ال windowsservercore والثانية على ال nanoserver وفرق الحجم كبير، لذلك سوف نستخدم ال nanoserver وسوف نوضح الفرق بينهم فيما بعد. [![](https://informatic-ar.com/wp-content/uploads/2018/05/install_docker_iis-1024x384.png)](https://informatic-ar.com/wp-content/uploads/2018/05/install%5Fdocker%5Fiis.png) لاحظ الأمر أعلاه، سوف تجد انه تم تحديد البورت 80 على ال host machine والبورت 80 داخل ال container لأنه هو البورت الذي سيعمل على ال iis، وايضاً ممرنا المعامل –d وهو اختصار لل detach بمعنى سيعمل ال container مباشرة ويرجع سطر الأوامر يستقبل الأوامر (كأنه نقوم بتشغيله بالطريقة العادية ومن ثم CTLR+C لكي نرجع لسطر الأوامر وفي نفس الوقت يكون ال container يعمل)، بمعنى كأنه يعمل في الخلفية الان. ايضاً قمنا بتحديد اسم له iis ومن ثم حددنا Tag حتى نقوم بتحميل ال nanoserver وليس latest. وبعد ان يتم التحميل ويعمل مباشرة سوف تلاحظ أنك الان في سطر الأوامر ويمكن مشاهدة قائمة العمليات وسوف تجده على القائمة docker ps. الان يمكنك فتح المتصفح والدخول الى localhost وستجد ال IIS يعمل ولديك الصفحة الافتراضية فيه: [![](https://informatic-ar.com/wp-content/uploads/2018/05/iis_localhost-1024x517.png)](https://informatic-ar.com/wp-content/uploads/2018/05/iis%5Flocalhost.png) هناك طريقة أخرى لم نتحدث عنها وهي كيف يمكن فتح الويب سيرفر بدون استخدام ال port mapping وانما باستخدام عنوان ال container واستخدام ال port للويب سيرفر بداخله، ولكن عليك بمعرفة عنوان ال IP لل container، ويمكن من خلال الأمر docker inspect containerId حيث تمرر ال containerId وسوف تجد معلومات حول ال container التي تعمل وستجد ال IP لها، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_inspect.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Finspect.png) يمكنك عمل ping لهذا ال IP وسوف يبدوا كأنه جهاز اخر وانما حقيقة هو Virtual Adapter وكل البرامج بداخل ال container سوف تتصل بهذا ال network adapter وهذا ال IP Address. [![](https://informatic-ar.com/wp-content/uploads/2018/05/ping_docker_ip.png)](https://informatic-ar.com/wp-content/uploads/2018/05/ping%5Fdocker%5Fip.png) ويمكنك استخدام هذا ال IP مباشرة بدلاً من ال Port Mapping كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/open_docker_ip.png)](https://informatic-ar.com/wp-content/uploads/2018/05/open%5Fdocker%5Fip.png) وهذا يعتبر جزء من ال Isolation التي توفره الدوكر وهو ال Network Isolation وسوف نتحدث عنها ونرى أنواعها وبشكل مبسط فقط، وبعد أن ننهي من الأساسيات كاملة. سوف نرى ماذا يحدث خلف الكواليس. # أنواع العزل Isolation في دوكر البرامج التي تعمل في ال containers تكون معزولة وكأنها تعمل على جهاز لوحدها، وليس لديها علم عن البرامج ونظام الملفات المتواجد على النظام ال host (ونعنى بال host نظام التشغيل الأساسي الذي يعمل عليه الدوكر)، وهناك أنواع من ال isolation كما يلي: - فصل وحجب قائمة العمليات Process - فصل وحجب نظام الملفات File System - فصل وحجب مسجل النظام Registry - فصل وحجب الشبكات Networking - فصل وحجب المستخدمين Users & Groups وسوف نتأكد من كل هذه الأنواع ونرى كيف أن البرامج بداخل ال containers لن تستطيع مشاهدة هذه التفاصيل وانما لها بيانات أخرى، فيما يلي، وسوف نبدأ بمثال أخير نقوم بتحميله ومن ثم مشاهدة تفاصيل ال Image ونبذة عنها وبعدها ندخل في ال isolation سوف نقوم بتحميل Microsoft/dotnet:nanoserver وهو ال DotNet Runtime ونقوم بتشغيلها مباشرة من خلال الأمر run كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_run_dotnet.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Frun%5Fdotnet.png) بعد ان تحميل وتشغيل ال dotnet فمباشرة ظهر C:\\ ومن ثم توقفت عن العمل، ولو نظرت لقائمة العمليات docker ps فلن تجدها، وسوف تجدها متوقفة في ال docker ps –a. [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_dotnet_stopped-1024x106.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fdotnet%5Fstopped.png) ولاحظ أن أمر التشغيل Command على ما يبدوا هو لتشغيل سطر الأوامر ولكن حصل trunc في المخرج، لذلك يمكنك استخدام المعامل –no-trunc حتى يظهر كامل المخرجات بدون اختصار: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_dotnet_no_trunc-1024x122.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fdotnet%5Fno%5Ftrunc.png) وكما يتبين فإن ال command line قد عملت مباشرة مع ال containers ومن ثم انتهت بعدها، لذلك يمكن ان نقوم بعمل attach لسطر الأوامر من خلال المعامل –it الذي سبق لنا تجربته حتى نستطيع التعامل مع سطر الأوامر ولا يتم انهاء ال container مباشرة. وبعد أن تقوم بكتابة سوف يتم مسح سطر الأوامر وكأنك تنتقل الى سطر أوامر اخر بداخل ال container. الصورة التالية توضح الأمر، وطالما هذه container بها dotnet فيمكن ان استعلم عن ال framework بها، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_dotnet_command.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fdotnet%5Fcommand.png) حتى هذه اللحظة ما زلنا بداخل سطر الأوامر بداخل ال container ويمكن العودة للسطر الأوامر الخاص بالهوست عن طريق كتابة أمر الخروج exit وسوف تعود لل host. يمكنك ان تجرب وتقوم بكتابة نفس الأمر dotnet على سطر الأوامر في الهوست، وإذا لم تكن لديك على جهازك الأساسي فسوف تحصل على رسالة خطأ، وهذا يعني ان البرنامج dotnet فقط يعمل بداخل ال container، أما إذا كانت ال dotnet متواجدة في ال host فسوف تعمل ايضاً. لكنها مفصولة تماماً عن التي متواجدة بداخل ال container وقد تكون النسخ مختلفة ايضاً، الصورة التالية تبين ذلك: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_dotnet_version.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fdotnet%5Fversion.png) وبهذا الشكل نستطيع تشغيل أي برنامج بداخل ال container سواءً كان البرنامج المحدد ان يعمل مباشرة مع ال container أو أي برنامج اخر نريده يمكن ان نقوم بتشغيله، كما في الموضوع الأول في المقدمة، حيث قمنا بفتح ال mongo client المتواجد داخل نفس ال container من خلال الأمر exec. ولكي نعرف كيف حصل هذا، يجب علينا أن نلقى نظرة الآن حول هذه ال image وبنيتها. # مكونات ال Image ذكرنا سابقاً أن ال Image هي القالب للبرنامج، والذي يتم تشغيله في Container، وهي التي تحتوي على ملفات البرنامج والاعتماديات الخاصة بها، ولكنها تحتوي أيضاً على شيء أضافي وهو مكتبات نظام التشغيل. الصورة التالية تعرض كيف تعمل البرمجيات في نظام التشغيل بشكل عام، حيث بداخل جهاز الحاسب، لدينا القطع المادية مثل المعالج، الذاكرة، القرص الصلب وغيرها، وبعد أن يعمل نظام التشغيل سوف يتم تحميل الكيرنل Kernel والذي يتعامل مع ال Hardware بشكل مباشر، بغض النظر عن نظام التشغيل ويندوز او لينوكس فهناك كيرنل يعمل بنفس المفهوم، ومن فوق الكيرنل هناك API مثل Win32 أو POSIX والتي يوفرها النظام لكل البرامج فيه، ومن فوق طبقة ال API فهناك طبقة برمجيات النظام التشغيل التي يأتي بها، مثلاً cmd وغيرها، واخيراً هناك طبقة برمجيات المستخدم التي يقوم بتنزيلها مثلاً جافا او بايثون أي دوت نت. [![](https://informatic-ar.com/wp-content/uploads/2018/05/software_layer.png)](https://informatic-ar.com/wp-content/uploads/2018/05/software%5Flayer.png) سوف تجد أن الطبقات مقسمة الى قسمين الأول ما يعرف بال Kernel Space والثانية هي ال User Space وهي تضم كافة البرمجيات ومكتبات نظام التشغيل الضرورية والتي تتعامل مع الكيرنل، وهذا التصميم شائع في أنظمة التشغيل حيث يوفر حماية لل Hardware من الوصول المباشرة بواسطة ال User Space ويجب ان يمر الطلب عبر الكيرنل والذي يحدد ويضبط العملية من خلال ال System Calls. وهذا يعني مثلاً برنامج الجافا سوف يطلب مكتبات نظام التشغيل Win32 والتي تقوم بعمل System Call الى الكيرنل لتنفيذ المهمة. الذي يهم في شرح هذه العملية هو أنه في السابق عندما نقوم بنشر البرمجيات فنركز على نشر الطبقة العليا "برمجيات المستخدم" فقط مثلاً جافا او Mongo وغيرها، ونقوم بتحميل ال Installer وهذا ال Installer يعتمد على أشياء يجب ان تكون موجودة مثلاً مكتبات نظام التشغيل والا فلن يعمل بشكل صحيح. ولكي يتم تفادي مشكلة ان المكتبات غير موجودة فال containers & images تقوم بتغير الطريقة التقليدية، فبدلاً من نشر MonogoDB لوحده فقط، فسيتم نشر كل ما يتواجد في ال User Space وبالتالي يعني البرمجيات + برامج النظام + مكتبات النظام كلها تكون متواجدة في ال image. [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_image.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fimage.png) وهكذا تكون ال Image مقسمها لعدة طبقات، منها طبقة برمجيات ومكتبات النظام، وبعدها طبقة البرمجيات والاعتماديات الخاصة بها، ويتم رفع ال image على ال Docker Hub. وهنا يجب الإشارة الى بعض النقاط المهمة: - بما أن ال Image بها برامج النظام، فيمكن ان يتم تشغيل البرنامج الأساسي في ال Image عند تشغيل ال Container، ويمكن ان يتم تشغيل أي من برامج النظام المتواجدة في ال Image ايضاً. - إذا تم تحميل مثلاً java Image لل windows فسوف تكون المكتبات والبرمجيات في الطبقة الأساسية Base Layer هي للويندوز، فعند تحميل ال image سوف تلاحظ أنها مكونة من طبقات كما شاهدنا في الأمثلة السابقة، الآن بعد أن ينتهي التحميل، واردنا تحميل image أخرى تستخدم نفس ال base layer فلن يتم تحميلها مجدداً لأنها متواجدة وسيتم تحميل فقط طبقة البرمجيات ويتم استخدام الطبقة المتواجدة، وهذا لأن كل الطبقات التي يتم تحميلها لا تتغير فهي للقراءة فقط Read only. وبالتالي لن تحتاج الى تحميل كل ال User Space في كل مرة كاملة في حال كان يتواجد لديك Layers محملة من قبل في Images تستخدم نفس ال layers. - عند تشغيل أكثر من Containers فلكل منهم كافة ال User Space ولكنهم في النهاية يشتركوا في نفس الكيرنل. وهذا ما يجعل ال Container أكثر كفاءة من ال VM حيث ال VM تستخدم كيرنل لكل VM يعمل بينما هنا كيرنل واحد لكل ال Containers. الآن بعد ان تعرفنا على أنه يمكن تشغيل أي من البرامج المتواجدة في ال image فسوف نستخدم نفس ال dotnet image ونقوم بتشغيل ال dotnet مباشرة بدلاً من ال cmd: [![](https://informatic-ar.com/wp-content/uploads/2018/05/run_docker_dotnet_version.png)](https://informatic-ar.com/wp-content/uploads/2018/05/run%5Fdocker%5Fdotnet%5Fversion.png) طبعاً بعد أن ينتهي البرنامج سوف تتوقف ال Container عن العمل. وبنفس المفهوم يمكن تمرير PowerShell بدلاً من ال dotnet للدخول على ال PowerShell بداخل ال container. الآن حان الوقت لكي نجرب فكرة ال Isolation ونرى هل هناك عزل أم لا. # عزل العمليات Process Isolation ويمكن اختبارها بسهولة، حيث يمكن الدخول لأي Container وتنفيذ دالة استخراج كل العمليات التي تعمل، وايضاً تطبق نفس الأمر على ال host وسوف ترى الفرق في العمليات وأن لكل منهم عملياته الخاصة كأنه يعمل في جهاز منفصل. [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_container_process.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fcontainer%5Fprocess.png) الان إذا قمت بالدخول على ال PowerShell في ال host وطبقت نفس الأمر Get-Process سوف تجد قائمة كبيرة مختلفة من العمليات، وبالطبع يمكن ان تجرب نفس الامر في لينوكس من خلال الامر ps وشاهد الفرق بين العمليات، ولكن الان سنركز على الويندوز لمشاهدة طرق العزل، ولكن نفس الفكرة يمكن تطبيقها بأوامر اللينوكس بسهولة. [![](https://informatic-ar.com/wp-content/uploads/2018/05/host_process.png)](https://informatic-ar.com/wp-content/uploads/2018/05/host%5Fprocess.png) # عزل نظام الملفات File System Isolation سوف نقوم الان بعرض نظام الملفات بداخل ال Container ومشاهدة نظام الملفات على ال host وسوف ترى الفرق، وان لكل منهم tree مختلفة من الملفات. الصورة التالية تعرض الملفات بداخل على القرص سي بداخل ال Container: [![](https://informatic-ar.com/wp-content/uploads/2018/05/container_filesystem.png)](https://informatic-ar.com/wp-content/uploads/2018/05/container%5Ffilesystem.png) بينما على ال host فالملفات مختلفة للغاية: [![](https://informatic-ar.com/wp-content/uploads/2018/05/host_filesystem.png)](https://informatic-ar.com/wp-content/uploads/2018/05/host%5Ffilesystem.png) # عزل الشبكات Network Isolation بنفس الفكرة فال Network Adapter سوف تكون معزولة هي الأخرى، وسوف نقوم الان باستخراج معلومات ال Adapters المتواجد باستخدام الأمر ipconfig [![](https://informatic-ar.com/wp-content/uploads/2018/05/ipconfig_host.png)](https://informatic-ar.com/wp-content/uploads/2018/05/ipconfig%5Fhost.png) وسنقوم بتطبيق الامر بداخل ال Container [![](https://informatic-ar.com/wp-content/uploads/2018/05/ip_container.png)](https://informatic-ar.com/wp-content/uploads/2018/05/ip%5Fcontainer.png) وبالتالي داخل ال container سوف تكون على شبكة أخرى كأنه بالضبط يعمل على جهاز آخر، وكما تلاحظ سوف تجد ان هناك Virtual Adapter على الهوست تكون هي ال Getaway المستخدمة في ال Container حتى يتم التخاطب بينهم من خلال هذه الشبكة. وهذا يعني أستطيع تشغيل ويب سيرفر على ال container في البورت 80، حتى لو كان البورت 80 مستخدم في ال host حيث لكل منهم شبكة منفصلة عن الأخرى. وهكذا سوف نجد ان العزل مطبق على كل هذه الأمور (العمليات، نظام الملفات، الشبكات) وأكثر من ذلك حيث يتم تطبيقه على متغيرات النظام Environment Variable وعلى مسجل النظام Registry وعلى المستخدمين في النظام ايضاً، وتستطيع التأكد منهم بنفس الطرق أعلاه، بعرض النتيجة في ال host وال container ومشاهدة الفرق بينهم. أخر نقطة بخصوص ال Windows Container فهي تطبق داخلياً أما بال Windows Server Container والتي تستخدم للويندوز سيرفر وسيتم التشغيل مباشرة على النظام، والطريقة الثانية وهي ال Hyper-V Containers وهي بالاعتماد على Hyper-V (وهذا ما يتم في ويندوز 10) لتشغيل ال containers. وستجد ان الثانية بالرغم من أنها تستخدم VM (لكنه أسرع بكثير من ال VM العادي) الا أنها ايضاً قد توفر حماية أفضل بسبب ال VM والذي يتم العزل كلياً. # خلاصة وصلنا لنهاية الموضوع الثاني، وكما نشاهد ان docker & container تساعدك على تشغيل البرمجيات وجعل حياتك أسهل عما كانت عليه سابقاً، الجدول التالي يعرض كيف كنا نعمل بالطريقة التقليدية سابقاً، وما هو طريقة دوكر لها، وما هو الأمر في دوكر لتطبيق هذا الأمر. [![](https://informatic-ar.com/wp-content/uploads/2018/05/compare_table-1024x523.png)](https://informatic-ar.com/wp-content/uploads/2018/05/compare%5Ftable.png) وسوف تجد أن الأمر الخاص بتحميل وتشغيل ال container في آن واحد لا يوجد له بديل بالطرق التقليدية وهذا ما يميز دوكر ويجعلها المستقبل للمطورين لتشغيل البرمجيات وتعلمها ايضاً بسهولة. ### مقدمة حول دوكر Docker URL: https://informatic-ar.com/docker-introduction/ Last updated: 2026-10-03T22:31:32.000Z **المواضيع في السلسلة:** 1. مقدمة حول دوكر Docker (الموضوع الحالي) 2. [البداية مع دوكر Docker](https://informatic-ar.com/getting-started-docker/) 3. [مشاركة المجلدات في دوكر Docker](https://informatic-ar.com/docker-volumes/) 4. [بناء ال Docker Images](https://informatic-ar.com/building-docker-images/) 5. [تشغيل قواعد البيانات في دوكر Database in Docker](https://informatic-ar.com/docker-databases/) 6. [تشغيل البرامج باستخدام ال Docker Compose](https://informatic-ar.com/docker-compose/) يعتبر مشروع دوكر Docker من المشاريع الإبداعية والتي تغير وتسهل طريقة التعامل مع البرمجيات سواءً من ناحية تنزيل Installing هذه البرمجيات، وطريقة نشرها للمستخدمين Deployment وايضاً اثناء التطوير والاختبارات Building للمطورين. ولأن دوكر Docker يعتبر أحد المشاريع التي تستخدم مفهوم ال Containers وال Images، وأيضاً هناك غيره من المشاريع يستخدم نفس المفاهيم مثل Rkt، فمن المهم أن تكون المقالة الأولى هو عن ماهية هذه المفاهيم (Containers & Images) وما هي أهميتها، وما الدور الذي جعلت المشاريع التي تستخدمها مثل Docker or Rkt من انجح المشاريع حالياً. وسوف نناقش ما يلي: - لماذا نحتاج الى طريقة جديدة في التعامل مع البرمجيات - الفرق بين ال Container وال Virtual Machine - ما هو ال Container وال Images - ما هو Docker # لماذا نحتاج لطريقة جديدة في التعامل مع البرمجيات؟ لكي نفهم ال Containers والحوجة لها وما الذي اضافته لنا، فسوف نقوم بعمل مقارنة بين الأسلوب القديم عندما نعمل على البرمجيات من أكتر من منظور: - عند تحميل البرامج – كمستخدم او شخص يريد تحميل أدوات لجهازه - أثناء البرمجة والتطوير ونشر البرمجيات على السيرفر –وكيف يتم توحيد بيئة التطوير والاختبارات وتسهيل عملية نشر البرمجيات وسنبدأ بأول نقطة وهي في العمل على البرمجيات: ولنفرض أنك اردت تحميل أي برنامج لكي تستخدمه على جهازك، وليكن مثلاً MySQL أو RabbitMQ أو برنامج اخر، فسوف تجد أن هناك خطوات عليك القيام بها: - البحث Finding: عن مكان لتحميل البرنامج، سواءً من موقعه الًاصلي، او من مواقع أخرى - التنزيل Downloading: فتح صفحة التحميل وقراءتها بتركيز لتحميل البرنامج المطلوب، على حسب نظام التشغيل، وهل هو 32bit/64bit والتأكد من تحميل النسخة الصحيحة - التنصيب Installing: قرائه صفحة الاعتماديات وهل هناك متطلبات لتحميل برامج وأدوات أخرى مطلوبة، ووضع متغيرات في النظام Environment Variables يحتاجها البرنامج في عمله - التشغيل Running: تشغيل البرنامج بشكل صحيح والقدرة على إيقافه وتشغيله متى ما اردت. الصورة التالية تبين كل هذه الخطوات سوف تقوم بها حتى تقوم بتحميل البرنامج ومن ثم تشغيله لأول مرة: [![](https://informatic-ar.com/wp-content/uploads/2018/05/traditioanl_way-1024x744.png)](https://informatic-ar.com/wp-content/uploads/2018/05/traditioanl%5Fway.png) وهذه الخطوات ليست بالسهلة وقد تخطئ في أحد الخطوات ولن تستطيع اكمال تنصيب او تشغيل البرنامج بشكل صحيح، وقد تلغى فكرة استخدام هذا البرنامج من الأساس وتبدأ في البحث عن بديل بسبب هذه المشكلة، ففي كل خطوة قد تجد العديد من المشاكل، على سبيل المثال: **البحث Searching:** - مكان البرنامج Where: وهل سيتم تحميله ال App Store مثلاً إذا كان يتواجد في نظام التشغيل، أم قد يكون نظام التشغيل به Package Manager كما في لينوكس وتستطيع تحميل البرنامج مباشرة، ولكن احياناً تكون بعض البرامج قديمة حتى في ال Package Manager ولا يوجد بها التحديث الأخير، بالإضافة الى أنه قد تجد برنامج معين في ال package manager في توزيعه لينوكس ما ولن تجده على توزيعه أخرى مثلاً. في نظام ويندوز او ماك قد تجد في الغالب أنك مضطراً لتحميل البرنامج من مواقع Standalone website بواسطة الموقع الأصلي أم مواقع أخرى توفر البرنامج - معلومات عن البرنامج Stats & Metadata: قبل تحميلك للبرنامج قد تحتاج بعض المعلومات لأخذ القرار هل تريد تنزيله ام لا، وقد لا يتوفر في مكان التحميل أي معلومات إحصائية عن عدد التحميلات واي معلومات أخرى عن البرنامج، مثلاً عدد المشاكل للمستخدمين وغيرها، أخر تحديث للبرنامج. وقد تجد بعض المواقع توفر هذه المعلومات والبعض الاخر لا يوفرها او يوفرها بطريقة مختلفة. - الثقة Trust: قد تبحث عن البرنامج من خلال محرك البحث، ومن ثم تصل الى موقع ما يوفر خاصية التحميل، لكن هل هو موقع موثوق، او موقع مزيف به برمجيات خبيثة ملغمة؟ وهل الموقع يوفر حماية عند تنزيل المشروع باستخدام HTTPS؟ بالإضافة الى انه أحياناً هل الموقع يعمل دائماً Availability ام احياناً به مشاكل ولا أستطيع تحميل البرنامج، وغيرها من المشاكل اثناء هذه المرحلة. **التحميل Installing:** - نظام التشغيل Operation System: هل سيعمل على نظام التشغيل الحالي، هل هو Cross-Platform وسوف يعمل على هذه النسخة المعينة بالضبط version/build من نظام التشغيل، هل سيعمل على هذه ال CPU التي اعمل بها والمعمارية التي يعمل بها Architecture. - ماهية البرنامج Format: هل سيأتي السورس كود فقط واقوم ببنائه وتشغيله، أو يأتي على شكل executable يعمل مباشرة، أم يأتي به الملف التنفيذي مع كامل المكتبات المطلوبة به في ملف مضغوط Zip؟ أو على شكل Setup به كامل الملفات، وهل سيأتي معه ال Runtime المطلوبة مثل ال JRE أو .NET ام لا. - طريقة التحديث Updates: كيف ستكون وبأي الية وهل سيتم مسح القديم كلياً وإعادة تنصيبه، وهل من خلال برنامج اخر أم ما هي التفاصيل لذلك. **التشغيل Running:** - التوثيق للبرنامج Documentation: وهل هناك توثيق محدث بحيث اتبع الخطوات فيه ام احتاج للبحث في الانترنت عن كيفية تشغيله. - المسارات Path: هل احتاج لوضع متغيرات في النظام Environment Variables يحتاجها البرنامج في عمله أم يقوم بها اثناء التنزيل - الاعتماديات Dependencies: هل سيقوم بتحميلها كاملة او ام أقوم بتحميلها قبل تحميل البرنامج، وهل تحتاج هذه الاعتماديات الى اعتماديات اخرى - كيفية التشغيل والايقاف Service Registration: هل من خلال الواجهة ام احتاج اغلق البرنامج، هل تحتاج لوضعه ك Service لكي يعمل مباشرة بعد إعادة التشغيل أو تريد إيقافه وتشغيله مجددا - هل هناك تعارض مع البرامج الأخرى: إذا كان البرنامج سوف يفتح بورت معين Port أو يحتاج وصول لملفات معينة فيجب التأكد من عدم استخدامها بواسطة برمجيات أخرى - هل التحديثات القادمة سوف تؤثر على البرنامج: مثل تحديثات نظام التشغيل في ال Shared Library التي يستخدمها البرنامج ويعتمد عليها. لذلك قد تجد أنك تحتاج لساعات عديدة فقط لتنصيب برنامج قبل البدء باستخدامه، مثلاً وجدت قاعدة بيانات Casandra أو مثلاً Elastic Search وغيرها من البرمجيات التي قد تود أن تتعلمها، فقد تمضي ساعات عديدة وتلغى فكرة تعلمها بسبب واحد هو تعقيد مسألة التعامل مع البرمجيات بشكل عام، بدءاً من البحث، التنزيل، والتشغيل. فهل توجد الية ما بحيث نجعل هذه العملية في منتهى البساطة، وتكون موحدة وتعمل في أي مكان، وكل ما في الأمر أقوم بتحميل البرنامج من مكان موثوق بسهولة، وبأمر واحد يعمل بكافة الاعتمادية المطلوبة ويكون البرنامج معزول عن بقية البرامج الأخرى، ويتم تشغيله بسرعة وايقافه بسرعة وتشغيل أكثر من نسخة بسهولة ايضاً؟ **مرحباً بك في ال** **Containers & Images** **\- ودوكر :)** **ملاحظة**: كثيراً من المواضيع عندما تتحدث عن ال Docker & Containers فمباشرة تبدأ بالحديث عن الفرق بين ال Containers وال Virtual Machines وبالرغم من أن هناك فروق وسوف نتحدث عنها ايضاً، لكن البداية بهذا الشكل قد يعطي الانطباع بأن ال Docker وال Containers هي بديل لل Virtual Machines بحيث تكون Virtualization من نوع آخر، وبالرغم من أن هذا صحيح وأنه يمكن ان يكون بديل لل Virtualization الا ان ال Containers بشكل عام ليست عن ال Virtualization وانما هي لتسهيل التعامل مع البرمجيات (مثلاً يمكنك اختصارها بأنها لل Software Delivery أو لل Running Software) بدءاً من البحث، التحميل، التنزيل، التشغيل. بهذه المنظور سوف يسهل عليك العمل على ال Containers والدوكر وسوف تفهمها بالطريقة الصحيحة، ولن تتفاجأ فيما بعد مثلاً بأن هناك بعض البرمجيات او ال Containers سوف تعمل على ال VM بسبب معين سوف نتحدث عنه فيما بعد (مثلاً تشغيل ال Linux Containers بداخل الويندوز، فهذا يتطلب تشغيل Linux VM ومن ثم تشغيل ال Container بها، ولكن الامر هذا سيتم في ثواني معدودة وبشكل Abstracted بحيث لا تدري عنه ماذا يحصل داخلياً) لكن الفكرة هو النظر لدوكر على انه Build, Ship, Delivery وليس بديل لل Virtualization. ## **مثال:** سوف نقوم الان بتنصيب برنامج بالطريقة السابقة التي كنا نتعامل معها طوال السنوات السابقة، ومن ثم نجرب الطريقة الحديثة ولنرى كيف أصبحت العملية في منتهى البساطة بها، ويمكنك ان تقرا الان بدون تجربة في هذا المثال، لأن الفكرة هو اعطائك نظرة وفهم لما يحدث، وسوف نتناول فيما بعد تنصيب الدوكر خطوة بخطوة، ولكن لنركز على ال Big Picture الآن. سوف نقوم بتنزيل MongoDB بالطريقة اليدوية، وسوف نبدأ بالبحث في Google عن MonogoDB وسوف تجد خرجت الصفحة الرئيسية، وبعدها انتقل لصفحة التنزيل، وهناك سوف أجد عدة خيارات سوف اختار منها ال Community Server، واختار نسخة نظام التشغيل الذي اعمل عليه، وايضاً تحدد هل تريد تحميله مضغوط Zip أو على شكل Installer بالامتداد msi، وسوف تجد ايضاً رابط لدليل وخطوات التحميل. [![](https://informatic-ar.com/wp-content/uploads/2018/05/mongo_website-1024x593.png)](https://informatic-ar.com/wp-content/uploads/2018/05/mongo%5Fwebsite.png) بعدها سيتم التحميل بامتداد msi، واثناء ذلك يمكن فتح دليل التنصيب في الرابط وسوف نجد ان ال Installer يقوم بالعديد من الخطوات، لكن مثلاً تحتاج الى وضع البرنامج في متغيرات النظام ان اردت تشغيله مباشرة من أي مسار على سبيل المثال. بعد ان يتم التحميل يمكن الضغط على التالي Next-Next ليتم تنصيب البرنامج بالإعدادات الافتراضية وبعدها لن تجد شيئاً، وسوف تحتاج ان ترجع للدليل لكي تعرف كيف سيتم تشغيل ال MongoDB server: [![](https://informatic-ar.com/wp-content/uploads/2018/05/mongo_instructions.png)](https://informatic-ar.com/wp-content/uploads/2018/05/mongo%5Finstructions.png) سوف تجد في الصورة مكان تواجد البيانات الافتراضي هو /data/db وهذا المسار عموماً بطريقة اللينوكس بينما في الويندوز نحتاج لتحديد البارتيشن الذي يتواجد فيه اولاً، وسوف نقوم بفتح مسار ال MongoDB من سطر الأوامر وتنفيذ أمر التشغيل: [![](https://informatic-ar.com/wp-content/uploads/2018/05/mongo_server_error-1024x289.png)](https://informatic-ar.com/wp-content/uploads/2018/05/mongo%5Fserver%5Ferror.png) سوف تجد خطأ في المسار، وعليك أن تقوم بإنشائه وسوف ننشئ المجلد على القرص C، وبعدها نقوم بتشغيله مجددا وسوف يعمل الان، وسوف تجد رسالة بأن ينتظر الاتصال على البورت 27017: [![](https://informatic-ar.com/wp-content/uploads/2018/05/mongo_server_running-1024x286.png)](https://informatic-ar.com/wp-content/uploads/2018/05/mongo%5Fserver%5Frunning.png) سوف نفتح سطر أوامر اخر لكي نتصل بهذا السيرفر الذي ينتظر أي كلاينت يتصل به، وسوف نكتب الامر mongo.exe لتشغيل الكلاينت، ونكتب أي أمر مثلاً لمشاهدة قواعد البيانات show dbs: [![](https://informatic-ar.com/wp-content/uploads/2018/05/mongo_client_connected.png)](https://informatic-ar.com/wp-content/uploads/2018/05/mongo%5Fclient%5Fconnected.png) سوف نقوم الان بنفس التجربة ولكن بمفهوم ال Containers/Images ولنرى الفرق الكبير بينهم، فسوف نبدأ بالبحث عن mongodb docker في محرك البحث وسوف يظهر اول نتيجة في ال docker hub: [![](https://informatic-ar.com/wp-content/uploads/2018/05/mongo_docker_hub-1024x560.png)](https://informatic-ar.com/wp-content/uploads/2018/05/mongo%5Fdocker%5Fhub.png) لتحميل ال MongoDB من ال Docker Hub سوف نقوم بعمل ال Pull (وهو أمر التحميل) وسيتم تحميل ما يعرف بال Image (ويمكن تصورها على أنها البرنامج) وهي فعلياً تحتوي على البرنامج مع كل الاعتماديات الأخرى. وسوف تلاحظ وجود ال Official Repository في أعلى الصفحة للدلالة على ان هذا البرنامج (ال Repository) من مصدره الرسمي. الصورة التالية تبين طريقة التحميل وتم تحديد اسم البرنامج، بالإضافة الى تحديد windowsservercore وذلك لأني أعمل على Windows وأريد تشغيل البرنامج) بداخل نظام التشغيل مباشرة وبالتالي احتاج الى ان استخدم نسخة الويندوز وليس اللينوكس، وبالرغم من انه يمكن تشغيل نسخة اللينوكس على الويندوز بأي حالة ولكن هذا يستوجب اخبار دوكر واعداده ليشغل نمط ال Linux Container، سوف نتحدث عن هذه النقطة بالتفصيل فيما بعد، الذي يهم الان هو تحميل ال Image بهذا الأمر: docker pull mongo:windowsservercore [![](https://informatic-ar.com/wp-content/uploads/2018/05/mongo_image.png)](https://informatic-ar.com/wp-content/uploads/2018/05/mongo%5Fimage.png) سوف يأخذ تحميل البرنامج بعضاً من الوقت نظراً لأن بها كل الاعتماديات بالإضافة الى ان ال Windows Images عموماً أكبر جحماً (وكما ذكرنا يمكن أن نستخدم ال MongoDB Linux Image بداخل الويندوز بسهولة وسيتم توضيحها فيما بعد)، والآن بعد أن ينتهي تحميل ال Image نكون أنهينا الخطوة الثانية وهي التحميل، ولننتقل الى الخطوة الثالثة وهي تنصيب البرنامج، وفي عالم الدوكر يمكن ان نتخيل عملية التنصيب هي ال Container او وضع ال Image بداخل ال Container، بمعنى ان ال Container هو البرنامج بعد تنزيله والذي يكون جاهزاً للتشغيل، والخطوة الأخيرة هي تشغيل ال Container، وسوف نرى كيف يمكن ان نشغل ال MongoDB Image الان في ال Container. الصورة التالية توضح الخطوات لتحميل البرامج باستخدام ال Containers/Images وسوف نجد أنها أصبحت بسيطة للغاية، والنقطة الأهم موحدة لجميع البرامج: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_way-1024x569.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fway.png) حتى هذه اللحظة فقد قسمنا هذه العملية الى خطوات لأخذ تصور عما يحدث، ولكن الأمر الخاص بالتشغيل سوف يقوم بكل هذه العمليات في آن واحد، بمعنى هو أمر واحد سوف يقوم بتحميل ال Image وتشغيلها في Container مباشرة (سوف يقوم بتحميل ال Image في حال لم تكن موجودة ويقوم داخلياً بعمل Docker Pull، وإذا كانت موجودة فيتم استخدامها) وهو كما يلي: docker run:mongo:windowsservercore [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_run_mongo.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Frun%5Fmongo.png) سوف تجد الأن نفس المخرج السابق بالضبط عندما قمنا بتشغيل وتنصيب البرنامج بالطريقة التقليدية ونفس البورت ايضاً. الان سوف نجد أن البرنامج يعمل بداخل ال Container (وهذا يسمي بال Attached to Container) ولذلك سوف نفتح سطر أوامر أخر، وسوف نقوم بالدخول على نفس ال Container التي تعمل من خلال هذا الأمر: docker exec –it 27 mongo وهي ستقوم بتشغيل ال mongo client بداخل نفس ال Container السابق، ومن ثم إعطاء الأمر show dbs وسوف تجد نفس المخرج ايضاً، وسوف نأتي فيما بعد لوصف الأوامر وشرحها بالتفصيل، ولكن ال 5c في الأمر هو رقم ال container الذي يعمل وتم استخراجه من خلال الأمر docker ps، المهم الان سوف ندخل الى داخل ال container التي يعمل بها السيرفر، وقمنا بتشغيل برنامج الكلاينت mongo بها، وسوف نضع نفس الأمر الان لعرض قاعدة البيانات: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_container_client.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fcontainer%5Fclient.png) وسوف نجد نفس النتيجة السابقة عندما قمنا بالطريقة التقليدية، الفرق الان انه الكلاينت والسيرفر يعملان على بداخل ال container، وليس بداخل process عادية في نظام التشغيل. وفي الحقيقة سوف نجد أن هذه ال container هي عبارة عن process ايضاً في نظام التشغيل، ولكنها معزولة isolated بحيث يكون لديها نظام الملفات الخاص بها، الشبكات الخاصة بها، قائمة العمليات process الخاصة بها، وكل شيء من داخل ال container سوف يكون كأنه يعمل مفصول تماماً، ولكن من الخارج فهي مجرد process أخرى. ولكي ناكد هذا المفهوم سوف ندخل الى ال container التي تعمل الان، بنفس الطريقة التي اشغلنا بها برنامج ال Mongo client ولكن هذه المرة لكي نشغل سطر الأوامر فيها، ونرى بعض الأمور، وسنقوم بتنفيذ الأمر: docker exec –it 5c cmd ، وبعدها قمنا بالدخول الى ال PowerShell كما يلي، وكان بالإمكان ان ندخل اليه مباشرة طالما هو برنامج على ذلك ال container: [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_container_cmd.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fcontainer%5Fcmd.png) الان سنقوم بعرض كافة العمليات التي تعمل على ال container من خلال Get-Process كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2018/05/get_process.png)](https://informatic-ar.com/wp-content/uploads/2018/05/get%5Fprocess.png) وكما تلاحظ فان ال Mongo server وال Mongo Client يعملان الان بداخل ال container وحتى ال PowerShell الذي قمنا بتشغيله فهو يعمل الان ايضاً. بقية العمليات هي services خاصة بالويندوز. النقطة المهمة هنا هو ان العمليات معزولة ولن ترى عمليات process من نظام التشغيل الأساسي، وانما فقط العمليات بداخل ذلك ال Container. من النظام الأساسي ال Host Machine سوف تجد في قائمة العمليات كافة العمليات التي تعمل على النظام الأساسي، بالإضافة الى كافة العمليات التي تعمل ايضاً على ال Container سوف تجدها تعمل كأنها Process عادية، ولكنها كلها تشترك في ال Job ID وجميعهم يكون بنفس الرقم. وهذا يعني أن البرامج التي يتم تشغيلها في ال Container سوف تعمل على النظام الأساسي وكأنه تم تشغيلها مباشرة بفتحها مباشرة من ال host machine. هكذا سوف نرى ان العملية أصبحت أسهل سواءً في التحميل، التنصيب، التشغيل، بالإضافة الى وجود كمية من البرمجيات توجد على شكل Images قابلة للتشغيل ك Containers سواءً في ال Docker Hub (المستودع الأساسي لل Docker Images) أو غيرها من المواقع التي توفر هذه الخدمة. اذاً يمكن القول بأن ال Image هي القالب Read only Template المكونة من عدة ملفات بشكل Layered مثل ملفات نظام التشغيل، ملفات للفريم وورك مثلاً NodeJS وغيرها من البرمجيات. ال Container هو البرنامج المعزول الذي يتم انشائه من ال Image ويمكن أن يكون قيد العمل، او متوقف ويمكن أن يتم نقله ايضاً أو حذفه. وهذه العملية سريعة للغاية فيمكن انشاء وتشغيل وإيقاف ال Containers بسهولة. **ملاحظة**: يعتبر ال Docker Hub أحد المواقع لتسجيل ال Docker Images بسهولة بحيث يمكن تحميلها فيما بعد، بمعنى مستودع للبرامج Repositories (مثل متجر تطبيقات Google Play store مثلاً) التي تحتاجها وهو المستودع الرئيسي في مشروع الدوكر. وبه العديد من البرمجيات مثل MySQL, WordPress, Ubuntu وغيرها، وتستطيع تحميلها pull بسهولة سواءً من جهازك الشخصي، أو من داخل أي سيرفر، ويمكنك بعد ذلك تشغيل ال image بداخل ال container. وحتى يمكن لك أيضاً أن تعدل على ال container بأي إضافات تريدها، وبعد ترفع ال image المعدلة وبالتالي تكون image جديدة بها التعديلات التي قمت بإضافتها ويمكن لمن يرغب ان يقوم بتحميله إذا قمت برفعها بشكل عام ومتاح للجميع، وانت فقط من تستطيع رفع التحديثات على تلك ال repository. أما في حالة ال repositories الخاصة فيمكن لمن لديه صلاحية فقط بتحميلها وايضاً رفع التحديثات بها وهي مناسبة لكافة أعمالك ومشاريعك الخاصة او أي مشاريع للشركات. ويمكن أيضاً بناء Private Registries بداخل الشركات مثل ال Docker Hub بحيث يكون مستودع لكل برمجيات الشركة داخلياً بدلاً من رفعه على أي Online Registry. [![](https://informatic-ar.com/wp-content/uploads/2018/05/docker_hub-1024x521.png)](https://informatic-ar.com/wp-content/uploads/2018/05/docker%5Fhub.png) ايضاً كما نلاحظ في الصورة سوف نجد بجانب كل برنامج عدد التحميلات والمفضلة وتستطيع الذهاب لصفحة التفاصيل وسوف تجد كيفية التشغيل لهذا البرنامج، بالإضافة الى ال Images الرسمية ستجد محدده بهذا الاسم، وايضاً سوف تجد نتيجة فحص الأنتي فايروس لكل من ال Images المرفوعة على Docker Hub. وأيضاً سوف تجد رابط ال Docker Store وهو لل Images المدفوعة غير المجانية حتى تقوم بشراء الرخصة والحصول على الترخيص. ولن تقلق من ناحية نظام التشغيل مجدداً، فاذا كنت تعمل في نظام التشغيل ويندوز فتستطيع تشغيل ال Windows Container وايضاً تستطيع تشغيل ال Linux Container كما سيأتي بعد ذلك. وايضاً لن تقلق من ناحية شكل وFormat البرنامج لأنه سيأتي بطريقة موحدة وهي ال Image. وطريقة مسح وتحديث ال Images بسيطة فكل ما عليك بتنزيل الإصدار الجديد وتشغيلها وإيقاف القديمة، وهكذا سنجد انها حلت اغلب أو كل المشاكل التي كنا نواجها بالطريقة التقليدية سابقاً. **أثناء البرمجة والتطوير:** فهناك العديد من الفوائد للمطورين في دوكر: - **تسريع دخول المطورين** **Onboarding**: سوف نجد أنه بسهولة يمكن عمل ال Development Environment فمثلاً غالب المشاريع تحتوي على العديد من الأجزاء مثل Database و Queue Server و Caching Server و Indexing Server وستجد من الصعوبة تنصيب هذه الأجزاء وقد يقع أي مطور في مشكلة التنصيب هذه، لذلك يتوفر ال Images وتشغيلها فمن الممكن توفير الكثير من الوقت والجهد للمطورين، واذا كان المشروع يعمل بفكرة ال Microservices فمن الممكن ان تكون هناك Images أخرى مثلاً لل Identity وواحدة لل Orders مثلاً وهكذا سوف تجد المطور يعمل ويركز على المهام التي لديه ويستخدم المكونات مباشرة كما هي والتي أتت من فرق أخرى. - **لا تصادم بين النسخ** **Software Conflicts** **:** فمن الممكن ان يعمل السيرفر على نسخة معينة من الفريم وورك ويكون به أكثر من برنامج يعتمد عليه، ولن تستطيع تحديثه حتى لا تقع البرامج الأخرى التي تعمل في مشاكل، ولكن بتطبيق فكرة ال Containers فسوف تجد أن كل برنامج يحتوي على ال Framework التي يحتاجها بغض النظر عما يتواجد في الجهاز الأساسي، وهذا يعني انه لن تكون هناك تصادم بين البرمجيات مجدداً. - **عدم توافق ال** **Environment**: مثلاً لو كان لديك بيئة تطوير Development Environment وبيئة للاختبار والتأكد من المشروع قبل رفعه على السيرفر النهائي Staging Servers ومن ثم السيرفر النهائي Production Server، فسوف تجد بدون دوكر فمن الصعوبة وضع كل البرمجيات والمتطلبات على هذه السيرفرات، وقد تحدث مشاكل بسبب اختلاف نوع النظام او البرامج الموجودة عليه، لكن مع دوكر ستجد من السهولة توحيد تلك الأجهزة Consistency وتستطيع نقل ال Images وتشغيلها على هذه الأجهزة وسيعمل ال containers كما هو عليها جميعاً. - **نشر المشاريع بشكل أسرع** **Ship Software Faster**: يمكن الاستفادة من أدوات الأتمتة وبناء Pipeline كامل بمجرد عمل push للسورس كود، فسيتم مباشرة بناء الكود كاملاً، ومن ثم في حال النجاح يتم تشغيل كافة الاختبارات وبعدها في حال النجاح يتم رفع ال images على ال Docker hub (أو على أحد ال Registries) واخيراً تحميلها من السيرفر وعمل Update حتى يعمل التحديث في السيرفر، وكل هذا بمجرد ضغطة زر واحدة فقط Automated Workflow. وهذا الأتمتة مهمة جداً للعديد من المشاريع، خصوصاً في الشركات الناشئة والتي تعمل بمفهوم ال Continues Update والتي تضيف وتحسن الخصائص بشكل مستمر، فوجود أتمته كاملة لنشر البرمجيات ورفعها امر مهم وسوف يحفظ الكثير من الوقت الضائع في عملية رفع التحديثات يدوياً الى السيرفر (أو السيرفرات في حالة كان البرنامج يعمل على أكثر من سيرفر). # ما الفرق بين ال Containers وال Virtual Machines؟ وهذا يعد من أكثر من التساؤلات في دوكر وال Containers عموماً، حيث كما ذكرنا سابقاً فأن ال Containers توفر عزل من ناحية نظام الملفات، الشبكات، قائمة العمليات وبداخل كل Container سوف تجد كأنك تعمل على جهاز منفصل، وهي نفس فكرة ال Virtual Machine والتي أيضاً توفر عزل كامل بين ال VM المختلفة وكأنها تعمل على ذلك الجهاز فقط. لكن هناك فرق بينهم، حيث ال VM يجب أن يكون هناك نظام تشغيل لكل VM وبعدها يتم تنصيب البرمجيات على ذلك النظام، بينما في ال Containers فسوف نجد أنه بالإمكان ان يعمل مباشرة على ال Host، وهذا اوفر للموارد وأسرع في العمل بكل تأكيد بالإضافة الى أنه يحفظ وقت مدير النظام بدلاً من إدارة 5 برامج تعمل على خمسة أنظمة تشغيل في ال 5 VMs ويقوم بتحديث الأنظمة باستمرار، فالان سوف يعمل على نظام تشغيل واحد يحدثه باستمرار وكافة البرمجيات تعمل في ال containers ومعزولة عن الأخرى. [![](https://informatic-ar.com/wp-content/uploads/2018/05/vm_docker.png)](https://informatic-ar.com/wp-content/uploads/2018/05/vm%5Fdocker.png) لكن بأي حال ال Containers هو ليس بديلاً عن ال VMs وقد تجد في كثيراً من الأحيان ان تستأجر VMs وتقوم بتشغيل عليه مجموعة من ال Containers بدلاً من شراء سيرفر مخصص Physical Servers وتحفظ الموارد وتقلل التكلفة. ومن ثم تستخدم ال Docker فيها لكي تستفيد من ال Containers. # ما هو دوكر Docker؟ شركة دوكر Docker Inc. من سان فرانسيسكو بدئت في السابق بتطوير Platform as Service PaaS اسمها dotCloud لتقديم خدمات للمطورين على ال Amazon web services، وكانت تستخدم داخلياً مفهوم ال Containers للتشغيل بالإضافة الى نشر Deployment المشروع، وبالرغم من أنه المشروع الأساسي كان dotCloud فتم تطوير المشروع الداخلي واعطي الاسم Docker ونشر كمشروع مستقل في 2013. وبهذا القرار الصائب، أصبحت شركة دوكر الأولى في تقديم ال Containers وحصلت على استثمارات عالية للمضي للأمام، وبيع مشروع ال dotCloud وركزت الشركة على ال Containers وخلال مسيرتها استحوذت على عدة مشاريع containers لشركات ناشئة أخرى، وهذا ما جعلها تتفرد في الساحة. وشركة دوكر Docker Inc. هي ليست المالكة لمشروع دوكر Docker Project الان، فهو مشروع مفتوح المصدر برخصة Apache وهذا يعني أي شخص بإمكانه تحميل الكود المصدر وتعديله واستخدامه طالما لم يخرق شروط هذه الرخصة. وهناك الكثير من الاسهامات بواسطة كبرى الشركات مثل IBM, RedHat وغيرها. ويهدف مشروع الدوكر Docker Project لتقديم كافة الأدوات اللازمة لبناء Develop ونشر Ship/Deploy البرمجيات بشكل أفضل عما كان عليه في السابق. وهذا يعني انه هناك العديد من الأدوات والبرامج بداخل مشروع الدوكر وكل منها يختص بجزئية معينة، وأهمها هو ال Docker Engine وهو الذي يقوم بتشغيل ال Containers وبقية الأدوات تكون مساعدة لل Engine في عمله. وسوف نجد أن مشروع الدوكر يتكون من العديد من الأدوات مثل ال Docker Engine, Docker Compose, Docker Swarm وغيرها والتي سوف يتم تناولها في مواضيع لاحقة بإذن الله. اخيراً لأن مفهوم ال Images يعتبر أحد العوامل من نجاح ال Containers Technology فقد اجتمعت العديد من الشركات المهتمة لكي تضع معيار ومقياس standards لبنية هذه ال Image وال Runtime Environment وتم نشر ال Specification لهم. وظهرت حركة [Open Container Initiative](https://www.opencontainers.org/?ref=informatic-ar.com) لوضع المعايير لل Containers بشكل عام، سواءً في شكل هذه ال containers وتشغيلها. وبهذا المقياس فيمكن أن تقوم بتشغيل أي Image متوافقة مع ال OCI في أي Runtime Engine متوافق مع ال OCI وهذا ما يجعل مثلاً Docker Image تشتغل على Rkt Engine على سبيل المثال.. المقالة التالية سوف تتحدث عن تنصيب دوكر، سواءً في ويندوز ولينوكس. ### التعقيد للخوارزميات Big O Complexity URL: https://informatic-ar.com/big-o-complexity/ Last updated: 2026-10-04T04:35:39.000Z قياس كفاءة أى خوارزمية أو ادائها يعتمد على **عدة عوامل:** 1-الوقت CPU (time) 2-الذاكرة memory usage 3- المساحة فى الهارد disk usage 4- الشبكة network usage كل هذة العوامل مهمة وسوف نتحدث فى هذة المقالة عن الوقت CPU (time) **بداية لنكن حذرين فهناك فرق كبير بين:** 1-الاداء Performance :وهى تعرفك كم هو الوقت اللازم time /الذاكرة memory /المساحة فى الهارد disk /.. المستخدمين فعليا عند تشغيل البرنامج لحل مشكلة ما وهذة الاشياء تعتمد على الجهاز والمعالج compiler,وأيضا الكود. 2-التعقيد للخوارزمية Complexity:سوف تخبرك ماذا يحدث وما هو الوقت اللازم والresources اللازمة لعمل الخوارزمية او البرنامج **اذا زاد حجم المشكلة size of problem** Big O هى طريقة تخبرك مدى تعقيد الخوارزمية complexity of an algorithm **وعندما نتحدث عن** complexity of an algorithm فنحن لا نهتم بالوقت اللازم للخوارزمية ولا عدد الخطوات فقط **بل نهتم بالعلاقة بين عدد الخطوات اللازمة للخوارزمية number of operations or steps نسبة الى حجم المشكلة problem sizeاو حجم المدخلات** لكن من يهتم ؟ حسنا فى الغالب أنت سوف تستعمل خوارزميات لاشخاص أخرين ولكن حتى فى هذة الحالة من الجيد ان تعرف مدى complexity لتلك الخوارزميات لتختار ايهم افضل لحل مشكلتك لنفترض ان dr savealife يعمل لدى شركة ناسا NASA واراد ان يكتب خوارزمية للبحث عن المكان الذى سوف يهبط فية الصاروخ على سطح القمر .مع العلم أن شركة ناسا طلبت ان تكون الخوازمية التى سوف يستخدمها يجب ان تكون صحيحة وتعمل بشكل جيد وفى نفس الوقت سريعة تعطية النتائج فى مدة لا تزيد عن الـ 7 ثوانى dr savealife الان يقارن هل يستخدم خوارزمية البحث العادى ام[ البحث الثنائى](https://informatic-ar.com/binary-search/) ؟ وقال اذا استخدمت البحث الثنائى سوف يجد مكان هبوط الصاروخ بسرعة وفى وقت قصير لكن الكود ممكن يكون بة اخطاء فلا يمكن تحديد المكان بشكل صحيح اما خوارزمية البحث العادى فكتابة الكود سوف تكون اسهل وليس بة اخطاء ولكن الوقت سوف يكون كبير هو قرر ان يختبر الخوارزميتين اولا على قائمة بها 100 عنصر ليرى الوقت الذى سوف تستغرقة كل خوارزمية لو افترضنا ان الزمن اللازم لاختبار كل عنصر فى القائمة هو واحد ملى ثانية millisecond فخوارزمية البحث العادى سوف تأخذ **100 ملى ثانية** millisecond إما خوارزمية البحث الثنائى سوف تأخذ **7 ملى ثانية** لان log2 100 مساوى 7 [![](https://informatic-ar.com/wp-content/uploads/2018/01/big-o_1.png)](https://informatic-ar.com/wp-content/uploads/2018/01/big-o%5F1.png) dr savealife قرر أن يستخدم الخوارزمية العادية لانها سهلة فى كتابة الكود ايضا هى تحقق الشرط لشركة ناسا ان تعمل الخوارزمية فى مدة لا تتعدى 7 ثوانى-طبعا هو قام بخطأ كبير لانة لم يضع فى الحسبان ماذا اذا كبر space للبحث ما هى نتيجة اختيارة للتوضيح : ماذا اذا كانت القائمة بها عناصر اكثر بدل من 100 عنصر لنجعلها 1 بليون عنصر سوف نجد ان الخوارزمية العادية سوف تأخذ 1 بليون ms اى 11 يوم إما خوازمية البحث الثنائى سوف تأخذ 32 ms **وفى هذة الحالة الفرق كبير بين الخوارزميتين** [![](https://informatic-ar.com/wp-content/uploads/2018/01/big-o_2.png)](https://informatic-ar.com/wp-content/uploads/2018/01/big-o%5F2.png) اى معنى ذلك انة كلما زادت العناصر او زاد مجال البحث كلما كانت خوارزمية البحث الثنائى افضل واسرع ولذا كان علينا ليس معرفة ما هى سرعة الخوارزمية فقط بل ما مدى كفاءة الخوارزمية عند زيادة مجال البحث من **هنا جاءت طريقة الـbig O** Big O طريقة تخبرك ما هو مدى سرعة برنامجك او خوارزميتك **نسبة الى عدد الخطوات فية iteration عندما يزيد حجم المشكلة**. فمثلا فى الخوارزمية العادية لاختبار كل عنصر بقائمة تحتوى على عدد n عنصر سوف يكون لديك n خطوة اى O(n) ما هو الوقت اللازم بالثوانى **Big O لا تحسب الوقت بالثوانى بل بالخطوات فمقارنة البرامج الان سوف تكون على اساس كم خطوة iteration سوف يأخذ برنامجك لايجاد النتيجة .** مثال اخر خوارزمية البحث الثنائى تحتاج الى log n خطوة لكى تختبر قائمة بها n عنصر لذا نقول ان big O لها هى O(log n) اى ان الخوارزمية تأخذ log n خطوة **كيف تعمل الـbig O :** لتصور كيف تعمل الbig O سوف نختبرها على اكثر من خوارزمية ونرى كيفية حسابها بمثال عملى بسيط يحتاج فقط ورقة صغيرة وقلم لنبدأ: لو قلت لك ارسم 16χ16 مربع هل تستطيع؟ [![](https://informatic-ar.com/wp-content/uploads/2018/01/big-o_3.png)](https://informatic-ar.com/wp-content/uploads/2018/01/big-o%5F3.png)الخوارزمية الاولى: انك سوف ترسم مربع مربع كما بالشكل التالى [![](https://informatic-ar.com/wp-content/uploads/2018/01/big-o_4.png)](https://informatic-ar.com/wp-content/uploads/2018/01/big-o%5F4.png)فى هذة الحالة سوف تأخذ 16 خطوة لكى ترسمهم اذا فيكون قيمة big O تساوى O(n) الخوارزمية الثانية : انك تقوم بعمل ثنى fold للورقة كما بالشكل فى المرة الاولى تجد انك حصلت على مربعين من خطوة واحدة ثم بتكرار عملية الثنى [![](https://informatic-ar.com/wp-content/uploads/2018/01/big-o_5.png)](https://informatic-ar.com/wp-content/uploads/2018/01/big-o%5F5.png)سوف تجد انك حصلت على 16 مربع فى اربع خطوات فقط اذا قيمة big O لها هى O(log n) **كيف نحسب نسبة التعقيد للخوارزمية how to determine the complexities** كثير من يسأل كيف احسب big O لاى خوارزمية الان سوف نكون مثل الجراحين جزء جزء لنتعلم ولنأخذها على حالات : 1-الحالة الاولى: اذا لدى تتابع من الجمل Sequence of statements ```pseudocode statement 1; statement 2; ... statement k; ``` فلو كل statement اخذت وقت معين فسيكون الوقت الكامل عبارة عن مجموعهم total time = time(statement 1) + time(statement 2) + ... + time(statement k) ولوكانت العملية بسيطة مثل(جمع -ضرب -قسمة ..) فأن الخوارزمية تكون O(1) اى a constant-time algorithm 2\-الحالة الثانية :اذا كان هناك جمل شرطية IF ```pseudocode if (cond) then block 1 (sequence of statements) else block 2 (sequence of statements) end if; ``` فى هذة الحالة سوف نحسب حسب الblock الذى سوف يقوم بعمليات اكثر لانها اللحالة الاسوء اى سوف نأخذ max(time(block 1), time(block 2)) 3\-الحالة الثالثة: لو هناك تكرار Loops ```pseudocode for I in 1 .. N loop sequence of statements end loop; ``` فى هذة الحالة الloop سوف تكون N مرة لذا ايضا مجموعة statements سوف تتكرر N مرة لو اعتبرنا ان كل statement تنفذ O(1) فيكون قيمة التعقيد الكلى N \* O(1) اى مساوية O(N) 4-الحالة الرابعة: التكرار المتداخل Nested loops ```pseudocode for I in 1 .. N loop for J in 1 .. M loop sequence of statements end loop; end loop; ``` فى هذة الحالة الloop الخارجية outer loop سوف يتم تنفيذها N مرة وفى كل مرة تنفذ الloop الداخلية inner loop عدد M مرة فبذلك يكون التعقيد O(N \* M). 5-الحالة الخامسة:المناداة على ميثود Statements with function/ procedure calls ```pseudocode for J in 1 .. N loop g(J); end loop; ``` فى هذة الحالة لدى outer loop سوف تتنفذ N مرة وفى كل مرة تنادى على ميثود function لا تنزعج كل ما عليك معرفة التعقيد للfunction فاذا كان مثلا N فسيكون التعقيد الكلى O(N2) **بعض Big O run times الشائعة:** 1-O(1): وتعرف بأنها ثابتة a constant-time algorithm 2-O(log n): وتعرف ايضا بأسم log time ومن امثلتها خوارزمية البحث الثنائى 3-O(n): وتعرف ايضا بأسم linear time ومن امثلتها البحث البسيط او العادى 4-O(n \* log n): وهى تعتبر سريعة نسبيا ومن امثلتها خوارزميات الترتيب السريعة مثل quick sort 5-O(n2): وهى بطيئة ومن امثلتها خوارزميات الترتيب البطيئة مثل selection sort 6-O(n!): وهى تعتبر بطيئة جدا ومن امثلتها traveling salesperson [![](https://informatic-ar.com/wp-content/uploads/2018/01/big_6.png)](https://informatic-ar.com/wp-content/uploads/2018/01/big%5F6.png) **مراجع:** http://web.mit.edu/16.070/www/lecture/big\_o.pdf **رجاء:لا تنسونى من الدعاء عن ظهر غيب** **رَبِّ إِنِّي لِمَا أَنزَلْتَ إِلَيَّ مِنْ خَيْرٍ فَقِيرٌ** **الحمد الله رب العالمين والصلاة والسلام على أشرف المرسلين سيدنا محمد صلى الله علية وسلم وعلى اله وصحبة وسلم -اللهم دومها نعمة وأحفظها من الزوال -أمين** ### خوارزمية البحث الثنائى Binary search URL: https://informatic-ar.com/binary-search/ Last updated: 2026-10-04T04:35:40.000Z افترض انك تبحث عن شخص فى دليل التليفون وكان اسمة يبدأ بحرف الكاف (بالطريقة التقليدية) فأنت تبدأ بفتح الدليل وتبحث من الصفحة الاولى وتقلب الصفحات الى أن تصل للصحفة التى بها حرف الكاف . لكن كان من الافضل ان تبدأ من منتصف الدليل لانك تعلم أن حرف الكاف ليس من الحروف الاولى فى الترتيب الابجدى افترض انك مسجل فى Facebook عندما تقوم بعمل دخول sign in فأن Facebook يجب ان يتأكد ان اسم المستخدم موجود لدية فى الdatabase اى يبحث عنة ومن الافضل ان يبدأ البحث فى الdatabase من نقطة فى الوسط هذة التطبيقات وغيرها كثير عبارة عن مشاكل بحثsearch problem تستخدم او تحتاج لاستخدام خوارزمية binary search لحلها. **Binary search** هى عبارة عن خوارزمية المدخل لها عبارة عن قائمة بها عناصر مرتبة والمخرج منها عبارة عن موقع العنصر الذى تبحث عنة **مثال:** لكى نفهم كيف تتم العمليات فى binary search المثال عبارة عن لعبة التخمين وفيها اعطيك قائمة مرتبة بها الارقام من 1 الى 100 وانت عليك ان تخمن الرقم الذى اخترتة فى اقل عدد من التخمينات وكل مرة تقوم بتخمين رقم سوف ارشدك بأننى اقول لك الرقم كبير جدا too high او صغير جدا too low او صحيح correct **لنبدأ :** كما بالصورة الرقم الصحيح هو 7 وقام اللاعب بتخمين رقم واحد فكانت الاجابة too low [![](https://informatic-ar.com/wp-content/uploads/2017/12/guess_1.png)](https://informatic-ar.com/wp-content/uploads/2017/12/guess%5F1.png) ثم قام اللاعب بتخمين الرقم 2 وكانت الاجابة too low فقام بالغاؤة الى ان وصل اللاعب للرقم 7 وهو الرقم الصحيح , نلاحظ فى هذة الطريقة انك بدأت من البداية للقائمة بالرقم واحد وعندما كان خطأ خذفتة وقلت الرقم الذى يلية وهكذا الى ان تصل للرقم الصحيح . [![](https://informatic-ar.com/wp-content/uploads/2017/12/guess_2.png)](https://informatic-ar.com/wp-content/uploads/2017/12/guess%5F2.png)لكن هناك طريقة افضل فى البحث هى : ان تبدأ بالرقم فى الوسط middle وليكن 50 فتكون الاجابة too low لذلك انت تقوم بالغاء الارقام من 1 الى 50 دفعة واحدة [![](https://informatic-ar.com/wp-content/uploads/2017/12/guess_3.png)](https://informatic-ar.com/wp-content/uploads/2017/12/guess%5F3.png)ثم تبدأ بتخمين رقم جديد وليكن 75 فتكون الاجابة too high [![](https://informatic-ar.com/wp-content/uploads/2017/12/guess_4.png)](https://informatic-ar.com/wp-content/uploads/2017/12/guess%5F4.png) مرة اخرى تقم بالغاء الارقام من 75 الى 100 وتكون الارقام المرجح انها صحيحة بين 51 الى 74 وهكذا الى ان تصل للرقم الصحيح [![](https://informatic-ar.com/wp-content/uploads/2017/12/guess_5.png)](https://informatic-ar.com/wp-content/uploads/2017/12/guess%5F5.png) هذة هى خوارزمية البحث الثنائى لو اردنا حساب كم رقم ممكن ان نحذفة فى كل خطوة iteration فمهما كان الرقم الذى افكر فية فيمكنك ان تستنتجة فى 7 خطوات iteration فقط لانك فى كل مرة تخمن فيها رقم سوف تقوم بالغاء نصف الارقام . [![](https://informatic-ar.com/wp-content/uploads/2018/01/guess6.png)](https://informatic-ar.com/wp-content/uploads/2018/01/guess6.png)**للتوضيح اكثر تخيل اننا نبحث عن كلمة فى قاموس والقاموس بة 240,000 كلمة فكم عدد الخطوات سوف تأخذ الخوارزمية العادية وخوارزمية البحث الثنائى لايجادها** [![](https://informatic-ar.com/wp-content/uploads/2018/01/guess_7.png)](https://informatic-ar.com/wp-content/uploads/2018/01/guess%5F7.png) فى الخوارزمية العادية فى اسوء الحالات لو كانت الكلمة اخر كلمة فى القاموس فسوف تأخذ 240,000 خطوة لايجاد الكلمة اما خوارزمية البحث الثنائى ففى كل خطوة سوف تقوم بتقسيم الكلمات الى نصفين وتلغى احدهما الى ان تصل الى كلمة واحدة فقط [![](https://informatic-ar.com/wp-content/uploads/2018/01/guess_8.png)](https://informatic-ar.com/wp-content/uploads/2018/01/guess%5F8.png)اذا سوف تأخذ 18 خطوة فقط اكيد طبعا فرق كبير بين عدد الخطوات فى الخوارزميتين **بصورة عامة فأن خوارزمية البحث الثنائى اذا كان لدى قائمة مكونة من n عنصر فهى تأخذ عدد من الخطوات بمقدار log2 n لايجاد اى عنصر فى قائمة** **ملحوظة :** عند استخدام خوارزمية البحث الثنائى يجب ان تكون العناصر فى القائمة مرتبة sorted list **Running time** عند التحدث عن اى خوارزمية يجب ان نتحدث عن كفائتها من حيث إما : - **الوقت running time** - **أو المساحة فى الذاكرة space** بالنسبة لخوارزمية البحث الثنائى كم هو الوقت الذى نحفظة عند استخدامها ؟ لو نظرنا الى الخوارزمية العادية نجد اننا نختبر كل عنصرمرة تلو الاخرى اى اذا لدينا قائمة بها 100 عنصر فسوف نخمن 100 مرة واذا لدينا قائمة بها 4 بليون عنصر سوف نخمن 4 بليون مرة اى عدد التخمينات يكون مساوى لحجم القائمة ونطلق على ذلك ان **الخوارزمية تعمل فى linear time او** **O(n)** فى خوارزمية البحث الثنائى الامر مختلف اذا لدينا قائمة بها 100 عنصر سوف يكون عدد التخمينات 7 مرات واذا لدينا قائمة بها 4 بليون عنصر سوف يكون عدد التخمينات 32 مرة اى ان **الخوارزمية تعمل فى log time اى O(log(n))** كما موضح بالشكل [![](https://informatic-ar.com/wp-content/uploads/2018/01/guess_9.png)](https://informatic-ar.com/wp-content/uploads/2018/01/guess%5F9.png) **الكود بالبايثون:** اذا اردنا كتابة الكود للبحث عن رقم ما بالقائمة -فهذا الكود يبحث عن رقم 3 بالقائمة \[1, 3, 5, 7, 9\] ```python def binary_search(list, item): low = 0 high = len(list)—1 while low <= high: mid = (low + high)//2 guess = list[mid] if guess == item: return mid if guess > item: high = mid - 1 else: low = mid + 1 return None my_list = [1, 3, 5, 7, 9] print binary_search(my_list, 3) # => 1 print binary_search(my_list, -1) # => None ``` **الكود بالجافا** ```java public static int binarySearch(int[] a, int key) { int low = 0; int high = a.length - 1; while (low <= high) { int mid = (low + high) / 2; int midVal = a[mid]; if (midVal < key) low = mid + 1 else if (midVal > key) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found. } ``` **رجاء:لا تنسونى من الدعاء عن ظهر غيب** **رَبِّ إِنِّي لِمَا أَنزَلْتَ إِلَيَّ مِنْ خَيْرٍ فَقِيرٌ** **الحمد الله رب العالمين والصلاة والسلام على أشرف المرسلين سيدنا محمد صلى الله علية وسلم وعلى اله وصحبة وسلم -اللهم دومها نعمة وأحفظها من الزوال -أمين** ### خوارزمية K-Nearest Neighbor URL: https://informatic-ar.com/k-nearest-neighbor-algorithm/ Last updated: 2026-10-04T04:35:40.000Z تخيل أنك تحاول أن تتنبأ من هو الرئيس الذى سوف أنتخبة فى الانتخابات القادمة . أذا أنت لا تعرف أى شىء عنى سوى أين أسكن فمن الأمور المرجح أن تفعلها أنك سوف تنظر إلى جيرانى وتعرف من سينتخبوا؟ [![](https://informatic-ar.com/wp-content/uploads/2017/12/howmanytimes.png)](https://informatic-ar.com/wp-content/uploads/2017/12/howmanytimes.png) هذا هو أساس خوارزمية **K-Nearest Neighbour algorithm** Nearest Neighbors هى أحد خوارزميات التنبؤ Predictive Model وهى لاتحتاج الى تعلم معادلات رياضية معقدة بل تحتاج فقط إلى توفر شيئن فى البيانات DataSet: - طريقة لحساب المسافة distance بين البيانات - تحقيق افتراضية أن البيانات القريبة من بعضها تكون متشابهة والبعيدة عن بعضها تكون غير متشابهة؟ معظم التقنيات التى تستخدم فى خوارزميات التنبؤ Predictive Model تنظر الى مجموعة البيانات ككل من أجل معرفة أنماط البيانات لكن Nearest neighborsمن ناحية أخرى تهمل الكثير من المعلومات حيث ان فيها يتم التنبؤ لكل نقطة جديدة(مثال جديد new instance) اعتمادا فقط على عدد النقاط القريبة منها. الخوارزمية خطوة بخطوة: 1-نحدد قيمة المتغير k الذى يعبر عن عدد المجاورين 2-نحسب قيمة المسافة بين المثال الجديد والامثلة التى فى dataset 3-نرتب الامثلة لنحصل على المجاورين أعتمادا على أقل مسافة تم حسابها فى الخطوة السابقة ونأخذ منهم عدد k مجاور 4-نحدد الكلاس للمجاورين 5-الكلاس الاكثر اغلبية للمجاورين هو الكلاس المتوقع لهذا المثال اى الخوارزمية بأختصار **“Tell me who your neighbors are, and I’ll tell you who you are”** **مثال بسيط لتوضيح طريقة عمل الخوارزمية:** نفترض ان لدينا مصنع يقوم بصنع الاقلام وقمنا بعمل إستطلاع عن جودتهم من خلال السؤال عن بعض الخصائص (attributes) فى الاقلام وليكن منهم نوع الحبر ولنعبر عنة بالمتغير X1,وقابليتة للمسح ونعبر عنة بالمتغير X2 فتجمع لدينا هذة المعلومات | نوع الحبر x1 | قابليتة للمسح x2 | التصنيف Classification Y | | ------------ | ---------------- | ------------------------ | | 7 | 7 | BAD | | 7 | 4 | BAD | | 3 | 4 | GOOD | | 1 | 4 | GOOD | الان لدينا منتج جديد فى المصنع لقلم جديد ولة خاصيتين X1=3 , X2=7 هل يمكننا التنبؤ بتصنيف هذا القلم؟ لنتبع خطوات الخوارزمية: 1-نحدد قيمة للمتغير الذى سوف يعبر عن عدد المجاورين k وليكن قيمة k=3 2- نحسب قيمة المسافة بين المنتج الجديد والامثلة التى فى dataset ,فى مثالنا هذا سوف نعتمد على Euclidean Distance حيث يتم حساب المسافة بين نقطتين p,q من المعادلة: [![](https://informatic-ar.com/wp-content/uploads/2017/12/eq1.png)](https://informatic-ar.com/wp-content/uploads/2017/12/eq1.png) 3- نرتب الامثلة لنحصل على المجاورين أعتمادا على أقل مسافة تم حسابها فى الخطوة السابقة ونأخذ منهم عدد k مجاور وحيث k هنا قيمتة 3 مجاورين فقط وبترتيب الامثلة حسب المسافة الاقل سوف نلغى المثال الثانى من ان يكون من المجاورين [![](https://informatic-ar.com/wp-content/uploads/2017/12/table2.png)](https://informatic-ar.com/wp-content/uploads/2017/12/table2.png) 4-نحدد قيمة التصنيف للمجاورين كما فى العمود الاخير فى الجدول التالى [![](https://informatic-ar.com/wp-content/uploads/2017/12/table4.png)](https://informatic-ar.com/wp-content/uploads/2017/12/table4.png) 5- الكلاس الاكثر اغلبية للمجاورين هو GOOD حيث لدينا ثلاثة أمثلة اثنين منهم GOOD وواحد فقط BAD لذا الكلاس المتوقع لهذا القلم الجديد هو GOOD الان أعتقد أن الخوارزمية بسيطة جدا لنطبق على أمثلة معقدة او مشاكل حقيقية من الحياة Real World Problem **\~** **The Iris dataset** **\~** مجموعة البيانات زهرة Iris أو مجموعة البيانات Fisher's Iris هى مجموعة من البيانات متعددة المتغيرات multivariate data set وسميت بهذا الاسم نسبة الى العالم رونالد فيشر الذى اعلن عنها فى بحثة سنة 1936, مجموعة البيانات Dataset عبارة عن 150 مثال(عينة) لثلاثة أنواع من زهرة Iris: - Iris setosa - Iris virginica - Iris versicolor [![](https://informatic-ar.com/wp-content/uploads/2017/12/Iris_photo.png)](https://informatic-ar.com/wp-content/uploads/2017/12/Iris%5Fphoto.png)تم قياس أربع سمات او خصائص attributes or features من كل نوع: طول وعرض sepals وطول وعرض petals ,هذا جزء مستقطع من الـ Iris datasetلتوضيحها فى الجدول التالى: [![](https://informatic-ar.com/wp-content/uploads/2017/12/Iris_data_table.png)](https://informatic-ar.com/wp-content/uploads/2017/12/Iris%5Fdata%5Ftable.png) المشكلة التي نريد حلها هي، "طبقا لمجموعة البيانات السابقة DataSet" اذا حصلنا على زهرة جديدة ، هل يمكننا التنبؤ بنوعها من قياساتها ؟ بداية هل نكتب كود لعمل رسم بيانى لهذة الdataset لان رؤية البيانات بشكل مرئى يعتبر خطوة أولية لفهم هذة المشكلة "Visualization is a good first step" [![](https://informatic-ar.com/wp-content/uploads/2017/12/drsavealife_draw.png)](https://informatic-ar.com/wp-content/uploads/2017/12/drsavealife%5Fdraw.png) من الرسم البيانى نجد ان البيانات تنقسم الى مجموعتين كبيرتين :الاولى لزهرة Iris setosa والتى نرمز لها بالمربع الاحمر والمجموعة الثانية هى خليط من Iris virginica والتى نرمز لها بعلامة x الزرقاء و Iris versicolor بالدائرة الخضراء, الان اذا لدينا زهرة جديدة كما بالشكل التالى .هل يمكن تصنيفها ؟ [![](https://informatic-ar.com/wp-content/uploads/2017/12/what_is_new_class.png)](https://informatic-ar.com/wp-content/uploads/2017/12/what%5Fis%5Fnew%5Fclass.png) المشكلة تعتبر مشكلة تصنيف Classification problem حيث لدينا مجموعة بيانات dataset تتكون من مجموعة أمثلة instances كل مثال لة كلاس معين هل نستطيع تصميم قاعدة rule بحيث تجعلنا نتنبأ بالكلاس لاى مثال جديد غير موجود فى dataset, مشكلة التصنيف لها تطبيقات كثيرة فى الحياة منها تصينف الرسائل هل هى من الرسائل المزعجة spam او ليست من الرسائل المزعجة not spam وغيرها من المشاكل التى سوف نتطرق لها بعد ذلك بأذن الله تعالى **\~\~\~\~** **التطبيـــــق العمــــــلى بالكــــــــــــود** **\~\~\~\~\~\~** **أولا :التصنيف بأستخدام خوارزمية** **k-nearest neighbor** **من الصفر** **k-nearest neighbor from scratch** بعد تحميل ملف الـ iris dataset من الموقع: [https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data](https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data?ref=informatic-ar.com) 1-الخطوة الاولى التعامل مع البيانات Handle Data: نبدأ بقراءة الملف وتقسيم البيانات عشوائيا الى training set ,test set باستخدام الميثود loadDataset ```python import csv import random def loadDataset(filename, split, trainingSet=[] , testSet=[]): with open(filename, 'rb') as csvfile: lines = csv.reader(csvfile) dataset = list(lines) for x in range(len(dataset)-1): for y in range(4): dataset[x][y] = float(dataset[x][y]) if random.random() < split: trainingSet.append(dataset[x]) else: testSet.append(dataset[x]) ``` يمكن تجربة الميثود بأستخدام الكود التالى: ```python trainingSet=[] testSet=[] loadDataset('iris.data', 0.66, trainingSet, testSet) print 'Train: ' + repr(len(trainingSet)) print 'Test: ' + repr(len(testSet)) ``` 2-الخطوة الثانية إنشاء ميثود لحساب المسافة او التشابهة Similarity: نكتب الميثود euclideanDistance تحسب المسافة بين اى مثاليين وقد تعرضنا من قبل للصيغة الرياضية لها ```python import math def euclideanDistance(instance1, instance2, length): distance = 0 for x in range(length): distance += pow((instance1[x] - instance2[x]), 2) return math.sqrt(distance) ``` 3-الخطوة الثالثة ايجاد المجاورين Neighbors: هذة الميثود سوف تستخدم الميثود euclideanDistance لايجاد المسافة بين المثال الجديد والtrainingset ثم ترتب الامثلة تنازليا من التى لها مسافة أصغر للحصول على المجاورين ```python import operator def getNeighbors(trainingSet, testInstance, k): distances = [] length = len(testInstance)-1 for x in range(len(trainingSet)): dist = euclideanDistance(testInstance, trainingSet[x], length) distances.append((trainingSet[x], dist)) distances.sort(key=operator.itemgetter(1)) neighbors = [] for x in range(k): neighbors.append(distances[x][0]) return neighbors ``` 4-الخطوة الرابعة تحديد الmajority : بعد تحديد المجاورين للمثال الجديد الان نحدد الكلاس الذى لة اكثر اغلبية بينهم وهو النتيجة لتصنيف هذا المثال الجديد ```python import operator def getResponse(neighbors): classVotes = {} for x in range(len(neighbors)): response = neighbors[x][-1] if response in classVotes: classVotes[response] += 1 else: classVotes[response] = 1 sortedVotes = sorted(classVotes.iteritems(), key=operator.itemgetter(1), reverse=True) return sortedVotes[0][0] ``` 5- حساب دقة الخوارزمية Accuracy: فى هذة الخطوة نحسب كم عدد الامثلة التى تم التنبؤ بالكلاس الصحيح لها فى testSet ```python def getAccuracy(testSet, predictions): correct = 0 for x in range(len(testSet)): if testSet[x][-1] is predictions[x]: correct += 1 return (correct/float(len(testSet))) * 100.0 ``` **ثانيا :التصنيف بأستخدام مكتبة** **scikit-learn** **Classifying with scikit-learn** فى السابق كتابنا كود لبرمجة خوارزمية knn من البداية ولكن لغة python لغة مناسبة جدا لتعلم machine learning لأنها تحتوى على العديد من المكتبات الممتازة وخاصة المكتبة scikit-learn وفى هذا الجزء سوف نتعلم استخدامها لعمل تصنيف للبيانات باستخدام خوارزمية knn بداية يجب أن تعلم أن scikit-learn classification API تعتمد على أثنين ميثود : - **fit(features, labels)** - **predict(features)** الميثود fit وهى خطوة يتم فيها التعلم training من الامثلة التى لدينا ثم نستخدم الميثود predict للتنبؤ بالكلاس لمثال جديد الان لنبدأ بخطوات استخدام المكتبة scikit-learn : 1-الخطوة الاولى التعامل مع البيانات Loading the data: - بداية نعمل import لمجموعة البيانات dataset ```python # Import load_iris function from datasets module from sklearn.datasets import load_iris ``` - ثم نستخدم الميثود load\_iris() لجلب dataset كلها ثم حفظها فى الكائن iris ```python # save object containing iris dataset and its attributes iris = load_iris() ``` لجلب الامثلة instances نستخدم iris.data ```python # Store feature matrix in "features" features = iris.data· ``` لجلب الكلاس لكل مثال نستخدم iris.target ```python # Store response vector in "target" target = iris.target ``` - لمعرفة ابعاد dataset نستخدم الامر shape ```python # prints the shapes of features and target print features.shape ``` والنتيجة هى انة يوجد 150 مثال كل واحد يحتوى على اربع خصائص ```plain (150,4) ``` 2-الخطوة الثانية : التعامل مع scikit-learn فى 4 خطوات: 1) قم بعمل import للكلاس الذى سوف تستخدمة فى عملية التصنيف وسوف نستخدم هنا خوارزمية knn لذا نكتب ```python from sklearn.neighbors import KNeighborsClassifier ``` 2)قم بعمل كائن من Estimator وحدد عدد المجاورين ```python knn = KNeighborsClassifier(n_neighbors=1) ``` ```python print knn ``` والنتيجة هى:` ` ```plain KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=1, n_neighbors=1, p=2, weights='uniform') ``` 3)اعطى الكائن الذى انشأتة knn البيانات والتى هى الامثلة والكلاس المتوقع لها ```python knn.fit(features, target) ``` 4)استخدم الـ Estimatorللحصول على النتيجة اذا اعطيتة مثال جديد ```python knn.predict([[3, 5, 4, 2]]) ``` النتيجة هى ان هذا المثال الجديد تم التنبؤ لة بالكلاس 2 وهى زهرة virginica array(\[2\])) `array([2])` 5)يمكن ان تعطية اكثر من مثال للحصول على نتيجة مرة واحدة ```python X_new = [[3, 5, 4, 2], [5, 4, 3, 2]] knn.predict(X_new) ``` النتيجة هى ان هذا المثال الجديد الاول تم التنبؤ لة بالكلاس 2 وهى زهرة Virginica والمثال الثانى تم التنبؤ لة بأنة من زهرة versicolor ```plain array([2, 1]) ``` **\~ The digits dataset\~** هى عبارة عن مجموعة من البيانات تتكون من 1797 صورة وكل صورة عبارة عن مصفوفة 8X8 ، والصورة تمثل رقم ما كما هو موضح أدناه . [![](https://informatic-ar.com/wp-content/uploads/2017/12/digits_photi.png)](https://informatic-ar.com/wp-content/uploads/2017/12/digits%5Fphoti.png) اذا هذة الdataset عبارة عن مجموعة من الامثلة كل مثال عبارة عن صورة من 8x8 وعدد الكلاسات 10 ولدينا تقريبا 180 مثال لكل كلاس والمجموع الكلى للامثلة هو 1797 وسوف نمثل الصورة بvector ابعادة 64 الخصائص تمثل بأرقام من" صفر الى 16" | **Classes** | **10** | | --------------------- | ------------- | | **Samples per class** | \~180 | | **Samples total** | 1797 | | **Dimensionality** | 64 | | **Features** | integers 0-16 | لمزيد من المعلومات عن digits dataset [http://scikitlearn.org/stable/modules/generated/sklearn.datasets.load\_digits.html#sklearn.datasets.load\_digits](http://scikitlearn.org/stable/modules/generated/sklearn.datasets.load%5Fdigits.html?ref=informatic-ar.com#sklearn.datasets.load%5Fdigits) ما الذى تنتظرة الان لدينا dataset وفهمنا خصائصها لنستخدم مكتبة scikit-learn لعمل التصنيف classification [![](https://informatic-ar.com/wp-content/uploads/2017/12/savealife_code_knn.png)](https://informatic-ar.com/wp-content/uploads/2017/12/savealife%5Fcode%5Fknn.png) فى الكود السابق قمنا بالخطوات التى تعلمناها مسبقا عمل load الى dataset ثم قمنا بعمل تدريب training لهذة الdataset بخوارزمية knn حيث استخدامنا الميثود fit ثم اختبرنا البرنامج بمثال لصورة الذى ترتيبها رقم 99 باستخدام الميثود prdict ووجدنا انها اعطتنا تصنيف للكلاس الصحيح **الان هل يمكنك التنبؤ عن الرقم الموجود فى هذة الصورة:** [![](https://informatic-ar.com/wp-content/uploads/2017/12/test_digit.png)](https://informatic-ar.com/wp-content/uploads/2017/12/test%5Fdigit.png) اذا لا تعرف الاجابة عند تطبيق الكود السابق فهذة الصورة تعبر عن الرقم "1" **ما هى عيوب هذة الخوارزمية** 1-[![](https://informatic-ar.com/wp-content/uploads/2017/12/tomanychefs.png)](https://informatic-ar.com/wp-content/uploads/2017/12/tomanychefs.png) يجب علينا إختيار قيمة دقيقة لعدد المجاورين "Choosing the right value of k "لانة يمكن ونحن نحل احد المشاكل نجد المجاورين يكون الكلاس الذى لة اغلبية كبيرة اكثر من واحد فمثلا \['blue', 'blue', 'red', 'red'\]فكيف فى هذة الحالة نختار الكلاس الاكثر أغلبية؟ 2-ان الخوارزمية تعتبر Lazy-learning اى انة لايتم تكوين قاعدة للتنبؤ إلا عند الاحتياج لمعرفة ما هو الكلاس لمثال جديد. **المراجع** - [**http://scikitlearn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html**](http://scikitlearn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html?ref=informatic-ar.com) - [**http://machinelearningmastery.com/tutorial-to-implement-k-nearest-neighbors-in-python-from-scratch/**](http://machinelearningmastery.com/tutorial-to-implement-k-nearest-neighbors-in-python-from-scratch/?ref=informatic-ar.com) - Building Machine Learning Systems with Python Second Edition - [**http://people.revoledu.com/kardi/tutorial/KNN/KNN\_Numerical-example.html**](http://people.revoledu.com/kardi/tutorial/KNN/KNN%5FNumerical-example.html?ref=informatic-ar.com) **رجاء:لا تنسونى من الدعاء عن ظهر غيب** **الحمد الله رب العالمين والصلاة والسلام على أشرف المرسلين سيدنا محمد صلى الله علية وسلم وعلى اله وصحبة وسلم -اللهم دومها نعمة وأحفظها من الزوال -أمين** ### مقدمة عن بروتوكول ال HTTP URL: https://informatic-ar.com/http-introduction/ Last updated: 2026-10-03T22:31:35.000Z أغلب إن لم يكن كل المستخدمين قد سمع بال HTTP من قبل، على الأقل قاموا بكتابته في المتصفحات **http://** لفتح المواقع المطلوبة. وبالرغم من الاستخدام المتكرر الا أن كثير من المختصين (مطورين تطبيقات الويب، مختصي الحماية، مدراء الأنظمة) ليست لديهم معلومات كافية حول هذا البروتوكول، والذي أصبحت تعتمد عليه الكثير من التقنيات. بدءاً من مواقع الويب سواء كانت Static أو Dynamic، مروراً بخدمات الويب Web services مثل ال RESTful، ومروراً باليات حفظ الجلسات State Management والكوكيز Cookies وكيفية حمايتها، وانتهاءً بالخدمات المساعدة حتى يمكن تحمل المزيد من الطلبات مثل Load Balancer أو استخدام البروكسيات Proxies بأنواعها المختلفة. في هذا الموضوع سوف يتم الحديث بشكل سريع (**نظرة من 30000 قدم**) حول البروتوكول وكيف يعمل، وبعض المصطلحات الأساسية مثل ال Resources وال Transactions وشكل الرسائل المرسلة من الكلاينت الى السيرفر، بالإضافة الى أمثلة على ارسال طلبات HTTP باستخدام بعض الأدوات واللغات. بقية المواضيع القادمة، سوف تتناول هذه الفقرات مجدداً ولكن تقوم بالتفصيل فيها، بالإضافة لبعض المواضيع الأخرى، ومن هذه المواضيع: [![](https://informatic-ar.com/wp-content/uploads/2017/11/1.png)](https://informatic-ar.com/wp-content/uploads/2017/11/1.png) بعد انتهائك من جميع المواضيع، سوف تكون قادراً على: - كيف يعمل ال Cookies وكيف تحمي ال Cookies بشكل صحيح من ال Hijacking - ال Authentications وأنواعها المختلفة - كيف يعمل ال HTTPS وتفاصيله - ال Headers المستخدمة في الحماية مثل ال HSTS وال HPKP وال CSP في المتصفح. - استخدام ال HTTP Caching بكفاءة - معرفة ال Load Balancer وال Proxies بأنواعها - كيف تعمل ال Web Crawling وال Data Scrapping - كيف يعمل ال Web Servers وتقوم بكتابة ويب سيرفر صغير - كتابة HTTP Proxy لعمل Logging وفلترة للبيانات - كتابة HTTP Tunnel وارسال بيانات عبر بروتوكولات غير مدعومة في الشبكة. باختصار سوف تكون مبرمج مواقع وخدمات ويب أفضل، مدير مواقع ويب أفضل، فهم أفضل في حماية مواقع وخدمات الويب، ولذلك ال HTTP هو الأساس في الويب. # مقدمة **ال** **HTTP** هو البرتوكول الذي يعمل عليه الويب، وهو الذي من خلاله تتواصل مع السيرفر وترسل البيانات فكل الطلبات التي تقوم بها من المتصفح (**الكلاينت** **Client**) أو التطبيقات في الجوالات، سواءً كانت تعبئة النماذج والتسجيل، أو شراء المنتجات وتصفح مواقع التواصل الاجتماعي أو مشاهدة المقاطع على اليوتيوب. فكلها تستخدم برتوكول ال HTTP. وهذه الطلبات سوف تذهب الى السيرفر (**ال** **Web Server**) ويتم بها تحديد المحتوى الذي يريد المستخدم الوصول اليه (**ال** **Resource**) وذلك من خلال الرابط للمحتوى (**ال** **URI**) ويكون لها صيغة معينة في الطلب **HTTP Request Message** يتم تحديد بعض المعلومات حول الكلاينت الذي أصدر الطلب **Request Headers**. ومن ثم يقرأ السيرفر الطلب، ويجلب المحتوى ويرسله الى الكلاينت (ال **HTTP Response Message**)، وفي الرد سوف يحدد السيرفر **نوعية المحتوى** **MIME Type** حتى يستطيع المتصفح عرضه بالطريقة المناسبة بالإضافة الى نتيجة الرد سواء كان المحتوى موجود أو غير موجود **Status Code** وبعض المعلومات الأخرى. داخلياً فال HTTP يقوم بفتح اتصال **TCP Socket** مع السيرفر من خلال عنوانه IP Address ورقم البورت (الافتراضي 80 إذا لم يتم تحديده)، ومن ثم بعد فتح الاتصال يتم ارسال الطلب **HTTP Request**، وعادة ما يتم الرد ال HTTP Response في نفس الاتصال أو يمكن ان تكون كلاً في اتصال TCP مختلف عن الآخر. انتهت المقدمة وسوف نبدأ الآن الجولة مع **المصطلحات السابقة** ونأخذ لمحة بسيطة عن كل منهم. # الفرق بين الويب Web والإنترنت Internet قبل الولوج في ال HTTP، نذكر بالفرق بين الانترنت Internet وال World Wide Web (اختصاراً WWW أو Web)، حيث يخلط الكثيرين بينهم، حيث ان الويب Web ما هو إلا جزء من الانترنت Internet. فالإنترنت هي الشبكة العالمية والتي تقدم العديد من الخدمات مثل الويب، البريد الالكتروني، تبادل الملفات FTP وغيرها من الخدمات. بينما الويب هو أحد الخدمات التي تعتبر الأكثر استخداماً لذلك قد يطلق البعض على الويب بالإنترنت. [![](https://informatic-ar.com/wp-content/uploads/2017/11/2.png)](https://informatic-ar.com/wp-content/uploads/2017/11/2.png) # السيرفر Web Server والكلاينت Web Client كل محتوى المواقع التي تزورها توجد على سيرفرات في مكان ما، وهذه ال Web Servers يتواجد بداخلها ذلك المحتوى وهي التي تقوم بخدمتك بهذا المحتوى، وهذه ال Web Servers تستخدم بروتكول ال HTTP لذلك احياناً تسمى HTTP Servers. عندما تطلب موقعاً ما فأنت تستخدم ما يعرف بال Web Client وهو الذي يرسل طلب ال HTTP (ال HTTP Request) الى السيرفر، والذي يقوم بفهم الطلب وارجاع الرد المناسب لك (HTTP Response). [![](https://informatic-ar.com/wp-content/uploads/2017/11/3-1024x305.png)](https://informatic-ar.com/wp-content/uploads/2017/11/3.png) المتصفحات Web Browsers التي تستخدمها للدخول على المواقع (مثل Chrome, Firefox وغيرها) هي تعتبر Web Client وتقوم بإرسال طلب ال HTTP وعرض النتيجة من السيرفر بالشكل المناسب. مثلاً قمت بكتابة الرابط [http://ksu.edu.sa/](http://ksu.edu.sa/?ref=informatic-ar.com) في المتصفح وقمت بالدخول عليه، فسوف يقوم المتصفح بإرسال طلب ال HTTP الى سيرفر الموقع، ويقوم السيرفر بإيجاد الصفحة التي تريدها (الصفحة الرئيسية في الموقع) وإذا تم الايجاد فسوف يرسل لك محتوى الصفحة داخل الرد HTTP Response بالإضافة الى نوع المحتوى وحجمه وبعض المعلومات، حتى يقوم المتصفح بعرض المحتوى بالطريقة المناسبة لذلك المحتوى. # محتوى المواقع Resources السيرفرات Web Servers تحتوي على محتوى الموقع من صفحات وصور وملفات والخ، وهذا المحتوى نسميه بال Resources، سواء كانت صفحات Pages أو الصور Images أو حتى ملفات الصوت والفيديو كلها تعتبر Resources، ولو نظرت للويب فسوف تجد أن هناك بلايين ال Resources الموجودة. هذه ال resources يمكن ان تكون موجودة فعلياً في نظام ملفات السيرفر Physical Files (أو Static Files) أو أنها تكون غير موجودة على القرص وانما يقوم الموقع بتوليدها وقت الحاجة Dynamic Resources، سواء من خلال هويتك في الموقع، او على حسب الطلب الذي تريده او حتى الوقت الان. الصورة التالية تبين عدة انواع من ال Resources المختلقة، فمثلاً الصورة والملف نصي هما Static Resources بينما الاخرى هي Dynamic Resources تكون من داخل الموقع من قاعدة البيانات ويتم جلبها، أو من خدمات اخرى خارجية مثل خدمة الطقس او التاريخ والوقت وغيرها سواء كانت تتبع لنفس الجهة، أو من مواقع اخرى لجهات خارجية. [![](https://informatic-ar.com/wp-content/uploads/2017/11/4-1024x562.png)](https://informatic-ar.com/wp-content/uploads/2017/11/4.png) # أنواع المحتوى Media Types كل سيرفر في الويب يخزن العديد من انواع البيانات المختلفة، لذلك في ال HTTP يجب تحديد نوع كل محتوى object يتم ارساله عبر الويب، وهذه العلامة تعرف بال **MIME Type**. وال MIME هي اختصار ل **Multipurpose Internet Mail Extensions** وهي صممت في الأساس لحل مشكلة ارسال رسائل البريد بين الانظمة المختلفة، وتم استخدامها في ال HTTP لتحديد نوع المحتوى لكل ما يتم ارساله. الصورة التالية تبين ان السيرفر ارجع صورة للكلاينت وفي الرد لاحظ انه تم تحديد MIME Type (بالاسم **Content-Type**) وتم تحديد النوع كصورة image/png. وبعد وصول الرد للمتصفح فسوف يقوم بالتعامل مع المحتوى على حسب نوعه، وغالب المتصفحات تستطيع التعامل مع مئات الأنواع من الملفات المختلفة. حيث سيتم عرض الصورة إذا كان المحتوى صورة، او يتم عرض الصفحة إذا كان المحتوى هو HTML، أو يتم تشغيل ملف الصوت، أو استدعاء plugin لتشغيل ملف من نوع محدد. [![](https://informatic-ar.com/wp-content/uploads/2017/11/5-1024x305.png)](https://informatic-ar.com/wp-content/uploads/2017/11/5.png) وال MIME Type هو نص يوضع على ال Content-Type، ومن الأمثلة عليه: [![](https://informatic-ar.com/wp-content/uploads/2017/11/6.png)](https://informatic-ar.com/wp-content/uploads/2017/11/6.png) وغيرها المئات من ال MIME Types يمكنك مشاهدتهم في هذه القائمة ([قائمة أنواع ال MIME](http://www.iana.org/assignments/media-types/media-types.xhtml?ref=informatic-ar.com)). # الروابط URIs كل ال resources في السيرفرات لديها طريقة يمكن من خلالها الوصول لها (حتى تستطيع الوصول لها)، وهذا ما يعرف بال Uniform Resource Identifier واختصاراً URI. وال **URI** هو نص يعرف Identify اسم او مسار أو كلاهما (اسم ومسار) لأي resource. وهناك نوعين من ال URI، وكلاهما يعرفان ال resources: - **ال** **URL** وهو اختصاراً Uniform Resource Locator وهو أشهر اشكال تحديد ال resources. وهو يحدد **مسار** ال resource بالضبط في السيرفر، **واي** **URL** **يعتبر** **URI** **.** - **ال** **URN** وهو اختصار ل Uniform Resource Name. وهو يسمي ال resource، بغض النظر عن مكان تواجده. وهذه الخاصية (عدم تحديد المكان) تسمح لل resources بالانتقال من مكان لأخر وبالتالي يتم الوصول له من خلال نفس الاسم. **وأي** **URN** **يعتبر** **URI**. [![](https://informatic-ar.com/wp-content/uploads/2017/11/7.png)](https://informatic-ar.com/wp-content/uploads/2017/11/7.png) **ال** **URL** بشكل عام يتكون من 3 أجزاء: - ال Scheme ويتم تحديد البروتكول المستخدم مثلاً http (يبدأ الرابط http://) أو ftp:// - عنوان الموقع [example.com](http://www.example.com/?ref=informatic-ar.com) - مسار واسم ال resource مثلاً /products/mobile.png مثال على ال URI لصورة في أحد السيرفرات ([رابط الشعار](https://informatic-ar.com/wp-content/uploads/2016/05/informatic%5Flogo.png))، كما في الصورة التالية، ومن خلال ال URL (أو URI) سوف تجد أنك تستطيع الوصول للمحتوى Resource في السيرفر: [![](https://informatic-ar.com/wp-content/uploads/2017/11/8-1024x451.png)](https://informatic-ar.com/wp-content/uploads/2017/11/8.png) بينما ال URN فهي تبدأ دائماً ب **urn:** وهي يمكن ان تستخدم للملفات وغير الملفات مثل مفاهيم او أفكار معينة تحتاج لأسماء فريدة. وإذا كان ال URN يمثل اسم لملف فيمكن ان يتم تحويل ال URN الى URL من خلال محول يقوم بتحويل النوع الى الآخر. من الأمثلة على ال URN: - urn:ietf:rfc:2141 وهو يشير الى RFC 2141 - urn:ISBN0-486-27557-4 وهو يشير الى الرواية التي تحمل رقم ال ISBN ال URN ليست منتشرة وتحتاج لبنية معينة تستطيع فهم ايجاد ال resource ومكانه من الاسم الفريد. ولن نتحدث عن ال URN ومن هذه النقطة عندما نتحدث عن الروابط أو ال URI فغالباً نتحدث عن **ال** **URL**. # التواصل بين الكلاينت والسيرفر Transactions ال HTTP يعمل بمفهوم ال Request-Response وهذا ما نطلق عليه بال Transaction، حيث تبدأ ال Transaction من طلب الكلاينت HTTP Request الذي يرسل الى السيرفر، وينتهي بالنتيجة التي يرسلها السيرفر الى الكلاينت HTTP Response. وفي الطلب المرسل من الكلاينت، والنتيجة الراجعة من السيرفر يكون هناك محتوى أو بيانات نطلق عليها الرسالة HTTP Message بين الكلاينت والسيرفر. [![](https://informatic-ar.com/wp-content/uploads/2017/11/9-1024x480.png)](https://informatic-ar.com/wp-content/uploads/2017/11/9.png) # طرق الطلبات Methods ال HTTP يدعم أكثر من نوع للطلبات وتسمى بال HTTP Methods، وكل طلب يتم ارساله يجب ان يحدد الطريقة له. هذه الطريقة Method تخبر السيرفر بنوع العمل المراد تنفيذه مثلاً جلب صفحة الويب، تشغيل برنامج ما، حذف ملف، وهكذا. الجدول التالي يعرض أكثر 5 طرق استخداماً. | **الطريقة** **HTTP Method** | **الوصف** | | --------------------------- | -------------------------------------------------------- | | GET | جلب ال resource المحدد اسمه من السيرفر | | PUT | تخزين/تحديث البيانات المرسلة الى ال resource المحدد اسمه | | DELTE | حذف ال resource المحدد اسمه من السيرفر | | POST | ارسال البيانات الى السيرفر | | HEAD | ارسال ال HTTP Header لل resource المحدد اسمه | # نتيجة الرد Status Code كل الردود HTTP Response من السيرفر تأتي بنتيجة ما Status Code، وهو رقم من 3 خانات يخبر الكلاينت هل الطلب تم بنجاح أم ان هناك شيء على المتصفح القيام به مثلاً. من بعض هذه ال Status Code هي: | **ال** **HTTP Status Code** | **الوصف** | | --------------------------- | ---------------------------------------------------- | | 200 | اوكي، تم ارجاع الملف بنجاح | | 203 | قم بعمل Redirect واذهب للرابط للحصول على ال resource | | 404 | هذا ال resource غير موجود | وبعد ال Status Code يأتي وصف نصي يوضح النتيجة وهذا النص لا يتم قراءته بواسطة الكلاينت وانما يتم التعامل مع ال Status code. ولذلك النتائج التالية هي نفسها من وجه نظر الكلاينت ```java 200 OK 200 Document attached 200 Success 200 All’s cool, dude ``` بعض النتائج التي تأتي من السيرفر تكون توجيهاً للكلاينت بعمل مثل، مثل 203 حيث يخبر الكلاينت بان الرابط مؤقتاً غير موجود وقم بعمل Redirect حتى تستطيع الوصول اليه، وفي هذه اللحظة ضمنياً سوف يقوم المتصفح بعمل طلب اخر بالعنوان الموضح له في النتيجة. المثال العملي في اخر الموضوع سوف يوضح ذلك. # الصفحات تتكون من العديد من ال Resources عادة ما تتكون صفحات الويب من العديد من ال resources التي يجب جلبها، لذلك سوف يقوم المتصفح بإرسال العديد من الطلبات حتى يعرض صفحة واحدة (لكل Resource يتم ارسال طلب له)، حيث يقوم المتصفح اولاً بطلب الصفحة المطلوبة HTML واثناء عرضها في الصفحة سوف يقابل العديد من ال resources الاخرى مثل ملفات الجافا سكربت، الصور، ملفات ال css سواء كانت في نفس السيرفر او في سيرفر اخر، فسوف يقوم المتصفح بطلب هذه ال resources كلأ في طلب منفصل. [![](https://informatic-ar.com/wp-content/uploads/2017/11/10-1024x440.png)](https://informatic-ar.com/wp-content/uploads/2017/11/10.png) # محتوى الطلب والرد Messages بنية الطلب والرد تكون مطابقة لبروتكول ال HTTP حتى يستطيع الطرفان التحدث فيما بينهم، ومحتوى الطلب HTTP Request والرد HTTP Response نطلق عليه الرسائل Messages، وتركيبة هذه الرسائل Messages بسيطة، حيث هي مجموعة من الأسطر النصية plain text كل منها ينتهي بسطر فارغ new line (CRLF) ولهذا هي سهلة القراءة والكتابة حتى للمستخدمين أنفسهم. الصورة التالية تعرض الرسائل بعد اجراء Transaction (طلب ورد) [![](https://informatic-ar.com/wp-content/uploads/2017/11/11-1024x431.png)](https://informatic-ar.com/wp-content/uploads/2017/11/11.png) الرسائل المرسلة من الكلاينت الى السيرفر نسميها **Request Messages** بينما الرسائل المرسلة من السيرفر الى الكلاينت تسمى **Response Messages**. وهيكل هذه الرسائل مشابه لبعض بشكل كبير. وبشكل عام تتكون الرسالة HTTP Message من 3 اجزاء: - **السطر الأول** **Start Line**: وهو الذي يخبر ما الذي يجب فعله في حال كانت الرسالة هي طلب، او ماذا حدث إذا كانت الرسالة هي رد Response. - **حقول الترويسة** **Header Fields**: وهي مجموعة من الحقول تتكون من اسم وقيمة (بينهم نقطتان) حتى يسهل عمل قراءتها. والترويسة تنتهي بسطر فارغ Blank Line، وقد تكون هناك أكثر من ترويسة وقد لا يكون هناك اي شيء بها. - **المحتوى** **Body**: بعد السطر الفارغ يأتي الجزء الاختياري وهو المحتوى وقد يحتوي على اي بيانات. فاذا كانت الرسالة هي طلب فالمحتوى هي البيانات المراد ارسالها للسيرفر، وإذا كانت الرسالة رد فالمحتوى يكون البيانات المرسلة من السيرفر الى الكلاينت. وهذا الجزء Body يمكن ان يحتوي على بيانات سواء كانت نصية او حتى ثنائية binary مثل الصور، الفيديو، الملفات. الصورة التالية تعرض الرسائل بعد اجراء Transaction: [![](https://informatic-ar.com/wp-content/uploads/2017/11/12-1024x587.png)](https://informatic-ar.com/wp-content/uploads/2017/11/12.png) في الصورة اعلاه قام المتصفح بطلب الصفحة [http://informatic-ar.com/](https://informatic-ar.com/) وتم ارسال الطلب وبدء الطلب بال GET Method في السطر الأول، وبعدها مسار ال resource المطلوب وهو **/** (وهو الصفحة الرئيسية في الموقع) وحدد الطلب بأنه يستخدم النسخة 1.1 من البرتوكول HTTP. ولا يوجد اي body في الطلب لأنه لا يحتاج لإرسال بيانات لجلب GET صفحة من السيرفر. وقام السيرفر بإرسال الرد HTTP Response Message واحتوى على نوع البروتكول 1.1 وال Status Code، وصف له OK، ومن ثم مجموعة من **الترويسات** **Header Fields** وبعدها أتى محتوى الصفحة المطلوبة. من الترويسات المستخدمة هي طول المحتوى Content-Length ونوع المحتوى Content-Type. # نسخ البروتوكول Versions هناك العديد من النسخ المستخدمة في ال HTTP ومنها: - **HTTP/0.9** وهي اول نسخة مبدئية لل HTTP، واحتوت على مشاكل كثيرة في التصميم. وهي تدعم GET فقط، ولا تدعم ال MIME ولا ال HTTP Headers. وهي صممت فقط لجلب ال HTML فقط. - **HTTP/1.0** وهي النسخة التي انتشرت بعد ذلك، وتم اضافة رقم النسخة في ال Headers، وتم دعم ال methods الاخرى، وال MIME. وأصبح هنا بالإمكان عرض الصفحات التفاعلية واخذ المدخلات من المستخدم وهذا ما ساعد الويب World Wide Web في الانتشار. - **HTTP/1.0+** قامت العديد من الكلاينت مثل المتصفحات وال Web Servers بإضافة بعض الخصائص لل HTTP في منتصف التسعينيات بحسب احتياجاتهم. مثل ال keep-alive ودعم البروكسي، ولكنها لم تصبح رسمية. ولذلك هذه النسخة لم تعد كنسخة رسمية في البروتوكول - **HTTP/1.1** وهي الأكثر انتشاراً حالياً في كل المواقع والمتصفحات، وتم اصلاح العديد من المشاكل في تصميم البروتوكول، واضافة خصائص تحسين الاداء، وطور في أواخر التسعينيات - **SPDY** **سبيدي** حتى 2005 طُرِحت العديد من المقترحات حول تغيير ال HTTP الحالي، وكان أهم هذه الاطروحات هو SPDY بواسطة موظفين من قوقل، وقد حاز على العديد من الاهتمامات بواسطة الشركات، المبرمجين ودعم المتصفحات، مما جعل ال IETF تبدء في تطوير النسخة الثانية من ال HTTP ووضع المعايير لها وهذا ما يعرف بال HTTP/2 أو h2 اختصاراً. - **HTTP/2.0** النسخة الثانية من ال SPDY/2 اعتبرت هي نواة ال HTTP/2 وحتى المطورين الاساسين اشتركوا في وضع المعايير في ال HTTP/2، وبالتالي اصبح ال HTTP/2 هو المعيار الحالي لل HTTP ونشر المعيار في 2015، وهذه النسخة أصبحت binary بدلاً من نصية وأصبحت أكثر كفاءة في إدارة الاتصالات والضغط (ضغط ال Headers) وبالتالي أصبحت أسرع بكثير جداً. والجميل أن النسخة هذه لا تتطلب أي تغيير في تركيبة وبنية المواقع حتى تستطيع دعم هذه النسخة، وكل المتصفحات أصبحت تدعم هذه النسخة الان وكما أنها تجبر كل المواقع التي تدعم هذه النسخة باستخدام TLS، وبالتالي كل المواقع تصبح HTTPS مباشرة، وحسب الاحصائيات May-2017 (بواسطة W3Techs) فإن حوالي 13% من أشهر 10 مليون موقع أصبح يدعم هذه النسخة، ويمكن تجربة سرعة ال HTTP/2 من خلال الموقع [httpvshttps.com](https://www.httpvshttps.com/?ref=informatic-ar.com)) والصورة التالية تبين الفرق في السرعة بين هذه النسخة والنسخة السابقة، سوف نتحدث عن النسخة هذه في مقالة منفصلة. [![](https://informatic-ar.com/wp-content/uploads/2017/11/13-1024x582.png)](https://informatic-ar.com/wp-content/uploads/2017/11/13.png) # كيف يتم اجراء الاتصال Connections أخذنا فكرة عن ال HTTP والرسائل وال Transaction، لنتحدث عن كيف تنتقل هذه الرسائل من مكان آخر، وأول معلومة هي ان بروتكول ال HTTP يعتبر من بروتوكولات طبقة التطبيقات Application Layer Protocol وهذا يعني ان ال HTTP لا يحتاج لأن يعرف التفاصيل الشبكية وكيف ستنتقل الرسائل حتى تصل للطرف الاخر. وسوف تكون من مسؤولية طبقات اخرى القيام بذلك. وهنا يأتي دور البرتوكول الشائع الموثوق في النقل وهو TCP. فبعد أن يتم فتح اتصال ال TCP يتم ارسال الرسائل بين الكلاينت والسيرفر، وال TCP سوف يضمن عدم فقدان الرسائل، وأنها تصل كاملة وبالترتيب الذي ارسلت فيه. وال TCP يعتمد على بروتكول ال IP والذي مهمته ايصال البيانات عبر الشبكات المختلفة وايجاد أفضل طريقة لإرسالها، وهكذا كل طبقة تقوم بوظيفة والطبقات ككل تؤدي العمل المطلوب [![](https://informatic-ar.com/wp-content/uploads/2017/11/14.png)](https://informatic-ar.com/wp-content/uploads/2017/11/14.png) بهذا الشكل، عندما يريد الكلاينت ارسال رسالة الى السيرفر، فيقوم اولاً بفتح اتصال TCP بين الكلاينت والسيرفر ويتصل بالسيرفر من خلال عنوانه IP Address ورقم البورت الذي يعمل على الويب سيرفر. لذلك يمكن للمبرمج التواصل مع ال HTTP Server باستخدام مكتبات ال HTTP أو انه يقوم مباشرة بفتح ال TCP Socket وكتابة البيانات HTTP Request مباشرة بدون الحاجة الى **مكتبات** **HTTP**. وفتح الاتصال TCP مشابه لعملية اتصال على أحد ما داخل شركة، فسوف يتم الاتصال أولاً برقم الشركة، ومن ثم بعدها ادخال رقم التحويلة للشخص المراد الوصول له. وفي ال TCP تحتاج الى عنوان السيرفر IP Address حتى تقوم بالاتصال عليه، ورقم البورت الذي يسمته اليه البرنامج الموجود على السيرفر. ويتم جلب عنوان السيرفر ورقم البورت من خلال اURL، وهذه أمثلة على عدة URL وكيف يمكن جلب البيانات منها، ويتم معرفة العنوان والبورت، حيث بعدها يستطيع المتصفح فتح اتصال TCP بسهولة | **الرابط** | **معرفة العنوان والبورت** | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | http://207.200.83.29:80/index.html | يوجد عنوان السيرفر ورقم البورت فيه، وبالتالي يتم الاتصال بهذه البيانات مباشرة | | http://www.example.com:80/index.html | لا يوجد عنوان السيرفر وانما يوجد اسم الموقع domain name (وهو www.example.com)، واسم الموقع هو للتسهيل بدلاً من كتابة عنوان الموقع، وسيتم الحصول على ال IP لل hostname من خلال خدمة ال DNS واختصاراً Domain Name Service. | | http://www. example.com/index.html | لا يوجد البورت في ال URL، ولذلك سيتم استخدام رقم البورت الافتراضي لل http:// وهو البورت 80. | والخطوات التي يقوم بها المتصفح هي: - يقوم المتصفح باستخراج ال Hostname من ال URL - يقوم المتصفح بالحصول على ال IP Address من خلال ال DNS - يقوم المتصفح بالحصول على ال port (إذا وجد، والا استخدم الافتراضي) - يقوم المتصفح بفتح اتصال TCP مع الويب سيرفر - يرسل المتصفح HTTP Request Message الى السيرفر - السيرفر يرسل ال HTTP Response Message الى الكلاينت - يغلق اتصال ال TCP ويقوم المتصفح بعرض النتيجة [![](https://informatic-ar.com/wp-content/uploads/2017/11/15-1024x509.png)](https://informatic-ar.com/wp-content/uploads/2017/11/15.png) **ملاحظة**: حالياً في البروتوكول HTTP/1.1 فالاتصال لن ينقطع مباشرة في الوضع الافتراضي، وانما سينقطع بعد فترة من عدم الاستخدام idle time، وذلك حتى يسمح للطلبات التالية بأن ترسل وبالتالي يتحسن الأداء، وليس كما كان عليه في السابق، في موضوع ال Connections سوف نتحدث عن هذه الخاصية Connection Persistence بالتفصيل. # معمارية الويب Web Architecture في هذا الموضوع، تحدثنا عن كيفية التواصل بين برنامجين (المتصفح Web Browsers والسيرفر Web Servers) والرسائل بينهم في ال Transaction. ولكن هناك العديد من البرامج الاخرى التي قد تتعامل معها، ومنها: - ال Proxy وهي برامج تكون موجود بين الكلاينت والسيرفر - ال Caches وهي برامج تحفظ البيانات الأكثر شيوعاً قرب الكلاينت - ال Gateways وهي برامج ويب سيرفر ولكنها تتعامل مع تطبيقات اخرى - ال Tunnels وهو بروكسي يقوم بإرسال رسائل ال HTTP - ال Agents وهي برامج تقوم بعمل طلبات ال HTTP تلقائياً. وفيما يلي وصف موجز حول هذه البرامج ودورها في تشكيل معمارية الويب. **البروكسيات** **Proxies** وال HTTP proxy server من أحد البرامج المهمة، في الأمان، تحسين الأداء وغيرها من المهام. الصورة التالية تعرض بروكسي سيرفر بين الكلاينت والسيرفر، وهذا البروكسي يستقبل كل طلبات الكلاينت، ومن ثم يرسلها الى السيرفر (ويستطيع تعديلها إذا اراد). [![](https://informatic-ar.com/wp-content/uploads/2017/11/16-1024x319.png)](https://informatic-ar.com/wp-content/uploads/2017/11/16.png) البروكسي تستخدم عادة للأمان والحماية، حيث تمر عليه كل الطلبات ويستطيع عمل فلترة للطلبات أو حتى للردود، كأن بكشف الفيروسات عند تحميل البرامج في شركة، أو فلترة الاشياء الاباحية في مدرسة. **الحفظ المؤقت** **Caches** ال caching proxy هو بروكسي يقوم بحفظ نسخ من الملفات الأكثر طلباً وتمر بالبروكسي، بحيث إذا اتى الطلب التالي لنفس الملف فيقوم بإعطائها من الكاش بدلاً من طلبها من السيرفر مرة اخرى، وهكذا الكلاينت سوف يحصل على الملف بسرعة. وال HTTP حدد بعض الترويسات التي تخص ال caching وكيف يمكن التعامل مع الملفات الخاصة بالمستخدم وهل يتم حفظها أم لا. [![](https://informatic-ar.com/wp-content/uploads/2017/11/17-1024x501.png)](https://informatic-ar.com/wp-content/uploads/2017/11/17.png) **ال** **Gateways** وهي برامج تكون وسيطة لسيرفرات اخرى، وتستخدم لتحويل ال HTTP Traffic الى برتوكول اخر، مثلاً Gateway يستقبل طلبات ال HTTP عبر ال URL ومن ثم يقوم بطلب الملف من خلال بروتكول FTP. ومن ثم يرجع الرد الى الكلاينت في HTTP Response. والكلاينت قد لا يعلم انه يتواصل مع Gateway. [![](https://informatic-ar.com/wp-content/uploads/2017/11/18-1024x331.png)](https://informatic-ar.com/wp-content/uploads/2017/11/18.png) **ال** **Tunnels** وهي تطبيقات HTTP يمكنك من خلالها ارسال بيانات ببروتوكولات غير مسموحة في الشبكة من خلال بروتوكولات مسموحة. مثلاً داخل شبكة العمل إذا كان لا يسمح الا بإجراء HTTP أو HTTPS فقط وكنت تريد اجراء اتصال SSH فسوف يتم اغلاقه بواسطة الجدار الناري، لذلك سوف تلجأ الى عمل SSH Tunnel over HTTP أو ال HTTPS (إذا كنت تريد ارسال بيانات مشفرة)، وبالتالي سوف يتم ارسال بيانات عبر HTTP هذه البيانات تحتوي على بيانات ال SSH. ويأخذها السيرفر المعد لهذا الأمور ومن ثم يستخرج ال SSH ويتعامل معها، وسيرفر الشبكة الداخلية والجدار الناري لن يعترضا على ذلك. مثال آخر، حيث سابقاً لم تكن كل الكلاينت تدعم ال HTTPS لذلك كانت تتصل على HTTPS Tunnel تأخذ ال HTTP Request وبداخلها يكون الطلب ومن ثم يتم اجراء ال SSL مع السيرفر الحقيقي ويرجع البيانات المشفرة الى الكلاينت. [![](https://informatic-ar.com/wp-content/uploads/2017/11/19-1024x267.png)](https://informatic-ar.com/wp-content/uploads/2017/11/19.png) **ال** **Agents** وهي برامج client programs تقوم بعمل HTTP Requests، واي تطبيق يقوم بإرسال الطلبات يعتبر HTTP Agent، وذكرنا أحد ال http agent وهو المتصفح web browser ولكن هناك العديد من البرامج الاخرى، مثلاً العناكب Spiders او ال Web Robots وتقوم بزيارة المواقع وتصفح محتواها، لغرض بناء ارشفة للويب أو الموقع، أو لأحد محركات البحث، او حتى للبحث في اسعار المنتجات كمقارنة بين شركة واخرى. [![](https://informatic-ar.com/wp-content/uploads/2017/11/20-1024x564.png)](https://informatic-ar.com/wp-content/uploads/2017/11/20.png) # أمثلة عملية خلال المواضيع القادمة سوف تتناول العديد من البرامج والأدوات مثل: 1. **Telnet** وهو برتوكول يستخدم للوصول للأجهزة باستخدام TCP/IP. 2. **Fiddler** وهو proxy يتم تنزيله على الجهاز بحيث يكون وسيط بينك وبين السيرفر. 3. **Wireshark** وهو برنامج لتحليل كل ال Traffic الصادرة والواردة من كرت الشبكة في الجهاز 4. **Postman** وهي أداة تستخدم لإرسال طلبات ال HTTP 5. **Browser Tools** وغالب المتصفحات تحتوى على أدوات للمطورين، تستطيع من خلالها مشاهدة الطلب HTTP Request والرد HTTP Response والكوكيز وغيرها 6. **برمجياً** **Programmatically** ويمكن برمجياً باستخدام أي لغة ارسال طلبات HTTP أو حتى التعامل مع ال TCP Socket كما سنشاهد لاحقاً. # مشاهدة النتيجة من أحد المواقع قم بفتح المتصفح والدخول على الموقع التالي [www.ksu.edu.sa](http://www.ksu.edu.sa/?ref=informatic-ar.com)، وبعد ظهور النتيجة سوف تلاحظ ان الرابط اصبح ksu.edu.sa (بدون ال www). [![](https://informatic-ar.com/wp-content/uploads/2017/11/21-1024x476.png)](https://informatic-ar.com/wp-content/uploads/2017/11/21.png) فما الذي حدث؟ يمكنك فتح ال developer tools في متصفحك (في كروم قم بالضغط على F12)، وقم بفتح ال Network Tab وقم بفتح الموقع [www.ksu.edu.sa](http://www.ksu.edu.sa/?ref=informatic-ar.com) مجدداً. [![](https://informatic-ar.com/wp-content/uploads/2017/11/22-1024x215.png)](https://informatic-ar.com/wp-content/uploads/2017/11/22.png) سوف تشاهد في صفحة ال Network كل الطلبات التي يقوم بها المتصفح لعرض الصفحة، وهذا يشمل كل الصور والملفات وغيرها، ويمكن ان تضغط على أي طلب حتى تفتح صفحة تشاهد تفاصيل ال HTTP Request وال HTTP Response لكل من الطلبات التي تمت في هذه الصفحة. الذي يهم هو أول طلبين، ولاحظ الأول كان [www.ksu.edu.sa](http://www.ksu.edu.sa/?ref=informatic-ar.com) والنتيجة هي 301 والتي تعني أنه هذه الصفحة انتقلت لمكان اخر بشكل نهائي، قم بالضغط عليها لمشاهدة تفاصيل النتيجة بشكل أوسع، كما يلي: [![](https://informatic-ar.com/wp-content/uploads/2017/11/23.png)](https://informatic-ar.com/wp-content/uploads/2017/11/23.png) يمكنك الضغط على view source لمشاهدة كامل ال Headers للطلب والرد، والمتصفح الان يقوم بمحاولة عرض اهم الأشياء في الترويسات، وكما هو واضح النتيجة هي 301 Moved Permanently وسوف تجد في الرد ال Location الجديد كما هو واضح ksu.edu.sa. وفي الطلب كما يتضح ان ال host هو الموقع الأول. لنرجع الى ال Network Tab (بالضغط على Esc لإغلاق التفاصيل) وادخل لتفاصيل الطلب الثاني ksu.edu.sa وسوف تجد التالي: [![](https://informatic-ar.com/wp-content/uploads/2017/11/24.png)](https://informatic-ar.com/wp-content/uploads/2017/11/24.png) وستجد ان الطلب كان للموقع ksu.edu.sa في ال Request Header وهذا صحيح لأنه طلب اخر، وسوف تجد الرد 200 OK وان الملف هو HTML وقام المتصفح بعرضه بشكل صحيح. لا تقلق بخصوص بقية ال Headers والتي سيتم مناقشتها ودور كل منها في المواضيع القادمة. سوف نقوم بالتجربة الآن من خلال أي طريقة نستطيع عمل HTTP Request، وسوف نقوم بطريقتين الأولى باستخدام أداة جاهزة وسوف نستخدم ال Telnet (وان كنا لن نستخدمها كثيراً لاحقاً، وسنركز على الأدوات الأخرى)، وبرمجياً كما يلي. # ارسال ال HTTP Request من خلال ال Telnet لأن ال HTTP يستخدم ال TCP فيمكن استخدام الأداة Telnet وستقوم بالاتصال TCP بالعنوان ورقم البورت المحدد وستقوم بطباعة النتيجة مباشرة، وال Telnet يتصل عادة بالبورت 23 ولذلك يجب اخباره بالاتصال بالبورت المناسب وهو 80 في الوضع الافتراضي. وال Telnet في الوضع الافتراضي في انظمة ويندوز 7 أو 8 أو 10 غير موجود، ويجب تفعيله من خلال ال Windows Features. [![](https://informatic-ar.com/wp-content/uploads/2017/11/25.png)](https://informatic-ar.com/wp-content/uploads/2017/11/25.png) بعد ذلك يمكن الاتصال به من خلال سطر الأوامر. والطريقة كالتالي: - قم بفتح سطر الأوامر Command Line من خلال قائمة run ثم cmd - ادخل امر الاتصال بالموقع من خلال ال Telnet وذلك بكتابة الامر **telnet www.ksu.edu.sa 80** وبعد الضغط على **Enter** سوف تجد أنك انتقلت لصفحة ال Telnet بعد الاتصال لكي تقوم بكتابة الطلب HTTP Response بنفسك. وقم بإدخال الاوامر التالية والتي تمثل الطلب (ولن تراها): - قم بإدخال **GET / HTTP/1.1** ومن ثم اضغط **ENTER** 1. وهي بمعنى جلب ال resource الموجود في الرابط / (ال root او الصفحة الرئيسية). وتم تحديد طريقة الجلب وهي GET ونسخة البرتوكول HTTP 1.1 - قم بإدخال **host: www.ksu.edu.sa** ومن ثم اضغط **ENTER** 1. وقيمة ال host مطلوبة في ال HTTP 1.1، حيث هي تبين للويب سيرفر الموقع الذي تريده، فهناك سيرفرات تستضيف أكثر من موقع، ولذلك قيمة ال Host هي التي تحدد اي موقع من المواقع الموجودة على الويب سيرفر تريده (إذا كان هناك أكثر من موقع)، ولكن هذه القيمة مطلوبة حتى لو كان هناك موقع واحد في السيرفر - قم بالضغط على **ENTER** مجدداً لإرسال الطلب الى السيرفر سوف يظهر الرد بعد ذلك: [![](https://informatic-ar.com/wp-content/uploads/2017/11/26.png)](https://informatic-ar.com/wp-content/uploads/2017/11/26.png) وهي تقول بأن الصفحة الرئيسية في الموقع [www.ksu.edu.sa](http://www.ksu.edu.sa/?ref=informatic-ar.com) تم نقلها ، وفي السطر الثالث تم توضيح الرابط لل resource الجديد. ولاحظ أن ال Telnet لم يقوم بعمل إعادة اتصال للموقع الجديد اكتفى بعرض نتيجة الطلب كما جاءت من السيرفر، وليس مثل المتصفح، وهذا يعني انه على حسب الكلاينت وكيف تمت برمجته على بالتعامل مع هذه النتيجة. **لماذا قام الموقع بإرجاع** **301** **بدلاً من ارجاع النتيجة مباشرة؟** وهذا النوع من الردود يعرف بالتحويل او ال Redirect وهو شائع كثيراً خصوصاً تحويل طلب [www.example.com](http://www.example.com/?ref=informatic-ar.com) الى example.com حتى يتم عمل URL canonicalization. **ما الفائدة من ال** **URL canonicalization** **؟** وهي تستخدم في تحسين أداء محركات البحث SEO، حيث محركات البحث لديها حساسية من المحتوى المكرر، حيث يرى المحرك بأن www.exmaple.com و example.com هما موقعان مختلفان ووجود نفس المحتوى فيهم سوف يؤثر على ظهور موقعك في محركات البحث، ولذلك سوف يقوم المطور بتحويل طلبات ال www. الى الموقع بدونها من خلال ارسال نتيجة 301 في الرد على الطلب الأول، وهذه يمكن عمل من اعدادات الويب سيرفر أو الموقع نفسه، وهي تختلف من framework لأخر ومن web server لأخر. يمكنك تكرار التجربة في ال Telnet وجعل ال Host هو **host: ksu.edu.sa** وسوف تجد أن الرد اظهر محتوى الصفحة الرئيسية كاملاً. # ارسال طلب ال HTTP برمجياً كل لغات البرمجة بها مكتبات سواء مع اللغة للإرسال طلبات ال HTTP أو مكتبات مساعدة، وسوف نأخذ مثالاً على لغة الجافا باستخدام الكلاس URLConnection ونرسل طلب HTTP: ```java import java.io.*; import java.net.*; public class SendRequest { public static void main(String[] args) { String host = "http://www.ksu.edu.sa"; try { URL url = new URL(host); URLConnection urlConnection = url.openConnection(); //HttpURLConnection connection = (HttpURLConnection) urlConnection; //connection.setInstanceFollowRedirects(false); // show headers for (int j = 1;; j++) { String header = urlConnection.getHeaderField(j); if (header == null) break; System.out.println(urlConnection.getHeaderFieldKey(j) + ": " + header); } System.out.println(); // show html response try (InputStream raw = urlConnection.getInputStream()) { InputStream buffer = new BufferedInputStream(raw); Reader reader = new InputStreamReader(buffer); int c; while ((c = reader.read()) != -1) { System.out.print((char)c); } } } catch (MalformedURLException ex) { System.err.println(host + " is not a parseable URL"); } catch (IOException ex) { System.err.println(ex); } } } ``` عند تشغيلك للمثال، سوف تتفاجأ بأن الطلب بالرغم من انه كان للموقع [www.ksu.edu.sa](http://www.ksu.edu.sa/?ref=informatic-ar.com) الا انه تم جلب بيانات ال ksu.edu.sa، وهذا يرجع لطبيعة عمل ال URLConnection حيث هي كما نص التوثيق بأنها **تتبع ال** **Redirection** **مثل المتصفح**. ولذلك سوف تقوم مباشرة بعد مشاهدة نتيجة 301 بعمل طلب اخر للرابط في ال Location بدون أي تدخل منك. [![](https://informatic-ar.com/wp-content/uploads/2017/11/27.png)](https://informatic-ar.com/wp-content/uploads/2017/11/27.png) قم بإلغاء التعليقات في السطرين 11 و12 حتى تلغى خاصية تتبع الروابط وبعدها سوف تجد المخرج كما هو متوقع بالضبط. [![](https://informatic-ar.com/wp-content/uploads/2017/11/28.png)](https://informatic-ar.com/wp-content/uploads/2017/11/28.png) # نهاية البداية انتهت النظرة السريعة حول بروتوكول ال HTTP، وتحدثنا عن الروابط، وشكل الرسائل بين الكلاينت والسيرفر، وبعض الخدمات الاخرى التي تلعب دوراً في معمارية الويب. والمقالات التالية سوف تسلط الضوء على كل من هذه الأمور بالتفصيل. # المصادر - كتاب HTTP: The Definitive Guide ([الرابط](https://www.amazon.com/HTTP-Definitive-Guide-Guides/dp/1565925092?ref=informatic-ar.com)) - مقالات Scott Allen عن ال HTTP ([الرابط](http://odetocode.com/Articles/745.aspx?ref=informatic-ar.com)) - كتاب Learning HTTP/2 ([الرابط](https://www.amazon.com/Learning-HTTP-Practical-Guide-Beginners/dp/1491962445?ref=informatic-ar.com)) ### البرمجيات الخبيثة والفيروسات Malicious Software URL: https://informatic-ar.com/malicious-software/ Last updated: 2026-10-03T22:31:36.000Z هذه المقالة سوف توضح التسلسل التاريخي للبرامج الخبيثة والفيروسات، وكيف تطورت عبر الزمن، وأنواع هذه البرامج الخبيثة وكيف يعمل أي نوع منها، أيضاً توضح كيف يتم تسمية الفيروسات بهذا الإسم وما هي الأليات لذلك. ## كيف تطورت البرامج الخبيثة عبر الزمن منذ بدايات القرن ال20 وحتى تاريخ هذه اللحظه وما زال البشر يقوموا بمحاولة بناء جهاز قادر على التفكير وحل المشاكل مثل الإنسان ، وبغض النظر عن تسمية هذا الجهاز سواء بالحاسب Computer أو بالإنسان الألى Robotics فالمحاولات كانت جارية منذ القدم. فأول محاولة كانت لبناء جهاز universal machine قادر على حل أي معادلة رياضية وقام بها المشفر البريطاني الآن تيورنغ Alan Truing. هذا الجهاز سمى فيما بعد بـ Truing Machine ومن هذا النموذج استمدت فكره أول جهاز حاسب أو على الأقل أول جهاز شبيه بالحاسب ولذلك اطلق على الآن تيورنغ Alan Truing بأبو الحاسبات الشخصية Father of Personal Computer. جهاز تيورنغ جهاز بسيط ويعتمد في عمله على 3 مكونات ، المكون الأول وهو شريط الذاكرة tape لحفظ البيانات ويمكن أن يشبة بالذاكره memory(في الذواكر هذه الأيام). أما المكون الثاني فهو الرأس head وهو الذي يقرأ ويكتب البيانات من والى الtape ، والمكون الاخير وهو والبرنامج Application وهو الذي يتحكم بالعملية بالكامل .وكان حاسب تيورنغ تمهيد لفكرة بناء الحاسبات الشخصية Computer . [![turing](https://informatic-ar.com/wp-content/uploads/2016/11/turing.jpg)](https://informatic-ar.com/wp-content/uploads/2016/11/turing.jpg) الشكل ‏1‑1 يبين نموذج الآن والذي سمى بTuring Machine استلم بعدها زمام الأمور العالم الهنغاري Von Neumann جون فون نيومان الذي له الكثير من الإسهامات في مجال الرياضيات والفيزياء والتحليل المنطقى للإستراتجيات والذي سمى فيما بعد بالGame Theory . وقام جون ببناء ما يعرف الأن ب Von Neumann Architecture وهو تصميم لأجهزه الحاسب تستخدم فيه وحده للمعالجة ويحتفظ بالبيانات وبالتعليمات في الذاكرة RAM . وتفوق ذلك التصميم على أجهزه الحاسب القديمه في ذلك الوقت مثل الحاسب ENIAC و Colossus التي كانت تبرمج بضبط المفاتيح switch وادخال البطاقات المثقبة للجهاز punched card . [![vonneumann](https://informatic-ar.com/wp-content/uploads/2016/11/VonNeumann.png)](https://informatic-ar.com/wp-content/uploads/2016/11/VonNeumann.png) الشكل ‏1‑2 يبين معمارية جون فون نيومان Von Neumann Architecture وكطبيعه العلماء وحبهم لتمثيل الطبيعه من خلال نماذج صغيره Models قام جون في الفترة ما بين 1948 الى 1956 بعد دراسه مفهوم التكاثر Replication بتقديم فكرة ال Self- Reproducing Automate وهي فكرة رياضية نظرية Theory لبناء أجهزه أو أنظمة قادرة على اعاده بناء نفسها بنفسها Self-Replicate وأطلق عليها اختصارا الإسم Automation. تكون النموذج الذي وضعه جون من 3 مكونات رئيسية هي أله تيورنغ Universal Machine وUniversal Constructor و Tape يحتوي على المعلومات . وطريقة عمله بأن تقوم أله تيورنغ Universal Machine بقرائه شريط المعلومات Tape وباستخدام المعلومات التي توجد على هذا الشريط فإن الجهاز سوف يستطيع اعادة بناء Rebuilding نفسه قطعه قطعه باستخدام المكون الثاني Universal Constructor. الجهاز الذي وضعه جون لن يستطيع فهم ما يجري بالطبع فكل ما عليه هو اتباع التعلميات التي توجد على شريط المعلومات ومهمه الجهاز فقط في إختيار القطعه المناسبة من بين جميع القطع وذلك بالبحث عنها قطعه قطعه الى أن يتم ايجاد القطعه الملائمه. وحين ايجاده يتم تركيبها مع القطعه السابقة على حسب التعليمات في الشريط وتستمر الأله في العمل الى أن يستطيع الجهاز اعاده بناء نفسه بالكامل. [![automata](https://informatic-ar.com/wp-content/uploads/2016/11/automata.png)](https://informatic-ar.com/wp-content/uploads/2016/11/automata.png) الشكل ‏1‑3 يبين نموذج Self-Building Machine قام جون بعد ذلك بتحويل النموذج واستخدم فيه ال cellular automate بدلا من استخدام الأجهزه Machine ولكن النظام الذي اقترحه جون كان معقد للغاية حيث طبقت أول نسخة مبسطة منه بعد 40 سنة على الهارديور. [![sm](https://informatic-ar.com/wp-content/uploads/2016/11/sm.png)](https://informatic-ar.com/wp-content/uploads/2016/11/sm.png) الشكل ‏1‑4 يبين أحد نماذج الأجهزه القادره على بناء نفسها في تلك الأثناء كانت هناك العديد من المحاولات لتبسيط نموذج جون وأشهرها محاوله جون كانوي John Conway والتي سميت فيما بعد بالإسم Game of Life. الأعمال التي قام بها جون نيومان كانت تأسيس لفرع جديد في علم الحاسبات وهو الCellular Automate Theory وفتح المجال لغيره من الباحثين لعمل حواسيب قادرة على الإنشاء Creatures اضافة لفتحه لمجال الArtificial life . نتيجة لذلك وفي بدايات 1960 بدأ العلماء الإهتمام بأفكار جون وقام ثلاثة باحثين في معامل بل Bill Lab بوضع تلك الأفكار على محك التطبيق. وفي معامل Bill قام الباحث الأول فيكتور Victor Vyssotsky بعمل لعبة سميت بـ Darwin تقوم فيها برامج صغيره بالتنافس مع بعضها البعض في بيئة تخيلية وساعده في ذلك زميله الأخر Douglas McElroy الذي برمج الكثير في اللعبة بالإضافة الى كتابة المحاكي في اللعبة والباحث الثالث روبرت موريس –الأب- Robert Morris Sr قام بمساعدتهم حيث أضاف بعض الإضافات في اللعبة. استخدمت اللعبة وقتها في جهاز IBM 7090 ولن تشتهر في ذلك الوقت مما أدى لنسيانها. بعد ذلك وبتطور الحاسبات طورت اللعبة من قبل نفس الباحثين وأصبحت تسمى Core War والتي ما زال هناك العديد من المبرمجين والرياضيين يلعبوا بهذه اللعبة الى وقتنا هذا . حيث يستخدم اللاعب لغة أسمبلي مبسطة تسمى RedCode ويقوم بمهاجمة اللاعب الأخر. ويخسر اللاعب في حال كان مؤشر التعليمات Program Counter يشير لتعليمة خاطئة ليست من ضمن تعليمات الـ RedCode . كانت البرامج في الCore War تعمل في بيئة إفتراضية Virtual Machine ولكن ليس جميع هذه الألعاب تتطلب ذلك، فلعبة داروين Darwin لم تتطلب ذلك. [![corewar](https://informatic-ar.com/wp-content/uploads/2016/11/CoreWar.png)](https://informatic-ar.com/wp-content/uploads/2016/11/CoreWar.png) الشكل ‏1‑5 يبين صوره للعبة Core War تحت المحاكي PMars بضعه أعوام أخرى ومن نظرية Self-Replicate التي وضعها فون نيومان الى تطبيقها العملي ، حيث شهد أول تطبيق لها في أوائل 1970 حيث قام Bob Thomas في شركة BBN بالتجربه في ال Self-Replicating Program ونتج عن ذلك الفايروس Creeper حيث أصاب أجهزه شركه DEC(من النوع PDP-10) والتي كانت تعمل على نسخه من نظام تشغيل TENEX . دخل هذا الفايروس عن طريق الشبكه APANET وقام باصابة الأجهزة الأخرى على الشبكه . تاثير Payload ذلك الفايروس كان من خلال طباعة الجمله "I'm the creeper, catch me if you can!". بعد اصابة الـ Creeper للعديد من الأجهزه قام Bob Thomas ببرمجة برنامج الReaper لكي يتم القضاء على الفايروس Creeper وأستخدم نفس الألية للإنتشار التي أستخدمها الفايروس ، بالتالي يعد ال Reaper أول برنامج مضاد للفايروسات على الإطلاق. بالتعريف الصحيح للفايروسات سنجد أن ال Creeper لا يعد من الفايروسات ، ولكنه يصنف ضمن الديدان Worms وذلك لأنه يقوم بنشر نفسه عبر الشبكه Network Reproducing بالإضافه لعدم قدرته على إصابة الملفات التنفيذية . في هذه اللمحه التاريخيه لن نفصل كثيرا في تصنيف الMalware لذلك سوف نذكرهم جميعهم ب "فايروس Virus" كما هو اللفظ الشائع للبرامج الخبيثة عامة Malicious Software . بعدها وبمرور ثلاثة أعوام تحديدا في 1974 ، ظهر نوع جديد من الفايروسات يقوم بمحاولة أستهلاك مصادر الجهاز System Resources بقدر الإمكان ، بداية مع الوابت Wabbit حيث يقوم هذا البرنامج بنسخ نفسه مرات عديده في النظام الى أن تملتئ الذاكره ويقل أداء النظام حتى حصول Crash للنظام. أعتبر هذا البرنامج من نوع ال Fork Bomb وهو أحد أنواع تنصيف شامل لهذا النوع من البرامج وهو الـ Rabbit . ولم تسلم البرامج العادية أيضا من الفايروسات ، فاللعبة "Animal" التي تعد أشهر الألعاب على الأجهزة الضخمة Mainframe في فترة السبيعينات 1970 تم التعديل عليها لكي تشكل ضررا للمستخدم ، في الأساس تقوم فكرة اللعبة على سؤال المستخدم 20 سؤال عن نوع الحيوان الذي يفكر به المستخدم ، ويبدأ البرنامج بالتخمين حول هذا الحيوان . قام جون واكر John Walker وهو مبرمج نظم لأجهزه ال UNIVAC في ذلك الوقت بعمل تعديل بسيط على اللعبة حيث تحسنت بشكل جيد وبدأ الكثير من مستخدمين بارسال الأشرطه Tape لبريد جون واكر حتى يرسل لهم النسخه المعدلة من اللعبة والذي كان بدوره لا يتردد في ارسال اللعبة لمن يريد . ولكن عمليه التحميل والإرسال كانت مرهقة لجون لذا بدأ يفكر في كيفية ارسال اللعبه بنفسها بدون أي تدخل من المستخدم Self-Reproducing . ومن الألعاب الى الفايروسات حيث قام جون في 1975 بعمل نسخه جديده من ال Animal ولكن هذه المرة أحتوت على برنامج فرعي يسمى بـ Pervade ، يقوم هذا البرنامج الفرعي عندما تبدأ اللعبة بالعمل بفحص جميع مجلدات النظام ونسخ نفسه لأي مجلد وأستبدال أي نسخه قديمه لAnimal بالنسخة الجديدة . وبالرغم من قدم هذا الفايروس الإ أنه شوهد بعد فترة في أحد الأجهزه الصغيره Unisys 2200 والذي يعتبر أحد أحفاد الجهاز UNIVAC ، وهكذا يتم مرة أخرى اثبات أن الفيروسات لها طبيعة متقلبة unpredictable nature لا يمكن التكهن بها أو معرفتها . حينها أعتبر Pervading Animal أول فايروس ينتشر in the Wild . جون واكر بعدها بعدة سنوات في 1980 أسس شركة Autodesk وساهم كمبرمج مساعد في برنامج AutoCAD الشهير. بانتشار الأجهزه المكتبية Desktop Computer في ذلك الوقت ، لم يساعد ذلك فقط على ظهور العديد من الفايروسات ، بل ساعد على ظهور العديد من مبرمجين الفايروسات والذي تعلموا العديد من خفايا وأسرار عمل تلك الأجهزة . أحد أبرز المبدعين هو الطالب ريشتارد سكرينتا Rich Skrenta البالغ من العمر 15 سنه حيث تعلم العديد من الأمور حول أجهزه APPLE II وقام بكتابة العديد من البرامج الطريفة يقوم باعطائها لزملائه في الفصل . [![rich_skrenta](https://informatic-ar.com/wp-content/uploads/2016/11/rich_skrenta.jpg)](https://informatic-ar.com/wp-content/uploads/2016/11/rich%5Fskrenta.jpg) الشكل ‏1‑6 يبين ريتشارد سكرينتا أول مبرمج فايرس لأجهزة أبل جهاز APPLE II كان من الأجهزه التي تقلع من خلال نظام التشغيل يكون موجود على القرص المرن Floppy Disk ، وكان ريتش في بداية الأمر يأخذ أقراص أصدقائه لتوزيع الألعاب –على حد قوله- ويقوم باضافه برامجه الخاصة والتي كانت تقوم باعاده تشغيل الجهاز أو طباعة رسالة على الشاشة بشكل يزعج الطلاب . حينما أكتشف الطلاب أن ريتش هو من يقوم بذلك أمتنعوا عن أعطائه أقراصهم الخاصة وعدم أخذ أي شيء من ريتش . من هنا فكر ريتش بطريقة تجعل البرنامج يقوم بتوزيع نفسه self-propagating ويصيب أقراص APPLE II وقام بكتابة البرنامج –في عام 1982- الذي سمى ب Elk Cloner حيث يقوم الجهاز المصاب بإعادة تشغيل الجهاز كل 5 مرات تشغيل إضافة الى طباعة قصيدة على الشاشة كل خمسين 50 إعادة تشغيل للجهاز . [![elk-cloner](https://informatic-ar.com/wp-content/uploads/2016/11/Elk-Cloner.jpg)](https://informatic-ar.com/wp-content/uploads/2016/11/Elk-Cloner.jpg) الشكل ‏1‑7 يبين الPayload للفايروس Elk-Cloner في نفس العام ومن معامل Xerox PARC قام الباحثان John Shoch و Jon Hupp بعمل أول تجربة سموها فيما بعد بالإسم "الدوده Worm" نسبة الى رواية الخيال العلمي The Shockwave Rider والتي تحدثت عن برنامج "tapeworm" أنتشر حول شبكة تربط جميع أنحاء العالم ، ومن هذه الرواية أخذ الأسم الدوده worm كإسم للبرامج التي تنتشر عبر الشبكة. المحاولة التي قام بها الباحثان في معامل زيوركس كان الهدف منها هو عمل تحديث لبرنامج يقوم بقياس أداء الشبكة ، ولكن لم تمض تلك المحاولة كما تمنى الباحثان ، حيث أدى وجود خطأ Bug في البرنامج الى فشل التجربة بالكامل وتحطيم Crashed حوالى 100 جهاز كانوا من ضمن التجربة . وبمرور سنتين من تلك المحاولة (في 1984( وبالدارسة الأكاديمية التي أجراها الطالب فريد كوهين Fred Cohen والتي اعتبرت أول دراسة حقيقية أكاديمية في مجال البرامج ذاتية الإنتشار Self-Replicating Programs في جامعة Lehigh ، حينها وبمعاونه مشرفه أدلمان Adleman (أحد مخترعي شفره RSA والحرف الأول من اسم الشفره A يعود اليه ) تم تسمية تلك البرامج بالإسم "فايروسات Viruses" والذي تم اقتسابه من أحدى روايات الخيال العلمي ، ومن ذلك الوقت أنتشر ذلك الإسم والى اليوم يعرف فريد كوهين بأنه الأب للمصطلح الفايروسات Father of Computer Viruses. [![fred_cohen](https://informatic-ar.com/wp-content/uploads/2016/11/fred_cohen.jpg)](https://informatic-ar.com/wp-content/uploads/2016/11/fred%5Fcohen.jpg) الشكل ‏1‑8 يبين فريد كوهين أول من أستخدم المصطلح Virus . أربعة سنوات أخرى 1986 ومن قلب مدينة لاهور في باكستان ، قام الأخوة باسط فاروق -19 سنه- وأمجد فاروق بكتابة أول فايروس يصيب أجهزه IBM PCs . سمى ذلك الفايروس بBrain نسبة الى إسم الشركه التي أسسها الأخوان ، حيث يصيب هذا الفايروس قطاع الإقلاع في الأقراص المهيئة بنظام ملفات FAT ويقوم بنقل قطاع الإقلاع الى مكان أخر على القرص وأستبداله بنسخه من الفايروس . كما ذكر الأخوان لمجلة TIME فيما بعد أن القصد لم يكن تخريبي وإنما كان لحماية برامجهم الطبية التي يقوموا بصنعها من القرصنة ، وأن الرسالة سوف تظهر في حال قام من يريد انتهاك حقوق البرنامج . وربما كانوا صادقين في ذلك فالرسالة التي يخرجها الفايروس تحتوي على أسماء المبرمجين وأرقام هواتفهم وأحتوي على رساله تطلب الإتصال في حالة الإصابة > Beware of this VIRUS.... Contact us for vaccination... [![brain-virus](https://informatic-ar.com/wp-content/uploads/2016/11/Brain-virus.jpg)](https://informatic-ar.com/wp-content/uploads/2016/11/Brain-virus.jpg) الشكل ‏1‑9 يعرض صورة من قطاع الإقلاع لأحد الأقراص المصابة بفايروس الـ Brain. بمرور سنتان (1988( وبقيام الطالب الخريج روبرت موريس (يطلق عليه الإبن بسبب تشابه الإسم مع روبرت موريس –مبرمج اللعبة Darwin-) بكتابة دودة استغلت العديد من الثغرات في نظام Unix وأصابت حوالى 5% من الأجهزه المتصلة بالإنترنت. سيتم توضيح عمل هذه الدودة Internet Worm في الباب الرابع عند الحديث حول الديدان بشكل عام . كل هذا كان مجرد البدايه فقط فعند بداية التسعينات كان عدد الفايروسات المعروفة يقدر ب200 فايروس فقط ، ومن ذلك الوقت انتشرت فايروس أكثر خطوره وتطورا وأكثر ذكاء (وصلت عددها الى 70000 فايروس في 2003( بدءاً من فايروسات الماكرو Macro Virus (ظهرت في 1995( وفايروسات البريد Mass-Mailing والتي ترسل نفسها عبر البريد كمرفق Attachment (مثل فايروس ILOVEYOU الشهير والذي انتشر في 2000 ويقوم بارسال رسالة بعنوان ILOVEYOU تحتوي على الفايروس كمرفق( وانتهاءا بالفايروسات أو الديدان والتي استغلت برامج معينه في النظام مثل SQL Slammer (2003( والتي استغلت ثغره Buffer Overflow في برنامج MS-SQL Server وأدت لتقليل حركة traffic العديد من مستضيفي المواقع host ، وأخيرا الدودة الأخيره (ظهرت أول نسخه منها في 2008( المسمى ب Conficker أو Kido والتي استخدمت طرق متقدمة في الإصابة واستهدفت أنظمه الويندوز واستغلت ثغره في NetBIOS في الويندوز. [![confiker](https://informatic-ar.com/wp-content/uploads/2016/11/confiker.png)](https://informatic-ar.com/wp-content/uploads/2016/11/confiker.png) الشكل 1-10 يبين مدى انتشار الدودة في الولايات المتحده حيث انتشرت بمعدل 6% من الأجهزه المتصله بالإنترنت، ويبين انتشار الدودة Conficker في الولايات المتحده في April 2009 ## 1.1\. تعاريف أساسية Malware Definitions: هناك أربعة مخاطر رئيسية تصيب الأنظمة : - **Spam**: يشير المصطلح إلى إستخدام عدة طرق لإغراق البريد Mailbox الخاص بالمستخدمين. الدراسات تشير إلى أن حوالي 70% من حركه البريد Email traffic مصاب بالـ Spam Message . - **Bugs**: يشير المصطلح إلى الأخطاء البرمجية الموجودة في الأنظمة، وفي حال وجدت هذه الأخطاء يمكن أن تسبب في أبسط الحالات تحطيم للبرنامج Crash أو حذف البيانات وتخريبها أو تؤدي لوجود ثغرة في النظام Security Weakness يمكن أن يستفاد منها لاحقاً في إختراق النظام بالكامل. - **Denial of Services**: يشير المصطلح إلى الهجوم على النظام من خلال اغراقه بالطلبات وذلك لإستهلاك المصادر Consume Resources حتى يتحطم النظام بالكامل أو على الأقل تبطئ الـ Network Traffic. - **Malicious Software**: تعد البرامج الضارة (أختصارا بـ Malware) من أخطر أنواع المخاطر السابقة حيث يمكن من خلال هذه البرمجيات أن تسبب في كل المخاطر السابقة ، مثلا تقوم بارسال رسائل Spam من جهاز أحد المستخدمين العاديين ، أو أن تكون مرفق مع أحد رسائل الSpam ،أو أن تستغل وجود Bug في أحد الأنظمة لكي تخترقه ، أو تستخدم في القيام بهجوم من DoS. لذلك سوف يكون الحديث في هذا الباب عن هذا النوع من المخاطر وتفصيل أنواعه من Trojan Horse أو Worm أو Viruses أو Rabbit وغيرها من أنواع الMalware كما سيتبين ذلك لاحقا . ## 1.2\. The Myth of Absolute Security: قد تكون الحماية الكاملة هي الرغبة لأي مستخدم أو مبرمج أي نظام، ولسوء الحظ لا توجد حماية "مطلقة Absolute" والتي تعني الإجابة بنعم أو لا، اذ مهما بلغت إستخدام التقنيات والوسائل القوية في الحماية الإ أنها لن تصمد كثيرا في وجه مخترق يعرف تفاصيل هذه الحماية. وأحيانا يمكن أن يشكل المخترق العادي بوسائل التقليدية (السرقة،التسلل،اجبار المستخدمين على الحديث) ضررا كبيرا على النظام . من هنا تكون الحماية نسبية دائماً، ويمكن تقدير تلك النسبة اعتماداً على النقاط التالية: - أهمية المعلومات أو البيانات التي تقوم بحمايتها. - نوعية المخترق الذي يريد الحصول على هذه المعلومات. - نوعية المهارات والمصادر التي يجب أن تتوفر لهذا المخترق. - القيود المفروض لإستخدام النظام بشكل صحيح Legitimate. - المصادر المتاحة لتطبيق الحماية Security. بتقسيم مسألة الحماية بهذه الطريقة سوف تتحول المسألة من إجابة قاطعه (نعم\\لا) الى إدارة كاملة للمخاطر Risk Management . وبالتالي تطبيق الحماية سوف يكون أعتماداًَ على درجة الحماية المطلوبة، ومدى أستخدامية النظام Usability، وتكلفة تطبيق الحماية. ## 1.3\. التكلفة الناتجه من الإصابة بالفايروسات The Cost of Malware: للبرامج الضارة تأثير كبير من الناحية المالية Financial سواء كان ذلك بالنسبه للمؤسسات والشركات Business أو بالنسبه للمستخدمين العاديين. وبما أن الحماية كما ذكرنا هي عبارة عن إدارة للمخاطر فيجب أن تكون الخسائر الناتجة من الإصابة بهذه البرمجيات الخبيثة واضحة ومقدرة بشكل جيد. لكن عملية تقدير هذه المخاطر عملية معقدة وصعبة في أن واحد ولا يتفق عليها أثنان، فهناك خسائر واضحة Real Cost يمكن تقديرها كإصابة الأجهزة في أحدى الشركات بفايروس يؤدي لتعطيل العمل لعدة ساعات، في هذه الحالة يمكن تقدير الخسائر وذلك بتقدير الوقت المستهلك من غير عمل بالإضافه لحساب الوقت الذي يقوم به الفريق التقنى Support Stuff للقضاء على الفايروس. أما في حالة أصاب الفايروس بنك أو مؤسسة تجارية فعملية التقدير لن تكون دقيقة إطلاقاً حيث هناك Hidden Cost لا يمكن معرفتها ولا تقديره، فسمعة ذلك البنك على سبيل المثال سوف تنخفض ويقل عدد عملاء البنك ومثل هذه الأضرار لا يمكن تقديرها بشكل واضح. من جانب المستخدم العادي Users فيمكن أن تتسبب له الـ Malware بفقدان معلوماته الشخصية ككلمات المرور ورقم الحساب البنكي ومثل هذه المعلومات التي قد يخسر المستخدم من كشفها. ## 1.4\. عدد المخاطر The Number of Threats: بالرغم من تزايد عدد الفايروسات في كل يوم الإ أنه لا يمكن تحديد نوعية هذه الThreats أو تحديد عددها بالضبط، فقد تجد أن بعض البرامج المضادة للفايروسات تتعرف على عدد معين من الفايروسات يختلف من برنامج لأخر ومن شركه لأخرى. ذلك الإختلاف يرجع لعده عوامل منها حصول المضاد الأول على نوعية من الفايروسات لم يحصل عليها المضاد الأخر، أو قد تصنف أحدى المضادات برنامج ما على أنه فايروس Threat في حين يراه المضاد الأخر برنامج طبيعي. حتى طريقة حساب الفايروسات قد تختلف من مضاد لأخرى فمثلا الفايروسات التي تقوم بتوليدها برامج توليد الفايروسات بضغطه زر Virus Generated Tools هل يمكن أعتبارها فايروس واحد أم كل منها فايروس منفصل؟ حيث حدث في 1998 عندما تم توليد حوالى 15000 فايروس في ليلة وضحاها بواسطة أحد تلك البرامج. أعتبار أخر وهو الفايروسات غير المعروفة وغير المنتشرة، وبشكل عام يجب أن تتعامل البرامج المضادة مع الفيروسات المعروفة وغير المعروفة، ولكن القبض على جميع الفايروسات المستقبلية أمر يستحيل القيام به كما وضح فريد كوهين ذلك. أيضا الفايروسات التي لا تعمل الا في ظل وجود نظام معين ، ففي حال لم تجد تلك البيئه فلن تعمل وتكون خامده Dormant ولكن بمجرد نقلها ولو بطريق الخطأ الى البيئة المخصصة لعمل الفايروس سوف يبدأ بالعمل، لذلك قد نجد مضاد يتعامل معها ومضاد أخرى يتجاهلها باعتبار أنها ملفات غير مضره للنظام الحالي. ## 1.5\. سرعة انتشار الفايروسات Speed of Propagation: سرعة انتشار الMalware قد يختلف باختلاف طريقة التصميم أو الطريقه التي ينتشر بها، وبتخصيص الحديث حول الديدان worms نجد أن السرعة القصوى-بشكل نظري- لإنتشار دودة قوية تكون ما بين 510 ميلي ثانية –عند إستخدام UDP- الى 1.3 ثانية –عند استخدام TCP-لإصابة جميع الأجهزة والتي تحتوي على الثغرة التي تستغلها الدودة. الشكل 1-11 يبين المنحنى العام لكيفية انتشار دودة Worm propagation curve مع تقدم الزمن وإزدياد عدد الأجهزة المصابة . ![wromprop](https://informatic-ar.com/wp-content/uploads/2016/11/wromprop.jpg) الشكل ‏1‑11 يبين المنحنى لكيفية انتشار الدودة مع الزمن صاحب الدودة Worm Author سوف يريد أن تنتشر الدودة بأسرع وقت ممكن ، وبالتالى من جهته فهو يريد شكل منحنى الوقت يرجع قليلا لجهة اليسار، أما صاحب المضاد Defender فسوف يريد أن تبطئ الدودة من سرعة الإنتشار حتى يقوم بوضع المضاد اللازم fix أو أن يكون معدل إنتشار الدودة صغير جدا. [![wromprop2](https://informatic-ar.com/wp-content/uploads/2016/11/wromprop2.jpg)](https://informatic-ar.com/wp-content/uploads/2016/11/wromprop2.jpg) الشكل ‏1‑12 يبين المنحى من وجهه نظر المخترق والمدافع ## 1.6\. أنواع الفايروسات Malware Type: تقسم البرامج الخبيثة Malware لعدة أقسام أعتماداً على طريقة عمل هذه البرامج ، وبغض النظر عن تسمية هذه البرامج وتصنيفها يستطيع مضاد الفيروسات القضاء عليها جميعا . هناك عده أمور تشترك فيه هذه البرامج : - **الإنتشار الذاتي Self-Replication** : وتشير هذه الخاصية الى أن البرنامج لكي ينشر نفسه يقوم بانشاء نسخ عديده من نفسه instance بشكل متكرر. ويمكن أن ينتشر البرنامج عندما يقوم المستخدم بنقله بشكل يدوي الى جهاز أخر ، ولكن هذا لا يعني أنه Self-replicating. - **قابلية التغيير Population growth**: تشير هذه الخاصية إلى أن النسخة الجديدة من الفايروس يحصل بها تغيير وبالتالي تختلف عن الفايروس الأصلي ولو بشكل بسيط. بعض الـ Malware التي لا تعد Self-Replicating لها درجة الصفر في ال population growth والعكس غير صحيح حيث يمكن أن يكون ال Malware من ال Self-replicating لكنه له zero في معدل التغيير . - **التطفل Parasitic**: تشير هذه الخاصية إلى أن الـ Malware يحتاج إلى أن يلتصق –يتطفل- بأي كود تنفيذي executable code حتى يعمل. الكود التنفيذي يمكن أن يكون الكود في قطاع الإقلاع Boot block disk أو أي Binary code أو Interpreted code. وبعض الأحيان يكون الكود المصدر Source Code القابل لعملية الترجمة. فيما يلي نقوم بسرد أغلب أنواع هذه الMalware مع شرح مبسط لوظيفة وألية عمل كل منهم . ### 1.6.1\. Logic Bomb: **الإنتشار الذاتي Self-Replicate** : لا يوجد . **قابلية التغيير Population growth**: صفر zero. **التطفل Parasitic** : محتمل possibly . الLogic Bomb هو كود تنفيذي يتكون من جزئين ، الجزء الأول وهو الوظيفة Payload التي يقوم بها وهي غالبا ما تكون وظيفة لها طابع خبيث Malicious Effect. أما الجزء الثاني وهو الTrigger وهو شرط تنفيذ الPayload وقد يكون مرتبط ذلك الشرط بتاريخ معين متى تحقق يبدأ الPayload بالعمل، أو قد يكون الtrigger مرتبط بتسجيل دخول مستخدم معين أو أي شرط ممكن . من الممكن أن يتم ادخال الLogic Bomb في برنامج ما –بين الأسطر البرمجيه- Inserting to code أو قد يكون برنامج مستقل بذاته Standalone Application. الـ Pseudo-code في الشكل 1-13 يبين كيف يقوم Logic Bomb بتحطيم الجهاز عند اليوم ال 13 . [![logicbomb](https://informatic-ar.com/wp-content/uploads/2016/11/logicBomb.gif)](https://informatic-ar.com/wp-content/uploads/2016/11/logicBomb.gif) الشكل ‏1‑13 يبين الPseudo-Code للLogic Bomb مثل هذا الLogic Bomb صغير الحجم يكون عالى الخطورة في حال أدخل وسط عشرات الأكواد الأخرى حيث لا يمكن أكتشافه بسهوله،وقد حدث أنه تم طرد أحد الموظفين فقام بكتابة Logic Bomb ووضعه في سيرفر الملفات الذي يستخدمه الموظفين وكان الTrigger بالضبط بعد أخر يوم له في العمل ، وبالفعل عند ذلك اليوم تم مسح جميع بيانات وملفات الموظفين في السيرفر، وتم بعد ذلك قبض الموظف الغاضب وحبس 41 شهر نتيجة لما قام به. ### 1.6.2\. Trojan horse: **الإنتشار الذاتي Self-Replicate** : لا يوجد . **قابلية التغيير Population growth**: صفر zero. **التطفل Parasitic** : نعم yes. يعود الإسم Trojan Horse لقصة حصان طروادة الشهيرة ، حيث تمكن اليونانيين من الإستيلاء على مدينة طروادة وذلك عندما بنوا حصان خشبي ضخم وأختفى الجنود بداخل ذلك التمثال الخشبي وقدم هذا التمثال كهدية لمدينة طروادة ، وعندما أدخل للمدينة وفي منتصف الليل خرج الجنود من التمثال وأستولوا على المدينة. في عالم الحاسب لا يختلف طرواده كثيرا في المفهوم، حيث هو برنامج يعمل بشكل عادي ولكن يقوم ببعض المهام الضاره بشكل خفي، أحد الأمثلة على برامج الTrojan Horse هي برامج تسجيل الدخول المزيفة fake login حيث لها واجهة تشبة واجهة البرنامج تماماً، تقوم هذه البرامج بحفظ الباسورد Password-Grabbing الذي يدخله المستخدم ثم تخرج له رسالة خطأ حتى يعيد أدخال الباسورد مره أخرى حينها تقوم بتشغيل البرنامج الأصلي وسيدخل المستخدم للبرنامج وهو لا يعلم أن باسورده قد سرق الأن . ### 1.6.3\. Backdoor: **الإنتشار الذاتي Self-Replicate** : لا يوجد . **قابلية التغيير Population growth**: صفر zero. **التطفل Parasitic** : يحتمل possible . ال Backdoor هي أي وسيلة تمكننا من تجاوز الإجرائات الأمنية، وقد يقوم المبرمج بعمل Backdoor أحياناً لتجاوز عمليات الفحص password checking والتي قد تأخذ زمناً . ال Pseudo-code في الشكل 1-14 يبين لنا كيف يمكن للمبرمج أن يتجاوز الفحص والدخول مباشرة للنظام. [![backdoor](https://informatic-ar.com/wp-content/uploads/2016/11/backdoor.gif)](https://informatic-ar.com/wp-content/uploads/2016/11/backdoor.gif) الشكل ‏1‑14 يبين الPseudo-Code للباكدور هناك نوع من الbackdoor وهو Remote Administrator Tool أو له الأسم الأخر Remote Access Trojan على حسب من يقوم باستعماله وفي أية غرض. هذه البرامج RAT تسمح للمستخدم الوصول البعيد لجهازه والتحكم به عن بعد. أيضا تسمح لفريق الدعم الفني Helpdesk بالإتصال بجهاز المستخدم وتقديم الدعم الفني اللازم. في حالة أصاب الجهاز أي نوع من الـ Malware وقام بتنزيل RAT فسوف يعتبر هذا الRAT هو Remote Access Trojan . ### 1.6.4\. الفايروسات Viruses: **الإنتشار الذاتي Self-Replicate** : نعم yes . **قابلية التغيير Population growth**: نعم positive. **التطفل Parasitic** : نعم yes . الفايروس هو برنامج يقوم بنسخ نفسه في الملفات التنفيذية الأخرى، ويطلق على الملف الذي نسخ الفايروس نفسه فيه ملف مصاب Infected. وعندما يعمل الملف المصاب يقوم مره أخرى بنسخ نفسه للملفات الأخرى وهكذا. وهذه الميزه Self-Replicating هي التي تميز الفايروس عن غيره من الMalware مثل الديدان worms . اللفظ فايروس Virus استخدم لأول مره في أحدى روايات الخيال العلمي وذلك في 1970 في رواية الرجل المجروح Scarred man لـ Gregory Benford بنفورد . ولم تقتصر على ذلك ، فقد أستخدم مره أخرى بعد ذلك بسنتين في رواية لـ David Gerrold بعنوان When Harlie Was One. وتحدثت الروايتين عن برنامج استخدم للقضاء على تلك الفايروس ولكن لم يتم ذكر إسم معين له. ولم يقتصر عمل جريجوري بنفورد على ذكر كلمة الفايروس في الرواية ، بل في عام 1969 قام بكتابة بضعة فايروسات –غير ضاره- في ما تسمى الأن بـ Laboratory Lawrence Livermore National وكذلك في أول ظهور لـ ARPANET . أحد أقدم البحوث الأكاديمية في مجال الفايروسات كانت في 1983 بواسطة فريد كوهين Fred Cohen والذي أشار لأول مرة للإسم للإسم "الفايروس Virus" ووضح أن ذلك الإسم أقترحه مشرفه لين أدلمان Len Adleman . اعتبر فريد من تلك اللحظة الأب لفايروسات الحاسب The Father of computer Viruses بالرغم من صدور عده فايروسات قبل عمل فريد مثل Elk Cloner لريتش ( وهو أول فايروس يصيب الأجهزه العاديه Desktop Computer) أو فايروس pervade لجون واكر (وهو أول فايروس بشكل عام). هناك بعض ألعاب الحاسب تميزت بميزة الـ Self-Replicating حيث تقوم برامج في اللعبة بمهاجمة بعضها البعض وتعمل اللعبة في بيئة أمنه. فظهرت لعبة Core War في عام 1984 حيث يستخدم اللاعب لغة أسمبلي مبسطة تسمى RedCode ويقوم بمهاجمة اللاعب الأخر. ويخسر اللاعب في حال كان مؤشر التعليمات Program Counter يشير لتعليمة خاطئة ليست من ضمن تعليمات الـ RedCode . كانت البرامج في الCore War تعمل في بيئة إفتراضية Virtual Machine ولكن ليس جميع هذه الألعاب تتطلب ذلك، فلعبة داروين Darwin ظهرت قبل Core War بعده سنوات 1961 ولم تتطلب ذلك. الفايروسات تقوم بنشر نفسها داخل الجهاز ، ويمكن أن تنتقل لجهاز أخر عن طريق وسيط نقل Transported Media مثل الأقراص الصلبة Floppy Disk أو الأقراص الممغنطه CD/DVD ROM أو الفلاش USB/Flash Disk .ولا يقوم الفايروس بنقل نفسه عبر الشبكه فهذه من مهام الديدان Worms ،على أية حال الكلمة الشائعة "فايروس" كثيرا ما يقصد بها أي نوع من أنواع الـ Malware يقوم بـ Self-Replicating . الفايروسات بعد أن تصيب الجهاز يمكن أن نراها بأكثر من شكل، فالنسخة الأصلية من الفايروس يطلق عليها جرثومة Germ (قبل أي عمليه injection) ، والفايروس الذي لا يستطيع القيام بعملية النسخ (لوجود ثغره في الفايروس) أو أنه تعامل مع بيئة غير متوقعه يطلق عليه Intended. ويطلق علي الفايروس الموجود داخل النظام ولكنه لا يشكل ضررا للبيئة الحالية ب Dormant. على سبيل المثال أغلب الفايروسات التي تصيب الملفات التنفيذية في نظام ويندوز Win32 في حال تم نقلها لنظام لينوكس Linux أو أي نظام أخر غير عائله Win32 فهي لن تعمل وستكون Dormant ولكنها بمجرد نقلها بواسطه المستخدم الى نظام Win32 فلن تكون Dormant بعد الأن . ### 1.6.5\. الديدان Worms: **الإنتشار الذاتي Self-Replicate** : نعم yes . **قابلية التغيير Population growth**: نعم positive. **التطفل Parasitic** : لا no . تتشارك الديدان مع الفايروسات في الكثير من الخصائص وأهمها في أنها ذاتيه النسخ أو الإنتشار Self-Replicating ولكن عملية النسخ في الديدان تختلف قليلا عن الفايروسات حيث أن الديدان هي برامج قائمه بذاتها Standalone Application ولا تحتاج للإلتصاق في الملفات التنفيذية، إضافة إلى أنها تنشر نفسها من جهاز لأخر عبر الشبكه Network. الظهور الأول للإسم الدوده worm ظهر أيضا في أحدى روايات الخيال العلمي The Shockwave Rider للمؤلف John Brunner في 1975 (أستخدم المؤلف أيضا "الفايروس" في تلك الرواية). ومن التجارب الأولى في مجال الديدان هي Creeper حيث أستخدم في APRANET في 1970 واستخدم الReaper لإزاله الCreeper. ومن الأحداث التاريخيه في مجال الديدان في 1988 حيث قام طالب الPhD روربت مورس (الإبن) في جامعه كورنيل بعمل دودة وكان يريد أن تكون بطئية الإنتشار وغير مكتشفة حيث كان يريد –على حد قوله- أنه يريد قياس عمق الأنترنت. ولكن لم تسر الأمور كما شاء روبرت مورس حيث أنتشرت بسرعه رهييبه وعطلت الإنترنت بالكامل، وسميت تلك الدودة بInternet Worm أو أحياناً Morris Worm نسبه لصاحبها. قبض مورس أنذاك وحبس لمدة 3 سنوات على فعلته. ### 1.6.6\. Rabbit: **الإنتشار الذاتي Self-Replicate** : نعم yes . **قابلية التغيير Population growth**: صفر zero **التطفل Parasitic** : لا no . يطلق الـ Rabbit على أي برنامج يقوم بمهمة تتضاعف عددها بشكل سريع وأحياناً تسمى بالإسم الأخر باكتريا Bacteria. وهناك نوعين من الـ Rabbit الأول يقوم باستهلاك جميع مصادر الجهاز مثل الذاكره والقرص الصلب ، أحد الأمثله الشهيره هو الـ Fork Bomb وهو برنامج يقوم في حلقه لا نهائيه بانشاء Process حتى يستهلك الذاكره بشكل كامل. النوع الأخر من الRabbit يشبه الدودة ولكنه يحذف نفسه بعد عملية النسخ للجهاز التالي ، بهذا تكون هناك نسخه واحدة فقط على الشبكة من هذا الRabbit. ويطلق عليه ب Jumping Executable لكن هذا النوع قليل الظهور . ### 1.6.7\. برامج التجسس Spyware: **الإنتشار الذاتي Self-Replicate** : لا no. **قابلية التغيير Population growth**: صفر zero **التطفل Parasitic** : لا no . هذه البرامج تقوم بجمع معلومات من الجهاز وتقوم بارسالها لشخص أخر. أستخدم اللفظ "Spyware" لأول مره في 1995 في إحدى الطرف Joke ولكنه حاليا الأسم Spyware يشير لبرامج التجسس وهي من أحدى المخاطر Threat التي تشكل خطرا على الجهاز. المعلومات التي تجلبها هذه البرامج تختلف من برنامج لأخر، فبعضها يهتم فقط بجلب كلمات المرور وأسماء المستخدمين account & password حيث يقوم بجلبها من أماكن تخزينها على الجهاز أو يقوم بتسجيلها عندما يكتبها المستخدم Keylogger ، يختلف الKeylogger عن الTrojan Horse في أنها تقوم فقط بمهمة تسجيل ما يكتبه المستخدم keystroke وليس لديه أي مهمة أخرى. بعض برامج الSpyware يهتم بجلب وسرقة المعلومات البنكية أو سرقه مفاتيح تسجيل البرامج Serial Key ،وبعضها يقوم بسرقه عنواين البريد Emails ويقوم بارسالها للSpammers. قد نرى أن هناك فايروسات أو ديدان تقوم ببعض أو كل وظائف الSpyware ولكنها لن تعتبر Spyware لأنها ليست Self-Replicating. أخيرا يمكن أن تدخل الSpyware للجهاز بعده طرق منها وقت تحميل البرامج من موقع مشكوك أو استغلال ثغرة في متصفح الويب وهنا سوف يدخل الSpyware بمجرد زيارتك للموقع الذي يحتوي على الSpyware. ### 1.6.8\. Adware: **الإنتشار الذاتي Self-Replicate** : لا no. **قابلية التغيير Population growth**: صفر zero **التطفل Parasitic** : لا no . تشابه هذه البرامج الSpyware من حيث جمع المعلومات ولكن هذه البرامج تجمع المعلومات لغرض التسويق ومعرفة المنتجات والمواقع التي يزورها المستخدم باستمرار. بعض هذه البرامج قد تزعج المستخدمين من خلال رسائل الPopup تحتوي على اعلانات تسويقيه. وبعضها أكثر شراسه يقوم بتحويل الصفحه الرئيسية للمستخدم Home Page الى الموقع التجاري. على أي حال الهدف منها ليس التجسس وسرقة المعلومات بل لأغراض تسويقية بحته Marketing Purpose . ### 1.6.9\. Hybrids, Droppers and Blended Threats: البرامج بطبيعة الحال مرنة وقابلة للإضافة والتعديل، لذلك أغلب الMalware هذه الأيام لا يمكن تصنيفها بسهوله، حيث أنها تحتوي على خصائص من أي صنف مما قد يجعلها مهجنة Hybrid . أحد الأمثلة وهو مترجم لغه السي والذي كتبة كين تومبسون Ken Thompson (عند حصوله على ACM Turing Award) ، يقوم هذا المترجم عند ترجمة برنامج للدخول Login باضافه Backdoor يمكنه من تجاوز المطالبة بادخال اسم المستخدم وكلمة المرور. أيضا عند ترجمة المترجم نفسه تخرج نسخه تنفيذيه لها نفس الميزه، وبالتالى يعتبر هذا البرنامج Trojan Horse له القدره على الـ Self-Replicating بالإضافه الى انشائه للBackdoor . مثال أخر على اللعبة Animal والتي عدلها جون واكر وجعلها تقوم بالنسخ في جميع مجلدات النظام. بما أن عملية النسخ غير معروفه للمستخدم فيمكن أن يصنف الى الTrojan Horse وعملية النسخ من الأساس هي self-Replicating ولكن لا يمكن أعتبارها فايروس –على حسب تعريف فريد كوهين- والسبب أنها لا تصيب أية ملفات أخرى ، ولا يمكن أعتبارها دوده حيث هي لا تنتشر عبر الشبكة . اذا هي ليست فايروس وليست دودة ولكن بلا شك لها سلوك فايروسي Viral Behavior . هناك تصنيفات أخرى لل Malware على سبيل المثال ال Droppers وهي أي Malware يترك ورائه Malware أخر. مثلاً يمكن أن تقوم الدوده بعد اصابتها لنظام ما أن تقوم بوضع Trojan Horse وهكذا لجميع الأنظمة التي تصيبها. أيضا يمكن للفايروس أن يترك backdoor خلفه . أنواع أخرى من ال Malware يأخذ صفات الفايروس جميعها بالإضافه إلى إمكانية الإنتشار عبر الشبكة كما في الدودة ، يطلق علي هذه الMalware أحيانا بـ Blended Threats . ال Internet Worm سوف تعتبر Blended Threat طبقاً لهذا التعريف . ## 1.7\. Zombies: بعض المخترقين يقوموا باستخدام الأجهزه التي قاموا باختراقها في عمليات أخرى بدون علم المستخدم بالطبع، يطلق على تلك الأجهزه بالإسم Zombie ، عاده تكون المهام في إرسال رسائل الدعائيه Spam Message من ذلك الجهاز المخترق ، أو الدخول في عملية هجوم من نوع رفض الخدمه DoS. ارسال Spam من الZombie أمر تريده العديد من الشركات التجارية، حيث أن الرسائل الدعائية تعتبر أمر مرفوض للعديد من مزودي خدمة الأنترنت ISP ويتم اغلاق الموقع الذي يكتشف أنه يقوم بارسال رسائل Spam للمستخدمين، لذلك يلجأ الكثير للZombie حيث لا مشكله في حال انكشف أمر الجهاز لأنه ميت في كل الأحوال Zombie. الهدف الأخر وهو ايقاف الخدمه في موقع ما أو على الأقل أغراق الإتصال بالطلبات الى أن يحصل سقوط Down للموقع. هذه العملية لن تفيد كثيرا في حال كان عدد المشاركين بسيط والسبب أن ال Network Traffic تكون عاده كبيره وتستوعب عدد كبير من الBandwidths، اضافه الى أنه يمكن منع عنواين تلك الأجهزه من الاتصال بالموقع في حال عرف أنهم مشاركين في عمليه flooding. لذلك يريد المخترق أن يحصل على أكبر عدد ممكن من الأجهزه والهجوم على الموقع وهذا يعرف بالهجوم الموزع DDoS . أحد أهم القضايا في عملية الـ DDoS هي كيفية التحكم في الأجهزة المخترقه لكن عادة يلجأ المخترقين باستعمال الIRC حيث يمكن التحكم باستخدام مجموعة من الأوامرCommands. اذا تم استخدام ذلك يطلق على شبكة الأجهزه المخترقة بالإسم Botnets نسبة للبرنامج Bots المستخدم في الIRC. ## 1.8\. تسمية الفايروسات Naming of Viruses: عندما تصيب أحد الMalware الجديدة الأجهزة وتبدأ بالإنتشار، تكون الأولوية لشركات الأنتي فايروس هو ايجاد المضاد لهذا الفايروس، وموضوع التسمية يأتي في المرحلة الثانية مباشره. يتم أختيار اسم الفايروس من قبل محللي الفايروسات وباحثي الأنتي فايروس، وعاده يتم أختيار الأسم من خلال وظيفة الفايروس، أو من خلال المخرج الذي يظهر أو الذي يقوم بطباعته الفايروس (كما في فايروس ستوند Stoned حيث كان يخرج رسالة Your Pc is now Stoned!) . مبرمجي الفايروسات عندما لاحظوا ذلك بدؤا بإدخال أسمائهم ووضع عبارات واضحه في الفايروس على أمل أن يتم تسمية الفايروس بهذا الإسم ، لكن بالمقابل تجاهل محللى الفايروسات ذلك لكي لا يكونوا لعبة في أيدي كاتبي الفايروسات. لسوء الحظ لا توجد طريقة موحدة يتم فيها تسمية الفايروس، لذلك قد تجد فايروس واحد بعشرات الأسماء والألقاب، فلكل محلل وباحث طريقته في التحليل والتسمية. المشكله سوف تقع على كاهل المستخدم النهائي الذي يسمع الأخبار المتداولة بين الناس عن ظهور فايروس معين، ويبدأ بالشك في المضاد الذي يستخدمه في حال لم يُظهر الإسم الذي يتوقعه المستخدم وكان يستخدم أسم أخر. أوضح بعض الباحثين صعوبة وجود تسمية موحدة للفايروسات وحتى في المستقبل القريب، والسبب هو الإنتشار السريع للفايروسات بشكل شبه يومي يجعل عملية انتظار الإسم الذي يجب أن يوافق عليه أغلب المحليين أمر بطئ، السبب الأخر وهو عدم وجود معيار معين في عملية التسمية حيث لا يتضح ما الذي يجب النظر اليه لكي يتم تسمية الفايروس، ومن هنا سنجد أنه يمكن أن نسمى الفايروس بأسماء لا تنتهي كل اسم منها قد يكون نتيجه لوجهه نظر معينة. هناك نصائح وارشادات في عملية التسمية لكن لا يوجد من يتبع مثل هذه مقاييس ولكل شركه أنتي فايروس طريقتها الخاصة في التسمية. لذلك الحل الأفضل هو عند ظهور فايروس محدد القيام بأخذ جميع الأسماء من محللي الفايروس لهذه الفايروسات ومن ثم تخزينها كأسماء لهذا الفايروس. هناك أداة تقوم بشكل أتوماتيكي بهذا العملية تسمى VGreb قام بها Ian Whaley مبدأ عملها هو أخذ جميع الأسماء التي تخرجها برامج الأنتي فايروس للفايروس المحدد وتقوم بتخزينها للبحث عنهم لاحقاً . الشكل 1-15 يبين طريقة عمل الـ VGreb : [![vgrep](https://informatic-ar.com/wp-content/uploads/2016/11/VGrep.jpg)](https://informatic-ar.com/wp-content/uploads/2016/11/VGrep.jpg) الشكل ‏1‑15 يبين طريقة عمل أداه VGreb الأسماء التالية هي للدودة الأخيرة Conficker ويظهر أن لكل مضاد أسم مختلف عن الأخر: Common name: Conficker Aliases: Win32/Conficker.A (CA) W32.Downadup (Symantec) W32/Downadup.A (F-Secure) Conficker.A (Panda) Net-Worm.Win32.Kido.bt (Kaspersky) W32/Conficker.worm (McAfee) Win32.Worm.Downadup.Gen (BitDefender) Win32:Confi (avast!) WORM\_DOWNAD (Trend Micro) Worm.Downadup (ClamAV) بشكل عام يمكن أن تكون التسمية باتباع الأقسام التالية : - **Malware Type**: وهنا يتم تحديد نوع الـ Threat ، مثلا دودة Worm. - **Platform Specifier**: وهنا يتم تحديد البيئة التي يعمل عليها الفايروس ، مثلا win32 أو w32 وهذا يعني أنها تحتاج لنظام تشغيل ويندوز 32 بت، وبشكل أعم يمكن أن تكون الـPlatform Specifier هي بيئة التنفيذ مثلا VBS لبرامج Visual Basic Script. - **Family Name**: هنا يتم تحديد اسم مقروء وواضح لإسم الفايروس. - **Variant**: الكثير من الفايروسات الجديده يتم فيها تعديل نسخة سابقة من فايروس قديم ، هذه الفايروسات تسمى Variant.وعاده يتم إسناد حروف هجائية للإشارة للنسخه الجديدة (مثلا الحرف C) للتفريق من النسخه السابقة (مثلا B) . وفي حال أنتهت الأحرف (وصلت الأحرف لZ) يمكن البدء بAA وهكذا. - **Modifier**: هنا يتم إعطاء معلومات إضافية عن الفايروس، مثلا طريقة الإنتشار مثل MM وهي إختصار Mass Milling. هناك مواقع تقدم خدمة أختبار الملف في العديد من البرامج المضادة للفايروسات ، فقط تقوم برفع الملف وتخرج النتيجة كما يبين الشكل 1-16 للموقع VirusTotal.com . [![virustotal](https://informatic-ar.com/wp-content/uploads/2016/11/virusTotal.gif)](https://informatic-ar.com/wp-content/uploads/2016/11/virusTotal.gif) الشكل ‏1‑16 يبين نتيجة فحص فايروس من خلال الموقع VirusTotal.com على العموم القضية الأكبر لشركات الأنتي فايروس هي الكشف والقضاء على الفايروس مهما كان الإسم. > من كتاب مقدمة في علم القيروسات وطرق القضاء عليها. ### شرح ال Single Responsibility Principle URL: https://informatic-ar.com/single-responsibility-principle/ Last updated: 2026-10-03T22:31:37.000Z مفهوم ال Single Responsibility Principle واختصاراً SRP هو من أهم المفاهيم في أساسيات تصميم البرمجيات SOLID، وبنى هذا المفهوم اعتماداً على مفاهيم ال Coupling وال Cohesion وال Separation of Concerns والتي سبق أن تحدثنا عنها سابقاً هنا [ماذا تعرف عن ال Coupling وال Cohesion في تصميم البرمجيات؟](https://informatic-ar.com/coupling-and-cohesion/) محتويات السلسلة: 1. [مقدمة عن ال SOLID ](https://informatic-ar.com/solid/) 2. شرح ال Single Responsibility Principle (المقال الحالي) 3. شرح Open/Closed Principle 4. شرح Liskov substitution principle 5. شرح Interface Segaration 6. شرح Dependency Injection # ما هو مفهوم Single Responsibility Principle (اختصاراً SRP) ينص هذا المفهوم على أنه عليك كتابة أي كود بحيث تكون لديه مهمة واحدة فقط Single Responsibility، لأنه في تلك الحالة سوف يكون له سبب واحد في التغيير: > A class should have only one reason to change فاذا قمت بكتابة كلاس له العديد من المهام فهذا يعني أنه يوجد أكثر من سبب لكي يتغير هذا الكلاس وبالتالي لم تطبق المفهوم بشكل صحيح، وعليك أن تقوم بتقسيم المهام في ذلك الكلاس الى عدة كلاسات صغيرة ويكون لكل منهم ايضاً مهمة واحدة وبالتالي سبب واحد في التغيير، ونفس الفكرة تنطبق على الدوال ايضاً. عادة ما يكتب المطورين البرمجيات ويدمجوا جمل الاستعلام والتعامل مع القاعدة مباشرة مع ال View Code سواء كان للويب أو الموبايل أو في تطبيقات سطح المكتب، فسوف تجد داخل زر حدث الدالة أو داخل الصفحة كل الكود الذي يتعامل مع القاعدة ويعالج البيانات حتى يظهرها. وهذا الأمر يخالف مفهوم ال SRP لأنه يجعل الكود متداخلاً Coupling ويصعب تعديله والصيانة عليه فيما بعد. لذلك مبدأ ال SRP يحاول أن يفصل بين الأشياء التي قد تتغير بنفس السبب مع الأشياء الأخرى التي قد تتغير بسبب آخر. الصورة التالية تبين نتيجة وضع كل شيء في آن واحد، وليس بسبب أنك تستطيع وضع كل شيء معاً فهذا يعني أنه عليك بالقيام بذلك [![srp_items](https://informatic-ar.com/wp-content/uploads/2016/09/SRP_items.jpg)](https://informatic-ar.com/wp-content/uploads/2016/09/SRP%5Fitems.jpg) ## **لكن ماذا يقصد بمهمة واحدة** **Single Responsibility؟** الكثير من المبرمجين قد يخطئ ويعتقد أن المهمة الواحدة تعني دالة Method واحدة (أي كل كلاس يجب أن يكون فيه دالة واحدة)، ولكن هذا ليس صحيحاً، والصحيح هو أن الكلاس يجب أن يقوم بمهمة واحدة (بغض عن النظر عن عدد الدوال) لأنه سوف يكون هناك سبب واحد للتغيير. **وأسباب التغيير في المتطلبات** عادة تأتي من المسؤولين من البرنامج سواءً صاحب المشروع أو ال Stakeholder المشاركين في المشروع. **مثلاً**: تخيل ان تكتب برنامج يقوم بسحب كمية من التغريدات من موقع تويتر وتقوم بعمل خوارزمية ما على هذه التغريدات ومن ثم تقوم بعرضها بطريقة ما على الشاشة، فاذا قمت بدمج كود السحب مع الخوارزمية مع كود العرض في دالة أو كلاس واحد فأنت بهذا الشكل لديك أكثر مهمة وبالتالي أكثر من سبب للتغير، مثلاً يأتي الشخص المختص بالخوارزمية ويريد عمل تغييرات عليها فسوف تقوم بتعديل الكلاس بالرغم من أن الأشياء الأخرى المفروض انها لا تتغير، بنفس المنطق جاء المدير ويريد عرض البيانات بطريقة أخرى وسوف تقوم بالتغيير مجدداً وهكذا. ونفس الأمر في المثال السابق عندما يضع المبرمجين كل شيء في دالة واحدة، فقد يتم تغيير طريقة التعامل مع القاعدة (أو تغيير نوع القاعدة نفسها) وبالتالي سوف تحتاج أن تغير تلك الصفحة بالرغم من أن التعديل تم على القاعدة، ونفس الأمر يحدث عندما يتم التغيير على طريقة العرض وشكله، أو حتى على سير العمل، فالأشياء الأخرى لا يجب ان تتغير. **قد تقول ما المشكلة في التغيير طالما أن التغير في الجزئية المختصة بالمهمة الأولى داخل الكلاس/الدالة الذي يحتوي على العديد من المهمات؟** والجواب على ذلك أنه بهذه الطريقة سوف تصل الى مرحلة تجد أن الكلاس أصبح متداخل [Spaghetti](https://www.manshar.com/articles/3237/?ref=informatic-ar.com) بشكل كبير وأصبح أي تعديل بسيط يكلف وقتاً، بالإضافة الى أن أي مبرمج اخر قد لا يستطيع العمل او فهم هذا الكود، وبالتالي هذا الكود والذي قد يكون جزءاً في برنامج يعمل In Production ولكنه سيء وسيعيق التطور المستقبلي بكل تأكيد. لذلك دائماً اسأل نفسك بعد أن تكتب أي جزئية من الكود "هل المبرمج التالي الذي سوف ينظر لهذا الكود سوف يفهم بسهولة ماذا يحدث؟"، بالطبع الشخص التالي قد يكون أنت ايضاً ![domain](https://informatic-ar.com/wp-content/uploads/2016/09/domain.jpg) يقول Damian Conway في كتابه Pert Best Practices > عليك أن تبرمج جيداً كما أن الشخص الذي سوف يأتي بعدك مختل عقلياً وهو يعرف أين تسكن ## مشكلة الأعتماديات Dependencies المشكلة الثانية هي في الاعتماديات Dependencies: تخيل لديك كلاس يمثل المستطيل Rectangle ويقوم بعمل مهمتين مختلفتين، الأولى لحساب المساحة Area وذلك عن طريق تطبيق قانون حساب المساحة، والثانية لرسم المستطيل Render وعرضه على الشاشة GUI. فهذا الكلاس سوف يستخدم import/use مكتبات الرسم في اللغة حتى يستطيع عرض المستطيل على الشاشة. فاذا كانت هناك أجزاء أخرى من التطبيق تعتمد على هذا المستطيل: - مثلاً لديك جزء Module لرسم الاشكال على الشاشة Graphical Application ويستخدم هذا المستطيل، ولا توجد مشكلة لأن هذا الجزء سوف يحتاج الى دالة حساب المساحة وايضاً لرسم Rendering المستطيل على الشاشة وهذ الجزء بالتأكيد يعتمد على GUI. - لكن لو كان هناك جزء اخر في البرنامج يقوم بحساب مساحات الاشكال ولا توجد فيه واجهة رسومية Geometry Service، فهو سوف يستخدم المستطيل، ولكن بسبب أن المستطيل يستخدم ال GUI في الدالة render فسوف تجد أن تلك الجزئية التي لا توجد فيها واجهة أصبحت تعتمد على ال GUI هي الأخرى. الرسم التالي يوضح المشكلة: [![rectangle](https://informatic-ar.com/wp-content/uploads/2016/09/Rectangle-1.png)](https://informatic-ar.com/wp-content/uploads/2016/09/Rectangle-1.png) فاذا تم التغيير على ال GUI فهذا يتطلب ايضاً إعادة ترجمة المستطيل وايضاً ال Geometry Service بالرغم من أنها لا علاقة لها بالواجهة الرسومية اطلاقاً. وحل هذه المشكلة يكون عن طريق تقسيم المهام وتطبيق ال SRP فيكون لدينا جزئية تتعلق بحساب المساحة فقط، والأخرى لعرضه على الشاشة، وهنا سوف تعتمد ال Geometry Service على الجزئية الخاصة بالمساحة فقط. في المثال العملي التالي سوف ترى مشكلة تغيير المتطلبات وكيف أنه سوف يصعب التعامل معها بالكود غير الجيد. وسوف نطرح مشكلة معينة، ولنرى كيف سوف يتجاوب معها المبرمج الذي لا يراعي هذا المفهوم، ومن ثم نقوم بتطبيق المفهوم عليه وسوف ترى الفرق بين الطريقتين بعد ذلك. ## مثال عملي على SRP شركة لديها ملف به بيانات اتصالات الموظفين لشهر معين وتريد حساب التكلفة الاجمالية للموظف وتخزينها في ملف اخر. الملف يتكون من التركيبة التالية وكل عمود يفصل بينهم بفاصلة (أي Comma Separated Value واختصاراً CSV). **اسم الموظف \_ رقم الموظف، تكلفة الرسائل، تكلفة الاتصال، تكلفة الانترنت** [![records_csv](https://informatic-ar.com/wp-content/uploads/2016/09/records_csv.png)](https://informatic-ar.com/wp-content/uploads/2016/09/records%5Fcsv.png) وتريد كتابة برنامج يقوم بحساب التكلفة المطلوبة من كل موظف، ومن ثم اخراج النتيجة في ملف اخر بالصيغة: **اسم الموظف، رقم الموظف، اجمالي التكلفة** [![records_result](https://informatic-ar.com/wp-content/uploads/2016/09/records_result.png)](https://informatic-ar.com/wp-content/uploads/2016/09/records%5Fresult.png) اجمالي التكلفة أعلاه 111 هو مثال وليس التكلفة الصحيحة، ولحساب التكلفة هي مجموع تكلفة الاتصال، والرسائل، والانترنت. **إضافة أخرى في المتطلبات**: لقد ذكر لك احياناً هناك بعض الأسطر في الملف المدخل قد لا يحتوي على بيانات صحيحة وانما يجب تجاهله، ويجب قراءة أي مدخل يتكون من 4 أعمدة، وما عدا ذلك يتم تجاهله، ويجب عليك أن تظهر ذلك على الشاشة. الصورة التالية بها 3 أسطر يجب على البرنامج تجاهلها: [![records_malformed](https://informatic-ar.com/wp-content/uploads/2016/09/records_malformed.png)](https://informatic-ar.com/wp-content/uploads/2016/09/records%5Fmalformed.png) هذه هي المشكلة، وعادة البرامج التي تقوم بأخذ كمية من البيانات وتعالجها أو تخرجها نطلق عليها Batch Processing، مثلاً تأخذ مجلد به مجموعه من الصور تقوم بمعالجة الصور اما بتصغيرهم مثلاً أو وضع علامة مائية عليها، أو مجموعه من البيانات مخزنة على قاعدة معينة تقوم بقراءتهم ومعالجتهم وارجاعهم في قاعدة أخرى أو نفس القاعدة بشكل اخر، فجميعها تقع تحت مفهوم ال Batch Processing، لذلك سوف نطلق على كلاسنا DataProcessor أو EmployeesProcessor. **لتحصل على الفائدة بشكل أكبر** يمكنك التوقف هذه اللحظة وتطبيق حل للمشكلة قبل المضي قدماً في قرائة الموضوع. ## الحل الأول لنرى كيف يمكن أن يقوم المبرمج بوضع الحل للمشكلة، وسوف نطبق الحل الان بسي# ولكن حقاً لا يهم ذلك فنحن لن ندخل في تفاصيل اللغة أو حتى ال Syntax فالهدف الان هو ال Design. ```csharp public class DataProcessor { public void ProcessEmployees(Stream fileStream) { // read rows List<string> lines = new List<string>(); using (var reader = new StreamReader(fileStream)) { string line; while ((line = reader.ReadLine()) != null) { lines.Add(line); } } // parse employee List<Employee> employees = new List<Employee>(); int lineCount = 1; foreach (string line in lines) { string[] fields = line.Split(','); if (fields.Length != 4) { Console.WriteLine("WARN: Line {0} malformed, Only {1} field(s) found.", lineCount, fields.Length); continue; } string[] userParts = fields[0].Split('_'); if (userParts.Length != 2) { Console.WriteLine( "WARN: User part on line {0} not valid defiention, Only {1} field(s) found.", lineCount, fields.Length); continue; } decimal smsCost; if (!decimal.TryParse(fields[1], out smsCost)) { Console.WriteLine("WARN: SMS on line {0} not valid decimal '{1}'", lineCount, fields[1]); continue; } decimal mintuesCost; if (!decimal.TryParse(fields[2], out mintuesCost)) { Console.WriteLine("WARN: Mintues on line {0} not valid decimal '{1}'", lineCount, fields[2]); continue; } decimal dataCost; if (!decimal.TryParse(fields[3], out dataCost)) { Console.WriteLine("WARN: Data on line {0} not valid decimal '{1}'", lineCount, fields[3]); continue; } // calculate values decimal cost = smsCost + mintuesCost + dataCost; Employee employee = new Employee() { Name = userParts[0], Id = userParts[1], SMSCost = smsCost, MintuesCost = mintuesCost, DataCost = dataCost, Total = cost }; employees.Add(employee); lineCount++; } // Store on file List<string> outputLines = new List<string>(); foreach (var employee in employees) { outputLines.Add( string.Format("{0},{1},{2}", employee.Name, employee.Id, employee.Total)); } File.WriteAllLines(@"F:\test\records_result.txt", outputLines); Console.WriteLine("INFO: {0} employees processed", employees.Count); } } ``` كلاس الموظف: ```csharp public class Employee { public string Name { get; set; } public string Id { get; set; } public decimal SMSCost { get; set; } public decimal MintuesCost { get; set; } public decimal DataCost { get; set; } public decimal Total { get; set; } } ``` والدالة الرئيسية لتشغيل البرنامج: ```csharp class Program { static void Main(string[] args) { FileStream fs = File.Open(@"F:\test\records_csv.txt", FileMode.Open, FileAccess.Read, FileShare.None); new DataProcessor().ProcessEmployees(fs); Console.ReadKey(); } } ``` بعد تشغيل البرنامج سوف تخرج النتيجة وهي صحيحة: [![output_program](https://informatic-ar.com/wp-content/uploads/2016/09/output_program.png)](https://informatic-ar.com/wp-content/uploads/2016/09/output%5Fprogram.png) المثال أعلاه هو ليس مثال فقط على كلاس لديه أكثر من مهمة، ولكن في الحقيقة هو مثال على دالة واحدة لديها أكثر من مهمة. قم بإلقاء نظرة على هذا الكود، وبعد أن تلقى نظره جيدة عليه سوف تجد أن الكود يقوم بعمل الاتي: - يقوم بقراءة الأسطر من الملف ويضعهم على مجموعه من النصوص list of strings - يقوم بعمل parse لكل سطر ويستخرج منها البيانات ويضعهم على كائن من الكلاس Employee - ال parserايضاً يقوم بعمل validationsوعمل Logging على الشاشة السوداء console - كل كائن Employee سوف يتم وضعه على list بعد عمل فورمات له بشكل CSVومن ثم يتم كتابة البيانات على الملف. اذاّ لو أمعنت النظر في المهام في الكلاس DataProcessor سوف تجدها بشكل عام: 1. القراءة من الملف Streams 2. وعمل Parse للنصوص 3. عمل ال Validation للحقول 4. عمل ال Logging على الشاشة 5. الكتابة في الملف وعلى حسب تعريف ال SRP فإن هذا الكود لا يتبع هذا المفهوم لأنه لديه كثير من المهام وبالتالي الكثير من الأسباب للتغير، وسوف تعرف الان المشكلة الحقيقية في ذلك. كل شيء جميل ورائع وبرنامجك أصبح يستخدم بواسطة الشركة، ولكن بعد عدة أيام جاءك المسؤول وأراد إضافة بعض التغييرات، وهي كالتالي: - نريد البرنامج إذا مررنا له القيمة file يضع النتيجة في الملف كما في السابق، وإذا مررنا له القيمة db يضع النتيجة على القاعدة من نوع SQL Server، ولكن في المستقبل يمكن ان نضيف نوع اخر ايضاً. **السؤال الأول**: كيف ستتصرف مع هذا التغيير؟ **السؤال الثاني**: حاول أن تتخيل ماهي الأسباب التي تجعل البرنامج السابق يتغير؟ بالرغم من أن الكود السابق حل المشكلة الأولى، لكن فعلياً في عالم البرمجيات لا يوجد شيء لا يتغير، فكل المتطلبات تتغير وتزداد مع الوقت خصوصاً لو كان للبرنامج قيمة ويخدم مستخدمين بشكل ما. قد تقوم بعمل الحل، وتقوم بتعديل الدالة ProcessEmployees وجعلها تستقبل بالإضافة الى ال Stream متغير نصي، وفي داخل الكود قبل عملية الكتابة تقوم بالفحص إذا كانت القيمة ملف فاكتب في الملف، وإذا كانت من النوع db فاكتبها في القاعدة. وهكذا تم حل المشكلة بسهولة. ```csharp if (type == "file") { // Store on file List<string> outputLines = new List<string>(); foreach (var employee in employees) { outputLines.Add(string.Format("{0},{1},{2}", employee.Name, employee.Id, employee.Total)); } File.WriteAllLines(@"F:\test\records_result.txt", outputLines); Console.WriteLine("INFO: {0} employees processed", employees.Count); } else if (type == "db") { // Open SQL Connection // Write the data into the databae // Close the Connection Console.WriteLine("INFO: {0} employees processed", employees.Count); } ``` أو قد تقوم بحل أسوء وهو نسخ الكود كاملاً وعمل كلاس جديد وفقط تغيير الجزئية التي تتعامل مع الكتابة وتغييرها الى قاعدة البيانات. وهذه اسوء من السابقة، لأن أي تغيير في شيء اخر غير الكتابة مثلاً طريقة القراءة من الملف سوف تجبرك على ان تغير في الكلاسين، هذا في حال كنت محظوظ وتذكرت ذلك. عدة أيام أخرى، ويأتي المسؤول مجدداً ويقول لك نريد أن يقرأ من ملف له صيغة أخرى (غير ال CSV) ويقوم بنفس العملية، أو ملف من نوع آخر مثلاً XML File، وسوف تجد نفسك تقوم بإضافة المزيد من الشروط والتداخل في الكود الى أن يصل الى مرحلة يصعب فيها عمل أي تغيير جديد، أو اصلاح مشكلة ما. وكل ذلك لأن الكود ليس مرن Adaptable بما فيه الكفاية. لو نظرنا لكود المشكلة، سوف نجد أنه قد يتغير بأي سبب من الأسباب التالية: 1. عندما يتغير مصدر البيانات الأصلي من الملف Stream الى مصدر اخر مثلاً يكون من ويب سيرفس Web Service بدلاً من قرائه البيانات من ملف أو مثلاً من قاعدة بيانات ما. 2. عندما تتغير شكل البيانات القادمة Data Format مثلاً يتم اضافة حقول اضافية او حذف أي حقل 3. عندما تتغير طريقة ال Validation للبيانات 4. عندما تتغير طريقة ال Logging مثلاً تتغير من الشاشة الى ملف أو لمكان اخر 5. عندما تتغير طريقة التخزين بطريقة أو اخرى، مثلاً غيرت بدلاً من الكتابة في ملف الى قاعدة البيانات العلائقية، أو غيرت الى NoSQL أو حتى سوف تتعامل مع ويب سيرفس وتكون هي الكفيلة بالتعامل مع قاعدة البيانات. 6. عندما يتغير سير وتدفق العمل، فالكود السابق يتكون من الخطوات المتسلسلة (قرائه، استخراج، كتابة) وإذا تغير هذه التسلسل فهذا يتطلب التغير ايضاً. كل هذه الأسباب قد تحدث والكود السابق لا يمكنه ان يصمد بشكل جيد أمام هذه المتطلبات. انتهى وقت ال Coding وحان وقت ال Design وسنقوم باستدعاء مهندس البرمجيات (الذي يعرف مفهوم ال SRP) لكي يقوم بالتعديل، فبدون تصميم فالكود سوف يصل الى حالة متأخرة للغاية وقد يموت في نقطة ما. ## البدء بعملية التصحيح طالما كل الكود في دالة واحدة في كلاس واحد، سوف نقوم بالشيء المنطقي وهو فصل المهام الى دوال في نفس الكلاس، أي نطبق مفهوم ال SRP على مستوى تلك الدالة الضخمة، وبعد ذلك نرى كيف أصبح شكله والذي يفترض عمله بعد ذلك. وهذه العملية (نقل الأكواد الى دوال Delegation) هي فعلاً Refactoring لا أكثر. ### نقل الأكواد لزيادة المقروئية سوف نقوم بتقسيم الدالة الكبيرة الى دوال صغيرة كل منها تركز على مهمة واحدة، لو تذكر أن الحل كان عبارة عن 3 أشياء رئيسية هي القراءة من الملف، استخراج البيانات، والكتابة على الملف، ففي هذه الحالة يفترض بعد أن ننقل كل الأكواد ان نخرج بالشكل الجديد التالي للدالة ProcessEmployees: ```csharp public void ProcessEmployees(Stream fileStream) { IEnumerable<string> lines = ReadEmployeeData(fileStream); IEnumerable<Employee> employees = ParseEmployees(lines); StoreEmployees(employees); } ``` الآن أصبحت الدالة ProcessEmployees صغيرة بسبب أنها قامت بإسناد Delegate المهام الى الدوال الأخرى. الكود الأول (الذي كان يحتوى كل شيء في نفس الدالة) كان يقوم بعمل 3 أشياء وهي القراءة من الملف الى نصوص، وتحويل النصوص الى كائنات من Employee وكتابة كل كائن منهم على مكان التخزين. ولاحظ أن هذا الان هو ما يوجد في الكود اعلاه وأن المخرج من كل مهمة تكون مدخل للمهمة التالية فلا تستطيع حفظ الكائنات (دالة StoreEmployees) الا بعد أن يتم عمل ل parse وتحويلهم الى كائنات Employee، ونفس الشيء لا تستطيع استدعاء ParseEmployees الا بعد أن ترجع الاسطر من الملف من خلال ReadEmployeeData. للنظر الآن لكل دالة على حدة ولنبدأ بالدالة ReadEmployeeData ```csharp private IEnumerable<string> ReadEmployeeData(Stream fileStream) { var lines = new List<string>(); using (var reader = new StreamReader(fileStream)) { string line; while ((line = reader.ReadLine()) != null) { lines.Add(line); } } return lines; } ``` لاحظ أن الكود لم بتغير وانما وضع على دالة جديدة اسمها ReadEmployeeData وارجعنا الأسطر التي تمت قراءتها في ال IEnumerable<string>. لاحظ هذا يجعل ايضاً الاسطر الراجعة read-only بينما الكود الأول كان يسمح لأي جزئية من الكود بتعديلها واضافة أو حذف أسطر. الكود التالي يعرض دالة ال ParseEmployees وهي قد تغيرت بعض الشيء وليس كثيراً وذلك بسبب أن الكود الأول كان يحتوى على العديد من الأمور هنا، وقمنا بنفس الطريقة وهي جعل الكود يقوم باستدعاء دوال اخرى للقيام ببعض المهام المساعدة، فتم استدعاء دالة ال ValidateEmployeeData للقيام بعمل ال validation ، ودالة MapEmployeeDataToEmployee لتحويل البيانات الى كائنات Employee ودالة LogMessage للقيام بعملية ال Logging ```csharp private IEnumerable<Employee> ParseEmployees(IEnumerable<string> lines) { List<Employee> employees = new List<Employee>(); int lineCount = 1; foreach (string line in lines) { string[] fields = line.Split(','); if (!ValidateEmployeeData(fields, lineCount)) { continue; } Employee employee = MapEmployeeDataToEmployee(fields); employees.Add(employee); lineCount++; } return employees; } ``` كما ذكرنا الدالة قامت بعمل اسناد المهام (مهمة ال validation ومهمة ال mapping) الى دوال أخرى، وبدون هذه ال delegation فسوف تكون الدالة أصعب وايضاً لديها الكثير من الأسباب للتغير. لنرى الدالة ValidateEmployeeData في الكود التالي حيث ترجع Boolean للدلالة على أنه هل البيانات بالشكل الصحيح أم لا. ```csharp private bool ValidateEmployeeData(string[] fields, int lineCount) { if (fields.Length != 4) { LogMessage("WARN: Line {0} malformed, Only {1} field(s) found.", lineCount, fields.Length); return false; } string[] userParts = fields[0].Split('_'); if (userParts.Length != 2) { LogMessage("WARN: User part on line {0} not valid defiention, Only {1} field(s) found.", lineCount, fields.Length); return false; } decimal smsCost; if (!decimal.TryParse(fields[1], out smsCost)) { LogMessage("WARN: SMS on line {0} not valid decimal '{1}'", lineCount, fields[1]); return false; } decimal mintuesCost; if (!decimal.TryParse(fields[2], out mintuesCost)) { LogMessage("WARN: Mintues on line {0} not valid decimal '{1}'", lineCount, fields[2]); return false; } decimal dataCost; if (!decimal.TryParse(fields[3], out dataCost)) { LogMessage("WARN: Data on line {0} not valid decimal '{1}'", lineCount, fields[3]); return false; } return true; } ``` التغيير الوحيد عن الكود الأصلي هو أن ال validation هنا أصبحت تنادي logging method للقيام بعمل ال logging وذلك بدلاً من القيام بعمل طباعة مباشرة على الشاشة السوداء Console.WriteLine، وهذا يتضح من خلال الكود التالي ```csharp private void LogMessage(string message, params object[] args) { Console.WriteLine(message, args); } ``` لنعود الان الى الدالة ParseEmployees حيث قامت ايضاً بعمل اسناد لمهمة تحويل النصوص من كل سطر في الملف الى كائن من نوع Employee. وهذا يتوضح في المثال رقم 7 ```csharp private Employee MapEmployeeDataToEmployee(string[] fields) { string[] userParts = fields[0].Split('_'); decimal smsCost = decimal.Parse(fields[1]); decimal mintuesCost = decimal.Parse(fields[2]); decimal dataCost = decimal.Parse(fields[3]); decimal cost = smsCost + mintuesCost + dataCost; Employee employee = new Employee() { Name = userParts[0], Id = userParts[1], SMSCost = smsCost, MintuesCost = mintuesCost, DataCost = dataCost, Total = cost }; return employee; } ``` الدالة الأخيرة التي تم عملها بواسطة عملية التصحيح Refactoring هذه هي StoreEmployees حيث كما يتبين في الكود التالي تقوم بكتابة ال Employees على الملف، وايضا تستخدم دالة ال Logging. ```csharp private void StoreEmployees(IEnumerable<Employee> employees) { List<string> outputLines = new List<string>(); foreach (var employee in employees) { outputLines.Add(string.Format("{0},{1},{2}", employee.Name, employee.Id, employee.Total)); } File.WriteAllLines(@"F:\test\records_result.txt", outputLines); LogMessage("INFO: {0} employees processed", employees.Count()); } ``` **انتهينا** من عمل ال Refactoring وهذه النسخة أكثر وضوحاً من النسخ الأولى من الكود كما لاحظت، ولكن **ما هي الأشياء التي استفدنا منها؟** - الدوال أصبحت أكثر وأصغر بدلاً من دالة واحدة كبيرة Monolithic method - الكود أصبح أكثر مقروئية Readable - لديك القليل من ال Adaptability حيث يمكن تغيير طريقة عمل الدالة LogMessage وجعلها مثلاً تكتب على ملف بدلاً من الشاشة السوداء، لكن هذا يعني أنك ستغير كود ال DataProcessor لعمل هذا التعديل (وهذا الشيء بالضبط هو الذي نحاول أن نتفاداه من أول هذا الموضوع). على أية حالة هذه الخطوة الأولى كانت مهمة في وضع حجر الأساس وتمهيد الطريق لفصل المهام بشكل كامل، ما قمنا به حتى الان هو عمل Refactoring ولكن لجعل الكود أوضح for clarity وليس لجعل الكود Adaptable، ونحن الان لدينا كود اجرائي أكثر من كونه كائني. الخطوة الثانية والتي سنقوم بها هي عمل ال Abstraction المناسب حتى يكون الكود Adaptable (وسوف نقوم بأخذ هذه المهام الصغيرة الى كلاسات ونضعها خلف interfaces حتى نحصل على ال Adaptability المفيدة) ### ال Refactoring for Abstraction سوف نقوم ببناء عدة Abstractions يساعدنا في التعامل مع اي تغيير في المتطلبات، وسوف تلاحظ أن هناك الكثير من الكلاسات الصغيرة وهذا شيء طبيعي. فمن الشائع أن يكون التطبيق صغيراً كما في المثال الأول ولكن بمجرد أن يكون له مستخدمين فسوف يكبر وتضاف له الخصائص، وإذا لم تراعي ال SRP فإن كل الأكواد الجديدة سوف تضاف على القديمة ويزداد تعقيداً مع الوقت كما تحدثنا سابقاً. عادة تسمى التطبيقات الصغيرة Prototype أو Proof of Concept مثل الكود السابق، والتحويل من تطبيق مصغر الى تطبيق حقيقي Production Product هذا شيء شائع جداً، ولذلك خطوة ال Refactoring toward Abstraction تعتبر مهمة جدا وبدونها قد يكون لديك تطبيق مهم في المستقبل لكنه مكتوب بطريقة سيئة ودوال طويلة فيها الكثير من المهام ويصعب القيام باختبار ذلك الكود وبالتالي سوف يؤثر على مستقبل مشروعك. سنبدأ العمل الآن، وطالما نحن نعرف ان المشكلة كانت عبارة عن 3 مهام رئيسية هي القراءة من الملف Reading واستخراج البيانات من النصوص Parsing والحفظ في الملف Storing، فسوف نعمم الموضوع، ونقول أننا نريد القراءة بغض النظر عن المكان، وأننا نريد الاستخراج بغض النظر عن شكل البيانات، وأننا نريد الحفظ بغض النظر عن المكان. ولعمل ال Abstraction المناسب لهذه العمليات الثلاثة سوف نستخدم ال interface. اذاَ سوف نقوم بعمل Interface يمثل عملية القرائة وبالطبع نريده عاماً حتى نستطيع فيما بعد دعم اي نوع جديد، فيمكن أن نسميه IEmployeeDataProvider ووظيفة هذا ال interface هو ارجاع مجموعه من النصوص فقط كما يلي: ```csharp public interface IEmployeeDataProvider { IEnumerable<string> GetEmployeeData(); } ``` الان يمكن أن يكون هناك Implementation يطبق هذه ال interface ويرجع البيانات من ملف وممكن بسهولة ان نضيف كلاس اخر يطبق هذه ال interface ويقوم بارجاعها من ملف بصيغة اخرى. بنفس الطريقة سوف نقوم بعمل ال interface التي تمثل فكرة استخراج الموظفين من هذه البيانات وذلك من خلال IEmployeeParser: ```csharp public interface IEmployeeParser { IEnumerable<Employee> Parse(IEnumerable<string> lines); } ``` واخيراً طريقة الحفظ تكون عامة ايضاً IEmplyeeStorage ```csharp public interface IEmployeeStorage { void Persist(IEnumerable<Employee> employees); } ``` هكذا سوف نخرج بهذا التصميم Design كما في الصورة التالية حيث تبين كيف أصبح الDataProcessor يعتمد على ثلاثة Interfaces [![program-design](https://informatic-ar.com/wp-content/uploads/2016/09/program-design.png)](https://informatic-ar.com/wp-content/uploads/2016/09/program-design.png) *إذا كنت جديداً على موضوع ال* *Interface* *وفائدته في البرمجة، فينصحك بقراءة هذا الكتاب العربي "* *[ما هو ال Interface؟](http://www.kutub.info/library/book/19413?ref=informatic-ar.com)* *" حيث يناقش الكتاب الكثير من الأمور المتعلقة بال* *Interface* *وكيف تقوم بعمل* *Abstraction* *من خلاله وما هي الفائدة التي تجنيها من خلال استخدامك لل* *Interface* *.* لنرى كيف أصبح كود ال DataProcessor بعد ان أصبح يعتمد على ال interfaces: ```csharp public class DataProcessor { private readonly IEmployeeDataProvider _provider; private readonly IEmployeeParser _parser; private readonly IEmployeeStorage _storage; public DataProcessor(IEmployeeDataProvider provider, IEmployeeParser parser, IEmployeeStorage storage) { _provider = provider; _parser = parser; _storage = storage; } public void ProcessEmployees() { var lines = _provider.GetEmployeeData(); var employees = _parser.Parse(lines); _storage.Persist(employees); } } ``` ![conductor_with_music_score_483009](https://informatic-ar.com/wp-content/uploads/2016/09/conductor_with_music_score_483009.jpg) لاحظ أن الكلاس قد تغير بشكل كبير عما كان عليه في السابق، حيث أصبح لا يحتوي على اي من تفاصيل طريقة العمل Implementation Details ولكن هو الآن مجرد مخطط Blueprint لسير العملية وهذه هي **مسؤولية الكلاس الحقيقية وهو السبب الوحيد الذي قد يتغير هذا الكلاس من أجله**. وأصبح الكلاس هو المتحكم بالعملية مثل ال orchestrate بدون أن يهتم بتفاصيلها. في حال تغيرت العملية نفسها (الية سير او العمل) فقد يتغير لكن في حال تريد تغيير البيانات المصدر التي تقرأ منها أو طريقة ال Logging أو تريد تغيير طريقة الحفظ فهذا الكلاس سوف يكون كما هو. لاحظ أيضاً أن ال IEmployeeDataProvider لم يعد يستقبل ال Stream في الدالة، والسبب كما ذكرنا أنه قد تأتي مصادر اخرى للبيانات مثلاً من DatabaseDataProvider أو من ويب سيرفس، وال Stream اصلاً متعلق فقط عندما نتعامل مع الملف، لذلك من المهم جداً ان لا تضع اي اعتمادية عندما تصمم ال Abstraction ويمكنك ان ترسل هذه الاعتمادية Dependency لدالة البناء في الكلاس وبالتالي تستطيع ان تستخدم اي Dependency بدون ان تغير ال Interface، كما يلي في كود StreamEmployeeDataProvider: ```csharp public class StreamEmployeeDataProvider : IEmployeeDataProvider { private Stream _stream; public StreamEmployeeDataProvider(Stream stream) { _stream = stream; } public IEnumerable<string> GetEmployeeData() { var lines = new List<string>(); using (var reader = new StreamReader(_stream)) { string line; while ((line = reader.ReadLine()) != null) { lines.Add(line); } } return lines; } } ``` تذكر ان ال DataProcessor لا يعرف أي شيء سوى GetEmployeeData في ال IEmployeeDataProvider ولا يعرف من ال Implementation الحقيقي الذي سوف يعمل ولا يفترض به أن يعرف ذلك. بالنسبة لل Parser فهناك الكثير من ال Abstractions التي يمكن استخراجها، تذكر أن التحسين الأول أصبحت الدالة ParseEmployees تقوم بعمل Delegate للمهام (ال Validation وال Mapping) لدوال اخرى، وتستطيع القيام بها الان بشكل أفضل كما يلي حيث تم عمل Refactor لل SimpleEmployeeParser بحيث يكون لكل كلاس مهمة واحدة فقط. وسنقوم بعمل ال Abstraction المناسب لعملية ال mapping وايضاً لعملية validation حتى ندعم اي تغيير عليهم في المستقبل، ونبدأ بال validator: ```csharp public interface IEmployeeValidator { bool Validate(string[] fields); } ``` وايضاً ال mapper سوف يكون عام هو الآخر: ```csharp public interface IEmployeeMapper { Employee Map(string[] fields); } ``` هذه الطريقة التي قمنا بها (استخراج المهمات الى abstraction ومن ثم كتابة كلاسها تطبقها Abstracting responsibilities into interfaces) هي متكررة Recursive فعندما تفحص اي كلاس وتعرف مسؤولياته فعليك أن تقوم باستخراج المهام Refactor out حتى تبقى المهمة الوحيدة. الآن اصبح تصميم جزئية ال Parsing كما يلي: [![parse-design](https://informatic-ar.com/wp-content/uploads/2016/09/parse-design.png)](https://informatic-ar.com/wp-content/uploads/2016/09/parse-design.png) الكلاس التالي SimpleEmployeeParser سوف يطبق اولاً ال interface الخاصة بال Parser، وايضاً سوف يستخدم ال interface الخاصة بعمل ال validations وال Mapping بدون الحاجة الى أن يعرف ما هي طبيعة عمل الكلاس الذي سوف يطبق ال validation وال mapping. وهكذا أصبح هناك سبب واحد فقط لتغيير كلاس ال Parser وهي عندما تتغير شكل البيانات مثلاً من CSV الى مثلاً مفصولة ب tab أو مثلاً XML أو أي شكل اخر. ```csharp public class SimpleEmployeeParser : IEmployeeParser { private readonly IEmployeeValidator _validator; private readonly IEmployeeMapper _mapper; public SimpleEmployeeParser(IEmployeeValidator validator, IEmployeeMapper mapper) { _validator = validator; _mapper = mapper; } public IEnumerable<Employee> Parse(IEnumerable<string> lines) { List<Employee> employees = new List<Employee>(); int lineCount = 1; foreach (string line in lines) { string[] fields = line.Split(','); if (!_validator.Validate(fields)) { continue; } Employee employee = _mapper.Map(fields); employees.Add(employee); lineCount++; } return employees; } } ``` بالنسبة الى ال Mapper فأصبح بعد تطبيق ال interface بالشكل التالي: ```csharp public class SimpleEmployeeMapper : IEmployeeMapper { public Employee Map(string[] fields) { string[] userParts = fields[0].Split('_'); decimal smsCost = decimal.Parse(fields[1]); decimal mintuesCost = decimal.Parse(fields[2]); decimal dataCost = decimal.Parse(fields[3]); decimal cost = smsCost + mintuesCost + dataCost; Employee employee = new Employee() { Name = userParts[0], Id = userParts[1], SMSCost = smsCost, MintuesCost = mintuesCost, DataCost = dataCost, Total = cost }; return employee; } } ``` كود ال Validator أيضاً يطبق ال interface الخاصة بال validation سوف يكون بهذا الشكل ```csharp public class SimpleEmployeeValidator : IEmployeeValidator { private ILogger _logger; public SimpleEmployeeValidator(ILogger logger) { _logger = logger; } public bool Validate(string[] fields) { if (fields.Length != 4) { _logger.LogWarning("Line malformed, Only {1} field(s) found.", fields.Length); return false; } string[] userParts = fields[0].Split('_'); if (userParts.Length != 2) { _logger.LogWarning("User part not valid defiention, Only {1} field(s) found.", fields.Length); return false; } decimal smsCost; if (!decimal.TryParse(fields[1], out smsCost)) { _logger.LogWarning("SMS not valid decimal '{1}'", fields[1]); return false; } decimal mintuesCost; if (!decimal.TryParse(fields[2], out mintuesCost)) { _logger.LogWarning("Mintues not valid decimal '{1}'", fields[2]); return false; } decimal dataCost; if (!decimal.TryParse(fields[3], out dataCost)) { _logger.LogWarning("Data not valid decimal '{1}'", fields[3]); return false; } return true; } } ``` ننظر الان لكيفية التخزين وسوف تكون بنفس الطريقة ونقلها خلف ال abstraction المناسب: ```csharp public class FileEmployeeStorage : IEmployeeStorage { private ILogger _logger; public FileEmployeeStorage(ILogger logger) { _logger = logger; } public void Persist(IEnumerable<Employee> employees) { List<string> outputLines = new List<string>(); foreach (var employee in employees) { outputLines.Add( string.Format("{0},{1},{2}", employee.Name, employee.Id, employee.Total)); } File.WriteAllLines(@"F:\test\records_result.txt", outputLines); _logger.LogInfo("{0} employees processed", employees.Count()); } } ``` انتهينا من كل الكلاسات ما عدا ال Logging، وكما لاحظت اعلاه اننا استخدمنا ايضاً abstraction له بالاسم ILogger كما يلي: ```csharp public interface ILogger { void LogWarning(string message, params object[] args); void LogInfo(string message, params object[] args); void LogError(string message, params object[] args); } ``` كل من ال FileEmployeeStorage و ال SimpleEmployeeValidator يعتمدوا على هذا ال Interface ايضاً، ويمكن الان ببساطة عمل ال Implementation الجديد لل ILogger وتكتمل الصورة. لكن ماذا لو أردنا ان نستخدم أي مكتبة جاهزة لل Logging بدلاً من القيام بأنفسنا، مثلاً مكتبة Log4Net، بالطبع لن نستطيع جعل المكتبة تقوم بعمل Implement لل ILogger لأننا لا نستطيع التعديل عليها، وهنا يأتي دور ال Design Patterns فهي تساعد لحل تلك المشاكل، وال Pattern المناسب الان هو ال Adapter حيث سنقوم بعمل كلاس حول تلك المكتبة كما يلي حيث تبين عمل Adapter للمكتبة Log4Net. والتصميم التالي يوضح كيف أن كلاس ال Validator وال Storage يعتمدا على ال ILogger وان ال Implementation لل ILogger هو كلاس يستخدم مكتبة اخرى للقيام بذلك. [![adapter-design](https://informatic-ar.com/wp-content/uploads/2016/09/adapter-design.png)](https://informatic-ar.com/wp-content/uploads/2016/09/adapter-design.png) كما تم ذكره في الأعلى أن كل من ال SimpleEmployeeValidator وال FileEmployeeStorage لا يعرفوا الا دوال ال ILogger بغض النظر سواء كان الكود كتبته انت او مكتبة أخرى، وفي نفس الوقت قمت باستخدام مكتبة خارجية، وفيما يلي كود ال Adapter: ```csharp public class Log4NetLoggerAdapter : ILogger { private ILog _innerLogger; public Log4NetLoggerAdapter() { // initializing } public void LogWarning(string message, params object[] args) { // log warning } public void LogInfo(string message, params object[] args) { // log info } public void LogError(string message, params object[] args) { // log error } } ``` للننظر الان الى الدالة الرئيسية: ```csharp class Program { static void Main(string[] args) { FileStream fs = File.Open(@"F:\test\records_csv.txt", FileMode.Open, FileAccess.Read, FileShare.None); IEmployeeDataProvider provider = new StreamEmployeeDataProvider(fs); ILogger logger = new Log4NetLoggerAdapter(); IEmployeeValidator validator = new SimpleEmployeeValidator(logger); IEmployeeMapper mapper = new SimpleEmployeeMapper(); IEmployeeParser parser = new SimpleEmployeeParser(validator, mapper); IEmployeeStorage storage = new FileEmployeeStorage(logger); DataProcessor processor = new DataProcessor(provider, parser, storage); processor.ProcessEmployees(); Console.ReadKey(); } } ``` وعند تشغيله سوف تجد نفس المخرج السابق بالضبط، فقط مع الاختلاف الكبير بين تصميم الكود الأول وهذا الكود. نعود لقائمة تغيير المتطلبات التي قد تتغير وسوف نرى ان النسخة الجديدة تسمح لكل منهم بدون تغيير الكلاسات الموجودة: - طلب: قراءة ال Employees من ويب سيرفس بدلاً من ملف أو Stream؟ - الحل: تستطيع عمل كلاس جديد يطبق IEmployeeDataProvidor ويجلب البيانات من الويب سيرفس - طلب: شكل البيانات Data Format تغير وتم اضافة حقول جديد؟ - الحل: سوف تعدل على أي كود Implementation يطبق ال IEmployeeValidatorوال IEmployeeMapper وال IEmployeeStorage واضافة الحقل الجديد - طلب: ال validations rules تغيرت؟ - الحل: سوف تعدل على الكود الذي يطبق ال IEmployeeValidatorوتعدل الشيء الذي تغير - طلب: تغيير ال Loggin الى ويب سيرفس؟ - الحل من تغيير ال Adapter والذي يستخدم مكتبة Log4Netوالتي بها هذه الخاصية الى النوع الجديد - طلب: تغيير قاعدة البيانات الى نوع آخر؟ - الحل: يمكن تضيف كود جديد يطبق ال IEmployeeStorageمثلاً ال AdoNetEmployeeStorageأو عمل كلاس مثلاً MongoEmployeeStorage والتي تستخدم MongoDB او حتى كلاس ServiceEmployeeStorage لكي تكتب البيانات في الويب سيرفس. أعتقد أنك رأيت قوة هذا الأسلوب في العمل وكيف تجعل هناك سبباً واحداً للتغير وذلك يفيدك في جعل الكود أكثر مرونة في العمل المستقبلي وسوف يستطيع تحمل أي تغييرات مستقبلية تطرأ عليه وكل ذلك باتباع خطوتين بسيطة الأولى عمل Refactoring لتبسيط الكود الى دوال صغيرة، ومن ثم عمل Refactoring وضع الكود خلف Interfaces وعمل ال Abstraction المناسب، والاستمرار بعمل هذه الخطوة Aggressive Refactoring حتى تصل الى أي كود لديه سبب واحد في التغيير SRP. ## خاتمة ال SRP له تأثير قوي في Adaptability للكود، وتطبيق ال SRP يولد الكثير من الكلاسات الصغيرة ولها مهمه واحدة فقط، وهو يطبق من خلال Abstraction code خلف Interfaces ومن ثم استخراج المهام غير الضرورية للكلاس للخارج بغض النظر عن ال Implementation الذي سوف يعمل وقت التشغيل. هناك بعض ال design pattern المناسبة تأتي مساعدة مع SRP وهي ال Adapter Pattern وال Decorator Pattern وسوف يتم تناولهم في مواضع أخرى. نستطيع القول بأن تطبيق ال SRP يزيد ال Cohesion لأنه يجعل المهام المتعلقة مع بعضها، وفي نفس الوقت يقلل التداخل Coupling لأنه يجعل الأشياء غير المتعلقة مفصولة عن بعضها. لم نتناول كيف يمكن ان تنشئ هذه الكلاسات وترسل لل DataProcessor من الخارج بشكل أفضل من الموجود في الدالة main الأخيرة، وهذا موضوع منفصل واسمه Dependency Injection والذي لو فوائد عديدة، وكما تلاحظ أن العديد من ال Principles وال Patterns تستخدم مع بعضها لتقديم خلطة جيدة من الكود، وسوف نتحدث عتها في مقالة منفصلة بإذن الله. ## المصادر [Adaptive Code via C#: Agile coding with design patterns and SOLID principles](https://www.amazon.com/Adaptive-Code-via-principles-Developer/dp/0735683204?ref=informatic-ar.com) [SOLID Principles of Object Oriented Design](https://app.pluralsight.com/library/courses/principles-oo-design/table-of-contents?ref=informatic-ar.com) ### مقدمة لل SOLID URL: https://informatic-ar.com/solid/ Last updated: 2026-10-03T22:31:37.000Z # **ما هو مفهوم ال SOLID** ال SOLID هو اختصار لمجموعه من العادات والتي إن قمت باتباعها سوف تحصل على كود أكثر قابلية للصيانة وأسهل في التغيير والتكيف مع متطلبات المشروع المتغيرة Adaptive Code. وليس كما قد يدل الاسم SOLID على أنه شيء يجعل الكود جامد بل بالعكس تماماً، فهذه الأفكار تدور حول كيف يمكن أن يكون الكود مرن ويتكيف مع التغييرات التي تطرأ على المشروع، حيث كما هو معروف في عالم البرمجيات أنك لا تستطيع أن تجمع كل المتطلبات من اول المشروع، وحتى لو جمعتها فهي غالباً ما تتغير مع الوقت. ال SOLID هي لكي تحصل على الكود المرن القابل للتكيف والتغير في حال، مثلاً لم يكن الحل الأول مناسباً لحل المشكلة أو لم يكن مناسباً بعد التغيير في المتطلبات فهو يعطيك المرونة والقابلية للتغير. محتويات السلسلة: 1. مقدمة عن ال SOLID (المقال الحالي) 2. [شرح ال Single Responsibility Principle](https://informatic-ar.com/single-responsibility-principle/) 3. شرح Open/Closed Principle 4. شرح Liskov substitution principle 5. شرح Interface Segaration 6. شرح Dependency Injection **من المهم معرفة أن** ال SOLID هي عبارة عن مجموعة من المفاهيم والأفكار، وليست عبارة عن مكتبة أو دالة تستطيع استدعائها أو حتى فريم وروك بلغة معينة، وهذا يعني أن هذه المفاهيم تطبق على كل اللغات البرمجية سواء جافا أو سي++ أو سي# أو أي لغة أخرى، وهذا شيء جيد فهذا يعني أنك تستطيع بشكل عام قرائة أي مقالات تتحدث عن ال SOLID بغض النظر عن اللغة التي تستخدمها، ونفس الأمر في سلسلة المقالات التي سنطرحها لهذه المفاهيم حيث تستطيع تطبيقها في اللغة التي تشاء. كثير من المبرمجين يستخدموا اللغات الكائنية OOL مثل جافا وسي# ولكن لا يكتبوا أكواد كائنية بشكل صحيح، وانما يقوموا بأول خطوة فقط، حيث يتم كتابة كلاسات تمثل المفاهيم الواقعية مثلاً Book أو Animal أو Employee ولكن في الأخير يتم استخدام هذه الكائنات ضمن سياق اجرائي Procedure Logic وقد يظن المبرمج أن هذه هي ال OO ولكنها في الحقيقة هي مجرد استخدام كائنات ولكن ضمن Procedure Logic. وبالرغم من أنه يستخدم لغات OOL الا أن طريقة تصميم الكود هي إجرائية اكثير من كونها كائنية فكتابة كود بلغة OOL لا يعني ان الكود نفسه هو OO. لكن ان طبقت ال SOLID فسوف يكون أكثر OO، لذلك ال SOLID هي تعتبر مفتاحك الأساسي للدخول في عالم البرمجة الكائينية الحقيقية. ولا تستغرب أنها يطلق عليها أحياناً بأنها "**أساسيات التصميم الكائنية Principle of object oriented design**" وهي حقاً كذلك. عندما تطبق مفاهيم ال SOLID على الكود لديك فسوف تحصل على كود مختلف يختلف عن الشكل الاجرائي الذي كنت تكتبه في السابق. ولكن في نفس الوقت سوف تجعلك أكثر إنتاجية أنت وفريقك وتجعل الكود أكثر قابلية للصيانة. ## البداية [![robert_cecil_martin](https://informatic-ar.com/wp-content/uploads/2016/09/Robert_Cecil_Martin.png)](https://informatic-ar.com/wp-content/uploads/2016/09/Robert%5FCecil%5FMartin.png)هذه الأفكار ليست جديدة، حيث قدمها Bob Martin مهندس البرمجيات المعروف بأنكل بوب منذ حوالي 16 سنة في عام 1995 وسماها [بأساسيات التصميم الكائنية](http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod?ref=informatic-ar.com) وبعد ذلك قام Michael Feathers (مؤلف الكتاب [Working Effectively with Legacy Code](https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052?ref=informatic-ar.com) ) بترتيب هذه المفاهيم حتى تشكل اختصاراً يسهل تداوله بين المبرمجين. بالرغم من أننا في 2016 الا أن هذه العادات ما زالت غير متبعة وغير معروفة خصوصاً لدى المبرمجين الجدد، وهذه الأفكار والمفاهيم من أحد الفوارق بين كود مهندس البرمجيات الجيد والشخص الذي يضع كل شيء في دالة أو كلاس واحد. بخصوص الإختصار SOLID فهو في الحقيقة كل حرف هو اختصار لكلمة وهي كالتالي: [![solid](https://informatic-ar.com/wp-content/uploads/2016/09/SOLID.png)](https://informatic-ar.com/wp-content/uploads/2016/09/SOLID.png) - رقم 1 ال SRP تعني أن لكل كلاس يجب أن تكون سبب واحد للتغيير - رقم 2 ال OCP تعني ان الكلاس يجب أن يكون قابل للإضافات ومغلق لتعديلات - رقم 3 ال LSP تعني كيف يجب أن تعمل ال polymorphism وليس كيف تعمل وانما كيف يجب أن تعمل - رقم 4 ال ISP تعني كيف يمكن ان تصمم ال Interfaces - رقم 5 ال DIP وهي تصف العلاقة بين ال Abstraction وال Concrete Type وأنه يجب ان تعتمد على ال Abstraction. من المهم ايضاً تذكر أنه لا يوجد أي ترتيب في هذه المفاهيم من ناحية الأهمية، فمثلاً لا يعني ان SRP أكثر أهمية من OCP أو ال OCP أقل أهمية من ال SRP، وانما ظهرت بهذا الترتيب حتى تشكل اختصار جميل SOLID. [![solid2](https://informatic-ar.com/wp-content/uploads/2016/09/SOLID2.png)](https://informatic-ar.com/wp-content/uploads/2016/09/SOLID2.png) ايضاَ هناك علاقة بين هذه المفاهيم، فاذا اخذت كل منهم بشكل معزول عن الاخر وطبقته فسوف تحصل على فائدة ولكن ليست كبيرة، بعكس عندما تأخذ كل المصطلحات ككتلة واحدة package وتطبقها فسوف تحصل على الكثير من الفوائد. فاذا كان لديك كود وتريد جعله more solid (وهنا نعنى كود أفضل) فلا تقوم مثلاً بتطبيق ال SRP على كافة الكود اولاً ثم تطبيق بعد فترة المفهوم الثاني وهكذا، بل تأخذ جزء من الكود وتطبق عليه المفاهيم مجتمعة وهذا يجعله more solid. ولأننا في طور التعلم فسوف نقوم بشرح كل مفهوم على حدة في مقالة، ولكن تذكر أن تقوم باتباعها جميعاً متى ما استطعت في الوقت والمكان المناسب، وتذكر أن هذه العادات مثلها مثل البقية تحتاج ان تستخدم بتعقل بدون عمل overuse والا فسوف تقلل المقروئية بدرجة ما لكن سوف تحصل على قابلية التغيير Adaptive Code بلا شك. ## متى تستخدم ال SOLID ال SOLID عادة تستخدمها لإصلاح بعض المشاكل في التصميم design smell فاذا كان الكود لديك فيها بعض ال smells فال SOLID يكون مناسباً لإصلاح هذه المشكلة (راجع المقالة ما هي [رائحة الكود للمزيد عن ال Code Smell](https://informatic-ar.com/code-smell/)) ، وقد لا يكون ال SOLID مناسباً إذا كانت لديك مشاكل أخرى على أي حالة. لكن بعض الأشياء التي يمكن ان تحلها ال SOLID هي: - الكود يصعب التغيير او التعديل عليه Rigidity وربما لديك تصميم جيد للمشكلة ولكن بمجرد أن يأتي طلب جديد new requirement فمن الصعب تطبيق ذلك الطلب لأن التصميم جامد - التصميم يسهل جداً كسره Fragility بواسطة اي مبرمج جديد أو ان التصميم صعب إعادة استخدامه Immobility وتحتاج لتكرار الكود في أكثر من مكان. ## مقالات مقترحة: قبل الشروع في السلسلة فمن المفيد أن يكون لديك معلومات أساسية حول ما يلي: [ماذا تعرف عن ال Coupling وال Cohesion في تصميم البرمجيات ؟](https://informatic-ar.com/coupling-and-cohesion/) [كتاب البرمجة باستخدام ال interface](http://www.kutub.info/library/book/19413?ref=informatic-ar.com) ## المصادر: - [https://app.pluralsight.com/library/courses/principles-oo-design](https://app.pluralsight.com/library/courses/principles-oo-design?ref=informatic-ar.com) - [https://app.pluralsight.com/library/courses/encapsulation-solid](https://app.pluralsight.com/library/courses/encapsulation-solid?ref=informatic-ar.com) - [https://www.amazon.com/Adaptive-Code-via-principles-Developer/dp/0735683204](https://www.amazon.com/Adaptive-Code-via-principles-Developer/dp/0735683204?ref=informatic-ar.com) - [Microsoft .NET - Architecting Applications for the Enterprise](https://www.amazon.com/Microsoft-NET-Architecting-Applications-Enterprise/dp/0735685355/?ref=informatic-ar.com) - [Professional ASP.NET Design Patterns](https://www.amazon.com/Professional-ASP-NET-Design-Patterns-Millett/dp/0470292784?ref=informatic-ar.com) - [Beginning SOLID Principles and Design Patterns for ASP.NET Developers](https://www.amazon.com/Beginning-Principles-Patterns-ASP-NET-Developers/?ref=informatic-ar.com) - [https://www.amazon.com/Adaptive-Code-via-principles-Developer/dp/0735683204](https://www.amazon.com/Adaptive-Code-via-principles-Developer/dp/0735683204/?ref=informatic-ar.com)/ - [https://www.amazon.com/Beginning-Principles-Patterns-ASP-NET-Developers/dp/1484218477/](https://www.amazon.com/Beginning-Principles-Patterns-ASP-NET-Developers/dp/1484218477/?ref=informatic-ar.com) - [http://www.codeproject.com/Articles/703634/SOLID-architecture-principles-using-simple-Csharp](http://www.codeproject.com/Articles/703634/SOLID-architecture-principles-using-simple-Csharp?ref=informatic-ar.com) - [http://www.objectmentor.com/resources/articles/Principles\_and\_Patterns.pdf](http://www.objectmentor.com/resources/articles/Principles%5Fand%5FPatterns.pdf?ref=informatic-ar.com) - [https://www.youtube.com/watch?v=TMuno5RZNeE](https://www.youtube.com/watch?v=TMuno5RZNeE&ref=informatic-ar.com) ### ماذا تعرف عن ال Coupling وال Cohesion في تصميم البرمجيات ؟ URL: https://informatic-ar.com/coupling-and-cohesion/ Last updated: 2026-10-03T22:31:38.000Z # ماذا نعني بال Coupling وال Cohesion كثيراً ما نسمع عن هذا المصطلحان وأنه يجب أن تسعى الى أن يكون الكود قليل التداخل Low Coupling وأكثر تجانساً High Cohesion بدون توضيح مناسب لمعناها، في هذه المقالة سوف نوضح هذه المصطلحات والفرق بينهم بشكل مبسط ونوضح طرق مختلفة لتحقيق ال loose coupling عن طريق طرق التصميم Design Patterns المختلفة. ## نظرة تاريخية كما في كثير من الأفكار في علوم الحاسب فهي لم تتولد فجأة بين ليلة وضحاها في رأس أحدهم، وانما هي فكرة طرحها باحث وبعدها بعدة سنوات يناقشها او يطورها باحث اخر وهكذا فإن بدايه هذه الأفكار منذ السبعينيات. فترة من 1970 الى 1980 سميت بفترة "**أزمة البرمجيات Software Crisis** " حيث واجهوا تعقيداً وصعوبة في تطوير البرمجيات وظهرت مشاكل التأخير في التسليم، زيادة الميزانية وقلة الجودة، ولهذا عجت بالكثير من الأبحاث في هندسة ومعمارية البرامج، وبها ظهر الكثير من المفاهيم في تصميم البرمجيات، ومنها –موضوع حديثنا-مفهوم ال Coupling وال Cohesion. ![الباحث David Lorge Parnas ](https://informatic-ar.com/wp-content/uploads/2016/08/David_Parnas.jpg) بدء الأمر في عام 1972 حيث كتب الباحث ديفيد بارناس [Davied L. Parnas](https://en.wikipedia.org/wiki/David%5FParnas?ref=informatic-ar.com) ورقة علمية بعنوان [On the Criteria To Be Used in Decomposing Systems into Modules](https://www.cs.umd.edu/class/spring2003/cmsc838p/Design/criteria.pdf?ref=informatic-ar.com) وقارن فيها بين طريقتين لتقسيم Decomposing الأنظمة الى وحدات صغيرة Modules، وتوصل في خلاصتها أنه يجب تقسيم النظام الى وحدات على حسب مدى قابليتها لكي تتغير، بمعنى الأشياء التي يتوقع أن تتغير لسبب معين تفصل عن الوحدات الأخرى التي قد تتغير لسبب مختلف عنها. الباحث David Lorge Parnas من أوائل من بحثوا في هندسة البرمجيات واسهم في تعريف مفهوم ال Information Hiding في ال Modular Programming بعد ذلك بسنتين تحديداً، كتب العالم ديسكترا [Edsger Dijkstra](https://en.wikipedia.org/wiki/Edsger%5FW.%5FDijkstra?ref=informatic-ar.com) ورقة بعنوان [On the role of scientific thought](http://www.cs.utexas.edu/users/EWD/ewd04xx/EWD447.PDF?ref=informatic-ar.com) وفيها عرف مفهوم ال Separation of Concerns ![Edsger Dijkstra](https://informatic-ar.com/wp-content/uploads/2016/08/EdsgerDijkstra.jpg)ويطلق عليه اختصاراً SoC، وفكرته كانت عبارة عن تقسيم النظام الى عدة خصائص أو طبقات Layers ليست متداخلة Non-Overlapping Features، كل خاصية Feature تريدها في النظام سوف تمثل Concern فيه وهي تسمح للكود بأن يكون أكثر Modularized وقابل لإعادة الاستخدام. هذه الفكرة طبقت لاحقاً في الكثير من اللغات والأدوات، مثلاً من أشهر الأمثلة الحالية كتابة ال HTML لوصف المحتوى وبينما ال CSS لإظهار وتنسيق المحتوى وبالتالي فصلنا الاهتمامات، ايضاً نمط ال MVC هو أحد الأمثلة الأخرى على فكرة ال SoC. وال SoC وهي تعتبر من الأمور المهمة في تصميم الأنظمة وخصوصاً ال Enterprise Systems. بعد عدة سنوات ألف لاري قسطنطين ومجموعه من الباحثين كتاباً باسم [Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design](https://www.amazon.com/Structured-Design-Fundamentals-Discipline-Computer/dp/0138544719?ref=informatic-ar.com) وتحدثوا فيه عن البرمجة الهيكلة وعرفوا فيها مفهوم ال Coupling وال Cohesion والذي سنوضحه فيما يلي. أواخر التسعينات قام روبرت مارتن (المعروف بأنكل بوب) بتجميع هذه التعاريف على شكل مفهوم وسماه مفهوم ال Single Responsibility Principle بالإضافة الى عدة مفاهيم اخرى سماها بأساسيات تصميم البرمجيات. قام بعدها Michael Feathers بتسمية هذه المفاهيم بالاسم SOLID ([كل ما تحتاج معرفته عن مفهوم ال SOLID](https://informatic-ar.com/solid/)) بعد ترتيبها واخذ الحرف الأول من كل منها (ومايكل هو مؤلف لأحد الكتب المرموقة في البرمجيات وهو كتاب [Working Effectively with Legacy Code](https://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052?ref=informatic-ar.com) ). ال Coupling وال Cohesion مرتبطان ببعضهما البعض، والنظام المصمم بحيث يحقق الLow Coupling وال High Cohesion بشكل عام يكون قد حقق المقروئية العالية، قابلية الصيانة، سهولة الاختبار، واعادة الاستخدام. وفيما يلي نوضح الفكرة الأساسية لكل مصطلح مع مثال مناسب له. **ملاحظة** هذه الأفكار يمكن تطبيقها سواء في البرمجة الهيكلية او حتى الكائنية وليست حصراً على ال Object Oriented فهي قدمت اساساً قبل ظهور اللغات الكائنية لدعم ال Structured Design، والتصميم الكائني الجيد يتميز بال Low Coupling وال High Cohesion والذي يعني كل الكائنات (High Cohesion) تتعامل مع الكائنات الاخرى من خلال Interface مستقرة (Low Coupling). ## **التجانس Cohesion** وهي تشير الى التشابة او الترابط داخل كود ما (سواء كانت دوال داخل كلاس، أو كلاسات داخل namespace ما)، فكلما كانت تلك الأسطر متعلقة ومترابطة فيما بينها فهذا يعني أن تلك الوحدة لها High Cohesion، وكلما كانت تلك الدوال او الاسطر غير متعلقة ببضعها كلما كانت تلك الوحدة Low Cohesion. إذا نظرت لتعريف ال Cohesion في مجال اخر مثلاً الكيمياء ستجد تعريف ايضاً مناسب مشابه لل Cohesion في البرمجيات، وفي الكيمياء التماسك هو خاصية للمادة تشير الى الجاذبية الموجودة بين الجزئيات الشبيهة داخل الجسم. في المثال التالي سوف نجد كلاس لإرسال البريد وسوف تجد أن كل شيء فيه متعلق لإنجاز نفس الهدف، وهذا يعني أن الكلاس له High Cohesion. ![cohesion_example](https://informatic-ar.com/wp-content/uploads/2016/08/cohesion_example.png) بينما في هذا المثال سوف تجد أن هناك دالة تقوم بتوليد تقرير واستخراج النتائج ومن ثم عرضه بصيغة HTML وهي فعلياً لا علاقة لها بهدف ذلك الكلاس، ولذلك هذا الكلاس له Low Cohesion.[ ](https://informatic-ar.com/wp-content/uploads/2016/08/cohesion%5Fexample.png) [![low_cohesion_example](https://informatic-ar.com/wp-content/uploads/2016/08/low_cohesion_example.png)](https://informatic-ar.com/wp-content/uploads/2016/08/low%5Fcohesion%5Fexample.png) الكلاسات ال Highly Cohesive هي أكثر قابلية للصيانة واعادة للاستخدام بسبب أنها لا تحتوي على أي اعتمادية Dependencies، بعكس الكلاسات ال Low Cohesive تصعب فهم الكلاسات وتقدم لك هشاشة في البرنامج، أيضاً تقليل ال Cohesion يؤدي الى انشاء كلاسات تحتوي على مسؤوليات (دوال) كل منها له وظيفة بعيدة عن الاخرى. ![Ward_Cunningham](https://informatic-ar.com/wp-content/uploads/2016/08/Ward_Cunningham.jpg) لذلك دائماً ما ينصح الى انشاء كلاسات متخصصة بدوال قليلة تكون متقاربة في الوظيفة منطقياً، وعندنا تجد دوال متباعدة في الوظيفة في ذلك فقد تكون بحاجة لنقلها الى مكان أخر. وارد كانينغهام Ward Cunningham (من رواد ال Extreme Programming من أول من برمجوا أنظمة الويكي) يقول : > بأن درجة التجانس يتناسب عكسياً مع عدد الدوال في الكلاس وهذا تعريف مبسط وفي نفس يوضح الفكرة من التماسك بشكل جيد. ## **الترابط Coupling** وهي تقيس درجة الاعتمادية بين أكثر من وحدة في البرنامج ، مثلاً بين الكلاسات لو كان لدينا كلاس A وكلاس B فنقول أنهما مترابطان Coupled عندما تحتاج لعمل تغيير في الكلاس B كل مرة تقوم فيها بتغيير كلاس A. بعبارة اخرى انت تريد تغيير شيء في كلاس A ولكن بسبب الاعتمادية فسوف تجبر على أن تقوم بتغيير الكلاس B والا فالكود لن يترجم ولن يعمل. الترابط Coupling يقاس ايضاً من قليلة الى عالية ، وكلما كانت أقل كلما كان أفضل. وال Low Coupling لا تعني أن ال Modules معزولة تماماً عن بعض، فهي بالتأكيد سوف تحتاج للتواصل Communicate فيما بينها ولكن عن طريق Well Defined Interfaces. وكل Module سوف يعمل بدون الحاجة لمعرفة تفاصيل عمل ال Module الأخر. وبالعكس ال High Coupling سوف يعيق من اختبار الكود واعادة استخدامه ايضاً وجعل الكود أكثر صعوبة في الفهم وهو أحد الأسباب في التصاميم الجامدة Rigid والهشة Fragile. مثال: في الكلاس التالي نجد أنه متعلق بشكل قوي في ال MySQLDatabase (سواء استقبله كمعامل في دالة البناء او انشئه داخله) ونقول عنه high coupling [![high_coupling](https://informatic-ar.com/wp-content/uploads/2016/08/high_coupling.png)](https://informatic-ar.com/wp-content/uploads/2016/08/high%5Fcoupling.png) بينما التعديل التالي قمنا بإضافة contract بينهم وبالتالي ال StorageManager لا يعلم شيئاً عن نوع ال Implementation سواء كان على MySQL أو على ملف عادي أو أي نوع، وهذا ما نسميه بال Low Coupling. [![low_coupling_1](https://informatic-ar.com/wp-content/uploads/2016/08/low_coupling_1.png)](https://informatic-ar.com/wp-content/uploads/2016/08/low%5Fcoupling%5F1.png) للمزيد حول ال interface وكيف يساهم في جعل الكود أفضل، وال Repository Pattern فانظر للكتاب العربي: [البرمجة باستخدام ال interface](http://www.kutub.info/library/book/19413?ref=informatic-ar.com). لذلك من أهم الأمور في الكود البرمجي هو أن يكتب بشكل Loose Coupling ويقلل التداخل بين الوحدات فيه، حيث هي تقلل الصيانة وتجعل الكود أسهل في التطوير والتعديل في المستقبل. قد تتساءل الآن وتقول كيف يمكن لل low coupling أن تقدم هذه الفوائد، وهذا ما سيتبين اليك في الفقرة التالية وكيف يمكن لبعض ال design patterns أن تساهم في ذلك. ## **لماذا ال Tight Coupling تقلل من قابلية الصيانة؟** ![image1](https://informatic-ar.com/wp-content/uploads/2016/08/image1-1024x767.png)لنأخذ مثال في الحياة الواقعية وقد تجد في بعض الفنادق منخفضة التكلفة مجففات شعر موصلة مباشرة بالحائط كما بالصورة، ربما لأنهم لا يثقوا بالزبائن وبالتالي قاموا بإيصالها الى الحائط مباشرة بهذه الطريقة الرديئة، فماذا يحدث إذا تعطل الأول وأرادوا تركيب مجفف جديد؟ بالطبع باستخدام أدوات خاصة لذلك يتم فك القديم وتركيب الجديد وربما يحتاج الى إطفاء الكهرباء من الغرفة في تلك الفترة. هذا مشابه للكود المرتبط Tightly Coupled Code فهو مثل المجفف المرتبط بالحائط ولا يمكن تغييره بسهولة بدون التأثير في أكواد أخرى. في العادة فلا يتم توصيل الاسلاك على الحائط كما في الصورة السابقة، ويتم استخدام القابس Plugs والمقبس/المأخذ Socket، فالمقبس socket يمثل المكان الذي يتم تركيب القابس plug عليه ويجب أن يتطابقا وبالتالي يمكن تركيب اي جهاز على المقبس في حال كان له نفس الشكل. وفي البرمجيات فالمقبس socket فهي ال interface. [![image2](https://informatic-ar.com/wp-content/uploads/2016/08/image2.png)](https://informatic-ar.com/wp-content/uploads/2016/08/image2.png) باستخدام ال Sockets and plugs فمجفف الشعر الان أصبح loosely coupled [![image3](https://informatic-ar.com/wp-content/uploads/2016/08/image3-1024x617.png)](https://informatic-ar.com/wp-content/uploads/2016/08/image3.png) فكرة المقابس والأقباس سبقت الحواسيب بعدة عقود وهي التي تقدم الخدمة الاساسية للحواسيب ايضاً، وبالرغم من ان المصممين للمقابس sockets لم يعرفوا الحواسيب أو الأجهزة المستقبلية ولكن تصميمها مرن يجعل اي جهاز حتى لو لم يعرف ذلك الجهاز. الذي يهم هنا أنه يمكن ان نقارن بين ذلك النموذح مع العديد من ال software design and principles: **أولاً**: لن تكون محكوماً فقط بالمجفف، وتستطيع توصيل اي جهاز اخر مثلاً Computer، وبكل بساطة تنزع unplug المجفف وتوصل Plug الحاسب في نفس ال socket. [![باستخدام الsocket فيمكن توصيل اي جهاز أخر وهذا يشبه مبداً Liskov substitution principle](https://informatic-ar.com/wp-content/uploads/2016/08/image4-1024x448.png)](https://informatic-ar.com/wp-content/uploads/2016/08/image4.png) باستخدام الsocket فيمكن توصيل اي جهاز أخر وهذا يشبه مبداً Liskov substitution principle القدرة على تبديل طرف واحد بدون التأثير على الاخر هي مشابه لفكرة اساسية في تصميم البرمجيات ألا وهي ال [Liskov Substitution Principle](https://en.wikipedia.org/wiki/Liskov%5Fsubstitution%5Fprinciple?ref=informatic-ar.com) وينص هذا المبدأ على أنه يجب أن تكون لديك القدرة على تبديل أحد ال Implementation لل Interface بأي Implementation أخر بدون أي تأثير Breaking على الكلاينت، وهو يعتبر من أهم مبادئ تصميم البرمجيات حيث يمكننا اضافة المتطلبات في المستقبل حتى لو لم نكن على علم بماهيتها. الصورة التالية، تبين أنه يمكن يمكن فك توصيل unplug الحاسب في حال لم نعد نحتاج لتوصيله، وحتى في حال لم يمكن هناك شي موصل بالمقبس فان المقبس لن ينفجر. [![عدم توصيل الجهاز لا يؤدي لأي عطل سواء للحاسب أو للحائط، وهذا يمكن تشبيه بال Null object pattern.](https://informatic-ar.com/wp-content/uploads/2016/08/image5-1024x362.png)](https://informatic-ar.com/wp-content/uploads/2016/08/image5.png) عدم توصيل الجهاز لا يؤدي لأي عطل سواء للحاسب أو للحائط، وهذا يمكن تشبيه بال Null object pattern. في البرمجيات ففي الغالب فال client يتوقع الخدمة أن تعمل Available بشكل دائم، فاذا تم حذفها لأي سبب فسوف يحصل على الكلاينت على خطأ مثلاً NullReferenceException، وللتعامل مع هذه الحالة فيمكن عمل Implementation من interface لا يقوم بفعل شيء does nothing. وهذه من طرق التصميم design pattern وتعرف بالاسم Null Object، وهي وضعية عدم توصيل اي جهاز بالoutlet. فطالما نحن نطبق ال loose coupling فيمكن تبديل اي Real Implementation بأي Implementation أخر سواء يقوم بمهمة أو لا بدون التسبب في اي مشاكل. **ثانياً**: هناك العديد من الأمور الاخرى يمكن فعلها، مثلاً إذا كنت تعيش في منطقة متقطعة الكهرباء فيمكن أن توصل جهاز Uninterrupted Power Supply (UPS) بالمقبس وتوصل الحاسب بال UPS. [![ال UPS يجعل الحاسب يعمل لفترة في حال مشاكل الطاقة، وهي يشبه ال Decorator design pattern.](https://informatic-ar.com/wp-content/uploads/2016/08/image6-1024x400.png)](https://informatic-ar.com/wp-content/uploads/2016/08/image6.png) ال UPS يجعل الحاسب يعمل لفترة في حال مشاكل الطاقة، وهي يشبه ال Decorator design pattern. كل من الحاسب Computer وال UPS لديه مهمته الخاصة Single Responsibility ولا يتعارضان مع بعضهما، بالرغم من أن كل منهم قد صنع بواسطة مصنع manufactures مختلف، وربما في اوقات مختلفة وفي دول مختلفة. وحتى انه يمكن تشغيل الحاسب بدونه، ويمكن تشغيل ال UPS ايضاً مع اي جهاز أخر مثلاً مجفف الشعر. في البرمجيات فان اضافة الخصائص لشي موجود يشابه مبدأ [Decorator design pattern](https://en.wikipedia.org/wiki/Decorator%5Fpattern?ref=informatic-ar.com) حيث تستطيع اضافة المزيد من الخصائص أو حتى اضافة ال CROSS-Cutting Concerns بدون الحاجة لإعادة كتابة أو تغيير كمية كبيرة من الكود لديك. طريقة اخرى لإضافة خصائص اخرى لكود موجود هي عن طريق اضافة new implementation تحت كائن واحد وبالتالي تستطيع اضافة او حذف اي عدد تريده من ال Implementations وسيتم التعامل مع كائن واحد يمثل الواجهة لهم، وهذا ما يعرف بال [Composite Pattern](https://en.wikipedia.org/wiki/Composite%5Fpattern?ref=informatic-ar.com)، في المقبس الكهربائي إذا كنت تريد ان تشغل أكثر من جهاز في نفس المكان فيمكن ان تستخدم توصيلة كهربائية بها أكثر من منفذ وقابسها سوف يركب على المقبس مباشرة، الصورة التالية تبين ذلك. [![التوصيلة power strip باللون الأحمر تمكنك من اضافة أكثر من جهاز في نفس منفذ الحائط، وهذا يعرف في البرمجيات بال composite design pattern.](https://informatic-ar.com/wp-content/uploads/2016/08/image7-1024x639.png)](https://informatic-ar.com/wp-content/uploads/2016/08/image7.png) التوصيلة power strip باللون الأحمر تمكنك من اضافة أكثر من جهاز في نفس منفذ الحائط، وهذا يعرف في البرمجيات بال composite design pattern. التوصيلة لديها مقبس واحد plug يتم توصيلة على ال socket، ولكن في نفس الوقت يزودك بالعديد من ال sockets للأجهزة الأخرى، وهذا يمكنك من اضافة وازالة المجفف بينما الحاسب يعمل. وبنفس الفكرة فال Composite pattern يسهل لك اضافة وحذف الخصائص عن طريق تعديل مجموعه من ال composed interface implementations. **ثالثاً:** احياناً نقع في مشكلة ان القابس plug لا يدخل في المقبس socket وتجد ان المكان غير متطابق حتى يدخل، لذلك تستخدم محول adapter يسهل لك تركيب الجهاز، وبنفس الأمر في البرمجيات فال [Adapter design pattern](https://en.wikipedia.org/wiki/Adapter%5Fpattern?ref=informatic-ar.com) تقوم بنفس المهمة فهي تستطيع وضع واجهة جديدة للكود الذي تريده حتى يعمل ويتوافق مع الكود الأخر. [![تحتاج ل adapter عندما تود ان توصل اي جهاز لا يتناسب مع ال socket.](https://informatic-ar.com/wp-content/uploads/2016/08/image8-1024x370.png)](https://informatic-ar.com/wp-content/uploads/2016/08/image8.png) تحتاج ل adapter عندما تود ان توصل اي جهاز لا يتناسب مع ال socket. وهذا مفيد عندما يكون لديك API من مكتبة خارجية Third-Party API تريد أن تشغلها في كود لديك يتعامل مع interface محددة فتقوم بوضع تلك المكتبة وتحول ال API لها الى الشكل المناسب مع ذلك الكود باستخدام ال Adapter Pattern. الشيء المثير للدهشة في مثال ال Socket وتوصيلاتها، فبالرغم من أنها موجودة من عقود مضت فهي سهلة وتستخدم بنفس الطريقة بمجرد وضع الاساس فيمكن ان توصل أي جهاز بسهولة سواء مباشرة أو باستخدام adapter أو اي مطلبات اخرى قادمة. لذلك نفس الأمر في البرمجيات فال loose coupling يجعل الكود اسهل كثيراً في ال Maintainable. في مقالات قادمة ان شاء الله سوف نتحدث عن ال SOLID design principles وطرق التصميم design patterns المختلفة التي تحدثنا عنها بأمثلة برمجية. نراكم على خير. **المراجع:** - [https://www.amazon.com/Dependency-Injection-NET-Mark-Seemann/dp/1935182501](https://www.amazon.com/Dependency-Injection-NET-Mark-Seemann/dp/1935182501?ref=informatic-ar.com) - [https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html](https://blog.8thlight.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html?ref=informatic-ar.com) - [http://blog.ploeh.dk/2010/04/07/DependencyInjectionisLooseCoupling/](http://blog.ploeh.dk/2010/04/07/DependencyInjectionisLooseCoupling/?ref=informatic-ar.com) - [http://enterprisecraftsmanship.com/2015/09/02/cohesion-coupling-difference/](http://enterprisecraftsmanship.com/2015/09/02/cohesion-coupling-difference/?ref=informatic-ar.com) - [https://thebojan.ninja/2015/04/08/high-cohesion-loose-coupling/](https://thebojan.ninja/2015/04/08/high-cohesion-loose-coupling/?ref=informatic-ar.com) - [http://codurance.com/2016/03/03/cohesion-cornerstone-software-design/](http://codurance.com/2016/03/03/cohesion-cornerstone-software-design/?ref=informatic-ar.com) - [https://dotnetinterviewquestion.wordpress.com/2014/05/15/explain-cohesion-and-coupling-net-and-c-architecture-level-interview-question/](https://dotnetinterviewquestion.wordpress.com/2014/05/15/explain-cohesion-and-coupling-net-and-c-architecture-level-interview-question/?ref=informatic-ar.com) ### ما هي ال Scalability وكيف يتحمل الموقع ملايين الزوار ؟ URL: https://informatic-ar.com/web-scalability/ Last updated: 2026-10-03T22:31:39.000Z # **مقدمة** موضوع ال Scalability يعتبر من أهم المواضيع بالنسبة لمطورين الويب خصوصاً هذه الأيام مع تزايد عدد البيانات والمستخدمين سواء في التخزين أو المعالجة أو حتى في الارسال، والطلب على مطورين الويب الذي لديهم خبرة في ال Scalability أصبح اعلى بكثير من مبرمجي الويب بدون أي معرفة أو خبرة بهذا الموضوع. وبالرغم من تزايد الطلب حول هذا الموضوع، وحتى في متطلبات المشاريع خصوصاً في الشركات الناشئة أصبح موضوع ال Scalability مهم ويدخل ضمن الاعتبار في أولى المراحل، الا أنه يندر وجود مقالات أو مواضيع تتناول هذا الموضوع بشكل جيد. ومن هذا المنطلق بدئت فكرة هذه السلسلة من المقالات التي تتحدث حول هذا الموضوع وتم الاعتماد على أحد الكتب الجيدة (راجع قائمة المصادر في آخر المقالة) في هذا المجال كمصدر أساسي بالإضافة الى خبرتي الشخصية في بناء بعض من المواقع والشبكات الاجتماعية والتي احتاجت ان تكون Scalable. موضوع ال Scalability لا يعتبر من المواضيع السهلة، ولكن في هذه المقالة سوف نبدأ من اول الأساسيات ومن ثم تتدرج رويداً في المفاهيم، وأي مطور ويب ولو حتى مبتدئ سوف يستطيع إتمام هذه المقالة وبقية المقالات ان شاء الله. وبالطبع كل المفاهيم تطبق بغض النظر عن اللغة او المكتبة او حتى نظام التشغيل. # **ما هي ال** Scalability**؟** كثيراً ما نسمع عن هذا المصطلح ولكن لا ندري كيف يمكن أن نقوم به فعلياً ونطور أنظمة تكون قابلة لتحمل المزيد من البيانات والمستخدمين وهذا هو حال غالب التطبيقات والخدمات الجديدة خصوصاً لل Startup، لذلك الطلب وفرص العمل ستكون أفضل لمطوري تطبيقات الويب الذي لديهم معرفة في هذا الموضوع. **من أحد تعاريف ال** **Scalability (قابلية التوسع):** هو القدرة على جعل النظام يستطيع تحمل المزيد من البيانات، المستخدمين، الطلبات والعمليات بحيث يلبي أهدافك بتكلفة مناسبة بدون التأثير على أداء واستجابة النظام User Experience وفي نفس الوقت تستطيع ان تزيد Scale Up أو تقلل Scale Down متى ما أردت. **ال Scaling Down اقل أهمية من ال Scaling Up لكنه بالطبع يفيد في بعض الحالات مثلاً في الشركات الناشئة والتي تريد ان تقلل المنصرفات بأكبر وسيلة خصوصاً بعد ما يحصل تغير في العمل أو في بعض المتطلبات أو وجد أنه في وقت معين لا يوجد أي ضغط او استخدام على التطبيق فيمكن أن يقوموا بتقليل الموارد عليه في تلك الفترة.** عادة التحديات التي يحتاج أي نظام بدء الضغط عليه هي: - **التعامل مع المزيد من البيانات** **Data**: وهي أول هذه التحديات وأشهرها ايضاً، فكلما توسع مشروعك وأصبح شائعاً فكلما زاد البيانات فيه أكثر وأكثر. لذلك عليك أن تتعامل معها بشكل أكثر كفاءة مثلاً حسابات المستخدمين User Accounts، المنتجات Products والخ من أي معلومات في نظامك. معالجة هذه المعلومات سوف يضع حملاً على النظام فهذه المعلومات تحتاج أن ترتب Sorted، أو تبحث خلالها Search، أو أن تجلبها من ملفات Reading، أو تكتبها على القرص Writing، أو ترسل عبر الشبكة Sending. وهذه الأيام مع توفر أدوات تحليل البيانات الضخمة Big Data Analytics فالشركات أصبحت شهيتها مفتوحة لتخزين المزيد والمزيد من البيانات. - **التعامل مع المزيد من الطلبات في نفس اللحظة** **Concurrency**: ونعنى عدد المستخدمين الذين يمكن أن يستخدموا النظام في نفس اللحظة بدون التأثير على الأداء، وهذا الأمر صعب لأن السيرفرات التي يعمل فيها النظام لديها عدد محدود من الموارد (عدد معين من المعالجات CPUs) وال Threads ايضاً. وكلما زاد ال Concurrent Users كلما زاد عدد الطلبات المفتوحة Open Connections وزاد عدد ال Active Threads وزادت الأشياء التي ينبغي معالجتها في نفس اللحظة وبالتالي يزيد ال CPU Context Switching - **التعامل مع زيادة معدل ارسال واستقبال المعلومات** **Interaction Rate**: ونقصد معدل ارسال واستقبال المعلومات بين المستخدمين والنظام وهي قريبة من فكرة ال Concurrency ولكن باختلاف بسيط. على سبيل المثال في المواقع العادية فإن المستخدمين يقوموا بالتحرك في صفحاته كل 15 او 20 ثانية بينما مثلاً في الألعاب المتعددة المستخدمين فمعدل ارسال واستقبال المعلومات يكون أكثر من مرة في الثانية. لذلك فال Interaction Rate يمكن أن يكون أكبر أو اقل على حسب نوع التطبيق الذي تقوم ببنائه بغض النظر عن عدد المستخدمين الحاليين. التحدي هنا هو في التباطئ Latency فكلما زاد هذا الرقم فأنت تريد أن يستجيب النظام بشكل سريع وهذا يتطلب سرعة في عمليات القراءة والكتابة. ربما قد تكون قد لاحظت أن ال Scalability مرتبطة مع ال Performance ولكن هما ليسا نفس الشيء. فالأداء Performance يقيس كم الوقت المطلوب حتى يتم معالجة الطلب او القيام بعمل معين، بينما ال Scalability تقيس الى أي مدى ممكن ان نزداد Grow أو نقل Shrink. مثال: إذا كان لديك 100 مستخدم حالي Concurrent وكل منهم يرسل طلب كل 5 ثواني ففي هذه الحالة سوف نحتاج الى ان تكون استجابة الموقع هي حوالي 20 طلب في الثانية ويمكن ان توضح ضمن المتطلبات للمشروع (أي Throughput requirement). فالأداء Performance هنا يقيس الوقت المطلوب لخدمة 20 طلب في الثانية، بينما ال Scalability تقيس كم أكبر عدد مستخدمين وطلبات يمكن معالجتهم بدون تقليل في الأداء. أخيراً، فال Scalability لا تنحصر في جانب المشروع نفسه وانما ايضاً تدخل في المؤسسة أو فريق العمل نفسه، فكلما يكبر النظام فسوف تحتاج الى طريقة لترتيب العمل Organizational Scalability والا فلن يستطيع التغيير أو التكيف مع التغييرات بشكل سريع. وبالرغم من ان ال Organizational Scalability ليست متعلقة بالتقنية لكن لأنها تحدد بواسطة معمارية وتصميم المشروع. فاذا كان كود النظام مرتبط بشكل كبير Tightly Coupled فهذا يصعب زيادة عدد المطورين وادارتهم في هذا المشروع. **لكي تعرف كيف تؤثر ال Scalability على الشركات الناشئة فانظر من منظور الأعمال وأسأل نفسك "ما هي الأشياء التي تمنع المشروع من التقدم والكبر" فهي ليست في عدد المستخدمين والبيانات وانما يدخل بها طريقة التطوير Development Processes والفريق وبنية الكود ايضاً. وسوف يتم مناقشتها في آخر مقال في هذه السلسلة.** # **الانتقال من سيرفر واحد الى جمهور أكبر** سوف نوضح الآن مراحل التطور في مشروعك وكيف تصل من اول مرحلة صغيرة (استضافة صغيرة أو على سيرفر واحد) الى مرحلة تستطيع فيها أن تقوم بعملية ال Scaling وجعل تطبيقك يستحمل عدد أكبر بنفس الأداء (على عدة سيرفرات حول العالم). بعض من هذه المراحل سوف تعمل فقط في حالة إذا كنت قد خططت لها منذ البدء في المشروع. لكن للأسف في الحياة الواقعية فأغلب الحالات لا تكون كذلك، وقد تحتاج لإعادة كتابة النظام أو أجزاء منه عدة مرات حتى تصل لتلك المرحلة. وفي اغلب الحالات فإن النظام يولد في مرحلة من هذه المراحل ويظل على ذلك طيلة فترة حياته أو ربما يقوم يصعد سلم أو اثنين في الدرج حتى يوصل الى مرحلة لا يمكن بها التحسين Architectural Limits. كل النقاط سوف يكون بشكل مبسط high level، بحيث سوف يتم تغطية كل الأجزاء مجدداً في مقالات منفصلة حتى يتم الغوص في تفاصيل ذلك الجانب، وهكذا سوف تبقى المقدمة هذه هي المرجع لك للمراجعة وتذكر الشكل العام وكيف تعمل الأجزاء مع بعضها البعض. الصورة 0 تبين مخطط المراحل وكيف أنك ستبدأ عادة من الدرج الأول (سيرفر واحد) وكيف يمكن أن تتطور وتضيف أمور تساعد في جعل المشروع قادراً على تحمل الضغط مع مرور الوقت. وسيتم تناول هذه المراحل فيما يلي. [![خطوات ال scalability في المشاريع](https://informatic-ar.com/wp-content/uploads/2016/03/EvalutionSteps-1024x463.png)](https://informatic-ar.com/wp-content/uploads/2016/03/EvalutionSteps.png) الصورة 0 تبين المراحل التي يمكن ان يتوسع خلالها المشروع **تجنب إعادة كتابة التطبيق من الصفر بأي طريقة، خصوصاً لو هذه Startup. فكتابته من الصفر سوف تأخذ وقتاً أطول مما كنت تتوقعه وأصعب ايضاً مما تظن. وسوف توصل لنفس المشاكل في الكود والتصميم mess فقط بعد سنتين تاليتين.** ## بيئة مكونة من سيرفر واحد Single Server Configuration لنبدأ من أسهل وأول طريقة تبدأ بها كل المشاريع، الا هي سيرفر واحد فقط يتم وضع عليها كامل التطبيق فيها. الصورة 1 تبين كل الطلبات للمستخدمين تعالج بواسطة نفس السيرفر، وغالباً ال Domain Name System (اختصاراً DNS) يكون بواسطة خدمة مدفوعة من شركة أخرى ولا تعمل على نفس السيرفر. في هذه الحالة المستخدمين سوف يتصلوا بال DNS لكي يحصلوا على عنوان ال Internet Protocol (اختصاراً IP) للسيرفر الذي يوجد عليه الموقع، بعد أن يحصلوا على ال IP يتم ارسال طلبات HTTP للويب سيرفر. [![تبين Single Server Configuration](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-1-.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-1-.png) الصورة 1 تبين Single Server Configuration لأن هذه التركيبة (ال Setup) مكونة من جهاز واحد فهو يجب أن يقوم بكل المهام المطلوبة حتى يشغل تطبيقك، فقد يحتوي على نظام إدارة قاعدة البيانات MySQL أو Postgres أو أي نوع آخر، بالإضافة الى الصور واي محتوى اخر من التطبيق مثل الصور والفيديو والخ. الصورة 1 تبين أيضاً توزيع الضغط Traffic في هذه البنية، فالمستخدمين سوف يتصلوا اولاً بال DNS لكي يقوموا بالحصول (تسمى عملية Resolve ) على ال IP لعنوان موقعك وبعدها يتم ارسال الطلبات الى ذلك الويب سيرفر. وكل الصفحات والصور وملفات CSS والفيديو سوف يتم توليدها او ارسالها من هذا السيرفر. والخطوط في الصورة 1 (العريض أو النحيف) تبين مدى حجم الضغط على السيرفر أو على مزود ال DNS. التطبيقات مثل هذه تكون مناسبة لموقع الشركات البسيطة التي تحتوي على بضعه منتجات ومدونة ومنتدى حوار ومجموعه من الخدمات الصغيرة. هذه المواقع الصغيرة لا تحتاج الى سيرفر فيزيائي خاص لها Dedicated Server وفي العادة يكفي سيرفر وهمي Virtual Private Server (اختصاراً VPS) أو حتى استضافة مشتركة Shared Hosting. **ال Virtual Private Server واختصاراً VPS هو مصطلح يستخدم لكي يشير الى وجود Virtual Server تستطيع تأجيرها، فعندما تقوم بذلك فسوف تكون موجودة هي وغيرها من ال Instances على سيرفر واحد. وال VPS تتعامل معه كأنه سيرفر عادي ففيه نظام تشغيل وكل الصلاحيات لديك. ولكنه أرخص من السيرفر الفيزيائي dedicated server لأن الشركة المزودة تضع أكثر من Instance على نفس السيرفر بعكس الكامل يكون كامل لك.** **ال VPS هو بداية جيدة فهو رخيص ويمكن ان تزيد قدراته Upgraded سواء من الذاكرة Random Access Memory (اختصاراً RAM) أو من ناحية المعالج Central Processing Unit (اختصاراً CPU) بسهولة.** **الاستضافة المشتركة Shared Hosting هي الارخص، فأنت تشتري حساب بدون أي صلاحيات إدارية، وحسابك يكون موجود على سيرفر مع العديد من حسابات العملاء الاخرين، ايضاً هو بداية جيدة للمواقع الصغيرة او صفحات الهبوط Landing Pages لكنه محدود لذلك لا ينصح به.** للمواقع التي لا يوجد ضغط عليها فسيرفر واحد يكفي لمعالجة طلبات المستخدمين. لكن هناك العديد من الأسباب التي تجعل هذه التركيبة Configuration لا تنفع من منظور ال Scalability: - كلما زاد عدد المستخدمين، سوف يزيد الضغط traffic وهذا يستهلك الموارد مثل الذاكرة، وقت المعالج، وعمليات القراءة والكتابة على القرص. - كلما زادت البيانات زادت حجم قاعدة البيانات، وسوف تصبح بطيئة في الاستعلام بسبب متطلبات المعالج والذاكرة وال I/O الإضافية. - قد تضيف مجموعه من الخصائص الجديدة Features مستقبلاً في الموقع وهذا يضيف ايضاً حملاً على الموقع. ## جعل السيرفر أقوى عن طريق Scaling Vertically عندما يصل التطبيق لأخر حد في السيرفر بسبب زيادة الضغط أو كمية البيانات أو المستخدمين في نفس الوقت) فسوف تحتاج ان تقرر كيف سوف تقوم ب Scaling. وهناك طريقتين هما Vertical وHorizontal. سوف يتم توضحيهم فيما يلي وسنبدأ نبدأ بالأبسط والأكثر شيوعاً وهو ال Vertical Scalability. **ال Vertical Scalability تتم من خلال زيادة قدرة السيرفر سواء بزيادة مواصفات Upgrading hardware أو زيادة قدرة الشبكة Network throughput. وهي في العادة ابسط الحلول للقيام لعمل Scalability لفترة قصيرة (short-term scalability) حيث أنها لا تتطلب أي تغير في طريقة بناء architecture الكود). مثلاً إذا كانت الذاكرة في السيرفر هي 8GB فيمكن ان تجعلها 32GB أو حتى 128GB بسهولة عن تطريق تغيير القطع، ولا تحتاج لتغيير أي جزئية في الكود. إذا كنت تستضيف موقعك في VPS فال Vertical Scaling تكون سهلة من خلال عدة نقرات في شاشة لوحة التحكم لمزود الخدمة.** هناك عدة طرق لكي تقوم بال Vertical Scaling: - إضافة المزيد من ال I/O عن طريق المزيد من الأقراص Hard Drives في Redundant Array of Independent Disks (اختصاراً RAID). ال I/O throughput وال Disk Saturation هي من أحد ال Bottleneck في قواعد البيانات. إضافة المزيد من الأقراص وتهيئة ال RAID يساعد في توزيع عمليات القراءة والكتابة على هذه الأقراص. في السنوات الأخيرة فال RAID 10أصبح شائعاً ويعطي المزيد من ال throughput وال Redundancy. من وجهة نظر التطبيق ال RAID Array هي كأنها قرص واحد Single Volume لكن بالأسفل هي مجموعه من الأقراص توزع عليها عمليات القراءة والكتابة. - تحسين سرعة ال I/O من خلال استخدام ال Solid State Drive (اختصاراً SSDs). فال SSD أصبحت شائعة ايضاً والاسعار سوف تقل مع الوقت. عمليات القراءة والكتابة العشوائية أسرع بحوالي 10 الى 100 مرة على حسب طريقة ال Benchmark المستخدمة. وبتبديل الأقراص الى هذا النوع سوف تقلل وقت انتظار I/O في تطبيقك. لسوء الحط عمليات القراءة والكتابة المتسلسلة Sequential ليست سريعة ولن ترى أداء أفضل من الأقراص العادية. العديد من قواعد البيانات المفتوحة مثلاً MySQL حسنت داخلياً في هيكلتها والخوارزميات حتى تسمح بعمليات متسلسلة أكثر Sequential Disk Operation من العمليات العشوائية Random Access I/O. لكن بعض القواعد مثلاً Cassandra تستخدم عمليات متسلسلة بطيئة لكل عمليات القراءة والكتابة وبالتالي هذا يجعل ال SSD لا يفيد كثيراً هنا. - تقليل ال I/O Operations عن طريق زيادة الذاكرة RAM، فحتى ال 128GB هي في المتناول هذه الأيام إذا كان لديك Dedicated Hardware. بإضافة المزيد من الذاكرة يعني المزيد من المساحة لل File System Cache وذاكرة أكبر للتطبيق. حجم الذاكرة مهم ايضاً في قواعد البيانات Database Servers. - تحسين ال Network Throughput من خلال ترقية ال Network interface او إضافة المزيد منها، فاذا كان السيرفر يقوم بعمل Streaming للكثير من الوسائط والفيديو فيمكن أن تقوم بعمل upgrade لل network provider’s connection او upgrade لل network adapter حتى يسمح للمزيد من ال throughput. - الانتقال الى سيرفر به معالجات أكثر أو Virtual Cores أكثر. السيرفر ب 12 او 24 ثريد (او Virtual Cores) أصبح متناول ايضاً، وكلما زاد المعالجات وال Virtual Cores كلما زادت المعالجة في نفس اللحظة، ويصبح النظام أسرع وليس فقط بسبب ان العمليات لن تتشارك كلها في معالج ولكن بسبب ان نظام التشغيل سوف يقوم بعمليات Context Switchingأقل ولن يحتاج لتشغيل أكثر من عملية في نفس ال Core. ال Vertical Scalability هي خيار ممتاز خصوصاً للتطبيقات الصغيرة او إذا كنت قادر على تحمل تكاليف إضافة المزيد من ال Hardware. والشيء الجيد هنا أنك لا تحتاج لتغيير أي كود في التطبيق. لكن في المقابل هناك بعض من المشاكل فيها ايضاً، اولاً التكلفة. حيث ان ال Vertical Scalability سوف تصبح مكلفة جداً بعد مرحلة معينة. الصورة 2 تبين ان ال scale up (ال Vertical Scaling) في البدء يكون رخيصاً ولكن بعد مرحلة معينة فسوف يصبح مكلفاً جداً، على سبيل المثال الحصول على 128GB من الذاكرة يكلف حوالي 3000$ ولكن ال 265GB تكلف حوالي 18000$ وهي أكثر من الضعف. [![الصورة 2 تكلفة ال Vertical Scalability مع الوقت](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-2.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-2.png) الصورة 2 تكلفة ال Vertical Scalability مع الوقت المشكلة الثانية في ال Vertical Scalability هي انها لها نهاية أيضاً. بعض النظر عن المبلغ الذي تود أن تدفعه فهناك نهاية لن تستطيع بعدها إضافة المزيد من الذاكرة او المعالجات او ال Cores أو حتى سرعة القرص. لذلك هناك نقطة نهاية ولن تجد hardware متوفر يستطيع ان يدعم ذلك النمو. المشكلة الثالثة وهي أن نظام التشغيل والبرامج وحتى التطبيق نقسه قد لا يسمح بال Vertical Scaling بعد مرحلة معينة، على سبيل المثال لن تستطيع إضافة المزيد من ال CPUs لتحسين ال MySQL وذلك بسبب مشكلة زيادة ال lock contention (خصوصاً إذا كنت تستخدم المحرك القديم من ال MySQL الذي يعرف ب MyISAM). **ال Locks تستخدم لعمل ال Synchronization بين ال Threads التي تعمل على مورد مشترك مثلاً الذاكرة او ملف. مشكلة ال Lock Contention هي مشكلة في الأداء Performance Bottleneck بسبب عدم معالجة ال lock بشكل جيد. استخدام ال lock يجب أن يكون بشكل معقول والا فمعظم وقت البرنامج سوف يكون في انتظار إزالة ال Lock، وإذا وصلت لهذه المرحلة lock contention فإضافة المعالجات CPUs لن يضيف لك نتيجة جيدة** الصورة 3 تبين أن ال vertical scalability لا تغير من هيكلة وبنية النظام System Architecture ولن تغير أي شيء في الكود. فقط سوف تضيف قطعة hardware أو تبدلها بأخرى أسرع أو أفضل في الإمكانيات. [![الصورة 3 سيرفر واحد ولكن بمواصفات أقوى](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-3-.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-3-.png) الصورة 3 سيرفر واحد ولكن بمواصفات أقوى ## فصل الخدمات Services Isolation ال Vertical Scalability ليس الخيار الوحيد في المراحل الأولى من التطور، هناك خيار بسيط وهو نقل أجزاء مختلفة من النظام على سيرفرات مختلفة وذلك بوضع كل خدمة على سيرفر مستقل physical machine. مثلاً وضع الموقع على ويب سيرفر في سيرفر منفصل، ووضع قاعدة البيانات على سيرفر آخر. وحتى ايضاً على مستوى الخدمات الأخرى فيمكن فصلها مثلاً File Transfer Protocol (اختصاراً FTP) أو ال DNS أو ال Cache فكل منهم يمكن ان تعمل على سيرفر منفصل. ال Services Isolations هي تطور أفضل من ال Single Server Setup ولكن لن يأخذك بعيداً. على أي حالة بمجرد تنزيلك الخدمات على السيرفرات لن تستطيع لن تجد أي مساحة أخرى للنمو Grow. **ال Cache هو خدمة أو سيرفر مهمته تقليل التأخير والموارد وذلك بإرسال النتائج التي سبق وتم جلبها أو حسابها، وال Caching هي خطوة مهمة جداً في ال Scalability وسوف نتحدث عنها فيما بعد في مقالة مخصصة.** الصورة 4 تبين شكل ال Infrastructure لكل من هذه الخدمات حيث كل منها على سيرفر منفصل، وهذه البنية تبدوا مشابه لبنية السيرفر الواحد Single Server Setup ولكن هي فصلت ال load ووزعتهم على بعض السيرفرات. هذه السيرفرات سواء كانت VPS أو Dedicated فكلها في data center واحدة. وال data center هنا هي مجموعه من السيرفرات وكل منها لديه وظيفة معينة. [![الصورة 4 وضع كل خدمة على سيرفر منفصل](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-4.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-4.png) الصورة 4 وضع كل خدمة على سيرفر منفصل فصل الخدمات هي خطوة جيدة تأتي كخيار تالي ل Single Server Setup، حيث يتم توزيع ال load على العديد من السيرفرات وتستطيع ايضاً عمل Vertical Scaling لكل منهم بشكل منفصل. هذه التركيبة Configuration شائعة في المواقع الصغيرة وفي شركات البرمجة التي تقدم خدمات للشركات، حيث يمكن مثلاً وضع كل مواقع العملاء الصغيرة في سيرفر واحد بينما العميل الكبير يوضع على سيرفر منفصل وايضاً على قاعدة بيانات منفصلة. وهذه إدارة جيدة للموارد. بنفس الطريقة، يمكن أن يوضع الموقع على عدة أجهزة عن طريق تقسيم الموقع الى أجزاء منفصلة واستضافتها في سيرفرات منفصلة، مثلاً إذا كان هناك لوحة تحكم Administrative Console يستطيع العملاء إدارة حسابتهم فيمكن ان تكون على موقع منفصل وتوضع على سيرفر منفصل. **الفكرة من ال Services Isolation هي انه عليك تقسيم تطبيقك الكبير الى مجموعه من الوظائف التي يمكن ان تضعها على سيرفرات منفصلة، وعملية تقسيم النظام بناء على الوظائف حتى تقوم بعمل Scaling لأي وظيفة منها بشكل منفصل تسمى Functional Partitioning.** الصورة 5 تبين كيف يقوم الموقع بتقسم المهام functional partitioning لكي يوزع الضغط load على سيرفرات مختلفة. فكل جزء من التطبيق لديه subdomain وال traffic سوف يوجه اليه على حسب ال IP. لاحظ أن كل من هذه الوظائف سوف تكون على سيرفرات قد تختلف مواصفتها وكل منهم لديه احتياجات Vertical Scaling مختلفة. فهذه المرونة في ال Scaling لكل جزء من التطبيق امر جيد. [![الصورة 5 تبين ال Configuration عند عمل Functional Partitioning للتطبيق](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-5.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-5.png) الصورة 5 تبين ال Configuration عند عمل Functional Partitioning للتطبيق ## ال Content Delivery Network: عمل Scaling للموارد الثابتة Static Content كل ما يكبر التطبيق ويزداد الضغط عليه بواسطة المستخدمين، فمن المفيد نقل الضغط offload traffic لأي جهة ثالثة third party تتولى مهمة ال Content Delivery Network (اختصاراً CDN). **ال Content Delivery Network هي خدمة تتولى توزيع الملفات الثابتة مثل الصور، ملفات الجافا سكربت، ال CSS، الفيديو. فهي تعمل ك HTTP Proxy. فعندما يريد المستخدم (المتصفح) تحميل الصور أو أي من الملفات الثابتة فسوف يتصل بأحد السيرفرات التي يقدمها ال CDN بدلاً من سيرفرك. وإذا لم يتوافر ذلك الملف على ال CDN فسوف يطلبها من سيرفرك وبعدها يقوم بالاحتفاظ بها Caching لديه. بعد ذلك أي طلب اخر على ذلك الملف سوف يرسل مباشرة بدون الحاجة الى الاتصال بسيرفرك مرة أخرى.** بربط تطبيقك مع ال CDN Provider سوف تقلل الكثير من ال Bandwidth وسوف تحتاج مجموعه صغيرة من السيرفرات حتى تخدم طلبات ال static content. ايضاً سوف يستفيد المستخدمين من ال Resources Locality حيث ان ال CDN Providers هي شركات لديها data centers موجودة في أماكن مختلفة في انحاء العالم. فاذا كان ال data center موجود على شمال أمريكا فالمستخدمين من أوروبا سوف يكون لديهم تأخير واضح higher latencies وفي هذه الحالة ال CDN سوف يسرع عملية تحميل الصفحة page load time لهؤلاء المستخدمين لأنه يزود بالملفات الثابتة من أقرب data center متوفر لهم. الصورة 6 توضح كيف يرتبط التطبيق مع ال CDN Provider، العميل سوف يتصل بال DNS Server وبعدها يطلب الصفحات من سيرفر ويقوم بتحميل الموارد الأخرى مثل الصورة وملفات CSS من ال CDN Provider. والنتيجة هو أن سيرفرك سوف يقل عليه الضغط وتكون هذه مسؤولية ال CDN والتي سوف يحلها بشكل جيد وتكلفة أرخص مما لو كنت ستقوم بها بنفسك. [![الصورة 6 يوضح كيف يعمل Content Delivery Network Provider مع تطبيقك](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-6.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-6.png) الصورة 6 يوضح كيف يعمل Content Delivery Network Provider مع تطبيقك الشيء الذي يهم هنا هو أن هذه المرة الأولى التي نستخدم third party service للقيام بعملية ال Scaling. وربما قد تظنها أن هذه خدعة في عملية ال scalability ولكنها حقيقة خطوة جيدة خصوصاً للشركات الناشئة التي تكون في اول مراحل التطوير ولا تستطيع وضع جهد او مال في ذلك الموضوع. ## توزيع الضغط traffic عن طريق ال Horizontal Scalability كل المراحل evolution stages التي ذكرناها حتى الان تعتبر تعديلات بسيطة على تركيبة السيرفر الواحد Single Server Setup، بينما ال Horizontal Scalability هي أصعب في التطبيق وأغلب الأحيان تحتاج أن تضعها بالاعتبار قبل أن تبدأ في برمجة التطبيق. وفي حالات نادرة فيمكن ان تضاف لاحقاً عن طريق تعديل معمارية architecture المشروع لكن هذا يتطلب مجهود كبير في التطوير. تستطيع ان تتخيل الان انها عبارة عن تشغيل عدة مكونات على عدة سيرفرات وتستطيع إضافة عدة سيرفرات متى ما اردت. والأنظمة التي لديها القدرة على ال Horizontal Scaling لا تحتاج لسيرفرات قوية، بالعكس تماماً فهي عادة تعمل على سيرفرات عادية رخيصة. **ال Horizontal Scalability تكون عن طريق إضافة المزيد من السيرفرات، وهي الهدف في ال Scalability حيث تحل مشكلة التكلفة الضخمة المرتبطة بالسيرفرات القوية، وتستطيع إضافة العديد من السيرفرات ولا توجد نهاية في ذلك بعكس النهاية الموجودة في ال Vertical Scalability.** الصورة 7 توضح الفرق في التكلفة بين ال Horizontal Scalability وال Vertical Scalability، الخط المنقط يوضح التكلفة لل Vertical Scalability بينما الخط المتصل يوضح لل Horizontal Scalability. [![الصورة 7 يوضح الفرق بين التكلفة في ال vertical وال Horizontal.](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-7-.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-7-.png) الصورة 7 يوضح الفرق بين التكلفة في ال vertical وال Horizontal. عادة ما تتضح فائدة ال Horizontal Scaling في مراحل تالية في المشروع، حيث في البداية تكون تكلفتها اعلى لأنها تتطلب المزيد من العمل والتطوير لكي يدعم المشروع هذه الطريقة. وبعض الأحيان تكلف أكثر من ناحية السيرفرات المطلوبة في تشغيل المشروع بشكله الأولى. واحياناً بسبب أنك تحتاج لمهندسين لديهم خبرة أوسع في التطوير والتشغيل ايضاً. النقطة المهمة هو انه بعد أن تتعدى نقطة معينة من ال capacity فال horizontal scalability سوف تتضح ثمرته، حيث تقل التكلفة ولن تصل لمرحلة النهاية التي تحصل في ال vertical scaling عندما لا تجد أي hardware اقوى. **أيضاً عمل ال Horizontal Scaling باستخدام ال CDN Provider يقلل عليك الكثير من الأمور مثل التكلفة وبالرغم من أنه سيتم محاسبتك بشكل أكبر كلما زاد الضغط على الموقع، لكن التكلفة او السعر هي ثابت لكل سعة تريدها وسوف تتضاعف التكلفة مثلاً إذا تضاعف عدد الزوار، لكن هناك بعض المزودين يقل السعر كلما زاد الضغط على الموقع مثلاً Amazon CloudFront سوف يحاسبك 0.12$ لكل GB حتى اول 10 TB بعد ذلك سوف يقل السعر الى 0.08$ لكل GB.** للنظر الى ال Infrastructure الان في هذه المرحلة بعد أن قمنا بوضع كل جزء من النظام على سيرفرات مختلفة سوف يكون كما في الصورة 8. [![الصورة 8 تبين عدة سيرفرات لكل منها غرض معين](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-8.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-8.png) الصورة 8 تبين عدة سيرفرات لكل منها غرض معين الشيء الذي يميز ال horizontal scaling عن المراحل السابقة هي أن كل سيرفر يمكن أن يقوى scaled عن طريقة إضافة المزيد من السيرفرات. وهذا يمكن تطبيقه على مراحل فمثلاً بعض الخدمات تكون Horizontal Scalable وبعضها تكون لا تكون. ولأن القيام بال Horizontal Scalability أمر صعب ومكلف فيجب أن تقوم بعمل scaling Horizontal في الأماكن السهل القيام بها مثلاً الويب سيرفر وال caches وبعدها انظر للمناطق الأكثر صعوبة مثلاً قواعد البيانات أو أي طرق التخزين الأخرى. في هذه المرحلة من التطور بعض التطبيقات تستخدم Round Robin DNS لكي توزع ال traffic على العديد من السيرفرات. لكن ال Round Robin DNS هي ليست الطريقة الوحيدة لذلك، وسوف نناقش الطرق الأخرى في مقال آخر. **ال Round Robin DNS هو DNS Server يسمح بربط عنوان واحد domain name بعدة IPs. ال DNS العادي يأخذ Domain مثلاً informatic-ar.com ويجلب ال IP الوحيد له مثلاً 172.236.152.169\. ولكن ال Round Robin يربط اسم الموقع بعدة عناوين وكل منها مرتبط بسيرفر منفصل. وهكذا كل ما يطلب العميل رقم ال IP للموقع name resolution فيقوم بال DNS بالرد بأحد العناوين الموجود عنده. الهدف هو توزيع الطلبات على السيرفرات الموجودة للموقع فالمستخدمين قد يتعاملوا مع سيرفرات مختلفة بدون أي يدروا عن ذلك.** ## ال Scalability لمستخدمين من كل انحاء العالم Global Audience المواقع الكبيرة تصل لآخر مرحلة في ال evolution وهي للمستخدمين من كل انحاء العالم. فبمجرد أن يكون لديك ملايين من المستخدمين من كل انحاء العالم فسوف تحتاج لأكثر من data center. ال data center الواحد يحتوي على العديد من السيرفرات لكنه سوف يكون بطيئاً لمستخدمين في منطقة جغرافية أخرى. ولكن مع مجموعه من ال data center فهذا ايضاً يجعلك مهيئ للكوارث والاحداث النادرة مثلاً عاصفة أو حريق. ال Scaling للمستخدمين من كل انحاء العالم في الكثير من التحديات ويحتاج المزيد من العمل. أحد الأمور التي ينبغي العمل عليها هو استخدام GeoDNS. **ال GeoDNS هو DNS Service تسمح لأسماء المواقع بأن ترتبط Resolved بعناوين IPs تكون على حسب مكان العميل. ال DNS العادي يحول اسم الموقع الى عنوان IP بينما ال GeoDNS يقوم بنفس المهمة من وجهة نظر المستخدم لكن يعطي رقم IP مختلف بناء على موقع العميل. مثلاً العميل المتصل من أوروبا يحصل على عنوان IP يختلف عن العميل المتصل من استراليا. النتيجة أن كل عميل يتصل بال data center الأقرب لمكانه وبالتالي يقلل تأخير الشبكة network latency.** إضافة أخرى في ال infrastructure هي باستضافة عدة من سيرفرات Edge-Cache تكون موجودة حول العالم لتقليل تأخير الشبكة أيضاً. استخدام ال Edge-Cache Servers يعتمد على حسب طبيعة التطبيق، فال Edge-Cache Servers تكون أكثر كفاءة عندما تعمل ك Reverse Proxy Servers تقوم بعمل Caching كل الصفحات لكن ايضاً يمكن جعلها تقوم بمهام أخرى. **ال Edge Cache هو HTTP Cache server يكون موجود قرب العميل، ويسمح للعميل باحتفاظ جزئي partially cache لل http traffic. الطلبات التي تأتي من متصفح العميل تذهب الى ال Edge-Cache server وهو الذي يقرر هل يخدم الطلب من ال Cache أو ان يجمع الأجزاء الناقصة من الصفحة عن طريق ارسال طلبات الى ال web servers وايضاً يقرر إذا كانت الصفحة غير محتفظة uncacheable ويقوم بجلبها كاملة من السيرفر. ال Edge-Cache يخدم اما الصفحات كاملة او أجزاء منها cache fragments of HTTP response.** الصورة 9 يوضح كيف تعمل عدة data centers وتخدم طلبات المستخدمين الموجودين في شتى انحاء العالم. المستخدمين الموجودين في أوروبا سوف يحصوا على ال IP لأحد ال Edge-Cache القريب من ذلك المكان ويمكن ان تتم خدمتهم من ال Cache أو من أحد ال Web Servers. وايضاً يتم تحميل الملفات الثابتة كالصور وال CSS من خلال CND Provider، ولأن ال CDN Provider لديه ايضاً data centers موجودة في عدة دول فهذا الملفات سوف ترسل من أقرب data center للعميل. وهكذا كل ما ينمو التطبيق سوف تقسم الى المزيد من ال data centers تكون أقرب للجمهور. وهذا يقلل التأخير والتكلفة ايضاً. [![الصورة 9 توضح عدة عملاء وكيف تتم خدمتهم بواسطة ال local edge caches لهم](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-9-846x1024.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-9.png) الصورة 9 توضح عدة عملاء وكيف تتم خدمتهم بواسطة ال local edge caches لهم بعد أن ناقشنا هذه ال infrastructure بشكل مبسط، سوف ننظر كيف يعمل ال data center الواحد وكيف يدعم ال Scalability. # **نظرة حول ال** Data Center Infrastructure سوف نتحدث الان حول عدة تقنيات مختلفة تستخدم في تطبيقات الويب الحديثة، وسوف نضع الشكل العام للآلية وكيفية تنتقل الطلبات بين الوحدات المختلفة. [![الصورة 10 توضح الشكل العام لل Data Center Infrastructure](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-10-744x1024.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-10.png) الصورة 10 توضح الشكل العام لل Data Center Infrastructure الصورة 10 تبين الشكل العام للطلب Communication Flow بدئاً من جهاز المستخدم مروراً بعدة طبقات مختلفة في ال Infrastructure. وهذا التصميم يعتبر من الأمور المهمة في تصميم وتطبيق تطبيقات Scalable. لذلك يمكن ان تعتبرها كمرجعية لك تعود لها كلما تحدثنا في مقالات تالية عن موضوع ال Scalability. وايضاً المقالات التالية سوف تتحدث عن كل جزئ منهم بالتفصيل بحيث كل مقالة تتولى مهمة جزء (المرقمة بأرقام) من التصميم وتغطيه بشكل كامل. العديد من الأجزاء التي في الصورة 10 لها مهمة واضحة ويمكن ان تضاف او تحذف بشكل مستقل. على أي حالة فمن الشائع أن ترى كل الأجزاء تعمل مع بعضها البعض في غالب التطبيقات الكبيرة. وسنبدأ نلقى الضوء على أي منهم بشكل مبسط. ## الواجهة الامامية Font Line الواجهة الأمامية هي الجزء الأول من ال Stack وهي مجموعه من المكونات التي يتعامل معها المستخدمين بشكل مباشرة. جزء من ال font line يمكن أن تكون داخل ال data center أو تكون خارجها وذلك حسب ال configuration وال third party services المستخدمة. هذه المكونات components لا يوجد بها أي business logic وهدفها الأساسي هي زيادة ال capacity والسماح بال Scalability. لنبدأ بالطلب من المستخدم من الأعلى، حيث تذهب الطلبات الى ال GeoDNS Server ويقوم بعمل Resolving لاسم الموقع ويقرر ما هو ال data center المناسب لذلك العميل ويرجع له رقم ال IP لل Load Balancer على ذلك ال Data Center. **ال Load Balancer هو برنامج أو جهاز Software/Hardware يقوم بتوزيع الطلبات التي تأتي لرقم IP واحد الى عدة من السيرفرات والتي تكون مخفية أسفل ال Load Balancer. ال Load Balancer تأتيه كل الطلبات ويقوم بتوزيعها وهو يسمح للسيرفرات بأن تدخل أو تحذف، فلا مشكلة لأن كل الطلبات تأتي اليه وهو الذي يتولى توزيعها فالسيرفرات التي تضاف لن تؤثر على الخدمة.** الطلبات Traffic التي تأتي من الانترنت تكون موجهه الى رقم عنوان IP واحد ويكون وهو Hardware Load Balancer قوي، ويقوم بتوزيعها على ال Font Cache Servers (رقم 3 في الصورة) او مباشرة تذهب الى ال Web Application Servers (رقم 4 في الصورة). ال Front Cache Servers هي اختيارية، ويمكن أن توضع Deployed على مكان آخر خارج ال data center او ان لا تضعها من الأساس. في بعض الحالات يفضل أن تكون لديك هذه الطبقة Font-End Cache والسيرفرات بها سوف تقلل الضغط Load الذي يوضع على بقية ال Infrastructure. من الشائع استخدام أشياء جاهزة من جهة خارجية third party services مثلاً Load Balancer أو CDN أو Reverse Proxy servers. في هذه الحالات فأن هذه الطبقة قد تكون مستضافة على سيرفرات خاصة بمزود الخدمة. وسوف نتحدث عن الفوائد والعيوب في ال Scaling باستخدام جهة خارجية Third Party في مقالات تالية. ## طبقة تطبيق الويب Web Application Layer الطبقة الثانية في ال stack هي طبقة تطبيق الويب وهي تتكون من سيرفرات التطبيق (رقم 4 في الصورة) التي تكون مسؤولة عن معالجة طلب المستخدم وارجاع ال HTML أو البيانات من التطبيق. هذه السيرفرات تستخدم في الغالب مثلاً PHP, Java, Ruby, Groovy ويكون بها القليل من ال business logic لأن الهدف الأساسي من هذه السيرفرات هي عرض واجهة المستخدم User Interface. وسوف تقوم هذه الطبقة بمعالجة طلب المستخدم واستدعاء خدمات الويب المناسبة Web Services Call لتحقيق المهمة. وكلما تكون هذه الطبقة أخف Dumber كلما يكون أفضل ويكون كل ال business logic في ال web services. هكذا تزيد من ال Reuse وتقلل من التغييرات المطلوبة لأنه في الغالب ال Presentation Layer هي التي تتغير باستمرار. سيرفرات تطبيق الويب من السهل ان تقوم عمل Scaling بها لأنها Stateless بشكل كامل (أو يفترض ذلك). وإذا كانت Stateless فإضافة ال capacity لها هي مجرد إضافة المزيد من السيرفرات في ال Load Balancer Pool. المقالة الثانية في هذه السلسلة سوف تتحدث عن ال front layer بالإضافة الى تطبيق الويب بتفاصيلهما. ## طبقة ال Web Service الطبقة الثالثة في ال Stack هي طبقة خدمات الويب Web Services (رقم 7 في الصورة). وهي طبقة مهمة لأنها تحتوي على كل ال Business Logic، حيث جعلنا ال Font-End Servers خفيفة بدون أي Business Logic حتى نفصل ال Presentation Layer عن Business Logic. ايضاً بإنشاء خدمات الويب فمن السهل القيام بعمل ال Functional Partitions حيث يمكن انشاء خدمات ويب مختصة بالقيام بمهام معينة ونقوم بتقويتها Scaling بشكل مفصول عن الخدمات الأخرى. على سبيل المثال في التطبيقات التجارية E-Commerce قد يكون لديك ويب سيرفس خاصة بالمنتجات وخدمة ويب خاصة بملف المستخدم وكل منهم لديها وظائف خاصة ولديها احتياجات Scalability مختلفة. طريقة التواصل بين ال font-end applications مع ال web services تكون اما من خلال Representational State Transfer (اختصاراً REST) أو من خلال Simple Object Access Protocol (اختصاراً SOAP) من خلال بروتكول HTTP. وعلى حسب ال implementation فيمكن أن يكون خدمة الويب ايضاً سهلة ال Scaling. فطالما هي Stateless فال Horizontal Scaling سهلة بمجرد إضافة المزيد من الأجهزة على ال Pool. مؤخراً في السنوات الأخيرة أصبح ال Integration مع تطبيقات الويب شائعاً لذلك من الشائع ان تجد ال Web Services متاحة مباشرة للعملاء ولذلك يتم وضعها بالتوازي مع ال font end application servers بدلاً من اخفائها بالأسفل. المقالة الثالثة من السلسلة سوف تتناول خدمات الويب وكيف يمكن تقسيمها بحيث تسهل التطوير وال Scalability. ## مكونات أخرى Additional Components لأن كل من سيرفرات ال font-end وال web services يجب أن تكون states. فان ال web application عادة ما يوضع به عدة مكونات إضافية مثل object caches (رقم 5 في الصورة) وال message queues (رقم 6 في الصورة). سيرفرات ال caches تستخدم بواسطة ال font end application servers وايضاً ال web services لتقليل الضغط على قاعدة البيانات وتسرع ايضاً الرد لأنها تحفظ أجزاء من النتائج المسبقة حسابها partially precomputed results. ال message queue ايضاً تستخدم لتأجيل postpone بعض المعالجة لمرحلة تالية وايضاً لإسناد هذه المهمة لمجموعة من أخرى من الأجهزة التي تتنظر هذه الرسائل لكي تقوم بمعالجتها (رقم 11 في الصورة). الرسائل messages عادة ترسل الى message queues من خلال ال font end applications او ال web services ويتم معالجتها بواسطة أجهزة مختصة لمعالجة الرسائل. بعض الأحيان يكون لل web application عدة cluster من السيرفرات التي تعمل على تنفيذ ومعالجة أشياء في الخلفية batch processing servers أو سيرفرات تقوم بعمل بعض من ال jobs في وقت schedule معين (بواسطة cron مثلاً). هذه السيرفرات (رقم 11 في الصورة) لن تدخل في توليد الرد response على طلبات المستخدمين وانما هي offline job processing servers تقوم بعمل مهام مثلاً asynchronous notification أو order fulfillment وغيرها من المهام والتي ان وضعت في التطبيق سوف تبطئ سرعة الرد والاستجابة high latency functions لذلك يتم فصلها. سوف يتم مناقشة كل من ال caches وال message queues في مقالات منفصلة في هذه السلسلة. ## طبقة تخزين البيانات Data Persistence Layer وصلنا لطبقة تخزين البيانات (رقم 8 ورقم 9 في الصورة) وهي الطبقة الأصعب في ال horizontal scaling لذلك سوف تحتاج الكثير من النقاش حول الخيارات الموجودة لعمل ذلك ال scaling في هذه الطبقة. خصوصاً مع ظهور العديد من التقنيات الجديدة التي تسمى NoSQL أو Big Data حيث أن البيانات تزداد دائماً والتي تحتاج للتخزين والمعالجة بغض النظر عن المصدر والشكل. خلال ال 10 سنوات الماضية أصبحت هذه الطبقة بها بعض التغييرات فقد ذهبت الأيام التي تضع فقط قاعدة بيانات واحدة ضخمة single monolithic SQL database بها كل شيء. وكما قال Martin Fowler هي مرحلة ال Polyglot persistence حيث تستطيع استخدام عدة data store مع بعضهما حتى تضمن القيام ب scalability أفضل. سوف نتحدث عن هذه الطبقة في مقالة منفصلة. رقم 9 في الصورة هي للبحث Search Engine ولأن البحث تغير خلال السنوات السابقة وأصبح هناك العديد من الخيارات خارج إطار قاعدة البيانات فقد وضعناها كمكون منفصل لأن لها ايضاً العديد من التفاصيل غير المتعلقة بطريقة التخزين، وسوف يكون لها مقالة منفردة تتحدث عن الأساليب والتقنيات التي تستخدم في البحث. ## ال Data Center Infrastructure حتى هذه اللحظة لدينا الكثير من التقنيات في ال infrastructure وايضاً كما تلاحظ زاد العقيد بشكل أكبر عما كان عليه في السابق عندما كان كل شيء على سيرفر واحد Single Server Setup. الذي انجزناه هو قابلية توزيع الضغط على العديد من السيرفرات. وكل مكون في الصورة 1-10 لديه مهمة معينة ويساعد في عمل ال Scaling لملايين من المستخدمين. طريقة الطبقات ايضاً في الصورة كانت مقصودة حتى تقلل الضغط على المكونات البطيئة، فكما ترى أن كل الطلبات تأتي الى ال Load Balancer سوف تقسم على ال font end cache servers، ولأن بعض الطلبات هي cache hits فهذا يقلل ال traffic حيث يرجع ال font end servers النتيجة (رقم 4 في الصورة). ايضاً هناك ال application level caches (رقم 5 في الصورة) وال message queues (رقم 6 في الصورة) تساعد على تقليل الضغط بشكل أوسع وهذا يعني طلبات اقل سوف تصل لل backend web services (رقم 7 في الصورة). خدمات الويب تستطيع ايضاً استخدام ال message queue وال cache servers وإذا احتاجت فسوف تتعامل مع ال search engine وطريقة التخزين data store. بإضافة كل هذه ال scalable layer فوق ال data layer فتستطيع عمل ال scale على النظام بشكل جيد cost-effective way. من المهم تذكر أنه قد لا تحتاج لكل من هذه المكونات لعمل ال Scaling، وعليك باستخدام أفضل تقنيات ممكنة لأن إضافة أي تقنية جديدة سوف تضيف المزيد من التعقيد والصعوبة في الصيانة والتكلفة ايضاً بها. وجود مكونات كثيرة ربما شيء مثير وتجربة جديدة لكن يجعل الإصدارات releases والصيانة maintenance وإجراءات ال recovery أكثر صعوبة. إذا كانت تطبيقك يحتاج مجرد صفحة بحث بسيطة فربما يكفي وجود font end servers وال search engine cluster وهذا ما تحتاجه لل scale. فاذا كنت تستطيع عمل scale لأي طبقة عن طريقة إضافة المزيد من السيرفرات وسوف يعمل ال business جيداً فلماذا تزعج نفسك بإضافة العديد من الطبقات الإضافية. # **حول معمارية التطبيق** Application Architecture تحدثنا قبل قليل حول ال Infrastructure ومراحل تطويرها evolution stages والآن سوف ننظر الى التطبيق نفسه. معمارية التطبيق لا يجب أن تبنى حول أي تقنية أو framework معين، فالمعمارية Architecture هي ليست حول جافا أو PHP أو PostgreSQL أو حتى أي Database Scheme معينة، المعمارية يجب أن تبنى على حسب ال Business Model. هناك الكثير من الكتب والنقاشات حول موضوع ال domain-drive design ومعمارية الأنظمة يمكن ان تنظر لها للمزيد حول العادات الجيدة في بناء وتصميم البرمجيات. ولاتباع تلك العادة الجيدة سوف يكون ال business logic هو قلب المعمارية حيث أن متطلبات ال business هي التي سوف نعتمد عليها في أي قرارات نأخذها. وبدون هذه المتطلبات ال business logic الصحيحة وال domain model الواضح فربما كل طرق التخزين database أو ال message queues وحتى ال web formworks قد لا تكون مناسبة. بغض النظر كان التطبيق هو social networking website أو خدمة للأطباء مثلاً او أي فكرة أخرى فهي في الأخير سوف يكون لها احتياجات ومتطلبات واضحة business need. وبوضع هذا الأمر في الاعتبار عند تصميم ووضع المعمارية سوف تكون متأكداً من ان المكونات التي تضعها لها هدف يخدم ذلك ال business وليس العكس. لذلك عندما تضع التقنية أو اللغة اولاً قبل النظر لل business ربما تحصل على تطبيق تقني جيد مثلاً Rails Application ولكن لن يكون تطبيق يخدم الهدف وال business جيداً. **ال domain model يمثل كل الأجزاء الأساسية في المشروع بلغة ال business وليس بلغة المطورين أو الأشخاص التقنيين. ال domain model يوضح المصطلحات، ال actors والعمليات بغض النظر عن طريقة تطبيقها التقنية. ال domain model مثلاً Automated teller machine (اختصاراً ATM) سوف تعني أشياء مثلاً الكاش cash، الحساب account، السحب credit الإيداع debit والصلاحيات authentication والسياسات الأمنية security polices وغيرها. وفي نفس الوقت ال domain model لا يهتم كثيراً بطريقة التطبيق سواء كان software أو hardware. ال domain model هو أداة لإنشاء الصورة العامة لمشكلة ال business التي يفترض أن يقوم تطبيقك بحلها.** الصورة 11 توضح الصورة المبسطة لكيف يمكن أن تكون أجزاء التطبيق من الداخل، والمستخدم سوف ينظر له على انه نظام واحد فقط لكنه من الداخل يكون موزع على العديد من الخدمات. [![الصورة 11 توضح الشكل العام لمعمارية التطبيق](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-11.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-11.png) الصورة 11 توضح الشكل العام لمعمارية التطبيق سوف نناقش الان كل من هذه المكونات بشكل مبسط، وبنفس الطريقة التي ناقشنا بها ال Infrastructure diagram سوف نتبع نفس الطريقة وهي أن يأتي الطلب من الأعلى من المستخدم. ## ال Front End ال Font End لديه هدف واحد وهو ان يكون واجهة المستخدم User Interface. حيث يتعامل معها المستخدم سواء عبر صفحات الموقع أو تطبيقات الجوال أو استدعاءات خدمات الويب. بغض النظر عن طريقة التواصل معها فال front end application هي طبقة تأخذ الطلبات التي تأتيها من المستخدم وتقوم باستدعاء خدمات الويب internal services call. ويمكن أن تعتبرها ك skin أو plugin للتطبيق تقدم خدمات النظام للمستخدمين. وهي لا يجب أن تكون قلب النظام بل تكون خفيفة بقدر المستطاع. بجعل هذه الطبقة خفيفة dumb سوف تستطيع إعادة استخدام reuse ال business logic لأنها تكون موجود على طبقة خدمة الويب وبالتالي تتجنب خطر أن يكون هناك تشابك coupling مع ال presentation logic. بالطبع كود ال font end يرتبط مع ال web framework المستخدم سواء Spring أو Rails المهم هو ان تعمل من خلال HTTP بالإضافة الى دعم AJAX وال Web Sessions. بإخفاء كل هذا في هذه الطبقة فسوف تكون طبقة الخدمات web services layer مختصة فقط بال business logic وليس العرض أو أي شيء متعلق بتقنية ما. لهذا الأمر فوائد عدة أولها أن ال business logic يكون مفصول وبالتالي يسمح للتطوير عليه بشكل مستقل، الفائدة الأخرى هذا يسمح باستخدام عدة تقنيات لكل طبقة بشكل مختلف مثلاً تستطيع تطوير ال font end من خلال PHP أو Groovy بينما خدمات الويب تكون مثلاً بالجافا. **يمكن ان تنظر لل font end application على انه إضافة plugin يمكن أن تحذف وتكتب بلغة أخرى وتضاف تلك الجديدة. مثلاً تستطيع حذف http front end وتضيف مثلاً Mobile application أو command line front end وهكذا يكون لديك الخيارات وفي نفس الوقت مفصولة عن ال business logic*.*** ال font end لا يجب أن تعرف أي شيء عن ال database أو أي خدمات أخرى، والمشاريع التي تسمح بوضع business logic في ال font end code سوف تعاني من مشاكل التعقيد ونسخ الأكواد وصعوبة ال Reuse. اخر نقطة ال font end components يمكن ان ترسل رسائل الى message queues أو تستخدم cache back ends لأنهم يفيدان في السرعة فكل ما يكون لديك cache لكل صفحة ال html أو جزء منها كل ما قللت المعالجة وسرعت العملية. ## خدمات الويب Web Services خدمات الويب هي التي تحتوي على كل المعالجة وايضاً تحتوي على اغلب ال business logic. الصورة 1-11 بينت ال web service أنها قلب المعمارية وأنها في الوسط. وهذا في الغالب يدعى Service Oriented Architecture (اختصاراً SOA). مصطلح ال SOA له كثير من المعاني والتعاريف وقد يختلف على حسب السياق او الشخص ايضاً. **ال Service Oriented Architecture (اختصاراً SOA) هي معمارية تعتمد على ال Loosely coupled وال highly autonomous services وهدفها حل وتطبيق احتياجات ال business. في ال SOA فيفضل أن تكون لكل الخدمات عقد contract واضح وتستخدم نفس البروتوكول. بغض النظر عن ال Implementation سواء كان SOAP أو REST (باي صيغة JSON أو XML) لأن هذه تعتبر Implementation details. المهم هو ان الخدمات Loosely coupled** **ولديها هدف معين تريد القيام بها**. **انظر للتقارب في المصطلحات بين ال SOA (service oriented architecture) وال SOAP (الاختصار الأول كان هو simple object access protocol) وقد تبدو متشابه SOA وال SOAP ولكن في الحقيقة ان ال SOA هو architecture style بينما ال SOAP هي مجموعة من التقنيات تستخدم لتعريف وإيجاد واستخدام خدمات الويب. فيمكن أن يكون لديك SOA بدون SOAP ويمكن أن تستخدم SOAP ايضاً في أي architecture styles أخرى.** تذكر أن ال SOA هي ليست حل لكل المشاكل وهناك طرق أخرى architecture styles مثلاً layered architecture وهناك ايضاً hexagonal architecture وهناك أيضاً event-driven architecture. ال multilayer architecture هي طريقة لتقسم الوظائف الى مجموعه من الطبقات. المكونات في الطبقات السفلى تعطي Application Programming Interface (اختصاراً API) يمكن أن تتعامل معها الطبقة التي تكون أعلى منها ولكن لن تسمح لطبقة في الأسفل أن تعتمد على وظيفة معينة في أي من الطبقات الأعلى. مثال على ال Layered architecture هو في نظام التشغيل ومكوناته مثلاً الصورة 12 تبين ان هناك hardware وتعاريف الأجهزة ونواة نظام التشغيل، ومكتبات نظام التشغيل، والمكتبات الخارجية، واخيراً التطبيقات. كل طبقة منها تستخدم الخدمات التي تقدمها الطبقة التي تكون أسفل منها وليس العكس. مثال ايضاً هو بروتوكول TCP/IP حيث كل طبقة تضيف مهمة وتعتمد على ال contract التي يوجد في الطبقة التي أسفل منها. [![الصورة 12 تبين مثال على multilayer architecture](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-12.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-12.png) الصورة 12 تبين مثال على multilayer architecture تطبيق الطبقات بشكل صحيح تجبرك على عمل هيكلة structure وتقلل من التداخل لأن الطبقات التي في الأسفل سوف تكون بسيطة ولا تعتمد على شيء من النظام، ايضاً بالإمكان تبديل أي مكون في الطبقات السفلى بأي مكون طالما يطبق نفس ال API. لكن تغير ال API نفسها يكون أسهل في الطبقات الأعلى لأنه لا يوجد شيء تعتمد عليه بعكس الطبقات السفلى فعملية تغير ال API مكلفة لأن الطبقة الأعلى منها تعتمد عليها وتحتاج أن تتغير ايضاً. **ال Event Driven Architecture (اختصاراً EDA) ببساطة هي طريقة أخرى في التفكير حول الاحداث actions. وهو كما يدل اسمها فهي تكون حول كيف تتعامل مع الاحداث التي تحصل. الطريقة التقليدية هي الاستجابة للطلب بعد أن يصل وبعدها ينتهي الطلب وترجع النتيجة مثلاً تقوم باستدعاء دالة عمل الحساب createUserAccount وسوف تنتظر حتى تنتهي الدالة وتعود بالنتيجة. لكن في ال EDA انت لا تنتظر النتيجة، وفقط سوف ترسل ان الطلب تم ارساله userAccountFormSubmitted. طريقة التفكير هذه تقود الى العديد من الأشياء الجيد وسوف نتحدث عنها في مقالة منفصلة عن ال asynchronous processing.** [![الصورة 13 تبين الفرق بين الطريقة التقليدية وطريقة ال EDA.](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-13.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-13.png) الصورة 13 تبين الفرق بين الطريقة التقليدية وطريقة ال EDA. بغض النظر عن ال architecture style الذي تستخدمه، فكل المعماريات سوف تحصل على الفائدة عندما تقسم الى أجزاء صغيرة مستقلة. والهدف منهم هو عمل abstraction أكبر بحيث يخفى التعقيدات ويقلل الاعتماديات dependencies ويسمح بال scaling لكل جزئية بشكل منفصل وايضاً يسهل لفريق العمل التطوير على الأجزاء بالتوازي. **فكر ان طبقة خدمات الويب هي مجموعه من الخدمات الذاتية highly autonomous applications وكل خدمة تكون تطبيق بحد ذاتها. وقد تعتمد خدمة الويب على خدمة اخر لكن كلما قلت الاعتمادية كلما صارت أفضل. وال abstraction الذي تقدمة هذه الخدمات يسمح لك بفهم النظام بسهولة حيث كل خدمة تخفي التفاصيل في كيفية عملها وتقدم لك API يسهل التعامل معها.** الصورة 14 توضح مجموعه من خدمات الويب لمنصة e-commerce وسوف تجد أن خدمة تحليل النصوص هي خدمة مستقلة مهمتها تحليل النصوص وفهم المواضيع على حسب المحتوى ومثل هذه الخدمة لا تحتاج أي مساعدة من خدمات أخرى. [![الصورة 14 تبين مجموعه من الخدمات في طبقة خدمة الويب](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-14.png)](https://informatic-ar.com/wp-content/uploads/2016/03/Figure-1-14.png) الصورة 14 تبين مجموعه من الخدمات في طبقة خدمة الويب لسوء الحظ، فأحيانا يكون من المستحيل فصل كل الخدمات بهذا الشكل، وفي غالب الوقت سوف تجد العديد من الاعتماديات بين الخدمات. مثلاً خدمة Customer Segmentation هي خدمة تعتمد على أنشطة المستخدم وبيانات الشبكات الاجتماعية أو أي معامل اخر، ولوضع المستخدمين الى أي من هذه ال segments قد تحتاج الى بيانات المستخدم وتفاصيل الأنشطة وقد تتصل بخدمات أخرى موجودة (بخلاف خدمة تحليل النصوص فهي مستقلة). وحتى لو كانت third party وبغض النظر عن ال implementation الموجود في طبقة خدمات الويب تذكر ان الهدف الأساسي هو solve business need. ## تقنيات مساعدة Supporting Technologies الصورة 1-11 أظهرت بعض الخدمات الأخرى المساعدة حول خدمات الويب مثلاً message queue وال application cache وال main data store وال search engine. وهي جميعهم مستقلة ويمكن ان تطبقها بتقنيات ولغات مختلفة وغالباً ما تستخدم مكونات third party جاهزة او برمجيات جاهزة بها ولذلك تستطيع ان تنظر لها ك black box عندما تتحدث عن المعمارية لمشروعك. لاحظ ايضاً ان قاعدة البيانات (ال main data store) هي أيضاً صندوق صغير في الزاوية وذلك بسبب أن ال data store هي مجرد تقنية ومن وجهة نظر المعمارية هي وسيلة لكتابة وحفظ واسترجاع البيانات. بغض النظر عن كم عدد السيرفرات التي تحتاجها أو طريقة حفظ وتمثيل البيانات وما هي طريقة ال scalability بها أو ال replication أو حتى ال fault tolerance. **اعتبر ان ال data store مثلها مثل ال caches وال searching engine وال message queues عبارة عن إضافات plugin وإذا كنت تريد ان تحول طريقة التخزين او طريقة الكاش فكل ما عليك هو تغير ذلك المكون ولن تتغير المعمارية او التطبيق.** عن طريقة عمل ال abstraction لل data store فلك الحرية في استخدام أي نوع بها سواء MySQL او أي نوع آخر مثلاً NoSQL data store او حتى in-memory solution بناء على متطلبات النظام. تذكر أن ال data store ليست قلب المعمارية ولا يجب ان تحكم طريقة تطور النظام. اخيراً لو لاحظت في الصورة 11 تم وضع بعض الخدمات الأخرى third party services وذلك لأن أغلب الأنظمة بالفعل تتعامل مع عشرات من الأنظمة الخارجية external system وربما تعتمد عليهم ايضاً. ولأنهم خارج سيطرتك فهي خارج حدود النظام وعزلها بطبقة فوقها هي طريقة جيدة لتقليل المخاطر بحيث تستطيع فيما بعد تبديل خدمة بخدمة أخرى بسهولة. # **الخلاصة** المعمارية Architecture هي من وجهة نظر ال software engineer بينما البنية التحتية infrastructure من وجهة نظر ال system engineer وكلا وجهات النظر هما مجرد رؤية أخرى لنفس المشكلة الا وهي بناء Scalable software. بعد هذه المقالة يجب أن تكون قادراً على بناء الصورة العامة في كيف يمكن أن تلعب ال Architecture وال Infrastructure دوراً في دعم ال Scalability للمشروع. وهذه النظرة العامة مهمة قبل ان ندخل في تفاصيل كل مكون بالتفصيل في مقالات تالية. كما ترى فإن موضوع ال Scalability ليس بالسهل، لأنه يمس العديد من الجوانب في تصميم ومعمارية الأنظمة وايضاً يحتاج معرفة واسعة في العديد من التقنيات والأدوات المختلفة. ولعمل ال Scalability تحتاج فهماً لكل الأجزاء وكيف تعمل مع بعضها البعض وما هو دور كل جزء منها وأين هي مكامن قوة وضعف كل مهم. لكي تصميم Scalable Web Application على بمعرفة تأثير المعمارية Architecture والبنية التحتية infrastructure والتقنيات والخوارزميات وحاجة ال business وماذا يريد. المقالات التالية في هذه السلسلة سوف تبدأ بإلقاء النظر على المكونات والخدمات التي تم تناولها بالتفصيل. # **المصادر** [Web Scalability for Startup Engineers](http://www.amazon.com/Scalability-Startup-Engineers-Artur-Ejsmont/dp/0071843655?ref=informatic-ar.com) ### حديث حول ال Internet Explorer Compatibility View URL: https://informatic-ar.com/internet-explorer-compatibility-view/ Last updated: 2026-10-03T22:31:40.000Z هذه المرة حديثنا حول متصفح الIE ، فاذا كنت مطور ويب تقوم بصيانة مواقع تعمل على نسخ قديمة من ال IE ، أو تقوم بتطوير مواقع حديثه Modern Web (باستخدام CSS 3/JQuery) ولكنها سوف تعمل ك Intranet Web Applications (لاحظ انترانت وليس انترنت) بمعنى سوف تعمل ضمن الشبكة الداخلية Local Networks في الشركات Enterprise والمؤسسات، فهذا الموضوع سوف يهمك. حيث للأسف كثير جداً من المستخدمين في الجهات الرسمية ما زالوا يستخدموا IE 8/9 بشكل أساسي أما بسبب وجود تطبيقات Legacy ما زالو يستخدموها ك ERP أو CRM والتي في الغالب لها اعتماديات على نسخة المتصفح سواء في ActiveX Controls أو في من الCustom Features بها، أو بسبب تخوف من عملية الUpgrade عموماً وأنها قد تؤثر على سير العمل فيها بعد. لذلك تغيير نسخ المتصفحات أو حتى اعتماد متصفحات أخرى غير ال IE أمر لا يمكن تغييره في ليلة وضحاها خصوصاً للبنوك أو شركات التأمين أو اي جهة كبيرة بها الالاف من الموظفين متوزعين في مدن مختلفة، هو بالامكان نظرياً ولكن عملياً الأمر لا يتم كما قد تتصور. [![المتصفح IE 8 مازال يستخدم حتى هذه اللحظة](https://informatic-ar.com/wp-content/uploads/2015/05/IE_1-300x169.png)](https://informatic-ar.com/wp-content/uploads/2015/05/IE%5F1.png) المتصفح IE 8 مازال يستخدم حتى هذه اللحظة **مشكلة** ال Background Compatibility من المشاكل الأزلية سواء في انظمة التشغيل، البرامج وحتى المكتبات البرمجية، والتي تبدأ عندما تطرح نسخة جديدة من نظامك وفي نفس الوقت تريد الحفاظ على المشاريع أو البرامج التي تعتمد على تلك النسخة السابقة من نظامك بدون أي تأثير عليها. أحد الحلول المطروحة لهذه المشكلة في متصفح Internet Explorer هي اضافة [خاصية ال Compatibility View والتي قدمت بدءاً من الاصدار 8](http://blogs.msdn.com/b/ie/archive/2008/08/27/introducing-compatibility-view.aspx?ref=informatic-ar.com) والتي تحدد كيف سيُعرض Rendering موقعك في المتصفح وعلى اي نسخة IE Engine بالتحديد سوف يتم عرضه (هل IE 6, IE 7 , IE 8 , IE 9 والخ) ، وهكذا سمحت للمواقع القديمة أن تعمل على نسخ المتصفح الجديدة (بشرط تحديد الRendering Engine المناسب لذلك الموقع القديم) الى ان يقوموا المبرمجين بتحديث موقعهم حتى يتماشي مع المعايير القياسية والحديثة. مثلاً شركة لديها نظام داخلي منذ 15 سنة وهو يعمل على IE 7 بشكل ممتاز ولكنه لا يعمل على ال IE 9، لذلك اما ان يقوم كل مستخدمي الشركة بتحميل الIE 7، أو ان يحدد ذلك الموقع القديم أنه يحتاج أن يعمل على ال IE 7 في ال Compatibility View وبالتالي عندما يعمل الموقع على ال IE 9 سوف يعرف أنه قديم ويحتاج أن يعرض Render في ال IE 7 وهكذا يقوم ال IE 9 باختيار ذلك المحرك وعرضه، وسوف يعمل ذلك الموقع على المتصفح الجديد. هذا الحل ادخل بسبب أن ال IE 8 عندما ظهر بدء بتطبيق معايير الويب وبالتالي كل المواقع القديمة التي طورت في IE 6/7 (والتي لم تكن موافقة لمعايير الويب) لم تعمل جيداً في IE 8 بالوضع الافتراضي، لذلك طورت مايكروسوفت هذا الخاصية حتى تسمح للمطورين بتحديد مواقعهم بأنها تعمل على النسخة المعينة الى أن يقوموا بتحديث موقعهم وجعله متوافق مع النسخ الجديدة وبالتالي لا حاجة لاستخدام ال Compatibility View عندما يتم ترقية مواقعهم. **فقط باستثناء واحد** الا وهي المواقع التي تعمل في الشبكة الداخلية Intranet سوف تعرض بالوضع الافتراضي داخل Compatibility View (وذلك لفرضية أن أغلب مواقع ال Enterprise هي Legacy ولا يوجد بها تحديثات وتحتاج أن تعمل في نسخ قديمة من المتصفح "وهم محقين في ذلك!")، طبعاً الفرضية اتت بعد احصائيات لكثير من المواقع الداخلية وقد سمتها مايكروسوفت بال Smart وقتها [ Compatibility View and Smart Defaults](http://blogs.msdn.com/b/ie/archive/2009/06/17/compatibility-view-and-smart-defaults.aspx?ref=informatic-ar.com) هذا يعني حتى لو قمت بكتابة Modern Site يعمل في شبكة داخلية Intranet فسوف يعرض موقعك بهذا الوضع Compatibility View **ولن تستطيع تغييره برمجياً** كما سيتوضح فيما بعد، واذا لم تقم بتحديد النمط للموقع Document Mode فسوف يعرض بنمط قد يؤثر في شكل موقعك (في IE 9 سوف يعرض موقعك بIE 7 ولك أن تتخيل دعم الHTML5/CSS في تلك النسخة) وبالتالي فموقعك لن يظهر بشكل جيد وقد لا تعمل أحدى جزئياته، وهذه احد الاسباب التي دفعتنا لكتابة هذا المقالة. من أهم ما تم تطويره في خاصية ال Compatibility View هي ادخال فكرة ال Browser Mode وال Document Mode، يمكنك فتح المتصفح IE والضغط على F12 والذهاب للDeveloper Mode ومن ثم تستطيع مشاهدة الModes كما بالصورة التالية (في ال IE 11 غيرت اسمها ب Emulation لمشاهدة تلك الModes): [![تحديد ال Modes في نسخ ال IE](https://informatic-ar.com/wp-content/uploads/2015/05/IE_Modes-300x37.png)](https://informatic-ar.com/wp-content/uploads/2015/05/IE%5FModes.png) تحديد ال Modes في نسخ ال IE الBrowser Mode يحددها المتصفح تلقائياً، ولا تستطيع برمجياً تغيير ذلك، ومن النقاط المهمة في تحديد ال Mode وهل سوف يعرض بال Compatibility View أم لا: نسخه المتصفح نفسها، و بالاعتماد على الZone في الموقع (هل هو Internet أم Intranet) فجميع مواقع الويب تعتبر Internet وبالتالي تكون هذه الخاصية مغلقة Off عندما تقوم بفتحها وسوف يكون ال Browser Mode في هذه الحالة على حسب اصدار المتصفح نفسه IE 9 أو 10 الخ ، بينما المواقع الداخلية Intranet والتي تقوم بفتحها من خلال DNS مثلاً http://myPortal سوف تستخدم هذا الوضع On افتراضياً وبالتالي يتم تفعيل خاصية ال Compatibility View في ذلك الموقع، وفي حال قمت بفتح موقع داخلي من خلال العنوان FDQN فسوف يتم اعتباره داخل ال Internet Zone. الذي يهم المبرمج أن هذا الBrowser Mode لا تستطيع تغييرها من الCode وانما يتم فقط تغييره من اعدادات المتصفح نفسه من خلال Developer Tools بعد الضغط على F12. بعد أن يقوم المتصفح بتحديد ال Browser Mode فيقوم أيضاً بتحديد ال Document Mode المناسب على حسب خوارزمية ثانية يعمل بها وهي التي يتم من خلالها عمل Render للصفحة، وفي بعض الأحيان سوف يتم اختيار Document Mode لا يناسب موقعك وقد يؤدي لوجود مشكلة في كيفية عرض موقعك طريقة تحديد ال Document Mode والذي سوف يعرض موقعك بمحرك معين Rendering بناء **على معاملين أساسين**: - قيمة ال Compatibility View التي يحددها الموقع (اذا كانت هناك قيمة) - وجود ال !DOCTYPE في كود الموقع الموقع يمكن ان يحدد ال Compatibility View من خلال الMeta Tag أو من خلال اضافة HTTP Header في الRequest والأولوية سوف تكون للMeta Tag في حال قمت باستخدامهم الاثنين في نفس الوقت. استخدام ال Meta Tag بسيط، فقط كل ما عليك تحديد قيمة ال IE ووضعه في جزئية ال Head في صفحتك (تضع في البداية قبل أي JS/CSS) ```markup <meta http-equiv=“X-UA-Compatible” content=“IE=7” /> ``` أما ال HTTP Header ، سوف تحتاج أن ترسل القيمة التالية سواء تضعها برمجياً أو من خلال اعدادات الويب سيرفر تستطيع اضافتها مع اي طلب: ```markup X-UA-Compatible: IE=7 ``` تستطيع تبديل IE=7 بحسب النسخة التي تود أن يتم العرض بها، بدءاً من 5 وانتهائاً ب11 وتستطيع تحديد IE=Edge والتي تعني أن يستخدم اخر Rendering Mode متوفر في المتصفح (بمعنى استخدام نسخة المتصفح الحالية في العرض)، مثال على اضافة ال Header في ASP.NET: ```csharp Response.Headers.Add("X-UA-Compatible", "IE=edge, Chrome=1"); ``` تستطيع ايضاً استخدام اكثر من Mode وسوف يتم استخدام اعلى Mode متوفر (سواء كانت في الHTTP Request Header أو من خلال الMeta Tag): ```markup <meta http-equiv=“X-UA-Compatible” content=“IE=7; IE=8” /> ``` بعد تحديدك للCompatibility View يبقى وجود الDOCTYPE وقد يؤثر وجوده أو عدمه في بعض ال Modes وبالتالي قد يعرض المتصفح الموقع بMode أخر، فلو لم تكن DOCTYPE فسيتم عرض موقعك بQuark Mode وهذا يغير شكل الموقع ولا يعرضه بالشكل الذي صممته به ([لأنه سيعرض بال IE 5](https://msdn.microsoft.com/en-us/library/gg699338%28v=vs.85%29.aspx?ref=informatic-ar.com)!). [المخطط يوضح كيف يتم اختيار ال Document Mode في المتصفح](http://ie.microsoft.com/testdrive/ieblog/2010/Mar/02%5FHowIE8DeterminesDocumentMode%5F3.png?ref=informatic-ar.com) [![كيف يتم تحديد ال Document Mode في المتصفح](https://informatic-ar.com/wp-content/uploads/2015/05/mODE-195x300.png)](https://informatic-ar.com/wp-content/uploads/2015/05/mODE.png) كيف يتم تحديد ال Document Mode في المتصفح **خلاصة للمشكلة**:عندما يعرض موقعك في الIntranet فسوف يعرض بالCompatibility View وعليك بتحديد الDocument Mode حتى لا يفسد عليك التصميم للموقع. **الحل البرمجي**: اضافة X-UA-Compatible في ال HTTP Response (أو الميتا تاق) وتحدد ال IE=Edge وبالتالي سوف يكون الDocument Mode هو على حسب الاصدار اصدار متصفح المستخدم. **حلول أخرى:** يقوم المستخدم نفسه بتغيير خصائص المتصفح من خلال الاعدادات التي ستوضح بعد قليل ، أو يقوم مسؤول النظام بتطبيق هذا الخيار من [ال Group Policy على مستوى الشبكة أو الموقع](https://technet.microsoft.com/en-us/library/dn321457.aspx?ref=informatic-ar.com)، ولكن للأسف ايضاً هذا الخيارات غير عملية مع الجهات الكبيرة. **التحكم باعدادات ال Compatibility View من المتصفح:** أمر أخر في نفس السياق وهو ال Compatibility List وهي كل المواقع التي تعمل بنمط Compatibility وهي تكون معرفة داخل متصفحك (المستخدم يقوم باضافة اي موقع في هذه القائمة حتى يعمل مباشرة في ال Compatibility) أو من خلال المجموعه معرفة لدى ميكروسوفت، وتستطيع اضافة موقع في هذه القائمة من خلال Tools -> Compatibility View كما في الصورة التالية [![خيارات اعدادات ال Compatibility من المتصفح](https://informatic-ar.com/wp-content/uploads/2015/05/clip_image004_thumb.jpg)](https://informatic-ar.com/wp-content/uploads/2015/05/clip%5Fimage004%5Fthumb.jpg) خيارات اعدادات ال Compatibility من المتصفح لاحظ في الوضع الافتراضي ان هناك علامة صح على عرض كل المواقع الداخلية Intranet ب Compatibility View بشكل افتراضي، و ايضاً تستطيع وضع صح على تشغيل كل المواقع في الCompatibility View أو ازاله الصح وبالتالي تعمل بدون هذه الخاصية. **كيف تختبر موقعك في نسخ قديمة من ال IE:** اذا كنت مطور وتريد عمل بيئة مشابه لبيئة العميل من ناحية ال IE9/8 لاختبار موقعك، فالافضل تحميل هذه ال Virtual Machines الجاهزة على حسب نظام التشغيل الذي تود ان تعمل فيه بدءاً من اصدارة IE 6 من موقع [Modern.IE](http://dev.modern.ie/tools/vms/?ref=informatic-ar.com) **لا تنسى مسح كل ال console.log من أكواد الجافا سكربت حتى يعمل موقعك تحت IE**: أخر نقطة وهي Bug واجهتنا لأحد العملاء في الIE9 حيث اذا كنت تستخدم اي console/logs داخل كود الJavaScript فهذا سيمنع الملف من التحميل على الIE9، المشكلة سوف تنحل عندما تضغط على F12 فقط وستجد أن الموقع عمل وحمل الملف بشكل صحيح، ولكن عند فتح الموقع بشكل عادي من المتصفح فلا يستطيع ان يحمل تلك الملفات، لذلك وبشكل عام لا تستخدم Console Logs في الجافا سكربت في ال Production Environment ، للمزيد [Why Javascript only works after opening developer tools in IE once?](http://stackoverflow.com/questions/7742781/why-javascript-only-works-after-opening-developer-tools-in-ie-once?ref=informatic-ar.com) **الخلاصة:** 1. قمت بتطوير موقع حديث على الانترنت فلا حاجة لأن تستخدم اي من الCompatibility View Feature فقط ضع Standard DOCTYPE 2. لديك موقع قديم وتريد عرضه على الانترنت فعليك بتحديد ال Compatibility View وتحديد الDocument Mode المناسب لموقعك، حتى تقوم بترقيته لاحقاً وبعدها طبق خلاصة رقم (1) 3. قمت بتطوير موقع حديث ويعمل على شبكة داخلية (وظهر تحت الCompatibility View) فعليك أن تقوم بعمل Override للDocument Mode وتختار الEdge. 4. نمط ال Document Mode يعتبر لاغياً حالياً في IE 11 وأنه [Document modes are deprecated](https://msdn.microsoft.com/library/dn384051%28v=vs.85%29.aspx?ref=informatic-ar.com) 5. اذا كنت تعمل في قسم ال IT فالأفضل أن تتبنوا أخر الاصدارات من المتصفحات وتستطيع دعم المواقع القديمة من خلال خاصية الجديدة [Enterprise Mode in IE 11](http://blogs.msdn.com/b/ie/archive/2014/04/02/stay-up-to-date-with-enterprise-mode-for-internet-explorer-11.aspx?ref=informatic-ar.com) **مراجع مفيدة:** - [Introducing Compatibility View](http://blogs.msdn.com/b/ie/archive/2008/08/27/introducing-compatibility-view.aspx?ref=informatic-ar.com) - [Understanding Compatibility Modes in Internet Explorer 8](http://blogs.msdn.com/b/askie/archive/2009/03/23/understanding-compatibility-modes-in-internet-explorer-8.aspx?ref=informatic-ar.com) - [IE’s Compatibility Features for Site Developers](http://blogs.msdn.com/b/ie/archive/2010/06/16/ie-s-compatibility-features-for-site-developers.aspx?Redirected=true&ref=informatic-ar.com) - [Testing sites with Browser Mode vs. Doc Mode](http://blogs.msdn.com/b/ie/archive/2010/10/19/testing-sites-with-browser-mode-vs-doc-mode.aspx?ref=informatic-ar.com) - [Stay up to date with Enterprise Mode for Internet Explorer 11](http://blogs.msdn.com/b/ie/archive/2014/04/02/stay-up-to-date-with-enterprise-mode-for-internet-explorer-11.aspx?ref=informatic-ar.com) ### خوارزميات Error Correction Codes URL: https://informatic-ar.com/error-correction-codes/ Last updated: 2026-10-03T22:31:40.000Z هذه الأيام نحن معتادون على الوصول لأجهزة الحاسب كلما كنا في حاجة لها، الأمر لم يكن كذلك بالنسبة ل Richard Hamming الباحث الذي كان يعمل في معامل بل في 1940م، حيث أن جهاز الشركة الذي يحتاجه للعمل يستخدم ايضاً بواسطة أقسام أخرى في الشركة، ولا يستطيع الوصول له الا في عطل اخر الأسبوع Weekends، يمكنك ان تتخيل مدى الإحباط الحاصل بعد كتابتك للبرنامج وبسبب حدوث خطأ واحد يتطلب منك ان تقوم باعادة كتابة البرنامج مرة اخرى، يقول هامنغ: > "بعد[![Richard_Hamming](https://informatic-ar.com/wp-content/uploads/2014/10/Richard_Hamming-229x300.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/Richard%5FHamming.jpg) أسبوعين من العمل جئت ووجدت أن برنامجي وحسباتي توقفت وكأن شيئاً لم يكن، لقد اثار ذلك ازعاجي لأنه ذهب عمل أسبوعين، وقد تساءلت: إذا كان الجهاز يمكن الكشف عن الخطأ، فلماذا لا يمكن تحديد موقع الخطأ ومن ثم تصحيحه؟" وكما هو المثل الحاجة أم الاختراع، فقد قام هامنغ فيما بعد بإنشاء اول خوارزمية لتصحيح الأخطاء Error Correction Code، وهي خوارزمية تقوم باكتشاف الخطأ وتصحيحه على النظام، وبدون هذه الفكرة فإن أنظمة الحواسيب والاتصالات لن تكون بنفس السرعة drastically slower، والموثوقية less reliable مثل ما نراه ونستخدمه في كل يوم. ## **الحاجة لل** **Error Detection** **وال** **Error Correction** أحد أهم وظائف الحواسيب التي نستخدمها هي اجراء الحسابات Computations وذلك عن طريق المدخلات المطلوبة ويقوم الحاسب بالعمل عليها لكي يخرج لنا المخرجات المطلوبة. ولكن حتى يستطيع الحاسب القيام بالاجابه بشكل صحيح فسوف يحتاج الى حفظ تلك البيانات Storing سواء على الذاكرة أو الأقراص، وارسالها Transmitting عبر أي وسيط، وبدون تلك الخاصيتين فسوف يكون الحاسب مشابه للألة الحاسبة فقط ولن يكون مفيداً مثل الحواسيب التي تستخدمها حيث أنك لن تستطيع ارسال البيانات أو حفظها للرجوع اليها فيما بعد، لذلك خاصية الحفظ Storing والارسال Transmitting مهمات لأي حاسوب في الوقت الحاضر. التحدي المرتبط لهذه العمليتان هو أنه يجب أن يتم تخزين وارسال البيانات كما هي بالضبط، لأنه في بعض الحالات قد يؤدي خطأ صغير واحد في هذه البيانات لتحويلها الى شكل عديم الفائدة، تخيل أنك قمت بحفظ رقم هاتف صديقك على الحاسب فأنت تريد هذه العملية أن تتم بالضبط ويتم تخزين الرقم كما هو لأن أي خطأ في رقم واحد قد يؤدي لك نتيجة خاطئة سواء رقم لشخص أخر أو رقم خاطئ من الأساس، هذا مثال فقط وتزداد الأهمية في حالة الأنظمة الأكثر حساسية مثل الأنظمة المالية والبنكية فقد يؤدي عدم الدقه في الحفظ الى خسارة حقيقة في الأموال الموجودة وهذا أمر غير مقبول. بالمقارنه مع الأنظمة الورقية فأن البشر لديهم أخطاء في التسجيل والكتابه وقد يمكن تجنبها من خلال توخي الحرص والمراجعة خصوصاً في البيانات الحساسة مثل أرقام الحسابات والرصيد البنكي، ولكن بالنسبة للحواسيب فيجب أن لا يكون هناك خطأ على الاطلاق! لنفترض أن هناك حاسب مساحة القرص به هو 100 غيغا بايت (ولنقل انها تساوي 15 مليون صفحة نصية)، فاذا كان الحاسب يعطي خطأ واحد لكل مليون صفحة فهذا يعني أن هناك 15 خطأ في هذا الحاسب، وبنفس الفكرة عند ارسال البيانات فاذا قمت بتحميل ملف 20 ميغا بايت من الانترنت وكان هناك خطأ واحد في كل مليون بايت فيه ، فهناك 20 بايت خطأ في هذا البرنامج وهذا قد يؤدي الى فشل Crash البرنامج من العمل. الخلاصه أن حتى الحواسيب التي لها صحة 99.9999% لا تعتبر جيدة ولا يجب أن يعتمد عليها في الارسال والتخزين ولو كان الخطأ واحد في كل بليون بايت من البيانات. لكن الحواسيب يجب أن تتعامل مع مشاكل الاتصال Communications التي تتعرض لها الأجهزة الاخرى، مثل الهواتف فإن عملية ارسال الصوت الى الطرف الأخر قد يصل بشكل مشوش وغير واضح، وبنفس الأمر اشارات الأجهزه اللاسلكية Wireless تتعرض لنفس التشويش، ايضاً اقراص التخزين مثل ال CD, DVD يمكن أن تتخدش ووتعرض للغبار والأتربة، فكيف يمكن أن تضمن التعامل مع بلايين البيانات بدون حدوث أخطاء اثناء الحفظ والارسال كما تتعرض لها الأجهزة الأخرى. سوف نتحدث في هذا الموضوع حول أحد الأفكار العظيمة في علوم الحاسوب والتي ان استخدمتها بشكل صحيح فسوف يمكن ارسال البيانات بمعدل خطأ قليل جداً حتى في أكثر الوسائط المعرضه للتشويش Unreliable Channels وفي الواقع بشكل عملي يكون معدل الخطأ هو صفر. ## **فكرة التكرار** **Repetitions** الفكرة الأساسية التي يمكنك من خلالها ارسال البيانات عبر وسيط قابل للتشويش Unreliable Channel هي عن طريق تكرار البيانات أكثر من مرة، وهذا هو رد الفعل الطبيعي عندما تتحدث لشخص عبر الهاتف وحدث تشويش وأبلغك بأنه لم يسمع العباره السابقة فسوف تقوم بتكرار ما قلته مرة أخرى ، أو عندما ترسل رقم عبر الهاتف تقوم بتكراره مره أخرى للتأكد من الرقم صحيحاً ولا يوجد به أخطاء الحواسيب يمكنها أن تقوم بنفس الفكرة، لنفرض أنك تحول المبلغ 5213.75 الى رصيدك من بنك الى آخر، ولنفرض أن الشبكة غير موثوقه ومشوشه وأن هناك احتمال خطأ بنسبة 20% في اي رقم مرسل وقد يتغير ذلك الرقم لهذا السبب. بعد أن تمت العملية قمت باستلام مبلغ 5293.75 في رصيدك في الحساب الآخر. حالياً قد لا تعرف أن العملية صحيحة أو لا، لذلك بدلاً من ارسال الرقم مرة واحدة سوف نطبق فكرة التكرار ونقوم بارسال الرقم أكثر من مرة حتى تعرف الرقم الصحيح، تخيل أنك قمت بتطبيق هذه الفكرة 5 مرات وحصلت على الارقام التالية : [![image1](https://informatic-ar.com/wp-content/uploads/2014/10/image1.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image1.jpg) لاحظ أن بعض التحويلات لها أكثر من خطأ واحد وهناك بعض التحويلات ليس لديها أي خطأ (رقم 2 في الصورة السابقة) وحالياً ما زلت لا تعرف أين الخطأ ولن تستطيع أخذ رقم 2 لأنك لا تعرف أين الخطأ. الذي يمكنك فعله هو فحص الأرقام واختيار الرقم الذي تكرر أكثر من غيره، والنتيجة هي موضحه في الصورة بالأسفل في اخر سطر منها: [![image2](https://informatic-ar.com/wp-content/uploads/2014/10/image2-300x92.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image2.jpg) في كل التحويلات (الموجودة في الأعمدة) من 1 الى 4 كان الرقم هو 5 ، بينما في التحويلة 5 احتوت على الرقم 7 (4 تحويلات قالت 5 وواحدة قالت 7) لذلك الاحتمال هنا لصالح ال5\. ننتقل الى العمود الثاني وسوف ترى الاحتمال الأغلب هو 2 وهكذا بنفس الأمر في العمود الثالث الرقم 1 هو الأكثر تكراراً، وهكذا للبقية وسوف تكون النتيجة هي 5213.75 وهذا هو الرقم الصحيح. حتى هذه اللحظة قد حللنا المشكلة بشكل جزئي ولكن في الأعلى كان احتمال الخطأ في الرقم هو 20% وهناك بعض الوسائط لها نسبة خطأ أعلى بكثير، النقطة الأخرى أن الرقم قد يصدف ويرسل بشكل خاطئ لكل التكرارات وبالتالي تحصل على نتيجة خاطئة لحسن الحظ يمكن حل هذه المشاكل عن طريق زيادة عدد التكرارات حتى نحصل على نسبة الوثوقية Reliability التي نرغب بها، على سبيل المثال لو نظرنا للمثال السابق كانت نسبة الخطأ هي 20% ولنقل الآن أصبحت 50%، لذلك يمكن أن نرسل الرصيد 1000 مرة بدلاً من 5 مرات، ولأن نسبة الخطأ هو 50% فنصف البيانات ترسل صحيحة والنصف الأخر يكون خطأ بمعنى في العمود الأول النصف الأول سوف يكون 5 والنصف الآخر سوف يكون ارقام اخرى، هكذا سوف يكون هناك 500 من 5 وحوالي 50 لكل من الأرقام من الى 9 بدون 5، وهكذا لن تحصل على اجابه خاطئة أبداً واذا استخدم البنك هذه الطريقة فقد يعمل ملايين السنين بدون حدوث أي خطأ الخلاصه هي أنه يمكنك زيادة الاعتمادية عن طريق زيادة التكرار (وللتنبيه الأخطاء الوارد ذكرها هي الأخطاء العشوائية بسبب تشويش الوسيط وليست الأخطاء الناتجة عن طريق التغيير المتعمد لها، ففكرة التكرار لن تعمل اذا حدث تغيير متعمد للبيانات وهناك طرق اخرى تستطيع اكتشاف ذلك سوف يتم تناولها في اخر الموضوع). هكذا باستخدام فكرة التكرار Repetition فقد تم حل مشكلة الارسال على وسيط مشوش ولكن لسوء الحظ فهذه الفكرة ليست مناسبة للحواسيب حيث أن عملية الارسال المتكرر (ارسال الرصيد 1000 مرة) يكون مكلفاً (تخيل ارسال 200 ميغا بايت لتحميل برنامج بتكرار 1000 مرة) لذلك تحتاج الحواسيب لفكرة اخرى أكثر عملية من فكرة التكرار. ## **فكرة ال** **Redundancy** قد تتسائل لماذا تم شرح فكرة التكرار بينما هي في الواقع غير عملية وغير مستخدمة ؟ السبب أنها تمثل الفكرة الأساسية في ارسال البيانات عبر الوسيط المشوش لأنك لن ترسل الرسالة الأصلية فقط بل سوف ترسل معها شيء اضافي يزيد من موثوقية الرسالة. في فكرة التكرار كان الشيء الأضافي هو نسخ اخرى من الرسالة الأصلية، لكن هناك أمور أخرى يمكن أن تكون اضافيه للرسالة حتى تزيد الموثوقية، وعلماء الحاسوب يسموا هذه الزيادة ب Redundancy وهي تكون اما باضافه الى الرسالة الأصلية أو عن طريق تحويل الرسالة الى رسالة أطول وعندما تستلم الرسالة يتم تحويلها حتى لو حصل لها تشويش بسبب وسيط النقل poor communication channel. مثال لتوضيح الفكرة، لنرجع لنفس المثال السابق لارسال الرصيد البنكي 5213.75$ عن طريقة وسيط اتصال غير موثوق والذي يقوم بشكل عشوائي بتعديل 20% من الأرقام. الان بدلاً من ارسال الرصيد 5213.75$ سوف نقول بتحويلها الى رسالة أطول (رسالة Redundant) تحتوى على نفس المعلومات بالضبط، وسوف نقوم بكتابة الأرقام كتابة بالانجليزي، وسوف تصبح الرسالة: five two one three point seven five الان تم ارسال البيانات وحصل فقدان ل20% منها، سوف تصبح الرسالة الناتجه هي: fiqe kwo one thrxp point sivpn fivq بالرغم من أنها مزعجة في القرائه، لكن ما زال بامكان أي شخص يعرف الانجليزية أن يخمن الأرقام المفقودة ويحصل على الرصيد الصحيح. الفكرة هنا أننا بسبب استخدمنا فكرة ال Redundant Message فأصبح بمكاننا اكتشاف الخطأ وتصحيحه في الرسالة، فاذا قلت لك ان fiqe هي رقم وهناك خطأ في حرف واحد فسوف تستطيع معرفته بسهولة، وهذا عكس ما اذا قلت لك ان الرقم 367 يمثل رقم وهناك رقم واحد تم فقدانه فسوف يستحيل معرفته والسبب عدم وجود اي redundancy في الرسالة. حتى الان لم نرى كيف تعمل فكرة ال redundancy لكن فقط رأينا أن لها علاقه بجعل الرسالة اطول وكل جزء من الرسالة مربتط بنمط معين well-known pattern. وبهذا الشكل يمكن اكتشاف اي تعديل على الجزء لأنه لن ينطبق على النمط ومن ثم نستطيع تعديله حتى ينطبق مع النمط الصحيح. علماء الحاسوب يسموا هذه الأنماط Patterns بالاسم Code Word، في مثالنا السابق كان ال code words هي الارقام مكتوبة بالانجليزية مثل one, two, three وهكذا. حان الوقت لشرح فكرة الRedundancy والرسالة بشكل عام مكون من رموز Symbols (في مثالنا السابق هي الأرقام من 0 الى 9) وسوف نتجاهل علامة $ والفاصلة العشرية الان لتوضيح الفكرة الأساسية بشكل أبسط، كل رمز Symbol سوف يعطى قيمة من ال code work، في مثالنا الرمز 1 اعطي القيمة one من الcode word، و الرمز 2 سوف يعطي القيمة two وهكذا. بعد أن تقوم بالعملية لكل الرموز لديك وتقوم بأرسالها وتصل للطرف الاخر، سوف يقوم بالنظر لكل جزء منها والتأكد هي تمثل code word صحيح، اذا كانت تمثل بشكل صحيح مثلاً five فسوف نعطيها القيمة المقابلة لذلك الرمز 5، واذا لم تكن صحيحة مثلاً fiqe سوف نبحث عن اقرب code word تطابق ذلك القيمة وهي five وهكذا تكتشف خطأ وتعطيه القيمة 5. الصورة التالية هي مثال لما يحدث: [![image3](https://informatic-ar.com/wp-content/uploads/2014/10/image3-300x100.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image3.jpg) هذا كل ما في الأمر، والحواسيب تستخدم هذه الفكرة طول الوقت لتخزين وارسال المعلومات ولكن بجدول اصلح للحواسيب كما في الصورة التالية، وهي مثال ل Hamming Code والتي اكتشفها ريتشارد هامنغ في معامل بل Bell Lap في 1947 وذلك لحل مشكلة تعطل crash الحاسب التي ذكرناها في أول الموضوع، والفرق في هذه الجدول أنه يتعامل مع الصفر والواحد فقط (وهو يسمى 7,4 Hamming Code). [![image4](https://informatic-ar.com/wp-content/uploads/2014/10/image4-300x91.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image4.jpg) سوف تعمل هذه الطريقة كما وضحناها سابقاً، فعندما ترمز رسالة Encoding Message، فكل مجموعه من 4 ارقام يضاف اليها زيادة، وبالتالي توليد code word من سبعه ارقام digits. عندما تقوم بعمل ال decoding سوف تنظر للمطابق من ال7 ارقام التي وصلتك، واذا لم تجد فسوف تأخذ أقرب مطابق لها. من المهم معرفة لماذا يفضل ال Redundancy Trick على فكرة التكرار Repetition Trick؟ والسبب الأساسي هو في التكلفة Cost وعلماء الحاسوب يسموا التكلفة في ال error correction systems بالاسم ال overhead وهي مقدار المعلومات الاضافيه التي تحتاج لارسالها مع الرسالة لكي تضمن انها تصل بشكل صحيح. فال overhead في فكرة التكرار كبير لأنك تحتاج لأن ترسل عدة نسخ من الرسالة، بينما في ال redundancy فهذا يعتمد على ال code words الذي تستخدمه، مثلاً في المثال السابقة English Words فالرسالة الناتجه هي بطول 35 حرف بينما الرسالة الاصلية هي كانت 6 ارقام وكما هو واضح فان ال overhead ايضاً كبير. لكن اذا استخدمت code word افضل له اقل redundancy سوف تحصل على اداء افضل واقل overhead، ولهذا السبب الحواسب تستخدم فكرة ال redundancy بدلاً من فكرة التكرار. لاحظ أن الحديث ينطبق على التخزين ايضاً وليس فقط الارسال سواء على الCD, DVDs, Hard Disks فكلها تعتمد على انظمة تصحيح الاخطاء لضمان الموثوقية Reliability التي تراها وتستخدمها كل يوم. ## **فكرة ال** **Checksum** **:** حتى الان تناول بعض الطرق التي تسمح لاكتشاف detect وتصحيح correct الاخطاء في البيانات، باستخدام فكرة التكرار Repetition وفكرة ال Redundancy، لكن هناك طرق لنفس المشكلة، ولنركز الان حول فكرة اكتشاف الأخطاء فقط (بدون التصحيح). للكثير من التطبيقات فقط اكتشاف الأخطاء يكفيها، لأنه اذا اكتشفت الخطأ فيمكنك ان تقوم بطلب البيانات مرة أخرى، وهكذا حتى تحصل على بيانات بدون أخطاء. هذه الفكرة تستخدم كثيراً، مثلاً كل الاتصالات في الانترنت تستخدم هذه الفكرة، وسوف نسميها checksum trick لسبب سوف تعرفه فيما يلي. لفهم ال checksum trick، سوف نتعامل الان مع رسائل مكونة من أرقام فقط حتى تستوعب الفكرة بسهولة، وهذا منطقى لأن كل الحواسيب تخزن كل المعلومات على شكل ارقام وتفسر هذه البيانات على شكل نصوص أو صور عندما تعرضها لك. على اي حالة من المهم فهم أن أي مجموعه معينة من الرموز لا تؤثر على الطرق التي وضحانها خلال هذا الموضوع، فقط بعض الأحيان نستخدم الأرقام من 0 الى 9 للسهولة والتركيز على الفكرة، وأحياناً من الاسهل وضع المثال بالأحرف alphabetic symbols من a الى z. لكن ببساطة يمكنك اعطاء الرقم 0 الحرف a والرقم 1 للحرف b وهكذا يمكنك استخدام اي منهم في أي من الامثلة المعروضة سواء كان رقم أو حرف. هذه اللحظة سوف نعرف ما هي ال checksum وهناك عدة أنواع مختلفة منها، وسوف ناخذ أسهل نوع فيها وهي ال Simple checksum. وحساب ال simple checksum لأي رسالة أمر في غاية البساطة: فكل ما عليك هو اخذ الأرقام في الرسالة وجمعها ومن ثم حذف جميع الأرقام في النتيجة ما عدا اخر رقم منها وهي تكون ال simple checksum. مثال لدينا الرسالة: 46756 عندما تقوم بحساب الجمع لها سوف تحصل على 4+6+7+5+6 وهو يساوي 28، الان سوف نحذف كل النتيجة ما عدا الرقم الاخير منها وهو 8، هكذا ال simple checksum لهذه الرسالة هو 8. السؤال الان كيف يستخدم هذا ال checksum؟ وذلك عن طريقة اضافته في اخر الرسالة الاصلية وقبل ان ترسلها، وعندما تصل للطرف الأخر سوف يقوم بحساب ال checksum مرة اخرى ويقارنه بالموجود فاذا تطابق فهذا يعني انه صحيح. لكي تتذكرها بسهولة افهمها على انها check للsum من الرسالة (ولهذا اتي المصطلح checksum). هكذا سوف ترسل الرسالة: 467568 وعندما تصل الرسالة ويعرف المستقبل أنها تستخدم فكرة ال checksum سوف يقوم بحساب 4+6+7+5+6 ويخرج 28 ويأخذ الرقم الاخير ويقارنه مع الموجود في الرسالة 8 وهكذا يعرف أنه ارسلت بشكل صحيح. لكن ماذا اذا حصل خطأ وقت الارسال، ووصلت الرسالة ب 463568 (بدل الرقم 7 ب 3 )، وهكذا سوف تحسب الchecksum وتجد أنه 24 ومن ثم اخذ الاخير وهو 4 وهو غير مطابق مع 8 وبالتالي هناك خطأ وقت الارسال، وفي هذه اللحظة يمكن ان تطلب البيانات مرة اخرى والقيام بعمل الفحص مجدداً وسوف تستمر حتى يكون ال checksum صحيحاً. حتى الان الامور تبدوا جيدة، فهي تعمل ولها overhead قليل (الأقل من منا سبق ultimate low overhead system)، لأنه بغض النظر عن طول الرسالة سوف يضاف رقم واحد فقط اليها the checksum حتى تكتشف الخطأ. المشكلة هي ال simple checksum يستطيع اكتشاف خطأ واحد فقط في الرسالة، فاذا كان هناك أكثر من خطأ فقد يمكن اكتشافها وقد لا يمكن بهذه الطريقة، لنأخذ مثال: [![image5](https://informatic-ar.com/wp-content/uploads/2014/10/image5-300x124.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image5.jpg) الرسالة الأصلية هي 46756 وال checksum لها هو 8، في السطر الثاني هناك خطأ واحد (الرقم الأول اصبح 1 بدلاً من 4) ولذلك ال checksum اصبح مختلفاً 5، السطر الثالث هناك خطئين وتم اكتشاف الخطأ لأنه الchecksum اصبحت 4، المشكلة التي نتحدث عنها هي في السطر الرابع حيث هناك خطئين ايضاً ولكن الchecksum حسب بأنه 8 وبالتالي لن يتم اكتشاف اي خطأ في هذه الرسالة بالرغم من وجود رقمين خاطئين بها. لحل هذه المشكلة يمكن أن نضيف بعض التعديلات على فكرة ال checksum وسوف نطلق على التعديل الجديد بالاسم staircase checksum (نسبة للدرج) وسميناها كذلك لأنه فكرتها كأنك سوف تصعد درج اثناء الحساب. وتخيل أنك بأسفل الدرج وكل درج مرقم من 1و2و3 وهكذا. لحساب ال staircase checksum سوف تضيف الأرقام كما في السابق لكن كل رقم سوف تضربه برقم الدرج (الخانة) الذي انت فيه، وهكذا تصعد خطوة لكل رقم تريد حسابه، وفي النهايه سوف تحذف كل شيء ما عدا الرقم الاخير. مثال لدينا نفس الرسالة 46756 وسوف نقوم بحساب بضرب رقم الخانه لكل رقم في الرسالة (اول رقم سوف ينضرب في 1، ثاني رقم في 2 ، وهكذا). [![image6](https://informatic-ar.com/wp-content/uploads/2014/10/image6-300x43.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image6.jpg) وسوف تحذف الرقم الاخير وبالتالي ال staircase checksum هو 7. الان اذا تم دمج ال simple و ال staircase مع بعضهما البعض سوف تستطيع اكتشاف اي خطئين في الرسالة. هكذا الان سوف نرسل الرسالة وبها رقمان في الأخير الأول هو simple checksum والثاني هي staircase checksum، مثلاً الرسالة 46756 سوف تصبح 4675687 ، ولنرى الجدول التالي هو نفس الرسالة السابقة، ولكن بعد اضافه عمود ال staircase checksum: [![image7](https://informatic-ar.com/wp-content/uploads/2014/10/image7-300x174.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image7.jpg) عندما يكون هناك خطأ واحد فسوف نكتشف الخطأ من خلال الsimple و staircase، وعندما يكون هناك خطئين فمن المحتمل ايضاً ان يكتشف في الاثنين، فمثلاً في السطر الثالث تم اكتشاف الخطأ بالطريقتين، لكن في السطر الرابع فتم اكتشاف الخطأ بطريقة ال staircase checksum، وفي السطر الأخير ايضاً رسالة بها خطئين وتم اكتشاف الخطأ بطريقة ال simple checksum. وخلاصه سوف تستطيع اكتشاف اي اخطاء مكون من اثنين أول أقل بهذه الطريقة. الان بدئنا باستيعاب المفهموم الأساسي من الفكرة، ويجب معرفة ان فكرة ال checksum تعمل فقط على الرسائل القصيرة (10 ارقام وما أقل)، لكن هناك افكار قريبة منها جداً تعمل على الرسائل الطويلة. فكرة ال checksum trick اعلاه تخرج رقمين checksum فقط (ال simple والstaircase)، لكن اي checksum حقيقي سوف يكون أطول وربما يصل الى 150 رقم، وهذا رقم صغير مقارنة بحجم الرسائل الكبيرة، مثلاً لو كنت تستخدم checksum من 100 رقم، واستخدمه مع بيانات بحجم 20 ميغابايت (مثلاً برنامج تنزله من الانترنت) فهذه ال100 هي أقل واحد في الألف من 1٪ من حجم البرنامج الأصلي.، وهي أنواع معينة من الchecksum تسمى Hash Function، ايضاً اذا كان من المحتمل ان يتم تعديل البيانات بشكل يدوي أو بواسطة مخرب فمن الأفضل استخدام ال hash function لأنه من الممكن ان يعدل على البيانات الأصلية بحيث تنتج نفس الchecksum حتى لا يتم اكتشاف الخطأ وبالتالي قد يحصل الضرر من ذلك، والأفضل في تلك الحالة هو استخدام ال hash function والذي يضمن أن اي تغيير في رقم واحد سوف ينتج لمخرج مختلف نهائياً عن المخرج الأصلي. ## **فكرة ال** **pinpoint** تحدثنا الان عن فكرة الchecksum ، لنرجع الحديث حول المشكلة الأصلي وهي اكتشاف وتصحيح الأخطاء وقد عرفنا طريقة الRepetition وطريقة ال Redundancy، ولننقاش مسألة كيف يمكن توليد ال code words فقد طرحنا مثال الأرقام بالأحرف كتابة ووجدنا أنها أقل كفائه، وايضاً طرحنا مثال على hamming code بدون اي توضيح حول كيف ولدت هذه ال code words من الأساس. سوف نعرف مجموعه اخرى من ال code wordsالتي يمكن تستخدم في ال Redundancy Trick وسوف نسميها pinpoint لأنها حالة خاصة تسمح لك بايجاد الخطأ بسرعه quickly pinpoint error ومن هنا اتي الاسم pinpoint trick. سوف نعمل على رسائل بطول 16 ولكن هذا لا يعني ان هناك limit معين ويمكنك تقسيم رسالتك الطويلة الى اجزاء chunks من 16 حرف، واعمل على كل جزء بشكل منفصل، واذا كانت الرسالة أو الجزء الاخير منها اقل من 16 فيمكنك ملء البقية بالأصفر حتى تصل ل16 رقم. مثلاً الرسالة هي: 4837543622563997 وسوف تبدأ بعملها على شكل مربع من اليمين الى اليسار كما في الشكل التالي: [![image8](https://informatic-ar.com/wp-content/uploads/2014/10/image8.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image8.jpg) بعد ذلك تحسب ال simple checksum لكل صف من الصفوف [![image9](https://informatic-ar.com/wp-content/uploads/2014/10/image9.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image9.jpg) تذكر الsimple checksum هي مجرد جمع واخذ الرقم الاخير فقط، مثلاً الصف الثاني 4+5+3+6 ويساوي 18 وبالتالي يكون 8. الخطوة التالية حساب ال simple checksum لكل عمود كما في الشكل: [![image10](https://informatic-ar.com/wp-content/uploads/2014/10/image10.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image10.jpg) بعد ذلك عمل تجميع للأرقام بنفس الطريقة التي وضعوا بها من اليسار لليمين وبالتالي الرسالة سوف تكون من 24 خانه هي: 483725436822565399784306 ولنفرض انه حدث خطأ واحد وانك استقبلت الرسالة 483725436827565399784306 سوف نقوم الان بالتأكد منها وتصحيح الخطأ الموجود بهذه الطريقة. بعد أن وصلت الرسالة 483725436827565399784306 أول خطوة هي وضعها على مربع 5 \* 5 كما في الشكل: [![image11](https://informatic-ar.com/wp-content/uploads/2014/10/image11.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image11.jpg) الان قم بحساب ال simple checksum لكل اربعه ارقام من لكل صف وعمود وقم باضافتها كما بالشكل: [![image12](https://informatic-ar.com/wp-content/uploads/2014/10/image12.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image12.jpg) وكما هو واضح ان لديك الان ال checksum الذي ارسل بالاضافه الى ال checksum الذي قمت بحسابه، وبالطبع اذا تساوت الارقام فهذا يعني صحة الرسالة واذا اختلف فيعني خطأ في هذه الرسالة، وهنا لدينا خطئان: [![image13](https://informatic-ar.com/wp-content/uploads/2014/10/image13.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image13.jpg) هكذا موقع الخطأ اصبح معروف: العمود الثاني الصف الثالث، وهو كما موضح ادناه الرقم 7: [![image14](https://informatic-ar.com/wp-content/uploads/2014/10/image14.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image14.jpg) الان تم تحديد موقع الخطأ، ولتصحيحه نقوم باستبدال الرقم 7 برقم يجعل حساب ال checksum للعمود والصف صحيح، يمكنك اجراء الحسابات بسهولة، والرقم الصحيح هو 2: [![image15](https://informatic-ar.com/wp-content/uploads/2014/10/image15.jpg)](https://informatic-ar.com/wp-content/uploads/2014/10/image15.jpg) الان تم ايجاد الخطأ وتصحيحه، بقي فقط ايجاد الرسالة الاصلية وهي تكون بتجميع الارقام في الجدول من اليسار الى اليمين واهمال ال checksums وبالتالي تكون الرسالة هي: 4837543622563997 وهي نفس الرسالة الأصلية. في علوم الحاسب هذه الفكرة اسمها Two Dimensional Parity ، والاسم parity هو مطابق لل simple checksum في الحاسب عندما يتم التعامل مع الارقام الثنائية. هذه ال two dimensional parity استخدمت في بعض الاجهزة لكنها ليست ذات كفائه مقارنة بطرق اخرى، وتم توضيحها فقط لايصال فكرة انه يمكن ايجاد وتصحيح الاخطاء بدون الحاجة لمعادلات رياضية معقدة كما يتم الاعتقاد بذلك. ## **ايجاد وتصحيح الأخطاء في العالم الحقيقي** ظهرت فكرة ال Error Correction Code في 1940م بعد ظهور الأجهزة الالكترونية، حيث كانت الأجهزة القديمة غير موثوقة Unreliable ومكوناتها تنتج الكثير من الأخطاء بشكل متكرر، ولكن الاستفادة التي كانت هي في أنظمة الاتصالات والهاتف. وأحد أهم الأحداث التي تسببت في وجودها هي التي كانت في معامل بل للباحث Claude Shannon والباحث Richard Hamming واللذان كانا يعملان في ذلك الوقت في معامل بل. الباحث Hamming كما سبق وتطرقنا له كان سبب اكتشافه لأول أنظمة تصحيح الأخطاء هي انزعاجه من مكوث إجازة اخر الأسبوع في حل لمشاكل فشل الأجهزة Crashes وهذا أدى لظهور Hamming Code. على أي حال فان ال Error Correction Code هي جزء واحد من فرع يسمى نظرية المعلومات Information Theory، والذي نشأ بواسطة الباحث شانون في ورقته المشهور The Mathematical Theory of Communication في عام 1948م والذي اثبت فيها رياضياً أنه من الممكن من حيث المبدأ تحقيق درجة عالية من الموثوقية بشكل خالي من الأخطاء على وسيط نقل معرض للأخطاء وبه كثير من التشويش، وبعد عدة عقود تمكن العلماء من تحقيق ذلك المعدل الذي وصفه شانون في ورقته. استخدم Hamming code في الأجهزة القديمة ويستخدم حتى الان في بعض الذواكر Memory Systems، وبعدها أتى نوع مهم اسمه Reed-Solomon code والذي يستطيع تصحيح مجموعه كبيرة من الأخطاء (بالمقارنة مع Hamming Code والذي يستطيع تصحيح خطأ واحد لكل 7 أرقام)، ال Reed-Solomon Code بنى على أساس رياضي Finite Field Algebra ويمكنك ان تقول هو عباره عن Staircase checksum مع ال two-dimensional pinpoint وهو يستخدم في CD، DVD وحتى الأقراص الصلبة Hard drives. ال Checksums ايضاً له استخدام واسع بشكل عملي (لاكتشاف الأخطاء)، واحد أهم الأمثلة هو في شبكات الايثرنت Ethernet وهو البرتوكول الشبكي الذي يستخدمه أي جهاز حالياً، ال Ethernet يستخدم ال CRC-32 Checksum لاكتشاف الأخطاء، ايضاً بروتكول ال TCP يستخدم ال checksums لكل Packet يقوم بإرساله، والباكتات التي لها checksum خاطئ فهي تهمل لأن ال TCP مصمم لكي يعيد ارساله مجدداً اذا تطلب ذلك. برامج الحاسب الموجودة على الانترنت ايضاً يتم التحقق منها من خلال checksum، أحد أنواعها المشهورة هي MD5 وSHA-1، وهي لها استخدامات في التشفير، وتستطيع معرفة أي تغيير على البيانات سواء كان كذلك مقصودا أم غير مقصود. اخيراً، علم ال Error correction وال Error Detection في تقدم، ومنذ 1990 ظهرت طريقة Low-Density Parity-Check وتلقت الكثير من الاهتمام، وهي تستخدم في ال Satellite TV وغيرها من التطبيقات، لذلك المرة القادمة إذا كنت تستمع في قضاء وقتك على المحطات الفضائية في عطلة نهاية الأسبوع، فتذكر أن معركة شانون مع تلك الأجهزة لها دور في ذلك. > من كتاب " Nine Algorithms That Changed the Future" ### ما هو الـ stacktrace؟ URL: https://informatic-ar.com/java-stacktrace/ Last updated: 2026-10-04T04:35:41.000Z في لغة الجافا، تنقسم ذاكرة الـ JVM إلى عدة أقسام، ومنها ما يسمى بالـ stack. عند استدعاء دالة س (method) فإنه يُنشأ frameبداخله معلومات الدالة س ويوضع هذا الـ frame أعلى الـ stack. وعند الانتهاء من تنفيذ الدالة س، يتم إخراج الـ stack frame الخاص بالدالة س من الـ stack ويرجع مسار التنفيذ (flow of control) إلى الدالة السابقة والتي استدعت الدالة س. ```csharp public class Main { public static void main(String[] args) { x(); } public static void x() { y(); } public static void y() { // ... } } ``` شكل الـ stack سيكون كالتالي: [![stacks](https://informatic-ar.com/wp-content/uploads/2014/10/stacks.png)](https://informatic-ar.com/wp-content/uploads/2014/10/stacks.png) بدايةً يقوم الـ JVM بإنشاء الـ main thread ثم يقوم باستدعاء دالة ()main ويضع frame خاص بهذه الدالة في الـ stack. خلال تنفيذ دالة الـ ()main سيقوم باستدعاء الدالة ()x وتباعاً يضع frame خاص بها في الـ stack. وخلال تنفيذ الدالة ()x يستدعي الدالة ()yويضع frame في الـ stack. عند الانتهاء من تنفيذ الدالة ()y يقوم الـ JVM بإزالة الـ frame الموجود بأعلى الـ stack وهو الـ frameالخاص بالدالة ()y. بعد ذلك يعود لاستكمال تنفيذ الدالة ()x ويزيل الـ frame الخاص بها عند الانتهاء من تنفيذها. وأخيراً يعود للدالة()main ويكمل تنفيذها، وعند الانتهاء منها يزيل آخر frame موجود في الـ stack ويقوم بإنهاء الـ main tread (ليس شرطاً أن ينتهي البرنامج بانتهاء الـ main thread). ### ما هو الـ stacktrace؟ الـ stacktrace باختصار هو سلسلة من الاستدعاءات للدوال method invocations مطابقة للـ stack frames. يستخدم الـ stacktrace عادةً في معالجة الأخطاء واكتشاف مصدرها. لنأخذ مثال بسيط لتتضح الصورة: ```csharp import java.io.IOException; public class Main { public static void main(String[] args) throws Exception { x(); } public static void x() throws Exception { y(); } public static void y() throws IOException { throw new IOException("IOException in y()"); } } ``` يتم طباعة الـ stacktrace على الشاشة في حالتين: \- إما أن نمسك الـ exception في try and catch وبداخل الـ catch نطبع الـ stacktrace عن طريق الدالة ()e.printStackTraceحيث أن e هو مرجع للخطأ reference for the exception. \- أو نقوم برمي الـ exception وتحويله (exception propagation) إلى الدالة التي استدعت الدالة الحالية، حتى يصل الـ exception إلى آخر frame في الـ stack كما فعلنا في المثال الأخير. برنامج الجافا في المثال الأخير سيطبع الـ stacktrace التالي: ```javastacktrace Exception in thread "main" java.io.IOException: IOException in y() at Main.y(Main.java:17) at Main.x(Main.java:12) at Main.main(Main.java:7) ``` وكقاعدة عامة في أي stacktrace: \- السطر الأول يتكون من ٣‎ معلومات مهمة: اسم الـ thread، ونوع الـ exception، ورسالة الخطأ (إن وجدت). في المثال السابق اسم الـthread كان main، ونوع الـ exception كان java.io.IOException، ورسالة الخطأ كانت ()IOExcpetion in y. \- السطر الثاني يكون دائماً مصدر الـ exception، وفي الكود يكون المصدر هو الـ constructor الخاص بالـ exception. في المثال السابق كان مصدر الخطأ في السطر ١٧ وهو ;("()throw new IOException("IOException in y. \- السطر الأخير يحتوي على اسم الـ method التي تكون مدخل الـ thread وبدايته. في المثال السابق، مدخل الـ thread هو الدالة main. ### الـ stacktrace مع Caused by أحياناً يكون خطأ ما سببه خطأ آخر، ولذلك يمكن في لغة الجافا ربط exception معين على أنه سبب لـ exception آخر عن طريق تمريره كـ parameter في الـ constructor أو عن طريق استخدام الدالة ()initCause على مرجع الـ exception الجديد. وربما الخطأ س سببه الخطأ ص، والخطأ ص سببه الخطأ ع .. وهكذا. الـ stacktrace الناتج سيحتوي على معلومات الـ exception الجديد وجميع الـexceptions المتسببة فيه مفصولة فيما بينهم بالجملة Caused by. المثال التالي سيوضح ذلك: ```csharp import java.io.IOException; public class Main { public static void main(String[] args) throws Exception { x(); } public static void x() throws Exception { try { y(); } catch (IOException e) { // ex is caused by e Exception ex = new Exception("Exception in x()"); ex.initCause(e); throw ex; // or // throw new Exception("Exception in x()", e) } } public static void y() throws IOException { throw new IOException("IOException in y()"); } } ``` والـ stacktrace الناتج يكون كالتالي: ```javastacktrace Exception in thread "main" java.lang.Exception: Exception in x() at Main.x(Main.java:19) at Main.main(Main.java:7) Caused by: java.io.IOException: IOException in y() at Main.y(Main.java:30) at Main.x(Main.java:14) ... 1 more ``` نفس القاعدة السابقة تنطبق على الـ stacktrace الحالي باستثناء أنه لا يكتب اسم الـ thread في الـ exception الموجود بعد Caused by. أيضاً يوجد اختلاف طفيف هنا وهو السطر الأخير. السطر المفقود هذا يمكنك الحصول عليه من الـ exception السابق له. المثال الآتي سيوضح كيف يمكنك الحصول على السطور المفقودة من الـ stacktrace: لنفرض أنه لدينا الـ stacktrace التالي: ```javastacktrace Exception in thread "main" ExceptionA at A5 at A4 at A3 at A2 at A1 Caused by: ExceptionB at B6 at B5 at B4 ... 3 more Caused by: ExceptionC at C8 at C7 at C6 ... 5 more ``` كقاعدة عامة، إذا كان المفقود س من السطور في exception معين، نأخذ آخر س من السطور من الـ exception السابق له. وبالتالي الـstacktrace الكامل سيكون بالشكل التالي: ```javastacktrace Exception in thread "main" ExceptionA at A5 at A4 at A3 at A2 at A1 Caused by: ExceptionB at B6 at B5 at B4 at A3 at A2 at A1 Caused by: ExceptionC at C8 at C7 at C6 at B5 at B4 at A3 at A2 at A1 ``` ### الـ stacktrace مع javac options -g & -g:none عند عمل compile بالأمر javac -g فإن الـ stacktrace يكون محتوياً على أرقام الأسطر كما في الأمثلة السابقة، بينما لو تم عملcompile بالأمر javac -g:none فإن الـ stacktrace للمثال الأخير سيكون كالتالي: ```javastacktrace Exception in thread "main" java.lang.Exception: Exception in x() at Main.x(Unknown Source) at Main.main(Unknown Source) Caused by: java.io.IOException: IOException in y() at Main.y(Unknown Source) ... 2 more ``` لاحظ الاختلاف! ### الـ stacktrace بدون exception يمكن في لغة الجافا الحصول على stacktrace من أي مكان في الكود وطباعته مباشرة، دون الحاجة إلى exception. لاحظ المثال التالي: ```csharp public class Main { public static void main(String[] args) { x(); } public static void x() { y(); } public static void y() { Thread currentThread = Thread.currentThread(); StackTraceElement[] elements = currentThread.getStackTrace(); for (StackTraceElement element: elements) { System.out.println(element); } } } ``` والناتج سيكون كالتالي: ```javastacktrace java.lang.Thread.getStackTrace(Unknown Source) Main.y(Main.java:16) Main.x(Main.java:10) Main.main(Main.java:5) ``` ### ما هو ال Hashing ولماذا يجب أن لا يستخدم كوسيلة تشفير Encryption ؟ URL: https://informatic-ar.com/hashing-and-encryption/ Last updated: 2026-10-03T22:31:41.000Z كثير من الأحيان نجد من يستخدم ال Hashing algorithms كطريقة للتشفير Encyrption وهذا الأمر ليس صحيح (مثلاً استخدام MD5 أو SHA لتشفير البيانات، أو حتى فهم فكرة الHashing على أنها تشفير للبيانات)، فالتشفير يحتاج مفتاح key أو باسورد معين أو حتى بالطرق الكلاسيكية القديمة سوف تحتاج لطريقة معينة (هذه هي الsecret) حتى تسترجع البيانات وهو يعتبر كوسيلة اخفاء المعلومات حتى لا ترى البيانات بشكل الصحيح، بينما الHashing هو وسيلة لحفظ البيانات بشكل غير قابل للاسترجاع ابداً، وبالتالي الفرق كبير بينهم وشتان ما بين تطبيقاتهم، فالتشفير تستطيع استرجاع البيانات باستخدام الطريقة المقابلة لفك التشفير ، بينما الHashing لا يمكنك على الاطلاق. ## ال Hashing Function (أو الاسم الرياضي لها One Way Function) هي دالة تأخذ مدخل بأي طول وتخرج نص له طول معين على حسب الدالة، فسواء كانت MD5 وأجدادها القديمة مثل MD4 ، أو كانت ال SHA بأنواعها SHA1 و SHA2 وحتى ايضاً هناك RIPEMD وهي دالة هاش معروفة ايضاً. الجدول التالي يبين انواع الخوارزميات المختلفة [![hashing algorithms bit size md5, sha1](https://informatic-ar.com/wp-content/uploads/2014/08/hashingalgorithms-300x138.png)](https://informatic-ar.com/wp-content/uploads/2014/08/hashingalgorithms.png) المدخل هو اي binary data سواء كانت ملف ، باسورد، صورة، Image من القرص الصلب، وبشكل اساسي يمكنك أن تقول اي بيانات continuous/stream digital input لاحظ اسم الدالة one way function أي أنه لا يمكن الرجوع ابداً من الهاش الناتج واستخراج البيانات الأصلية، وايضاً نظرياً لا يمكن ان تجد بيانات مختلفة لها نفس الهاش.. اخيراً اي تغيير ولو bit واحد في المدخل سوف يؤدي لناتج مختلف تماماً عن ما سبق. بالنسبة للاستخدامات ، فعملياً تستخدم في عدة أمور: ## **اختبار التكامل أو عدم حدوث تغيير في البيانات Integrity** فمن خلال الHashing لأي بيانات سوف تحصل على الهاش المخرج، في حال قمت بأخذ الهاش لنفس البيانات يجب أن تحصل على نفس الهاش الناتج، اذا تغير الهاش فهذا يعني أن البيانات قد تغيرت.. مثلاً لدى ملف واريد مراقبته هل تم تغييره أم لأ، فأسهل طريقة أحسب الهاش للملف، وفي اي وقت أخر اقوم بفحص القيمة بما هو مخزن وهكذا سوف أعرف هل تم التغيير عليه أم لأ.. مثال اخر عندما تحمل بعض البرامج من الانترنت سوف تجد بجانب زر التحميل الهاش لذلك الملف حتى بعدما ان تقوم بتحميله يمكنك ان تتأكد من صحة الملف وأنه وصل لك كما في الموقع ## **البحث Searching** فمثلاً اريد البحث عن الملفات المتشابه في قرص، فأسهل طريقة هي حساب هاش كل ملف، ومعرفة الهاشات المتطابقة وهكذا سوف اعرف ان هذه الملفات متطابقة لأن لها نفس الهاش، والذي نظرياً لا يمكن أن يكون لأي ملفين مختلفين نفس الهاش هذا يتضمن ال Blacklist Searching مثلاً لدي مجموعه من الملفات (صور مخلة، ملفات منتشره مشتبه بها ) و اريد البحث عنها في قرص معين، سوف احسب الهاش لتلك الملفات وابحث عليها داخل اي وسيط وبالتالي يمكن ايجادها بسرعه. وايضاً يتضمن ال WhiteList Searching فمثلاً أريد البحث في ملفات في قرص النظام ولكن اريد أن يتجاهل ملفات معينة مثلاً ملفات ال dll الخاصة بالنظام وغيرها من الملفات المعروفة أنها تأتي مع النظام، فسوف احسبها واخزنها في قاعدة بيانات، وبالتالي عندما أبحث و اجد اي ملف له هذا الهاش فسوف اتجاهله لأنه ملف نظام وأنا اعرفه وهذا سيقلل وقت البحث والملفات التي تريد البحث حولها.هذه الطرق تستخدم في بعض برامج مكافحة الفيروسات، فمثلاً هناك ملفات فيروسية تكون معروفه وبالتالي اذا لديك قاعدة بيانات من الهاش لتلك الفيروسات تستطيع ببساطة البحث عنها في القرص ومقارنه اي ملف بالهاش في القاعدة واذا وجدته فهذا يعني انك وجدت فايروس ، طبعاً في الأنتي فابروس هذه ليست الطريقة الوحيدة على ايه حالة ## **تخزين البيانات بشكل غير قابل للاسترجاع** فكما هو معروف انه يجب تخزين كلمات السر في القاعدة على شكل Hashing والسبب أنه اذا حصل اختراق للقاعدة فسوف يتم جلب كل الباسوردات بسهوله، لذلك كان الأمر كتصعيب للمخترق، وبالتالي اي مستخدم يسجل لديك سوف تخزنه باسورده كهاش، وعندما يسجل دخول سوف تأخذ الباسورد وتحسب الهاش فاذا تطابق مع ما يوجد في القاعدة فسوف تتأكد من صحة الباسورد وبالتالي تسمح له بالدخول بالنسبة للعيوب أو الهجمات (ما يعرف بكسر الشفرات) فالحاصل أنه لا يتم ذلك ولا توجد اي طريقة لذلك الا من خلال حسابات مسبقة أو وجود تصادم في البيانات: 1. Collision attack 2. Preimage attack المشكلة الأولى هي أن بعض الخوارزميات كشفت بها ضعف وبالتالي امكن ايجاد مدخلين مختلفين لهم نفس الهاش ، وهذا يسمى Collision ، وخوارمية ال MD5 اشهر من تعرضت لهذه الهجمة لذلك الأفضل أن تستخدم SHA2 سواء 265 أو حتى 512 بت) وحتى ال SHA-1 قد تعرضت لمثل هذه الهجمات. احتمال ايجاد اي Collision هو قليل جداً، فمثلاً في ال SHA-1 احتمال ايجاد تصادم واحد يكون بين كل 2 اس 80 رسالة وهو رقم كبير جداً ولا يستطيع ان يقوم به اي جهاز عادي. اما المشكلة الثانية هي أن منها الفكرة أنه يتم توليد هاشات لأي نصوص سواء كانت عشوائية أو يتم سحبها من اي قاعدة بيانات أو حتى عن طريق عمل crawling على الويب والمنتديات وسحب الtextual information ويمكن أن تكون بدايه للبيانات، وحساب هذه النصوص وما يقابله من الهاش وتخزينها على جداول في قاعدة بيانات ما (هذه الجداول في العادة تسمى Rinbow Tables)، وبالتالي اذا حصلت على هاش معين وصدف انه محسوب من قبل فسوف تجد النص الأصلي له ، ايضاً هناك من يقوم بعملية حساب الهاش بالاستفادة من قدرات المعالجة الحديثة Multi-Cores ويقوم بتوليد الهاشات بكل الcores المتاحة، وهناك من يستخدم ال GPU داخل كرت الشاشه للقيام بذلك ، وهناك من يقوم بالعملية على Distributed Systems وفي النهايه كلهم يقوموا بالتخزين على أمل ان يصدف الهاش بها، ولكن اذا كانت كلمة المرور معقدة فسوف يصعب ذلك ## **خاتمة** هناك من يقومو قبل تخزين الهاش باضافه نص اخر عليه Salt وبالتالي اذا حصل أن المخترق وصل لقاعدة البيانات فحتى لو كان المستخدم باسورده هو 123 والذي يسهل استرجاع النص الأصلي من الهاش المخزن فسوف يصعب على المخترق بسبب أن الsalt اضيف لذلك الباسورد وبالتالي اذا كان الsalt صعب ايضاً فقد لا يتم ايجاد النص الأصلي لكلمة المرور السهله هذه، ولكن في النهايه ال salt يجب ان يخزن في مكان ما ، سواء في القاعدة أو داخل الكود أو في مكان ما المهم ان يكون الكود قادراً على استرجاعه بسهولة وبسرعه ، ربما بعض ال Obufscation يصعب على المخترق في هذا الأمر آمل أن تكون المقالة الصغيرة قد افادتك وبينت لك فوائد ال Hashing، ولو لاحظت أن تطبيقاتها كثيرة خصوصاً في مجال التحقيق الجنائي forensics وفي البحث عن البيانات، فيمكنك بسهولة الان القيام ببضعه تطبيقات صغيرة والتي عرضنا افكارها في المقالة ### سلسلة معمل البيوانفورماتيك :الفصل الاول~أين يبدأ نسخ الجينوم؟ URL: https://informatic-ar.com/bioinformatics-genome-replication/ Last updated: 2026-10-03T22:31:42.000Z بسم الله الرحمن الرحيم والصلاة والسلام على أشرف المرسلين سيدنا محمد صلى الله علية وسلم **Bioinformatics Lap** الفصل الاول بعنوان أين يبدأ نســــــــــــــــــــــخ الجينوم ( Chapter 1: Where Does DNA Replication Begin ) الهدف من الفصل :**ايجاد منطقة بداية النسخ فى البكتريا بمختلف أنواعها** ملخص لفقرات الفصل: 1-نستعرض فية مقدمة عن عملية النسخ للحمض الوراثى DNA 2-ما هى اهمية معرفة منطقة بداية النسخ replication origin؟ 3-كيف تعرف الخلية ان منطقة معينة هى منطقة بداية النسخ replication origin للجينوم؟ 4-ما هى الرسالة المخفية فى منطقة بداية النسخ replication origin؟ 5-هل تختلف هذة الرسالة من بكتريا الى اخرى؟ 6-ما هى طريقة التعلم in silico approach ؟ 7-هل تعلم ما هو لغز The Gold-Bug؟ 8-شرح لعملية النسخ بواسطة DNA polymerase 9-ماذا يقصد ان عملية النسخ Asymmetry 10-ماذا نستفيد من معرفة ان عملية النسخ Asymmetry فى تحديد منطقة بداية النسخoriC 11-كيف يمكننا معرفة الرسالة المخفية dnaA box بعد تحديد منطقة بدأ النسخ oriC 12- هل يمكن ايجاد dnaA box لبكتريا Salmonella typhimurium علما بأنها متشابهة كثيرا مع بكتريا Ecoil التى درسناها 13- مشاكل مفتوحة لابحاث فى المشكلة \----- Introduction to DNA Replication**\-**Hidden Messages in the Replication Origin Some Hidden Messages are More Surprising than Others**\-**An Explosion of Hidden Messages Asymmetry of Replication-Peculiar Statistics of the Forward and Reverse Half-Strands Some Hidden Messages are More Elusive than Others-A Final Attempt at Finding DnaA Boxes in E. coli-Open Problems [الفصل فى الرابط : الفصل الاول :أين يبدأ نسخ الجينوم](https://informatic-ar.com/wp-content/uploads/2014/01/%D8%A7%D9%84%D9%81%D8%B5%D9%84-%D8%A7%D9%84%D8%A7%D9%88%D9%84-%D8%A7%D9%8A%D9%86-%D9%8A%D8%A8%D8%AF%D8%A3-%D9%86%D8%B3%D8%AE-%D8%A7%D9%84%D8%AC%D9%8A%D9%86%D9%88%D9%85.pdf) الاكواد بلغة البايثون بأذن الله تعالى بأذن الله تعالى نضع مراجع عند نهاية السلسلة **رَبِّ ابْنِ لِي عِنْدَكَ بَيْتًا فِي الْجَنَّةِ** **نسألكم بالله الدعاء لنا بالجنــــة وأن يتقبل الله عز وجل العمل خالصـــا لوجة- اميـــــــــــــــن** **الحمد الله رب العالميين –اللهم دومها نعمة واحفظها من الزوال –امين -اللهم ارزقنا بكل حرف مغفرة ورضوان امين يا رب** **إِنَّ اللَّهَ وَمَلَائِكَتَهُ يُصَلُّونَ عَلَى النَّبِيِّ يَاأَيُّهَا الَّذِينَ آمَنُوا صَلُّوا عَلَيْهِ وَسَلِّمُوا تَسْلِيمًا** ### حل مشكلة اللغة العربية في النوت باد (كيف يحلها المبرمج) URL: https://informatic-ar.com/arabic-text-encoding/ Last updated: 2026-10-03T22:31:42.000Z السلام عليكم ورحمة الله وبركاته إن الحديث عن موضوع ال Unicode وال Encoding يعتبر من الأساسيات الضرورية Fundamental Concepts لأي مبرمج ومهندس برمجيات، واكمالاً لسلسه ([أساسيات يجب أن يعرفها اي مبرمج](https://informatic-ar.com/unicode%5Fencoding/)) فقد طرحنا سؤالاً عن مشكلة عامه نراها كثيراً بشكل دوري الا وهي أن **النص العربي لا يظهر بشكل مقروء عند فتحه في ال Notepad** [![arabic_encoding](https://informatic-ar.com/wp-content/uploads/2014/01/arabic_encoding-150x150.png)](https://informatic-ar.com/wp-content/uploads/2014/01/arabic%5Fencoding.png) وقد تم أخذ مثال من الموسوعة الشاملة (وهي تتكون من ملفات txt كثيرة)، وكانت هذه الجزئية من الملف هي ال test case لسؤالنا ([حمله من هنا](https://dl.dropboxusercontent.com/u/63101982/file.txt?ref=informatic-ar.com)) : [![the_problem](https://informatic-ar.com/wp-content/uploads/2014/01/the_problem-300x189.png)](https://informatic-ar.com/wp-content/uploads/2014/01/the%5Fproblem.png) كما هو واضح من الصورة أن النص غير مقروء عند فتحه على الجهاز وهناك مشكلة في الترميز وتحتاج لاصلاح [![questions](https://informatic-ar.com/wp-content/uploads/2014/01/questions.png)](https://informatic-ar.com/wp-content/uploads/2014/01/questions.png) **السؤال الذي سنجيب عليه لماذا ظهر الملف هكذا عند فتحه وكيف يمكن اصلاحه وارجاعه الى نص مقروء ؟** الحل الذي ارسله أكثر المبرمجين ينطوي تحت كلمة Unicode وأنه يجب أن يتم تحويل الملف الى UTF-8 حتى يتم اصلاح الملف وارجاعه الى الصيغه الأصلية.. لنرى هذه الحلول وكيف **أنها قد لا تحل المشكلة**: تحويل ترميز الملف باستخدام الNotepad كما تلاحظ نفس المخرج بالضبط بعد تحويله ل UTF-8: [![converting](https://informatic-ar.com/wp-content/uploads/2014/01/converting-1024x401.png)](https://informatic-ar.com/wp-content/uploads/2014/01/converting.png) وبنفس الفكرة هنا بعد ترميز الملف ب UTF-8 باستخدام Notepad++ أو اي طريقة اخرى ستجد نفس المخرج **ملاحظة*: في حال ظهر الملف بعد تحميله بشكل صحيح وبعد تغيير ال Encoding بأحد الطرق السابقة سوف يظهر بشكل صحيح وذلك لأنك تستخدم نظام لغته الافتراضيه هي العربية Default Locale is Arabic ولذلك فلن تلاحظ هذه المشكلة وستجد الملف باللغه العربية الصحيح، لكن يمكنك القرائه في الموضوع حتى تعرف سبب المشكلة خصوصاً انها كانت بسبب شخص لديه نفس اعداداتك.* **اذاً ما هو الحل اذا لم يظهر النص بشكل صحيح؟** في مثل هذه المسائل عليك بمعرفة نوع الترميز الذي استخدم في انشاء الملف، هناك طرق كثيرة مثلاً : رمي الملف على المتصفح وسوف يحاول المتصفح معرفة الترميز بنفسه وسوف تجده من قائمه الاعدادت في المتصفح (اسم الترميز المستخدم لهذه الصفحة) [![browser](https://informatic-ar.com/wp-content/uploads/2014/01/browser-1024x366.png)](https://informatic-ar.com/wp-content/uploads/2014/01/browser.png) كما تلاحظ باستخدام المتصفح وجدنا ان الترميز هو Windows-1256 ، وهناك مواقع تستطيع الكشف عن ال Encoding للملف الذي ترسله لها ، ويمكنك كتابه برنامج هكذا ان احببت ايضاً. طبعاً المتصفح عرف الترميز الصحيح لأنه يستخدم خوارزميات لمعرفة نوع الترميز المستخدم في الملف حتى يقوم بعرضه وسوف تجد أنه تمكن من ذلك بشكل صحيح. بعد ذلك يجب ان تعلم أن برنامج ال Notepad ليس به خوارزميات متقدمة مثل البرامج الأخرى كالمتصفح ، فعندما يقوم بقرائه اي ملف فانه يحاول معرفة ال Encoding له وفي حال تعرف على ال Encoding يقوم بعرضه وسوف يظهر كما انشئه صاحب الملف، **أما في حال أنه لم يتعرف على الملف فسوف يقوم بترميزه باستخدام ال Default Locale في النظام**. والNotepad فقط يستطيع التعرف على UTF-8 و UTF-16 (أغلب برامج الويندوز تطلق عليه Unicode) و ANSI (والذي هو عباره عن الترميز الافتراضي لديك في النظام). نعود لمثالنا السابق، كان الملف ترميزه هو Windows-1256 والNotepad لا يستطيع فهم هذا الترميز فسوف يلجاً مباشره الى النظام ويجلب ال ANSI (أي ال Default Locale Encoding) ويقوم بعرضه.. للنظر اذاً للDefault Locale على جهازي: [![SystemLocale](https://informatic-ar.com/wp-content/uploads/2014/01/SystemLocale-300x212.png)](https://informatic-ar.com/wp-content/uploads/2014/01/SystemLocale.png) كما هو واضح الان، الDefault هو اللغه الانجليزية وهكذا يعني أن ال Notepad سوف يعرض اي ملف مكتوب باستخدام ال Windows-1256 (وأي Encoding أخر لم يستطيع التعرف عليه) باستخدام ال Windows-1252 ولذلك سوف يظهر بشكل خاطئ. لكن في حال أن ال Default Locale لديك هو عربي سوف يظهر النص بلا مشاكل على الاطلاق (حيث يتم ترميزه باستخدام ال Windows-1256 وهو الافتراضي لكل الملفات النصية غير القابلة لمعرفة ترميزها.. والملف سوف يعمل طبعاً لأنه كتب بنفس الترميز الذي عرض به) **اذاً الحل المتقرح** هو بتحويل ال Locale في جهازك الى العربي وسوف يعمل معك**..** **وطبعاً هذا هو الحل المنتشر في اغلب المنتديات وهو الحل المعروف للجميع والذي كانت أغلب الاجابات حوله، ولكن هل حقاً هو حل جيد ؟** - ماذا اذا كنت تكتب برنامج وتريد ان يعمل على مستخدمين في مناطق متوزعه ؟ - ماذا اذا كتبت ملف Read-me أو help وأردت نشره لأكثر من 10000 مستخدم ؟ هل ستطلب منهم جميعهم تحويل ال Locale ؟ - ماذا اذا كان المستخدم لا يوجد لديه Locale عربي في جهازه ؟ كل هذه الأمور تجعل حل تغيير ال Locale هو غير فعال خصوصاً للمبرمجين أو الناشرين للكتب والملفات الرقمية... **اذا ما هو الحل الصحيح في مثل هذا الأمر ؟** يجب عليك أن تحول الملف الى ال Unicode حتى يعمل على جميع الاجهزه ، و كما شاهدنا انه عندما قمت بالتحويل مباشره من ال ANSI (الذي كان Windows-1252 ) الى UTF-8 حصلت على مشكلة في المخرج وهنا كان مكمن السؤال، **لأنه قبل أن تحول النص لترميز جديد يجب أن يكون ذلك الملف بالترميز الصحيح، ولذلك عليك بتحويل الملف الى الترميز الصحيح اولاً والذي هو Windows-1256 وبعد ذلك سوف تحصل على النص العربي ومن ثم تقوم بتحويله ال UTF-8 وسوف تحصل على النص العربي الذي يعمل على جميع الأجهزه بغض النظر عن ال Locale**. تستطيع ذلك من خلال طرق كثيرة مثلاً: فتح الملف من خلال المتصفح ، ومن ثم نسخ النص من المتصفح ووضعه على النوت باد ثم حفظ باسم وحدد ال UTF-8 أو من خلال الNotepad++ هي أن تحول الترميز ال Windows-1256 واحفظ الملف ثم حوله بعد ذلك الى UTF-8 من خلال قائمة Encoding. ## حل مشكلة اللغة العربية في النوت باد بخطوات بسيطة: [![encoding](https://informatic-ar.com/wp-content/uploads/2014/01/encoding-300x201.png)](https://informatic-ar.com/wp-content/uploads/2014/01/encoding.png) اذا لم تحول الملف الى UTF-8 بعد أن حولته ال Windows-1256 وقمت بحفظ الملف، فعندما تفتحه مره اخرى بال Notepad فلن يظهر بشكل صحيح لأن ال Notepad لا يعرف هذا الترميز وسوف يقرأ ال Locale من النظام واذا كان انجليزي فسوف تحصل على النص خاطئ ايضاً. **قد تتسائل الان لماذا صاحب الملف لم ينتبه لهذا الأمر ، السبب مرة اخرى هو ال أن برنامج النوت باد بدائي حقيقة وانه عندما تحفظ الملف ب ANSI فالترميز سوف يكون بناء على ال Locale الموجودة لديك في النظام :** - اذا كان ال Locale لديك عربي قم بفتح ملف Notepad واكتب اي نص عربي وقم بالتخزين بترميز ANSI (عن طريق الحفظ Save) سوف تجد أن عمل الملف بشكل عادي في جهازك بدون أي تحذير من البرنامج - لكن اذا كان ال Locale انجليزي فسوف تأتيك رساله تحذير بأن الملف به أحرف ليست ANSI وانه يتوجب عليك الحفظ بال Unicode. لذلك صاحب الملف والذي تسبب بالمشكلة كان لديه Locale عربي وقام بعمل الملف وحفظه على ANSI وشاهده بعد الحفظ وقام بنشره مطمئناً والحقيقة المرة أنه لن يعمل الا على ال Arabic Local Machines وسوف تراه بهذا الشكل عندما يكون جهازك English Locale. **الخلاصه:** 1. قبل تحويل الترميز في الملف يجب ارجاع الملف لأصله وبعدها قم بالترميز والا سيخرب الملف 2. لا تخزن ملف نصي بصيغه Windows-1256 والا فلن يعمل على الأنظمة التي بها English Locale 3. اجعل برنامجك يتعامل مع ال Unicode ولا تتعامل مع ال Default Locale في جهازك فقط فقد يختلف عن المستخدمين 4. عندما تتعامل مع مشاكل ال Unicode لا تستعجل بطرح حل قبل ان تفهم المشكلة جيداً وتعرف مسبباتها دمتم سالمين ### بنية دروبال – المجلدات وأهم الملفات URL: https://informatic-ar.com/drupal-files-and-directories/ Last updated: 2026-10-03T22:31:43.000Z **مقدمة:** من المهم التعرف على تركيبة دروبال من حيث المجلدات والملفات وما تقدمه من فهم عميق للزائر الجديد لعالم دروبال سواء كان مهتماً بالجوانب التطويرية والبرمجية لدروبال (Back End) أو الجوانب التصميمة والجمالية (Front End)، ولكن قبل البدء بشرح التركيبة الشجرية لدروبال لنتحدث قليلاً عن كيفية الحصول على الملفات المصدرية لدروبال وكيفية إستخدامه بصورة عامة. **الحصول على دروبال 7 وإستخدامه:** يمكن تنزيل دروبال 7 من الموقع الرسمي لدروبال ( Drupal.org ) ، وعند إكتمال عملية التنزيل نجد أننا حصلنا على مجلد بداخله مجموعة من المجلدات والملفات بإمتدادات .php و .jS و .css وغيرها من الإمتدادات ، وببساطه يمكنك تحويل المجلد الرئيس لدروبال 7 إلى عنوان مشروعك الذي تريد وإعتبار جميع المجلدات والملفات التي بداخله كبنية قاعدية للمشروع الذي نرغب ببنائه بمعنى أننا سنبنى على ما هو موجود بداخل هذا الملف وسنضيف عليه ونعدل لحين إكتمال المشروع وفي حال رغبنا ببناء مشروع جديد سنقوم بتنزيل نسخة جديدة من دروبال 7 والبناء عليها وهكذا دواليك. **التركيبة الشجرية لدروبال 7:** الصورة التالية توضح التركيبة الشجرية لدروبال 7 وسنبدأ بشرح مكوناتها: [![بنية دروبال 7](https://informatic-ar.com/wp-content/uploads/2014/01/the_drupal_7_structure-150x300.png)](https://informatic-ar.com/wp-content/uploads/2014/01/the%5Fdrupal%5F7%5Fstructure.png) مجلد (Includes): يحوى هذا المجلد ملفات بإمتداد .php والتي توفر الدوال Functions والفئات Classes التي تمثل في مجملها جميع الـAPI الخاصة بدروبال. مجلد (Misc): يضم هذا المجلد بعض المكونات الأساسية اللازمة لعمل دروبال مثل ملفات الـJavaScript والJquery وبعض الصور. مجلد (Modules): وهو المجلد الأكثر أهمية من ضمن مجلدات دروبال لأنه يضم الوحدات Modules الخاصة بقلب دروبال Core ، والتي ينصح بشدة بعدم إجراء أي تعديل عليها لأن إي تعديل أو تغيير قد يسبب مشاكل أثناء الإستخدام أو التحديث أو الترقية للنسخ المستقبلية وقد تتسبب أي تعديلات في هذا المجلد إلى إيقاف كامل النظام. يضم هذا المجلد بداخله مجلدان الأول (database) ويحوي بداخله مجموعات الملفات الخاصة بإدارة قواعد البيانات المختلفة والتي يمكن لدروبال الإرتباط بها مثل Mysql أو Postgere وغيرها والثاني (filetransfer) وهو خاص بإدارة العمليات المتعلقة بحركة الملفات من وإلى نظام دروبال ( عمليات التحديث ، الترقية ، إضافة وحدات جديدة ، حذف وحدات ... ألخ ). مجلد (Profiles): وهو المجلد الخاص بملفات تهيئة دروبال للعمل أول مرة ( التنصيب Installation ) كما بالإمكان تخصيص عدة Profiles لتكون كخيارات تنصيب للمستخدم. مجلد (Scripts): هذا المجلد خاص بعمليات التطوير بحيث يضم الملفات التي تحوى أوامر الـShell. مجلد (ٍSites): هنا حيث بإمكان المطورين بناء الوحدات الخاصة بهم وإجراء أي تعديلات مطلوبة وهو يضم مجموعة مجلدات كالتالي: \- مجلد (Site/all/libraries): خاص بالمكتبات الإضافية والتي يمكنها العمل مع دروبال مثل Wysiwyg Editor. \- مجلد (Site/all/modules): خاص بالوحدات الجديدة التي يبنيها المطورون وهنا حيث تتركز أغلب الإضافات الخاصة بالمشروع. \- مجلد(Site/all/themes): خاص بملفات التنسيق الـThemes الجديدة التي يبنيها المصممون. \- مجلد (Sites/default): يحوي هذا المجلد ملف الـSetting.php الخاص بالضبط الإفتراضي لدروبال. \- مجلد (Sites/default/files): وهو المسار الإفتراضي لرفع الملفات بأنواعها المختلفة من قبل المستخدم وبالإمكان لاحقاً تخصيص إضافية مثلاً مجلد خاص بالملفات ومجلد خاص بالصور وهكذا. مجلد (Themes): يضم هذا المجلد مجموعة من قوالب الـTheme الجاهزة والتي تأتي مع دروبال أو يمكن إنزالها من الموقع الرسمي لدروبال Drupal.org. **بعض الملفات الهامة:** بعد تنزيل دروبال وفك الضغط نلاحظ أن المجلد الرئيسي يحتوى على بعض الملفات الأساسية وهي كالتالي: ملف (authorize.php): يضم الكود الخاص بعمليات التحقق من الملفات المنزلة أو المحملة مثلاً أثناء عمليات التحديث والتحميل للـTheme أو الـModule الجديدة. ملف (cron.php): يضم هذا الملف مجموعة من العمليات الأساسية والتي يتم تنفيذها على النظام بصورة دورية وروتينية كعمليات التحديث والمراقبة والإختبار. ملف (index.php): يمثل النقطة الرئيسة لإستقبال كافة الطلبات الموجهه لنظام دروبال ويقوم بتحليل الـURL المرسل بواسطة المستخدم ومن ثم تحديد الجزئية المراد تنفيذها من النظام. ملف (install.php): بعد تنزيل نسخة دوربال وفتحها عبر الـLocalhost أو الـServer فإن ملف Install.php هو الملف الخاص بإجراء عمليات الإعداد والتنصيب الأولية لدروبال وإعداده للعمل. ملف (update.php): في كثير من الأحيان نجد أن الـModules التي نستخدمها في نظام دروبال ينتابها التحديث المستمر والتطوير ، ويعلن عن هذه التحديثات عبر الموقع الرسمي لدروبال ، ويأتي دور ملف Update.php لمعرفة هذه التحديثات وتنبيه المستخدم بها ومن ثم بعد موافقة المستخدم إجراء هذه التحديثات وتتبع كافة التغييرات اللازمة لتطبيق هذا التحديث سواء على مستوى قاعدة البيانات أو مستوى الملفات والـModules الأخرى. ملف (xmlrpc.php): تمثل طلبات الـXML Request جزء أساسي من المشاريع التي تستخدم إضافات الـWeb Services خصوصاً تلك التي تربط بين أنظمة من لغات مختلفة أو أجهزة مختلفة ، دروبال 7 يدعم خدمات الـWeb Services ويخصص هذا الملف لإدارة الـXML Request المرسلة أو المستقبلة ، أما في حال كان المشروع لا يحتاج لمثل هذه الخاصية فبالإمكان حذف هذا الملف دون التأثير على بقية أجزاء دروبال 7. ### دورة أندرويد – إعداد البيئة التطويرية URL: https://informatic-ar.com/android-course1-lesson2/ Last updated: 2026-10-03T22:31:43.000Z قبل الخوض في أساسيات برمجة تطبيقات أندرويد، سنقوم بتحميل وتثبيت الأدوات التطويرية اللازمة. بطبيعة الحال، يوجد خيارين لبرمجة تطبيق أندرويد: 1. تثبيت بيئة تطويرية متكاملة (IDE)، حيث تسهل لنا عملية التطوير بشكل كبير وتخفي على المبرمج الكثير من العمليات من compiling و packaging وغيرها، واستبدالها بضغطة زر واحدة. يتوفر العديد من الـ IDE المجانية لبرمجة تطبيقات أندرويد، مثل: Eclipse و NetBeans و IntelliJ و Android Studio. 2. إجراء جميع العمليات عن طريق الـ CMD، وفي هذه الحالة، سيكون المبرمج هو المسؤول عن جميع العمليات اللازمة لعمل تطبيق أندرويد، ابتداءً من مرحلة كتابة الكود إلى مرحلة النشر على المحاكي أو جهاز هاتف. سنعتمد في هذه الدورة على الخيار الأول، وقد نشرح الطريقة الثانية كإضافة للدروة في المستقبل. سنعتمد برنامج الـ Eclipse في شرح الدروس، وقد نتطرق إلى الـ Android Studio في المستقبل. **\- تحميل وتثبيت الـ JDK (نظام الويندوز):** 1. قم بزيارة صفحة تحميل الـ JDK على موقع أوراكل: [http://www.oracle.com/technetwork/java/javase/downloads/index.html](http://www.oracle.com/technetwork/java/javase/downloads/index.html?ref=informatic-ar.com) 2. قم بتحميل النسخة الأخيرة من الـ JDK (آخر النسخة وقت إنشاء الدرس هي 7 التحديث 45: [http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html?ref=informatic-ar.com)) 3. اضغط على "Accept License Agreement" ثم حمل النسخة الموافقة لنظام التشغيل الخاص بجهازك. على سبيل المثال، إذا كانت المعمارية 64-بت، حمل النسخة Windows x64، ولو كانت المعمارية 32-بت، حمل النسخة Windows x86، وهكذا. 4. بعد التحميل، قم بتثبيت الـ JDK على جهازك. 5. بعد التثبيت، أضف JAVA\_HOME إلى الـ System variables، ثم أضف القيمة JAVA\_HOME%\\bin%; إلى المتغير PATH، وذلك من أجل أن تعمل أداوات الجافا (java و javac وغيرها) من الـ CMD. الصور التالية توضح خطوات إضافة JAVA\_HOME: [![1](https://informatic-ar.com/wp-content/uploads/2014/01/1-283x300.png)](https://informatic-ar.com/wp-content/uploads/2014/01/1.png) خطوات إضافة المتغير JAVA\_HOME: الخطوة الأولى [![2](https://informatic-ar.com/wp-content/uploads/2014/01/2.png)](https://informatic-ar.com/wp-content/uploads/2014/01/2.png) خطوات إضافة المتغير JAVA\_HOME: الخطوة الثانية [![3](https://informatic-ar.com/wp-content/uploads/2014/01/3.png)](https://informatic-ar.com/wp-content/uploads/2014/01/3.png) خطوات إضافة المتغير JAVA\_HOME: الخطوة الثالثة [![4](https://informatic-ar.com/wp-content/uploads/2014/01/4-270x300.png)](https://informatic-ar.com/wp-content/uploads/2014/01/4.png) خطوات إضافة المتغير JAVA\_HOME: الخطوة الرابعة [![5](https://informatic-ar.com/wp-content/uploads/2014/01/5-300x130.png)](https://informatic-ar.com/wp-content/uploads/2014/01/5.png) خطوات إضافة المتغير JAVA\_HOME: الخطوة الخامسة [![6](https://informatic-ar.com/wp-content/uploads/2014/01/6-269x300.png)](https://informatic-ar.com/wp-content/uploads/2014/01/6.png) خطوات إضافة المتغير JAVA\_HOME: الخطوة السادسة [![7](https://informatic-ar.com/wp-content/uploads/2014/01/7-300x129.png)](https://informatic-ar.com/wp-content/uploads/2014/01/7.png) خطوات إضافة المتغير JAVA\_HOME: الخطوة السابعة وللتأكد من إتمام العملية بنجاح، افتح الـ CMD واكتب الأمر: ```powershell javac -version ``` سيطبع النص الآتي: ```powershell javac 1.7.0_45 ``` --- **\- تحميل وتثبيت الـ Eclipse (نظام الويندوز):** 1. قم بزيارة موقع الـ Eclipse على هذا الرابط: [http://www.eclipse.org/downloads](http://www.eclipse.org/downloads?ref=informatic-ar.com) 2. قم بتحميل النسخة Eclipse IDE for Java Developers. 3. بعد التحميل، قم بفك الضغط عن ملف الـ ZIP. لاحظ أن برنامج الـ Eclipse لا يأتي على هيئة installer، بل على هيئة ملف ZIP، لذلك ننصح بعد فك الضغط بوضع المجلد الناتج في مكان مختلف عن سطح المكتب حتى لا يتم إزالته. مثلاً، ضع المجلد eclipse في المسار التالي: C:\\Program Files ثم قم بعمل رابط مختصر للملف eclipse.exe على سطح المكتب. --- **\- تحميل الـ Android SDK والـ Eclipse ADT plugin (نظام الويندوز):** 1. افتح برنامج الـ Eclipse. 2. سيسألك عن الـ workspace، وهو المجلد الذي سيحتوي على مشاريع الأندرويد التي تقوم بتطويرها. ننصح بوضع علامة صح على الخيار "Use this as the default and do not ask again" حتى لا يسألك نفس السؤال عند كل مرة تقوم فيها بتشغيل البرنامج. 3. بعد فتح برنامج الـ Eclipse، ستظهر لك الشاشة الترحيبية، أغلقها بالضغط على علامة x الموجودة بجانب كلمة Welcome. 4. اختر من القائمة Help الخيار Install New Software. 5. ضع الرابط https://dl-ssl.google.com/android/eclipse في الحقل الموجود بأعلى النافذة ثم اضغط على ENTER. إذا لم يعمل الرابط، جرب http بدلاً من https، وإذا لم يعمل، فالعتب على برنامج الحماية في جهازك :) 6. انتظر ثواني، وستظهر لك قائمة بالـ plugins المتاحة. اختر منها Developer Tools ثم اضغط على Next. 7. اضغط على Next، ثم اقبل الشروط، ثم اضغط على Finish. 8. انتظر حتى ينتهي من تحميل وتثبيت الـ plugins. قد تظهر لك رسالة تحذير، تجاهلها واضغط على OK. سيطلب منك إعادة تشغيل برنامج الـ Eclipse، اضغط على Yes. 9. ما قمنا به الآن هو تحميل الـ ADT، وعلينا كخطوة تالية تحميل الـ Android SDK. 10. بعد إعادة تشغيل برنامج الـ Eclipse، ستظهر لك رسالة خطأ وتخبرك بأنه لم يتم تحديد مكان الـ Android SDK، تجاهلها واضغط على close. 11. ستظهر لك نافذة أخرى معنونة بـ "Welcome to Android Development"، اختر Install new SDK وضع علامة صح على كلا الخيارين (وذلك لتحميل آخر إصدار من نظام الأندرويد، بالإضافة إلى الإصدار 2.2) ودع مسار الـ SDK كما هو C:\\Users\\AndroidDeveloper\\android-sdks (بافتراض أن اسم المستخدم الحالي هو AndroidDeveloper). الآن اضغط على Next، ثم اضغط على Finish. 12. انتظر حتى يتم جلب الملفات المتاحة للتحميل. ستظهر لك نافذة أخرى، وافق على الشروط ثم اضغط على Install. 13. انتظر حتى ينتهي من التحميل والتثبيت. 14. قم بإضافة المتغير ANDROID\_HOME إلى الـ System variables واجعل قيمته C:\\Users\\AndroidDeveloper\\android-sdks، ثم أضف القيمة ANDROID\_HOME%\\tools%;ANDROID\_HOME%\\platform-tools%; إلى المتغير PATH، تماماً كما تم شرحه مسبقاً. --- **\- ما هو الـ Android SDK Manager:** هو أداة لإدارة مكتبات نظام الأندرويد وبعض الإضافات. يمكن تشغيل هذه الأداة إما عن طريق برنامج الـ Eclipse من القائمة "Window" أو عن طريق الملف التنفيذي الموجود في هذا المسار: C:\\Users\\AndroidDeveloper\\android-sdks\\SDK Manager.exe. قم بفتح الأداة ثم حمل الإضافات التالية (ما نحتاجه الآن فقط): - **Intel x86 Atom System Image:** تستخدم مع المحاكي (Emulator) لمحاكاة نظام أندرويد يعمل على معالج Intel x86 Atom. قم بتحميل الخاص بآخر نسخة من نظام الأندرويد (يرجى ملاحظة أن الإصدار 4.4.2 به العديد من المشاكل مع المحاكي، لذلك ننصح بالإصدار 4.3 مع المحاكي). - **Android Support Library:** مكتبة برمجية لاستخدام مميزات الإصدرات الجديدة من نظام الأندرويد، في الإصدارات القديمة من نظام الأندرويد. - **(Intel x86 Emulator Accelerator (HAMX:** مسرع للمحاكي، ويعمل على الأجهزة المدعمة بمعالجات Intel التي تدعم تقنية الـ Intel® Virtualization. بعد تحميل هذه الإضافة، ستجد الـ installer في المسار التالي: C:\\Users\\AndroidDeveloper\\android-sdks\\extras\\intel\\Hardware\_Accelerated\_Execution\_Manager\\IntelHaxm.exe. إذا كان جهازك يحتوي على معالج Intel يدعم تقنية الـ Intel® Virtualization، قم بتفعيل تقنية الـ Intel® Virtualization من الـ BIOS ثم بعد ذلك قم بتثبيت الـ HAMX عن طريق الـ installer. هذا المسرع وظيفته أن يجعل المحاكاة على مستوى الـ hardware وليس على مستوى الـ software. للمزيد قم بزيارة هذه الصفحة: [http://software.intel.com/en-us/videos/the-benefits-of-developing-android-apps-with-the-intel-hardware-accelerated-execution-manager/](http://software.intel.com/en-us/videos/the-benefits-of-developing-android-apps-with-the-intel-hardware-accelerated-execution-manager/?ref=informatic-ar.com). يرجى ملاحظة أن الإصدار 1.0.6 من HAXM وهو المتوفر عن طريق الـ SKD Manager حالياً، يسبب مشكلة شاشة الموت مع Windows 8.1 ومع (OS X Mavericks (10.9، لذلك قم بتحميل النسخة 1.0.7 من هنا، نسخة الويندوز: [http://software.intel.com/en-us/articles/intel-hardware-accelerated-execution-manager-end-user-license-agreement-windows-hotfix](http://software.intel.com/en-us/articles/intel-hardware-accelerated-execution-manager-end-user-license-agreement-windows-hotfix?ref=informatic-ar.com)، نسخة الماك: [http://software.intel.com/en-us/articles/intel-hardware-accelerated-execution-manager-end-user-license-agreement-macos-hotfix](http://software.intel.com/en-us/articles/intel-hardware-accelerated-execution-manager-end-user-license-agreement-macos-hotfix?ref=informatic-ar.com). --- **\- ما هو الـ (Android Virtual Device Manager (AVD:** هو أداة لإدارة المحاكيات، والمحاكي هو أداة لمحاكاة جهاز هاتف ذكي وذلك ليتسنى لنا اختبار تطبيقات الأندرويد عليه أثناء عملية البرمجة دون الحاجة لجهاز هاتف حقيقي. على أية حال، قد يفتقد المحاكي لبعض المميزات التي تتواجد في جهاز هاتف حقيقي مثل الـ GPS والذي يستخدم لتحديد الموقع. يمكن تشغيل هذه الأداة إما عن طريق برنامج الـ Eclipse من القائمة "Window" أو عن طريق الملف التنفيذي الموجود في هذا المسار: C:\\Users\\AndroidDeveloper\\android-sdks\\AVD Manager.exe. خطوات إنشاء محاكي جديد هي كالتالي: 1. قم بتشغيل الأداة AVD Manager. 2. اضغط على New. 3. الآن قم بتعبئة مواصفات المحاكي. دعنا نختار الاسم AndroidMobile كاسم للمحاكي. 4. في خانة Device، اختر Galaxy Nexus. 5. في خانة Target، اختر آخر إصدار لنظام الأندرويد. 6. في خانة CPU/ABI، اختر (Intel Atom (x86. 7. إذا كان جهازك مدعم بكاميرا، يمكنك ربطها بكاميرا المحاكي. 8. اجعل حجم ذاكرة الـ RAM يساوي 768 (لذاكرة أكبر انظر إلى هذا الجواب: [http://stackoverflow.com/a/11868277](http://stackoverflow.com/a/11868277?ref=informatic-ar.com))، وحجم ذاكرة الـ heap تساوي 64. 9. ضع علامة صح على الخيار Use Host GPU، وذلك لاستخدام الـ GPU بدلاً من الـ CPU أثناء عملية الرسم وبالتالي سرعة أكبر. 10. خيار الـ snapshot يعطي إمكانية لعمل ما يشبه بعملية الإسبات (Hibernate) في الويندوز. 11. اضغط على OK. 12. اختر المحاكي من القائمة ثم اضغط على Start. 13. ضع علامة صح على الخيار Scale display to real size، حتى تتحكم بأبعاد نافذة المحاكي وذلك لأنه مع الأبعاد الافتراضية فإن جزء بسيط من المحاكي يذهب خلف الشريط السفلي في نظام الويندوز. 14. في خانة Screen Size، ضع طول مناسب لنافذة المحاكي على شاشتك. احسب المسافة (بالـ Inch) من أعلى الشاشة إلى أسفلها، ثم اطرح طول الشريط السفلي. على جهاز الـ laptop الخاص بس، أستخدم القيمة 8. 15. في خانة Monitor dpi (وdpi اختصاراً لـ dots per inch)، اضغط على علامة الاستفهام. ستظهر لك نافذة صغيرة، أدخل حجم وأبعاد الشاشة ثم اضغط على OK. 16. الآن اضغط على Launch. 17. انتظر حتى يعمل المحاكي. --- **\- استخدام جهاز هاتف حقيقي:** بلا شك، استخدام جهاز هاتف حقيقي لتجربة تطبيق أندرويد عليه أفضل من استخدام المحاكي، ولكن يظل المحاكي يتفوق في جانب أنه يمكنك التعديل على مواصفات الجهاز وتعديل إصدار نظام الأندرويد المشغل له بكل يسر وسهولة، بعكس جهاز الهاتف الحقيقي. الخطوات التالية تبين كيفية إعداد جهاز هاتف حقيقي لاستخدامه لتجربة التطبيقات التي نقوم بتطويرها: 1. قم بتثبيت الـ driver الخاص بالهاتف على جهاز الكمبيوتر الخاص بك. لأجهزة الـ Nexus قم بتحميل الإضافة Google USB Driver من الـ SDK Manager. ولجهاز Galaxy Nexus والأجهزة الأخرى، قم بتحميل الـ driver المناسب من هذه الصفحة: [http://developer.android.com/tools/extras/oem-usb.html#Drivers](http://developer.android.com/tools/extras/oem-usb.html?ref=informatic-ar.com#Drivers). 2. من داخل الهاتف الذكي، ادخل إلى إعدادات الجهاز، ثم من Developer Options، قم بتفعيل الخيار USB debugging. 3. اربط جهاز الهاتف بجهاز الكمبيوتر عن طريق الـ USB cable. 4. افتح الـ CMD، واكتب الأمر adb devices. ستظهر لك قائمة بأجهزة الأندرويد المرتبطة بجهاز الكمبيوتر بالإضافة إلى المحاكيات التي تعمل الآن. ### دورة أندرويد - مقدمة عن الأندرويد URL: https://informatic-ar.com/android-course1-lesson1/ Last updated: 2026-10-03T22:46:30.000Z **\- ما هو الأندرويد:** الأندرويد ([Android](http://en.wikipedia.org/wiki/Android%5F%28operating%5Fsystem%29?ref=informatic-ar.com)) هو نظام تشغيل مفتوح المصدر مبني على لب نظام اللينكس [Linux kernel](http://en.wikipedia.org/wiki/Linux%5Fkernel?ref=informatic-ar.com). تم بناؤه في بداية الأمر لكي يعمل على أجهزة الهاتف الذكية المدعّمة بتقنية اللمس، ثم تم تطويره تدريجياً لكي يعمل على الأجهزة اللوحية. حالياً، أصبح نظام الأندرويد موجوداً في العديد من الأجهزة؛ وعلى سبيل المثال لا الحصر: الساعات، النظارات، أجهزة التلفاز، منصات الألعاب، أجهزة حاسوبية، ... إلخ. هنا قائمة بالأجهزة التي تعمل بنظام الأندرويد: [http://en.wikipedia.org/wiki/Comparison\_of\_Android\_devices](http://en.wikipedia.org/wiki/Comparison%5Fof%5FAndroid%5Fdevices?ref=informatic-ar.com) في 2005، قامت قوقل ([Google](http://en.wikipedia.org/wiki/Google?ref=informatic-ar.com)) بشراء نظام الأندرويد من شركة ناشئة .Android, Inc والتي أنشئت عام 2003\. قامت قوقل بالإعلان عن هذا النظام عام 2007\. في 23 سبتمبر من عام 2008، تم إصدار أول نسخة من النظام. في 22 أكتوبر من عام 2008، تم إصدار أول هاتف ذكي يعمل على نظام الأندرويد [HTC Dream](http://en.wikipedia.org/wiki/HTC%5FDream?ref=informatic-ar.com): ![HTC Dream](https://informatic-ar.com/wp-content/uploads/migrated/external-74e5f5657503808ce5c168668b0f9ad898f0ac3dbb1c1e7a104a42a76769f64f.jpeg) HTC Dream - أول هاتف ذكي بنظام أندرويد --- **\- تركيبة نظام الأندرويد:** يتكون نظام الأندرويد من 4 طبقات كما هو موضح بالصورة: ![تركيبة نظام الأندرويد](https://informatic-ar.com/wp-content/uploads/migrated/external-5dc73eefe6dfe8289c8b94badd0bad434b0ba90cac07764da8983375e23da03d.svg) تركيبة نظام الأندرويد **1- طبقة الـ Linux Kernel:** الـ kernel المستخدم في نظام الأندرويد يسمى [Monolithic](http://en.wikipedia.org/wiki/Monolithic%5Fkernel?ref=informatic-ar.com "Monolithic kernel")، وهو نسخة معدلة من الـ Linux kernel. تحتوي هذه الطبقة على [drivers](http://en.wikipedia.org/wiki/Device%5Fdriver?ref=informatic-ar.com) وهي عبارة عن modules مسؤولة عن التواصل مع عتاد الجهاز ([hardware](http://en.wikipedia.org/wiki/Computer%5Fhardware?ref=informatic-ar.com)). بالإضافة إلى الـ drivers modules، يوجد module خاص بالـ power management. **2- طبقة الـ Middleware:** وهذه الطبقة تحتوي على Android runtime و libraries، وتم تطوير غالبية هذه الطبقة بـ ++C/C: **أ. الـ Android runtime:** هذا الجزء مشابه لبيئة الجافا، ولكن بدلاً عن آلة الجافا الافتراضية ([JVM](http://en.wikipedia.org/wiki/Java%5Fvirtual%5Fmachine?ref=informatic-ar.com))، يحتوي هذا الجزء على آلة افتراضية تم تطويرها خصيصاً لتتناسب مع محدودية إمكانيات أجهزة الهواتف المحمولة حيث تستهلك أقل كمية ممكنة من الطاقة وتعمل بكفاءة مع ذاكرة عشوائية ([RAM](http://en.wikipedia.org/wiki/Random-access%5Fmemory?ref=informatic-ar.com)) صغيرة الحجم ومعالج ([CPU](http://en.wikipedia.org/wiki/Central%5Fprocessing%5Funit?ref=informatic-ar.com)) أقل سرعة من الموجود في أجهزة الحواسيب المكتبية والمحمولة. تسمى هذه الآلة الافتراضية بآلة دالفِك ([Dalvik VM](http://en.wikipedia.org/wiki/Dalvik%5F%28software%29?ref=informatic-ar.com)). بالإضافة للآلة الافتراضية، نجد المكتبات الأساسية ([Class Library](http://en.wikipedia.org/wiki/Java%5FClass%5FLibrary?ref=informatic-ar.com)) وهي ذاتها مكتبات منصة الجافا Java SE ولكن الـ [implementation](http://en.wikipedia.org/wiki/List%5Fof%5FJava%5Fvirtual%5Fmachines?ref=informatic-ar.com) ليس [Hotspot](http://en.wikipedia.org/wiki/HotSpot?ref=informatic-ar.com) (الخاص بشركة [Oracle](http://en.wikipedia.org/wiki/Oracle%5FCorporation?ref=informatic-ar.com)، وهذا هو الشائع والذي يستخدمه الأغلبية في الجافا) بل نسخة أخرى تسمى [Apache Harmony](http://en.wikipedia.org/wiki/Apache%5FHarmony?ref=informatic-ar.com). ولأن نظام الأندرويد كان موجه لأجهزة الهواتف الذكية، فقد تم إزالة بعض المكتبات الخاصة بأنظمة سطح المكتب مثل [AWT](http://en.wikipedia.org/wiki/Abstract%5FWindow%5FToolkit?ref=informatic-ar.com) و [Swing](http://en.wikipedia.org/wiki/Swing%5F%28Java%29?ref=informatic-ar.com). **ب. الـ libraries:** مكتبات مساندة تم تطويرها بـ ++C/C. من أهم هذه المكتبات: - **Surface Manager:** هذه المكتبة هي حلقة الوصل بين تفاعل المستخدم مع الشاشة وبين المكتبة Window Manager الموجودة في الطبقة العلوية. - **Media Framework:** توفر العديد من الكوديك ([codec](http://en.wikipedia.org/wiki/Codec?ref=informatic-ar.com)) اللازمة لتشغيل وتسجيل الـ [media](http://en.wikipedia.org/wiki/Multimedia?ref=informatic-ar.com) من فيديو وأصوات. - **SQLite:** عبارة عن [database engine](http://en.wikipedia.org/wiki/Database%5Fengine?ref=informatic-ar.com) للتعامل مع قواعد البيانات من نوع [SQLite](http://en.wikipedia.org/wiki/Sqlite?ref=informatic-ar.com). - **[OpenGL ES](http://en.wikipedia.org/wiki/OpenGL%5FES?ref=informatic-ar.com):** تستخدم هذه المكتبة للتعامل ([rendering](http://en.wikipedia.org/wiki/Rendering%5F%28computer%5Fgraphics%29?ref=informatic-ar.com)) مع الرسوميات 2D و 3D. - **[FreeType](http://en.wikipedia.org/wiki/FreeType?ref=informatic-ar.com):** مكتبة للتعامل مع الخطوط ([Fonts](http://en.wikipedia.org/wiki/Font?ref=informatic-ar.com)). - **[WebKit](http://en.wikipedia.org/wiki/WebKit?ref=informatic-ar.com):** عبارة عن [web browser engine](http://en.wikipedia.org/wiki/Web%5Fbrowser%5Fengine?ref=informatic-ar.com) يستخدم لعرض صفحات الإنترنت. - **[SGL](http://sgl.sourceforge.net/?ref=informatic-ar.com#intro):** اختصاراً لـ "Scalable Graphics Library" وهي مكتبة تستخدم أيضاً للتعامل ([rendering](http://en.wikipedia.org/wiki/Rendering%5F%28computer%5Fgraphics%29?ref=informatic-ar.com)) مع الرسوميات 2D و 3D. - **SSL:** تستخدم لدعم الاتصال عبر بروتوكول الـ [SSL](http://en.wikipedia.org/wiki/Secure%5FSockets%5FLayer?ref=informatic-ar.com). - **libc:** نظام الأندرويد يستخدم نسخة خاصة من مكتبات لغة السي تسمى [Bionic](http://en.wikipedia.org/wiki/Bionic%5F%28software%29?ref=informatic-ar.com) بدلاً من [BSD's standard C library](http://en.wikipedia.org/wiki/BSD%5Flibc?ref=informatic-ar.com)، وذلك حتى تتناسب مع إمكانيات أجهزة الهواتف المحدودة. **3- طبقة الـ Application Framework:** هذه المكتبات صُممت خصيصاً لنظام الأندرويد، وهي عبارة عن APIs تستخدم من قبل مطوري الأندرويد لاستغلال موارد النظام. من ضمنها: - **Activity Manager:** وظيفته التواصل مع الـ activities التي تعمل بالنظام. \[سيتم شرح الـ Activity بالتفصيل في درس لاحق\] - **Windows Manager:** هو المسؤول عن إنشاء وتنظيم النوافذ بالشاشة، وذلك بالتواصل مع الـ Surface Manager. - **Content Providers:** إدارة عملية مشاركة البيانات بين التطبيقات. \[سيتم شرح الـ Content Providers بالتفصيل في درس لاحق\] - **View System:** يحتوي على العناصر المكونة لواجهة المستخدم (UI) من buttons وغيرها. بالإضافة إلى خصائص هذه العناصر مثل event handlers. - **Notification Manager:** يستخدم لإرسال إشعارات للمستخدم. - **Package Manager:** يستخدم لجلب معلومات عن التطبيقات، مثل رقم الإصدار وأيضاً التصاريح التي لدى التطبيق. - **Telephony Manager:** يستخدم للتحكم بالمكالمات الصوتية والتفاعل معها. - **Resource Manager:** يستخدم للوصول إلى الموارد المرفقة مع التطبيق كالصور والنصوص وغيرها. - **Location Manager:** يستخدم للتفاعل مع الـ GPS لتحديد الموقع الحالي للمستخدم. - **XMPP Service:** كانت موجودة في الإصدارات الأولية من نظام الأندرويد، وكانت تستخدم فقط لخدمات [GoogleTalk](http://en.wikipedia.org/wiki/Google%5FTalk?ref=informatic-ar.com) (والتي تم استبدالها لاحقاً بخدمة [Google+ Hangout](http://en.wikipedia.org/wiki/Google%2B%5FHangouts?ref=informatic-ar.com))، ولكن تم إلغاؤها لأسباب أمنية (security issues). **4- طبقة الـ Applications:** وهذه الطبقة تحتوي على تطبيقات الأندرويد، سواء كانت تطبيقات موجودة مسبقاً في النظام (built-in) مثل تطبيق الأسماء (Contacts)، وتطبيق الاتصال (Phone)، وتطبيق الواجهة الرئيسية (Home)، أو ربما تطبيقات يتم تطويرها من قبل الشركة المصنعة لجهاز الهاتف الذكي، وقد يستبدلون التطبيقات الموجودة في النظام الخام بتطبيقات معدلة، لذلك ستجد إختلاف بين تطبيقات أجهزة الـ [Samsung](http://en.wikipedia.org/wiki/Samsung?ref=informatic-ar.com) وبين تطبيقات أجهزة الـ [HTC](http://en.wikipedia.org/wiki/HTC?ref=informatic-ar.com)، مثلاً. أيضاً، هذه الطبقة تحتوي على التطبيقات التي يتم تنزيلها من [متجر Play](http://en.wikipedia.org/wiki/Google%5FPlay?ref=informatic-ar.com)، وهو المتجر الرسمي لتطبيقات الأندرويد. --- **\- إصدارات نظام الأندرويد:** تعطي شركة قوقل اسماً مميزاً (codename) مع كل إصدار من نظام الأندرويد، وعادةً يكون الاسم له علاقة بالحلويات. هنا [قائمة بإصدارات نظام الأندرويد](http://en.wikipedia.org/wiki/Android%5Fversion%5Fhistory?ref=informatic-ar.com): - الإصدار **1.0**: بدون اسم - **API 1** \- أصدر في 23/9/2008. - الإصدار **1.1**: بدون اسم - **API 2** \- أصدر في 9/2/2009. - الإصدار **1.5**: يسمى Cupcake الكعك - **API 3** \- أصدر في 30/4/2009. - الإصدار **1.6**: يسمى Donut الدونات - **API 4** \- أصدر في 15/9/2009. - الإصدار **2.0**: يسمى Eclair الإكلير - **API 5** \- أصدر في 26/10/2009. - الإصدار **2.0.1**: يسمى Eclair الإكلير - **API 6** \- أصدر في 3/12/2009. - الإصدار **2.1**: يسمى Eclair الإكلير - **API 7** \- أصدر في 12/1/2010. - الإصدارات **2.2** و 2.2.1 و 2.2.2 و 2.2.3: تسمى Froyo الفرويو - **API 8** \- أصدرت في 20/5/2010 و 18/1/2011 و 22/1/2011 و 21/11/2011. - الإصدارات **2.3** و 2.3.1 و 2.3.2: تسمى Gingerbread خبز الزنجبيل - **API 9** \- أصدرت في 6/12/2010 و 12/2010 و 1/2011. - الإصدارات **2.3.3** و 2.3.4 و 2.3.5 و 2.3.6 و 2.3.7: تسمى Gingerbread خبز الزنجبيل - **API 10** \- أصدرت في 9/2/2011 و 28/4/2011 و 25/7/2011 و 2/9/2011 و 21/9/2011. - الإصدار **3.0**: يسمى Honeycomb قرص العسل - **API 11** \- أصدر في 22/2/2011. - الإصدار **3.1**: يسمى Honeycomb قرص العسل - **API 12** \- أصدر في 10/5/2011. - الإصدارات **3.2** و 3.2.1 و 3.2.2 و 3.2.3 و 3.2.4 و 3.2.5 و 3.2.6: تسمى Honeycomb قرص العسل - **API 13** \- أصدرت في 15/7/2011 و 20/9/2011 و 9/2011 و 9/2011 و 12/2011 و 1/2012 و 2/2012. - الإصدارات **4.0** و 4.0.1 و 4.0.2: تسمى Ice Cream Sandwich ساندويتش الآيس كريم - **API 14** \- أصدرت في 19/10/2011 و 21/10/2011 و 28/11/2011. - الإصدارات **4.0.3** و 4.0.4: تسمى Ice Cream Sandwich ساندويتش الآيس كريم - **API 15** \- أصدرت في 16/12/2011 و 29/3/2012. - الإصدارات **4.1** و 4.1.1 و 4.1.2: تسمى Jelly Bean جيلي بين - **API 16** \- أصدرت في 9/7/2012 و 23/7/2012 و 9/10/2012. - الإصدارت **4.2** و 4.2.1 و 4.2.2: تسمى Jelly Bean جيلي بين - **API 17** \- أصدرت في 13/10/2012 و 27/10/2012 و 11/2/2013. - الإصدارات **4.3** و 4.3.1: تسمى Jelly Bean جيلي بين - **API 18** \- أصدرت في 24/7/2013 و 3/10/2013. - الإصدارات **4.4** و 4.4.1 و 4.4.2: تسمى KitKat كتكات - **API 19** \- أصدرت في 31/10/2013 و 5/12/2013 و 9/12/2013. لاحظ بأنه مع كل إصدار جديد لنظام الأندرويد، يتم إضافة بعض الميزات الجديدة على الإصدار السابق. لذلك لا يمكن استخدام ميزات الإصدار 4.4 مثلاً عند التطوير بمكتبات الإصدار 2.2\. لحسن الحظ، قامت قوقل بتطوير مكتبة خارجية (ملف jar.) تسمى [Android Support Library](http://developer.android.com/tools/support-library/index.html?ref=informatic-ar.com) وتستخدم لإضافة مميزات جديدة على الأنظمة القديمة. لاحظ أيضاً بأنه عند برمجة تطبيق بمكتبات الإصدار 4.3 مثلاً، فإنه لا يمكن تشغيل التطبيق على إصدار سابق له (الإصدار 4.0 مثلاً)، ولكن يمكن تشغيل التطبيق على الإصدار 4.4 مثلاً. قم بزيارة صفحة الـ Dashboard باستمرار لمعرفة الإصدارات المستخدمة حالياً وكم نسبة الاستخدام لكل إصدار: [http://developer.android.com/about/dashboards/index.html](http://developer.android.com/about/dashboards/index.html?ref=informatic-ar.com) [![مع نهاية 2013: إصدارات نظام الأندرويد ونسبة الاستخدام](https://informatic-ar.com/wp-content/uploads/2014/01/dashborad-300x270.jpg)](https://informatic-ar.com/wp-content/uploads/2014/01/dashborad.jpg) مع نهاية العام 2013 - إصدارات نظام الأندرويد ونسب الاستخدام ### سلسلة دروس تعلم برمجة تطبيقات أندرويد URL: https://informatic-ar.com/android-course1/ Last updated: 2026-10-03T22:31:44.000Z ## **\-::| بسم الله الرحمن الرحيم |::-** \[آخر تحديث: السبت 4-1-2013 \~ 05:52PM\] يسرني أن أقدم لكم سلسلة دروس تعلم برمجة تطبيقات أندرويد، آملاً أن تزيد من عدد مطوري الأندرويد العرب وتثري المحتوى التقني العربي على الإنترنت. ستكون الدورة على هيئة دروس مكتوبة، كل درس في مقال منفصل على منصة إنفورماتيك. في بداية الأمر، كنت أريد أن أقوم بتأليف كتاب عربي عن برمجة تطبيقات أندرويد، ولكن قررت لاحقاً بأن أجعل الدروس متوفرة على الإنترنت بشكل مجاني، وذلك لكي تكون أسهل في النشر بالإضافة إلى إمكانية التعديل عليها في أي وقت. بإذن الله، ستكون هذه الصفحة المرجع الرئيسي للدروة حيث سيتم تحديث قائمة الدروس هنا بشكل دوري أو بالأحرى عند إضافة دروس جديدة. عند تحديث درس معين، سأقوم بإضافة تاريخ التحديث في بداية الدرس. ![](https://www.geeky-gadgets.com/wp-content/uploads/2011/12/android-training.jpg) --- **\- متطلبات الدورة:** 1. جهاز كمبيوتر، ونظام التشغيل يكون إما Linux أو Windows أو Mac. 2. معرفة مسبقة بلغة الجافا، وكل ما كانت المعرفة أكبر، كل ما أفادتك في برمجة تطبيقات أندرويد. لن أقوم بشرح أي درس عن الجافا، ولكن أنصح بدورة الأستاذ عبدالله عيد [Java 101](http://www.youtube.com/playlist?list=PL28DDB2DCF87BEE43&ref=informatic-ar.com) و [Java 102](http://www.youtube.com/playlist?list=PL138BE19EA2405C94&ref=informatic-ar.com) و [Java 103](http://www.youtube.com/playlist?list=PLA94A6FB67AB4CD0D&ref=informatic-ar.com) و [Java 104](http://www.youtube.com/playlist?list=PLqmVQqNLdVv11bup4o0bRR4zxG2Gu05gX&ref=informatic-ar.com)، فهي الحد الأدنى المطلوب للبدء في هذه الدورة. كإضافة، أنصح بدروس الأستاذ فاضل على اليوتيوب: [http://www.youtube.com/user/MrFadelK](http://www.youtube.com/user/MrFadelK?ref=informatic-ar.com). --- **\- نقطة البداية:** 1. **مقدمة عن الأندرويد:** [http://informatic-ar.com/android-course1-lesson1](https://informatic-ar.com/android-course1-lesson1) 2. **إعداد البيئة التطويرية:** [http://informatic-ar.com/android-course1-lesson2](https://informatic-ar.com/android-course1-lesson2) إن كان لديك أي استفسارات أو اقتراحات أو إبلاغ عن مشكلة بالدروس، يمكنك إضافة رد أسفل هذا المقال، أو راسلني عبر إيميلي الخاص: Engineer.Fouad@hotmail.com. لا تنسى مشاركة هذه الصفحة على مواقع التواصل الاجتماعي، حتى تصل إلى أكبر عدد ممكن من المطورين العرب ويستفيد منها الجميع. تقبلوا تحياتي\~ ### هل دروبال إطار عمل أم نظام لإدارة المحتوى؟ URL: https://informatic-ar.com/drupal-framework-or-cms/ Last updated: 2026-10-03T22:31:45.000Z ### **مقدمة:** ![drupaldeveloper](https://informatic-ar.com/wp-content/uploads/2014/01/drupaldeveloper-300x104.png) السؤال أعلاه تدور حوله كثير من التساؤلات والإستفسارات خصوصاً من القادمين الجدد لعالم دروبال، ومع كثرة اللغات والتقنيات وأطر العمل أصبحت عملية الإختيار والمفاضلة تحتاج لتمحيص دقيق وتقصى ، وفي إعتقادي أنه تساؤل أساسي ومهم خصوصاً إذا وضعنا في الإعتبار مقولة بيتر نورفق - Peter Norvig : " تعلم البرمجة في عشرة سنوات ". ### **مفهوم أنظمة إدارة المحتوى** نظام إدارة المحتوى كمفهوم لا يعني بالضرورة أن يكون نظام حاسوبي فطريقة إدارة الكتب وترتيبها وتخزينها وعرضها في مكتبة تمثل نظام إدارة المحتوى في تلك المكتبة، حيث طريقة الإدارة والتريب والتخزين والعرض تمثل ( النظام ) والكتب تمثل ( المحتوى ) وبهذا بإمكاننا الوصول لخلاصه مفادها أن أنظمة إدارة المحتوى تختلف بإختلاف ( المحتوى ) الذي تعمل على إدارته فكما في مثالنا السابق الكتب هي المحتوى فيمكن أن يكون هذا المحتوى: عينات دم، أجهزة إلكترونية، بيانات تلاميذ ، مقالات صحفية ... ألخ، وعليه مما سبق يمكننا تعريف أنظمة إدارة المحتوى كما ورد في ويكيبيديا بأنها : " مجموعة الإجراءات المتبعة لإدارة سير العمل في بيئة تعاونية، هذه الإجراءات إما أن تكون يدوية أو مبنية على الحاسوب "، لذا فإن التسمية الأدق والتي تخصنا هنا " الأنظمة الحاسوبية لإدارة المحتوى الرقمي ". ### **ما هو النظام المحوسب لإدارة المحتوى الرقمي** عبارة عن نظام محوسب معد خصيصاً لحفظ وتنظيم وعرض البيانات وتسهيل مشاركتها والإستفاده منها عبر عمليات النشر الإلكتروني المختلفة سواء تلك التي تتم بتدخل بشري ككتابة مقال مثلاً أو التي تتم بصورة آلية كتقنيات الـRSS، ويمكن لهذا النظام أن يعمل على الإنترنت أو الإنترانت أو حتى جهاز حاسوب مكتبي معزول ويتميز بأنه قابل للضبط والتخصيص بطرق عديدة ومختلفة ليتناسب مع إحتياجات أكبر قدر من العملاء. وتجدر الإشارة هنا إلى أن أنظمة إدارة المحتوى تهدف في الأساس لتوفير أنظمة سهلة الإستخدام لا تحتاج لخلفية برمجية وفهم في كتابة الكود البرمجي حتى يكون من السهل لمستخدمين قادمين من خلفيات غير برمجية إستخدام هذه الأنظمة وتخصيصها وإدارتها بما يتوافق مع إحتياجاتهم. ### **لماذا الغموض حول دروبال** عندما يكون الحديث عن وورد برس أو جملة مثلاً لا يختلف إثنان على أنهما أنظمة إدارة محتوى ولكن لما الغموض حول موقف دروبال من كونه نظام إدارة محتوى أو إطار عمل؟ تكمن الإجابة على هذا السؤال في بدايات دروبال وتركيبته وطريقة عمله. بدأ دروبال كموقع مصغر لتبادل المعلومات بين مجموعة من الطلاب بمعنى أن الهدف الأول لإنشائه أن يكون نظام إدارة محتوى بصورة مبسطة وهذا ما جعل هذه الصفة ملازمه له ومن ثم تطور لاحقاً ، كما نجد أن دروبال عبارة عن مجموعة من الوحدات (Modules) التي تعمل مع بعض بشكل متناغم وتنقسم هذه الوحدات لوحدات أساسية تمثل بنية دروبال وتسمى (Core Module) وينصح بعدم التعديل فيها على الإطلاق، ووحدات تشاركية (Contributed Module) وهي وحدات تم بنائها من قبل مجتمع دروبال وتم مراجعتها وتطويرها على نطاق واسع وتم إعتمادها بصورة رسمية ومتاحة للإستخدام والتنزيل عبر الموقع الرسمي لدوربال (drupal.org)، والمميز في هذه الطريقة إمكانية بناء وحدات لتنفيذ أي مهمة أو عملية وإضافتها لنظام دروبال بطريقة غاية في السهولة ويمكن لهذا الموديول أن يكون بسيطاً ينفذ مهمة واحدة وممكن أن يكون معقد ليؤدي مهام وعمليات معقدة ومترابطة. ### **دروبال كنظام إدارة محتوى** يوفر دروبال وبأداء عالي كافة العمليات والخدمات التي تؤديها أنظمة إدارة المحتوى كووردبرس وجملة مع الإحتفاظ بمقدراته في بناء وحدات إضافية تؤدي أي مهمة وفي أي مجال ، ولا تعني كلمة ( بناء وحدات ) هنا البناء البرمجي بإستخدام الكود فبإمكان المستخدم العادي الذهاب للموقع الرسمي لدروبال والإختيار من بين آلاف الوحدات المتاحة والمرتبة بصورة جميلة والتي تؤدي مهام في مجالات عديدة متعلقة بالنشر الإلكتروني وتنزيلها وتنصيبها في نسخته من دروبال عبر خطوتين غاية في السهولة. وفي إطار المنافسة بين أنظمة إدارة المحتوى المختلفة فاز دروبال ولسنوات متتالية بجائزة أفضل نظام إدارة محتوى وجائزة Infoworld Bossie للأعوام 2010، 2011، 2012 وعدة جوائز أخرى ، لذا بإمكاننا ( كخطوة أولى ) إعتبار دروبال نظام إدارة محتوى ولكن مع إمكانية تطوير وبناء إضافات تجعله يعمل في أي مجال آخر وهذا ما يخطو به نحو طريق أطر العمل الـ(Frameworks). ### **مميزات دروبال كنظام إدارة محتوى** \- دروبال حر ومفتوح المصدر مما يعني قلة التكلفة الأمان والضمان على المستوى البعيد. \- بناء دروبال بإستخدام لغة PHP وهي لغة رائدة في مجال تقنيات الويب يجعله يستفيد من كافة خصائص الويب. \- متعدد المستخدمين بمعنى إمكانية أي عدد من المستخدمين إستخدام النظام في نفس الوقت، ويمكن تقسيمهم إلى مجموعات: زوار مشتركين مدراء ... ألخ. \- يوفر دروبال مجموعة كبيرة ومتطورة من عمليات النشر الإلكتروني تتدرج من كتابة بوست صغير وإتاحة آلية التعليقات عليه إلى كتابة المقالات وعمليات التقييم المختلفة إلى المدونات وما يتعلق بها من عمليات إنتهاءاً بالمنتديات وتفاصيلها. \- أثبت دروبال مقدرته العالية في بناء مواقع المحتوى الضخمه التي تتكون من أكثر من مليون صفحة وتتجاوب مع أكثر من 20 ألف طلبية في الثانية ( حسب دريس بايتريت مؤسس دروبال). \- أظهر دروبال مقدرة عالية في الجوانب التصميمية ويتوفر له عبر الموقع الرسمي عدد كبير من الثيم المجانية. \- وأخيراً ما يميز دروبال مقدراته التأمينية العالية وما أظهره من قوه في هذا المجال. ### **ما هو إطار العمل** إطار العمل أو الـFramework عبارة عن بيئة برمجية وبنية معمارية تضم مجموعة من الأدوات التي يمكن أن تكون (Libraries, Classes, API ... etc) تساعد في بناء التطبيقات التي قد تكون خاصة بمجال محدد وبخصائص محددة أو قد تكون عامة ، ولكل إطار عمل طريقته المميزة في بناء التطبيقات ، منها ما يركز على مفهوم الـMVC ومنها ما يتشدد في تطبيق قواعد الـOOP ومنها المتراخي في ذلك. وتتمثل فوائد الـFrameworks في أنها تختصر الزمن على المبرمجين فلا حوجة لإعادة إختراع العجلة كل مرة كما أنها تهيئ بيئة تشاركية يمكن أن يعمل عبرها أي عدد من المطورين لأنهم جميعاً يستخدمون نفس الإطار وغيرها من المميزات ، ولكن من المهم هنا أن أطر العمل ليست للمستخدمين العاديين لأنها تحتاج خلفية عميقة في البرمجة وكتابة الكود البرمجي. بهذا التعريف السريع نجد أن الفرق واضح بين إطار العمل ونظام إدارة المحتوى حيث أن الإطار يستخدم لبناء تطبيقات بما في ذلك أنظمة إدارة المحتوى نفسها ونظام إدارة المحتوى يستخدم لإدارة ما يحتويه من معلومات وبيانات ( المحتوى المعرفي ). ### **دروبال كإطار عمل** وبالعودة إلى دروبال نجد أنه بإمكاننا إستخدام دروبال كإطار عمل وفي نفس الوقت بإمكاننا بناء وحدات أخرى إضافية لبناء تطبيقات مختلفة تماماً عن أنظمة إدارة المحتوى والنشر الإلكتروني كالأنظمة المالية والإدارية ، أنظمة الحكومة الإلكترونية ، الأنظمة التعليمية ، .... ألخ. ولكن السؤال هل دروبال يمثل أطار عمل متكامل؟ للإجابة عن هذا التساؤل نجد أن قابلية دروبال على التوسع ومرونته في بناء وإضافة وحدات جديدة تجعله يأخذ كافة ملامح أطر العمل، أضف إلى ذلك أن Drupal 7 جاءت بإضافات جوهرية مثل : الكيونونات Entity Type ، وإمكانية العمل بقواعد بيانات في عدة سيرفرات في آن واحد ، هذا بالإضافة للمميزات الأخرى لأطر العمل مثل: Caching ، Form API ، Theme System ، File API ، Roles and Permissions ، Web Services ، Testing Framework وعليه يمكننا القول أن أي تطبيقات يتم بناؤها بإستخدام Zend Framework أو Symfony يمكن بناؤها بإستخدام دروبال وعليه نخلص ( كخطوة ثانية ) إلى أن دروبال يمثل إطار عمل. ### **مميزات دروبال كإطار عمل:** يوفر دروبال طريقة هجينة لبناء التطبيقات بالإستفاده من خصائصه كنظام إدارة محتوى CMS وذلك بسماحه بإستخدام الوحدات الجاهزة وبيئة عمل مرئية Interface لضبط خصائص التطبيق، والإستفاده من خصائصه كإطار عمل Framework وذلك بسماحه بإستخدام الكود البرمجي في نفس الوقت لبناء أي جزئية في التطبيق والتعديل عليه عبر ما يسمى بالـHooks ، ومن هذه الإزدواجية في طريقة العمل يصبح المصطلح Content Management Framework CMF الأكثر قرباً. ### **دروبال على أرض الواقع** هذا الرابط ([هنا )](http://www.acquia.com/five-data-sheets-promoting-drupal?ref=informatic-ar.com) يعطينا لمحة إحصائية سريعة عن أكثر خمسة مجالات إستندت على دروبال في تقديم خدماتها وهي: **مجال التعليم:** 71 جامعة ضمن المائة جامعة الأوائل حول العالم تستخدم دروبال مثل: MIT ، Oxford ، Boston University ، Carnegie Mellon University ، Harvard University ، Imperial College London ، Massachusetts Institute of Technology ، Stanford University ، University of Edinburgh. **مجال التقنية:** أكبر الشركات في مضمار التقنية أما تستند في خدماتها على دروبال أو أن دروبال متكامل مع تطبيقاتها بطريقة ما ومن ذلك AOL، AT&T ، DELL ، Java.net ، Intel ، PayPal ، Symantec ، Twitter ، Facebook ، Yahoo ، Linux Foundation ، OpenSource.com ، Ubuntu . **مجال النشر الإلكتروني:** أشهر المجالات والمواقع الإخبارية حول العالم والتي تتواصل يومياً مع ملايين الزوار والقراء تستخدم دوربال ومن ذلك : BBC Focus ، Linux Journal ، The Economist ، topCar ، Us Weekly ، Bollywood.com ، Examiner.com ، France 24 ، The Washington Post . **مجال الحوكمة:** يستخدم دروبال في مجال تطبيقات الحوكمة بصورة عامة ولكن بصورة كثيفة من قبل حكومة الولايات المتحدة الأمريكية بالتركيز على قضايا (Openness, Transparency and Visibility) ، وأيضاً في إداراتها وأقسامها المختلفة مثلاً: Department of Commerce ، Department of Defense ، Department of Education ، Department of Energy وغيرها من الإدارات والبيت الأبيض بالتأكيد. **مجال الترفية:** بجانب المجالات الأخرى نجد أن دروبال برعت وحازت قصب السبق في هذا المجال حيث الأكثر أهمية في العالم في هذا المجال يستخدمون دروبال من أمثال: Bob Dylan ، Britney Spears ، Chris Brown ، Foo Fighters ، Beyonce ، Rihanna ، Sean Kingston ، Fox News ، MTV UK ، Oxygen ، والشهيرة WWE. ### مقدمة عن دروبال URL: https://informatic-ar.com/introduction-to-drupal/ Last updated: 2026-10-03T22:31:45.000Z ### الفكرة: بدأ دروبال في العام 1999م بواسطة طالب هولندي يدعى دريس بايتيرت (Dries Buytaert) حيث كانت تربطه وثمانية من زملائه داخل السكن الجامعي شبكة لاسلكية ولكن لاحظ دريس أن هذه الشبكة ليست ذات فائدة عملية في تبادل ونقل الملومات فيما بينهم فقرر بناء تطبيق يعمل عبر هذه الشبكة لتبادل المعلومات والملاحظات وتسهيل التواصل فيما بينهم فيما يشبه موقع الإنترنت. [![شعار دروبال](https://informatic-ar.com/wp-content/uploads/2013/12/drupal_branding.jpg)](https://informatic-ar.com/wp-content/uploads/2013/12/drupal%5Fbranding.jpg) شعار دروبال ولم يرى دروبال النور إلا بعد تخرج هؤلاء الزملاء حيث قرروا رفع الموقع على شبكة الإنترنت لمواصلة التواصل فيما بينهم فبدأ العمل على حجز إسم دومين لرفع الموقع فوقع الإختيار على إسم (drop.org) وDrop كلمة هولندية لديها أكثر من معنى فمن معانيها قطرة وكذلك قرية ، ولكن كان المقصود منها في ذلك الحين ( قرية ) للدلالة على المجتمع الصغير المترابط وفيه إشارة لمجموعتهم أثناء فترة الدراسة الجامعية. ### بداية التشكل: فور إنطلاق الموقع الجديد عبر شبكة الإنترنت بدأت النقاشات حول تطبيقات الويب الجديدة مثل Moderation و Syndication و Rating و Distributed Authentication وإمكانية إتاحتها عبر drop.org وبإستمرار النقاشات والمقترحات والتطوير بدأ drop.org يأخذ شكل برمجية أكثر منه موقع إنترنت وتمت النقلة الكبرى عندما قرر ديرس في يناير/2001 إتاحة هذه البرمجية بصورة مفتوحة المصدر لتوسيع قاعدة المشاركة في تطويرها وأطلق عليها إسم ( دروبال ) وكلمة دروبال Drupal هي النطق الإنجليزي للكلمة الهولندية drupel وتعني قطرة ماء. [![درس بايتريت مؤسس دروبال](https://informatic-ar.com/wp-content/uploads/2013/12/6a00d8341c153053ef01156fb21143970b-320pi-199x300.jpeg)](https://informatic-ar.com/wp-content/uploads/2013/12/6a00d8341c153053ef01156fb21143970b-320pi.jpeg) Dries Buytaert ومن ذلك الحين وبدأ دروبال في التطور وزيادة عدد المشاركين في تطويره وإستخدامه حيث بين يوليو/2007م و يونيو/2008م تم تنزيل دروبال من الموقع الرسمي Drupal.org أكثر من 1.4 مليون مرة بزيادة بلغت 125% عن العام السابق ، وفي أغسطس/2013 بلغ عدد المواقع المبنية على دروبال أكثر من 910,500 موقع (هذه المواقع فقط دون الأنظمة والتطبيقات الأخرى). ### حدث مهم: شهد نظام دروبال نقلة نوعية حدث مهم في العام 2004م عندما قام فريق تقنية المعلومات الخاص بمرشح الرئاسة الأمريكي ( Howard Dean) بإستخدام دروبال في إدارة شبكة لا مركزية من أكثر من 50 موقع تتواصل جميعها فيما بينها ومع الحملة ، وبعد إنتهاء الحملة إرتأي الشباب المكونون فريق تقنية المعلومات ذلك مواصلة مشوارهم مع دروبال وقاموا بتأسيس شركة ( CivicSpace Labs ) وهي أول شركة خاصة بإنتاج تطبيقات دروبال بمطورين متفرغين بدوام كامل ، وبعدها توالت الشركات المتخصصة في تطوير ودعم أنظمة دروبال. ### مجتمع دروبال: يقوم دروبال على مجتمع ضخم من المطورين والمستخدمين وتحت إدارة وتنسيق Drupal Association وهي منظمة غير ربحية تعمل على إدارة وتنسيق كامل العمليات المتعلقة بتطوير دروبال وإعتماد النسخ والحفاظ على الحقوق القانونية لمجتمع دروبال والحفاظ عليه متماسك ومترابط. الموقع الرسمي لدروبال هو Drupal.org وبه أكثر من مليون حساب لمستخدمين وأكثر من 30 ألف حساب خاص بالمطورين ( حسب إحصاءات نوفمبر/2013 ) ، ويضم مجتمع دروبال عدد كبير من المنتديات ومجموعات النقاش وقنوات الـIRC وأكثر من 30 مجتمع لغوي ( إنجليزية ، عربية ، فرنسي ، إلماني ... ألخ ). ### إصدارات دروبال: بدأ دروبال مع الإصدار رقم 1 ومن ثم تدرج سريعاً في الإصدارات 2 و 3 ولكنه توقف قليلاً عند الإصدار رقم 4 لعدة سنوات بداية من العام 2002 حيث صدر منها إصدارات جانبية 4.x وأنتهت هذه الإصدارات الجانبية عند الإصدار 4.7 في العام 2007 ليتم إصدار الإصدار 5 تلاه الإصدار 6 و 7 وحديثاً الإصدار الأخير 8 في العام 2013م. ### رخصة دروبال: يعمل نظام دروبال تحت الرخصة العمومية GPL (General Public License) وكاتبها ريتشارد ستولمن في العام 1989م ، وتشير عدة إحصائية بأن أكثر من 50% من البرمجيات تعمل بهذه الرخصة ، وتعطي هذه الرخصة ضمان للمبرمجين الذين يساهمون في بناء البرمجيات الحرة بأن عملهم سوف يفيد كل العالم ويبقى حراً ولن تستحوذ عليه شركات وتمنع نفعه عن المجتمع. من الجدير بالذكر هنا الإشارة لنقطتين هامتين بخصوص رخصة GPL ، الأولى أنها رخصة متشددة في موضوع حرية البرمجية وترفع مصطلح الحرية أو الموت (Live Free or Die) فلا مجال لغلق أو إحتكار أي جزء من البرنامج وهي بذلك تلغي الملكية الفكرية للبرنامج ، والثانية أن كلمة (Free) في عبارة (Free Software) لا تعني مجاني بل تعني حر أي (Free as in Freedom) فبإمكان المبرمج أو الشركة منتجة البرنامج بيع البرنامج بمقابل مالي لكن يشترط إتاحة الكود المصدري للآخرين وعدم إحتكاره. وكان الهدف من وراء طرح هذه الرخصة حل مشكلتين: الأولى أن المصنعين والموزعين كانوا يوزعون برامجهم في صورة تنفيذية دون توزيع المصدر البرمجي ( الكود ) ، والثانية أنه وفي حال توزيع المصدر البرمجي مع البرنامج كان بعض الموزعين يضعون بعض القيود على ذلك. عوداً على دروبال نجد أن هذه الرخصة عادت على دروبال بمنافع شتى فمن تضافر عدد كبير من المبرمجين والمطورين حول العالم لتطوير دروبال إلى وجود كم هائل من الوحدات (Module) والتي تعالج إحتياجات كبيرة وحقيقية متاحة مجاناً ومفتوحة المصدر وبطريقة تنزيل وتركيب غاية في السهولة. ### دعم اللغة العربية: بداية من دروبال 6، أصبح دعم اللغة العربية (واللغات التي تكتب من اليمين لليسار عموما) مدمجا في دروبال، حيث يمكن للنظام عرض المواقع من اليمين لليسار ببساطة ودون أي تعديلات في السمات المستخدمة. كما توجد وحدات إضافية لدعم خلط النصوص العربية مع غير العربية. ### الطريق إلى شهادة الجافا OCA 1Z0-803 URL: https://informatic-ar.com/oca_1z0-803_java_exam/ Last updated: 2026-10-03T22:31:46.000Z بدايةً، أشكر الله عز وجل الذي وفقني باجتياز الامتحان وبالتالي الحصول على شهادة الجافا OCA 1Z0-803\. سأكتب هنا تقريراً موجزاً عن تجربتي في التحضير لهذا الامتحان وما هي المصادر التي اعتمدتُ عليها، وكيفية التسجيل لهذا الامتحان وما مدى صعوبته. **\- ما هي شهادة OCA 1Z0-803؟** تقدم شركة Oracle العديد من الشهادات في تقنياتها، ومن ضمن هذه الشهادات شهادة الجافا OCA 1Z0-803، و OCA هي اختصارٌ لـ Oracle Certified Associate. تعتبر هذه الشهادة المستوى الأول من ضمن 3 شهادات لمنصة Java SE. يوجد أيضاً شهادات لتقنيات منصة Java EE ولكن للحصول عليها يتطلب الحصول على المستوى الثاني من الشهادات وهي OCP 1Z0-804 و OCP اختصارٌ لـ Oracle Certified Professional. وهذه لن يمكنك الحصول عليها إلى بعد الحصول على شهادة OCA. الشهادة الأخيرة في منصة Java SE هي OCM وتعتبر متقدمة جداً، و OCM اختصارٌ لـ Oracle Certified Master. وللحصول على شهادة الماستر، يجب الحصول على شهادتي OCA و OCP أولاً، وكلٍ منهما يتطلب اجتياز امتحان على حدة. للحصول على OCM تحتاج أيضاً أن تتقدم لأخذ دورة من أوراكل، بالإضافة إلى إنجاز مشروع كـ Assignment في مدة أقل من 6 أشهر، وأخيراً اختبار مقالي Essay مدته ساعتين (كل هذا؟ :O). للمزيد من التفاصيل: [Oracle Certified Associate, Java SE 7 Programmer](http://education.oracle.com/pls/web%5Fprod-plq-dad/db%5Fpages.getpage?page%5Fid=5001&get%5Fparams=p%5Fexam%5Fid:1Z0-803&p%5Forg%5Fid=&lang=&ref=informatic-ar.com "Oracle Certified Associate, Java SE 7 Programmer") [Oracle Certified Professional, Java SE 7 Programmer](http://education.oracle.com/pls/web%5Fprod-plq-dad/db%5Fpages.getpage?page%5Fid=5001&get%5Fparams=p%5Fexam%5Fid:1Z0-804&p%5Forg%5Fid=&lang=&ref=informatic-ar.com "Oracle Certified Professional, Java SE 7 Programmer") [Oracle Certified Master, Java SE 6 Developer](http://education.oracle.com/pls/web%5Fprod-plq-dad/db%5Fpages.getpage?page%5Fid=458&get%5Fparams=p%5Ftrack%5Fid:JSED6&ref=informatic-ar.com "Oracle Certified Master, Java SE 6 Developer") توجد أيضاً شهادات Upgrade، مثلاً لديك شهادة SJCP القديمة وتريد الحصول على شهادة OCP، يوجد اختبار مناسب لك دون الحاجة للحصول على شهادة OCA: [Oracle Certified Professional, Java SE 7 Programmer](http://education.oracle.com/pls/web%5Fprod-plq-dad/db%5Fpages.getpage?page%5Fid=5001&get%5Fparams=p%5Fexam%5Fid:1Z0-805&p%5Forg%5Fid=&lang=&ref=informatic-ar.com "Oracle Certified Professional, Java SE 7 Programmer") **\- التسجيل لهذا الامتحان.** قمتُ بالتسجيل للامتحان عن طريق الموقع [http://www.pearsonvue.com/oracle](http://www.pearsonvue.com/oracle?ref=informatic-ar.com "http://www.pearsonvue.com/oracle")، ومن هناك يتم تحديد موعد الامتحان والمركز أو المعهد الذي سيتم أداء الامتحان فيه. سجلتُ للامتحان قبل 3 أشهر تقريباً، واخترت TOPAZ Systems بشارع التحلية بالرياض. اختياري لهذا المكان ليس بسبب معرفته ولكن لأن مواعيده ناسبتني أكثر، حيث أن يوم السبت 30 نوفمبر 2013 هو يوم عطلة ولا يوجد لدي أعمال. قيمة الامتحان هي 245 دولار أمريكي، وسددت المبلغ عن طريق الفيزا (لا أدري إن كان هنالك حلول أخرى للتسديد). **\- مصادر للتحضير من أجل الامتحان.** خبرتي بالجافا هي 3 سنوات، وأستطيع أن أقول بأن هذا الشيء ساعدني كثيراً لاجتياز الامتحان. أيضاً مشاركتي بموقع stackoverflow لمدة 3 سنوات تقريباً ساعدتني أيضاً في معرفة خفايا لغة الجافا وخصوصاً على مستوى الـ syntax. بالنسبة للمصادر، اعتمدتُ بشكل كلي على الكتاب [Sun Certified Programmer for Java 6 Study Guide](http://www.amazon.com/SCJP-Certified-Programmer-Java-310-065/dp/0071591060?ref=informatic-ar.com "Sun Certified Programmer for Java 6 Study Guide") وخصوصاً من الفصل الأول إلى الفصل السادس. صحيح أن الكتاب يعتبر قديماً وهو موجه للشهادة SCJP للجافا الإصدار السادس وشهادة OCA هي للجافا الإصدار السابع، ولكن أساس اللغة واحد. الكتاب جميل جداً وفي نظري أفضل كتاب لتعلم أساسيات الجافا بشكل دقيق. قرأتُ الستة فصول الأولى من الكتاب خلال شهر واحد تقريباً (> 400 صفحة). بالإضافة لمنهج الكتاب توجد الميزات الجديدة التي أضيفت في جافا 7، وهي: - استخدام String داخل switch. - الـ diamond-operator، اعرف فقط ماذا تعني. - الـ try-with-resource. - الـ binary representation للأعداد؛ مثل: 0b1001011. - خاصية الـ underscore في الأعداد؛ مثل: 000\_000\_1. أيضاً اشتريتُ [OCAJP Oracle Certified Associate Java SE 7 Programmer Practice Exams](http://www.amazon.com/gp/product/B007SA1GNU/ref=kinw%5Fmyk%5Fro%5Ftitle?ref=informatic-ar.com "OCAJP Oracle Certified Associate Java SE 7 Programmer Practice Exams") وهو عبارة عن بنك أسئلة مشابهة تماماً لما في الامتحان (بعض الأسئلة من هذا الكتاب وجدتها بالامتحان). هنا قائمة بالمواضيع المدرجة في الامتحان: [Programmer Level I Exam](http://docs.oracle.com/javase/tutorial/extra/certification/javase-7-programmer1.html?ref=informatic-ar.com "Programmer Level I Exam"). **\- الامتحان ومدى صعوبته.** إجمالاً، الامتحان بسيط وبإمكان أي مطور جافا يفهم أساسيات اللغة أن يجيب عن الأسئلة بكل سهولة. ولكن، توجد أسئلة مخادعة (tricks) وهي كثيرة، وتوجد أيضاً أسئلة سخيفة لدرجة أنك تشك إن كان بها خدعة ما، وهذا ما يزيد من صعوبة الاختبار. هذا الامتحان يختبر مستوى فهمك للـ syntax وأساسيات الـ object-oriented مثل الـ inheritance وغيرها. بعض الأسئلة تختبرك وكأنك compiler لتكتشف خطأ ما بـ syntax وبالتالي compilation error، أو خطأ أثناء التشغيل وبالتالي exception at runtime، ولذلك هذا الامتحان يحتاج إلى تركيز عالي وقراءة السؤال أكثر من مرة حتى يُفهم بشكل تام. بالنسبة للوقت، أخبروني برسالة إيميل بأن الوقت سيكون 150 دقيقة، ولكنني تفاجأت داخل قاعة الامتحان بأن الوقت هو 140 دقيقة فقط! لا تخف، الوقت كافي جداً وسيتبقى الكثير من الوقت وأنت قد انتهيت من حل جميع الأسئلة. أنهيت الامتحان مع المراجعة في 90 دقيقة فقط. بالنسبة للأسئلة، فقد كانت 90 سؤالاً، بعضها يطلب منك أن تختار إجابة واحدة، وبعضها إجابتين، وبعضها ثلاثة. لا تقلق، سيخبرك بعدد الإجابات التي من المفترض أن يتم اختيارها. بعض الأسئلة يوجد بها tab إضافي ويسمى Exhibition ويحتوي على source code يشار إليه ضمن السؤال. مع كل سؤال، يوجد checkbox يسمى review وهو لوضع علامة على السؤال لمراجعته لاحقاً. بعد الانتهاء كلياً، تستطيع أيضاً مراجعة جميع الأسئلة من جديد بالضغط على الزر Review All. بالنسبة للأوراق المطلوب إحضارها وقت الامتحان، أنا أحضرت بطاقة الأحوال المدنية ورأيتُ شخصاً آخر أحضر جواز سفره. أيضاً، أحضرتُ بطاقة بنكية ووقعت في خلف البطاقة في المكان المخصص (لأول مرة أستخدمه D:) ثم أخذوا صورة من بطاقة الأحوال ومن التوقيع. أيضاً التقطت لي صورة في نفس مركز الامتحان. درجة النجاح في الامتحان هي 77%، والحمد لله اجتزتُ الامتحان بدرجة 96%. [![BaTgSNoCAAANzQb](https://informatic-ar.com/wp-content/uploads/2013/11/BaTgSNoCAAANzQb-300x101.png)](https://informatic-ar.com/wp-content/uploads/2013/11/BaTgSNoCAAANzQb.png) **\- أمثلة لبعض الأسئلة.** للأسف، عندما تعطيك أوراكل النتيجة، لا تخبرك بالأسئلة التي أخطأت فيها. على أية حال، يوجد سؤال كنت قد أجبت عليه بإجابة صحيحة ثم عدلت الإجابة مع الـ review: ```java <List<String> list = new ArrayList<String>(); list.add("EE"); list.add("SE"); list.add("ME"); list.add("SE"); list.add("EE"); list.remove("SE"); System.out.println(list); ``` في البداية أجبت إجابة صحيحة واخترت أن remove تحذف أول occurrence فقط، ولكن مع المراجعة اخترت أن remove تحذف جميع الـ occurrences. الحمد لله على كل حال. من الأسئلة المخادعة أيضاً استخدام String قيمته null داخل switch (الحمد لله أنني قد قرأت عن هذه الحالة من قبل D:): ```java String s = null; switch (s) { case "null": System.out.println("1"); break; case null: System.out.println("2"); break; default: System.out.println("3"); break; } ``` الجواب هو NullPointerException will be thrown at runtime. سؤال مخادع آخر (جزء من الـ source code): ```java String[] array = {"Item1", "Item2", "Item3", "Item4", "Item5"}; int i = 0; while (i++ < array.length) { System.out.println(i); break; } ``` كم سطراً سيتم طباعته؟ مع الارتباك ربما لن تنتبه للـ break وتعتقد أن الـ while سيكمل جميع دوراته. سؤال آخر: ```java do { int x = 0; x++; } while (x > 0); ``` لا أذكر السؤال جيداً، ولكن ما أذكره هو أن الجواب كان compilation error بسبب أن x لا يمكن استخدامها داخل while وذلك لأنها في scope آخر. **\- ما هي الخطوة القادمة؟** بعد الانتهاء من الامتحان، ستظهر لك رسالة مفادها أنه سيصلك إيميل من أوراكل بالنتيجة خلال مدة أقصاها نصف ساعة. وصلتني الرسالة في أقل من 5 دقائق. بالنسبة للشهادة، سترسلها لك أوراكل للبريد المسجل في موقع pearsonvue.com خلال مدة أقصاها 8 أسابيع بحسب ما قرأت. **تحديث:** ستصلك الشهادة كنسخة إلكترونية عبر إيميل من أوراكل خلال 48 ساعة من إكمال متطلبات الشهادة، وسيخبروك بطريقة طلب النسخة الورقية والتي من المفترض أن تصلك خلال 4-8 أسابيع. [![OCA_1Z0-803_Certification](https://informatic-ar.com/wp-content/uploads/2013/11/OCA_1Z0-803_Certification-300x227.jpg)](https://informatic-ar.com/wp-content/uploads/2013/11/OCA%5F1Z0-803%5FCertification.jpg) ### الـ Binary Serialization في الجافا URL: https://informatic-ar.com/binary_serialization_in_java/ Last updated: 2026-10-04T04:35:42.000Z السلام عليكم و رحمة الله و بركاته **الفهرس :** 1. ما هي عملية الـ Serialization ؟ 2. كيف نقوم بها ؟ 3. مثال تطبيقي 4. متى نستخدم transient ؟ 5. الواجهة البديلة Externalizable 6. اختبر قدراتك في الـ Serialization **1\. ما هي عملية** **الـ Serialization** **؟** الـ Serialization عبارة عن آلية تسمح بحفظ الكائنات في storage medium أي وسيلة تخزين مثل الملفات أو ذاكرات التخزين المؤقت (memory buffer) كما تُمكن أيضا من نقل الكائنات عبر شبكات الاتصال, تم دعمها في الجافا ابتداء من النسخة 1.1 من الـ JDK, تسمح هذه الآلية بالحفاظ على الـ Object Persistence حيث تُنقل الكائنات عبر binary support على شكل مجموعة من الـ bytes أو على شكل قابل للقراءة مثل ملفات XML. الـ format المستخدم لحفظ الكائنات لا علاقة له بنظام التشغيل حيث يُمكن إعادة بناء الكائن (تُسمى هذه العملية بـ deserialization) في نظام تشغيل مختلف عن الذي تمت فيه عملية الـ serialization. أثناء هذه العملية, يُمكن تخزين الكائنات على القرص الصلب أو في قاعدة بيانات كما يُمكن أيضاً إعادة بنائها في جهاز آخر في الشبكة حيث تتم إعادة إنشائها باستخدام الـ JVM الموجودة في الجهاز المستقبِل. (كما تفعل RMI مثلا) في هذه المقالة, سنشرح كيفية عمل الـ binary serialization مع العلم أنه يُمكن عمل الـ XML serialization في الجافا باستخدام الفئتين XMLEncoder و XMLDecoder حيث يتم تخزين البيانات بصيغة XML. **2** **. كيف نقوم بالـ** **Serialization** تعتمد الـ serialization على الـ streams لذا يجب أن تكون لديك معرفة مُسبقة بمعظم الفئات الموجودة في الحزمة java.io و كيفية التعامل معها. تُوفر الـ Java API الأدوات اللازمة لعمل الـ serialization و هي : - الواجهة Serializable - الفئتين ObjectOutputStream و ObjectInputStream الواجهة Serializable تسمح بتحديد الفئة التي نُريد عمل serialization لها, الفئتين ObjectOutputStream و ObjectInputStream يُمكّنان (على التوالي) من تحديد كيف ستتم عملية الــ serialization و الـ deserialization. **2.1 - الواجهة Serializable** لعمل serialization لكائن من فئة معينة, يجب على هذه الفئة أن تقوم بعمل implements للواجهة Serializable أو ترث فئة قامت مُسبقاً بعمل implements للواجهة Serializable. لا تحتوي الواجهة Serializable على أي دالة حيث يقتصر دورها على تحديد فئة معينة كــ serializable. يتم عمل الـ serialization لكافة حقول الكائن باستثناء تلك الغير serializable أو تلك التي تم الإعلان عنها باستخدام الكلمة static أو transient. **ملاحظات :** - جميع الأنواع الأساسية (مثل int, long, float, ...) يُمكن عمل serialization لها. - ليست كل الكائنات serializable فقد تكون مرتبطة بنظام التشغيل أو مرتبطة بسياق التنفيذ في الذاكرة مثل الـ Threads. - أصبحت File فئة serializable في النسخ الحديثة من جافا. - إذا حاولت عمل serialization لكائن من فئة لم تقم بعمل implements للواجهة Serializable, سيتم إصدار استثناء من نوع NotSerializableException. - استخدم الأداة Serialver من سطر الأوامر لتحديد هل الفئة تدعم الـ serialization أم لا, أمثلة : ```plain serialver java.awt.Graphics Class java.awt.Graphics is not Serializable. ---- serialver java.io.File java.io.File: static final long serialVersionUID = 301077366599181567L; ``` - الـ serialVersionUID عبارة عن رقم النسخة حيث يوجد في كل فئة قامت بعمل implements للواجهة Serializable, الهدف منه هو ضمان تطابق الفئة عند عمل الـ deserialization, وإذا تغيرت الفئة بين عمليتي الـ serialization و deserialization, يتم إصدار استثناء من نوع InvalidClassException. - كل فئة serializable يجب أن تُعلن بشكل صريح عن المتغير serialVersionUID حيث يجب أن يكون final, static و من نوع long أيضاً, مثال : ```java private static final long serialVersionUID = 42L; ``` - إذا لم تقم الفئة بالإعلان عن الحقل serialVersionUID, تتولى آلية الـ serialization توليد رقم تسلسلي للفئة بشكل تلقائي. ****2.2 - الفئتان ObjectOutputStream و ObjectInputStream** ورثت الواجهتان ObjectOutput و ObjectInput خصائص الواجهتين DataOutput و DataInput و أضافتا إمكانية كتابة/قراءة الكائنات, تحتوي كل من DataOutput و DataInput على دوال تُمكن من كتابة/قراءة الأنواع الأولية (مثل char, long, double, ..) حيث أضافت ObjectOutput و ObjectInput إمكانية كتابة/قراءة المصفوفات و الكائنات. قامت كل من ObjectOutputStream و ObjectInputStream على التوالي بعمل implements لـ ObjectOutput|ObjectInput. كلا الفئتين ObjectOutputStream و ObjectInputStream عبارة عن stream object, حيث يسمحان بعمل serialization/deserialization لكائن معين, على التوالي باستخدام إحدى الدالتين writeObject/readObject. **3** **.** **مثال تطبيقي** نبدأ مع الفئة التي سيتم عمل serialization لها : ```csharp public class User implements java .io.Serializable {     private String login;     private String password;     public User() {         this("anonymous", "");     }     public User(String login, String password) {         this.login = login;         this.password = password;     }     public String getLogin() {         return login;     }     public void setLogin(String login) {         this.login = login;     }     public String getPassword() {         return password;     }     public void setPassword(String password) {         this.password = password;     }     @Override     public String toString() {         return "User -> Login : " + this.getLogin() + "\t Password : "                 + (password.equals("") ? "(nothing)" : this.getPassword());     }     public static void main(String[] args) {         User user = new User("Snack3r", "#_#Sn@CkeRC00L");         System.out.println(user.toString());         User anonym = new User();         System.out.println(anonym.toString());     } } ``` و هذه الفئة المسئولة عن الـ Serialization : ```csharp import java.io.*; public class Serialization {     public File serializeUser(User u) {         try {             System.out.println("Création de : " + u.toString());             File fichier = new File("C:\\Users\\Snacker\\Desktop\\user.ser");             FileOutputStream fos = new FileOutputStream(fichier);             ObjectOutputStream oos = new ObjectOutputStream(fos);             try {                 oos.writeObject(u);                 oos.flush();                 System.out.println(u + " a été sérialisé");                 return fichier;             } finally {                 try {                     oos.close();                 } finally {                     fos.close();                 }             }         } catch (IOException ioe) {             System.err.println("Une exception de type Input/Output est declanchée");             return null;         }     }     public static void main(String... args) {         User utilisateur = new User("Mohamed AHMED", "123456");         new Serialization().serializeUser(utilisateur);     } } ``` تستقبل الدالة serializeUser كائناً من نوع User و تُعيد كائناً من نوع File, يُمثل الملف الذي تمت فيه عملية الـ serialization. في البداية, قمنا بإنشاء الملف الذي سيحوي الـ serialized object (في الحقيقة, الملف سيُخزن فقط حقول الكائن) و باستخدام الفئة FileOutputStream, قمنا بإرفاق stream writing للكائن fichier, ثم قمنا بكتابة بيانات المستخدم u داخل كائن الـ output stream باستخدام الدالة writeObject لنقوم بعد ذلك بإفراغ محتوى الـ oos باستخدام الدالة flush. مُخرجات الكود : ```plain Création de : User -> Login : Mohamed AHMED    Password : 123456 User -> Login : Mohamed AHMED Password : 123456 a été sérialisé ``` نأتي الآن إلى الفئة المسئولة عن الـ Deserialization : ```csharp import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class Deserialization {     public void deserializeUser(String chemin) {         User u = null;         try {             FileInputStream fis = new FileInputStream(chemin);             ObjectInputStream ois = new ObjectInputStream(fis);             u = (User)ois.readObject();             System.out.println("~OoO~ -------------------------------------- ~OoO~");             System.out.println("Les données de l'objet qui était sérialisé sont :");             System.out.println(u.toString());             System.out.println("~OoO~ -------------------------------------- ~OoO~\n");         } catch (IOException ioe) {             System.err.println("Une exception de type Input/Output est déclanchée");         } catch (ClassNotFoundException cnfe) {             System.err.println( "Une exception de type ClassNotFoundException est déclanchée");         }         if (u != null) {             System.out.println("[ " + u + " ] a été dé-sérialisé");         }     }     public static void main(String... args) {         new Deserialization().deserializeUser("C:\\Users\\Snacker\\Desktop\\user.ser");     } } ``` في البداية, قمنا بإنشاء كائن من FileInputStream يُمثل stream reading للملف الموجود في المسار chemin ثم قمنا بالإعلان عن كائن من ObjectInputStream من أجل إعادة بناء الكائن u باستخدام الدالة readObject. لاحظ أننا قمنا بعمل casting للحصول على النوع الحقيقي للـ serialized object. بطبيعة الحال, يجب أن تتم الـ deserialization بنفس الفئة التي تمت بها عملية الـ serialization. (بغض النظر عن المفاهيم البرمجية, يُمكننا استنتاج هذه الملاحظة اعتماداً على المنطق السليم). يتم إصدار استثناء من نوع StreamCorruptedException إذا تم تغيير محتوى الملف من خلال editor مثلا. يُمكن أيضاً إصدار استثناء من نوع ClassNotFoundException إذا تم تحويل الكائن إلى فئة لم يتم العثور عليها أثناء التنفيذ. مُخرجات الكود : ```plain ~OoO~ -------------------------------------- ~OoO~ Les données de l'objet qui était sérialisé sont : User -> Login : Mohamed AHMED Password : 123456 ~OoO~ -------------------------------------- ~OoO~ [ User -> Login : Mohamed AHMED    Password : 123456 ] a été dé-sérialisé ``` **4\. متى نستخدم** **transient** **؟** يُمكن رؤية محتوى المتغيرات من خلال الـ stream الذي تمت عملية الـ serialization بداخله و بالتالي يُمكن لأي شخص يستطيع الوصول إلى الـ stream, رؤية محتوى مختلف المتغيرات حتى لو كانت private و هذا يُمكن أن يُؤدي إلى وجود العديد من المشاكل التي تُهدد حماية البيانات, خصوصاً عند نقل معلومات حساسة عن طريق الشبكة. لهذا السبب قامت Java بتوفير الكلمة المحجوز transient التي تعني أن المتغير المرافق لها ينبغي عدم إدراجه في عملية الـ serialization و بالتالي الـ deserialization. للتأكد, يُمكنك تجربة transient مع المتغير الخاص بكلمة المرور في الفئة User. أثناء عملية الـ deserialization يتم إسناد القيمة null للمتغيرات التي تم الإعلان عنها باستخدام transient و هذا جيد من ناحية لكن في نفس الوقت قد يؤثر على عمل الدوال التي تستخدم تلك المتغيرات حيث سيتم إصدار استثناء من نوع NullPointerException. **5** **. الواجهة البديلة** **Externalizable** رأينا سابقاً كيفية عمل serialization من خلال كتابة كائن بمجمله في stream و إعادة بنائه من جديد. قد نحتاج في بعض الأحيان إلى عمل الـ serialization لمتغيرات معدودة لكائن معين لذا توفر جافا الواجهة البديلة Externalizable التي تُمكن من السيطرة الكاملة على عملية الـ serialization. (مع العلم أن الواجهة Externalizable قامت بعمل implements للواجهة Serializable) تحتوي الواجهة البديلة على دالتين هما writeExternal و readExternal, من خلال إعادة تعريفهما سنحدد المتغيرات التي ستدخل في عملية الـ serialization. وفقاً للحالة الافتراضية للواجهة Externalizable, لا تأخذ عملية الـ serialization في الاعتبار أي من متغيرات الكائن لذا لا فائدة من استخدام transient في هذه الحالة. عند إعادة تعريف الدالة readExternal ستحتاج إلى استخدام دوال DataInput للأنواع الأولية (مثل char, long, double, ..) و readObject للكائنات (مثل String, Object, Arrays ..). نفس الشيء بالنسبة للدالة writeObject و الواجهة DataOutput مع writeExternal. لنأخذ الفئة User كمثال : ```csharp import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class User implements Externalizable {     private int id;     private String login;     private String password;     public User() {         this(-1, "anonymous", "");     }     public User(int id, String login, String password) {         this.id = id;         this.login = login;         this.password = password;     }     public int getID() {         return id;     }     public void setID(int id) {         this.id = id;     }     public String getLogin() {         return login;     }     public void setLogin(String login) {         this.login = login;     }     public String getPassword() {         return password;     }     public void setPassword(String password) {         this.password = password;     }     @Override     public String toString() {         return "User -> Login : " + this.getLogin() + "\t Password : "                 + (password.equals("") ? "(nothing)" : this.getPassword());     }     @Override     public void writeExternal(ObjectOutput e) {         try {             System.out.println("-> starting writing...");             e.writeInt(this.getID());             e.writeObject(this.getLogin());             e.writeObject(this.getPassword());             e.flush();             e.close();             System.out.println("-> finishing writing !\n");         } catch (IOException ioe) {             System.out.println("Erreur reading...");         }     }     @Override     public void readExternal(ObjectInput e) {         try {             System.out.println("-> starting reading...");             System.out.println("ID : " + e.readInt());             System.out.println("Login : " + e.readObject());             System.out.println("Password : " + e.readObject());             System.out.println("-> finishing reading !\n");         } catch (IOException | ClassNotFoundException ex) {             System.out.println("Erreur reading...");         }     } } ``` و هذه الفئة المسئولة عن الـ serialization/deserialization : ```csharp import java.io.*; public class Test {     public static void main(String args[]) {         User u = new User(4507, "Snack3r", "#_#Sn@CkeRC00L");         try {             File fichier = new File("C:\\Users\\Snacker\\Desktop\\user.ser");             FileOutputStream fos = new FileOutputStream(fichier);             ObjectOutput oos = new ObjectOutputStream(fos);             u.writeExternal(oos);             FileInputStream fis = new FileInputStream(fichier);             ObjectInputStream ois = new ObjectInputStream(fis);             u.readExternal(ois);         } catch (IOException ioe) {             System.out.println("Erreur main");         }     } } ``` مُخرجات الكود : ```plain -> starting writing... -> finishing writing ! -> starting reading... ID : 4507 Login : Snack3r Password : #_#Sn@CkeRC00L -> finishing reading ! ``` **6\. اختبر قدراتك في الـ** **Serialization** قم بإنشاء فئة باسم CompteBank تُمكن من التعامل مع حسابات بنكية مع الأخذ بعين الاعتبار حساسية المبالغ المالية للزبناء. يتم تعريف كل حساب بالرقم التسلسلي, الاسم الكامل لصاحب الحساب, رصيد الحساب, بالإضافة إلى الضمان المالي. تتم عملية الـ serialization في ملف بامتداد ser يُخزن على سطح المكتب. قم بعمل الـ deserialization و أظهر أسماء أصحاب الحسابات. قم بإنشاء فئة أخرى باسم Client, يتم تعريف كل زبون برقم بطاقة الهوية (IC) بالإضافة إلى الاسم الكامل. قم بعمل الـ serialization في نفس الملف السابق الذي تم تخزينه على سطح المكتب ثم قم بعمل deserialization لجميع الكائنات الموجودة في الملف و أظهر أسماء الزبناء بجانب الضمان المالي لكل حساب. تحياتي. ### اختبر قدراتك في الـ Files, Threads, and Swing URL: https://informatic-ar.com/test_your_skills_in_java/ Last updated: 2026-10-03T22:31:47.000Z السلام عليكم قمتُ بكتابة هذا الموضوع نظراً لوجود العديد من الطلبات بخصوص مشاريع صغيرة تسمح للمبتدئين بقياس مدى فهم الأمور التي تعلموها في الجافا. الهدف من هذا المشروع هو إنشاء تطبيق صغير يسمح بإجراء عمليات معينة على حسابات بنكية و يُتيح تخزين بيانات كل عميل في ملف نصي على القرص الصلب. **المرحلة الأولى** : (إنشاء واجهة التطبيق) هذه صورة لواجهة التطبيق : [![interface graphique](https://informatic-ar.com/wp-content/uploads/2013/11/interface-graphique-205x300.png)](https://informatic-ar.com/wp-content/uploads/2013/11/interface-graphique.png) الـ package ذو الاسم GUI يحتوي على فئتين, الأولى باسم CompteFrame حيث تُمثل الواجهة الرئيسية للتطبيق و الثانية باسم Main حيث تُمثل الفئة الرئيسية للمشروع. 1. الفئة CompteFrame لا تحتوي على main. 2. تم بناء الواجهة بشكل يدوي (لم أستخدم طريقة Drag and Drop) **المرحلة الثانية** : (إنشاء حسابات المستخدمين) الـ package ذو الاسم Transaction يحتوي على فئتين, الأولى Compte و الثانية FileCompte. الفئة FileCompte تحتوي على دالتين, الأولى باسم compteToFile حيث تأخذ بيانات الحساب و تخزنها في ملف نصي و الثانية باسم fileTopCompte تأخذ رقم الحساب و تُعيد كائن من نوع Compte يحتوي على البيانات المخزنة داخل الملف. الفئة Compte تحتوي على : - constructeur يسمح بإنشاء حساب جديد. - الدوال getNom, getPrenom, getSolde, getDecouvert, getNumeroCompte, getHistorique. - دالتين, إحداهما لسحب مبلغ و الأخرى لشحن الحساب : crediter (double montant), debiter (double montant)l. - الدالة setDecouvert (montant nouveauDecouvert)l التي تسمح بتغيير المبلغ الأدنى الذي يجب أن يبقى دائما في الحساب. **المرحلة الثالثة** : (transactions management) لإدارة و تسيير العمليات التجارية, اعتبرتُ أن حساب العميل عبارة عن critical resource و بالتالي لا يُمكن إجراء عملية شحن و سحب مبلغ على حساب معين في نفس الوقت لذا قمتُ بتسيير الـ synchronization لكلتا العمليتين. قمتُ بإنشاء package باسم Synchro يحتوي على فئتين, الأولى باسم ThreadCompteRetrait و الثانية باسم ThreadCompteDepot لتسيير و إدارة عمليات السحب و الشحن على التوالي. **المرحلة الرابعة** : (إدارة الأحداث لجعل الواحهة تتفاعل مع المستخدم) الزر Creer compte يسمح بإنشاء حساب جديد انطلاقا من البيانات الموجودة في الخانات بالأعلى. بمجرد إنشاء حساب جديد, سيتم تخزين بيانات العميل في ملف نصي على القرص الصلب. قمتُ بأخذ الـ constraints التالية بعين لااعتبار : (الـ package ذو الاسم Filter يحتوي على الفئات المسؤولة عن هذه الـ constraints) - لإنشاء حساب جديد, يجب ملىء كافة الحقول و إلا سيتم إظهار نافذة صغيرة تُوضح سبب الخطأ. - الحقلين prenom و nom يجب أن يحتويان على حروف فقط, إذا حاول المستخدم إدخال أرقام أو رموز, يتوقف المؤشر عن الكتابة. - الحقلين solde و decouvert يجب أن يحتويان على أرقام فقط و عند محاولة إدخال أحرف, يتوقف المؤشر عن الكتابة. - الحقل NumeroCompte يجب أن لا يزيد طوله عن 10 أرقام, بمجرد تخطي عشرة أرقام عند عملية الادخال, يتوقف المؤشر عن الكتابة. - بالإضافة إلى ما سبق, يُمكن للمستخدم تشغيل الزر بمجرد أن يضع عليه الـ focus و يضغط على enter. - إذا تمت عملية الإنشاء بنجاح, يتم إظهار نافذة صغيرة توضح ذلك. فائدة الزر Historique du compte تأتي في إظهار بيانات العميل في الخانة الكبيرة الموجودة وسط النافذة. لإظهار معلومات عميل معين, يكفي إدخال الرقم الخاص به و الضغط على الزر Historique du compte. الزرين Depot و Retrait يسمحان بسحب أو شحن الحساب على التوالي. (هاتان العمليتان تم عملهما كـ synchronized) بمجرد إدخال رقم العميل بالأسفل, يمكن سحب أو شحن مبلغ معين. المبلغ يجب أن يتكون من أرقام فقط. أيضاً, لسحب أو شحن مبلغ معين يمكن أيضا إدخال البيانات و الضغط على enter في الخانة الثانية أو الثالثة حسب نوع العملية. أخيراً, قمتُ بعمل jar لكل من synchro و transaction و قمتُ بعمل import لملف الـ jar في المشروع, فأصبح هكذا : [![jarrer les classes](https://informatic-ar.com/wp-content/uploads/2013/11/jarrer-les-classes-300x147.png)](https://informatic-ar.com/wp-content/uploads/2013/11/jarrer-les-classes.png) لتحميل المشروع كاملا مع ملف jar لتجربة البرنامج : [Gestion des comptes bancaires](https://informatic-ar.com/wp-content/uploads/2013/11/Gestion-des-comptes-bancaires.rar) تحياتي. ### الزعيم NoSQL و أحفاده الأربعة URL: https://informatic-ar.com/nosql/ Last updated: 2026-10-04T04:35:43.000Z السلام عليكم و رحمة الله و بركاته [![nosql-dbs](https://informatic-ar.com/wp-content/uploads/2013/11/nosql-dbs-300x178.png)](https://informatic-ar.com/wp-content/uploads/2013/11/nosql-dbs.png) **لمن هذه المقالة ؟** هذه المقالة مُوجهة لكل من تعامل سابقا مع أحد توابع الــ RDBMS و لديه إلمام بمعظم مفاهيم الــ advanced databases. الهدف من هذه المقالة هو التعريف بأهمية NoSQL و مقارنتها ببقية الأنظمة المنافسة لها. **الفهرس :** 1. تعريف 2. متى ظهر الزعيم ؟ 3. ACID و BASE أيهما الأفضل ؟ 4. أحفاد الزعيم الأربعة 5. جولة سريعة مع العملاق الرائع MongoDB 6. الخاتمة **1\. تعريف** يُمثل NoSQL نمطاً أو نوعاً جديداً من أنظمة إدارة قواعد البيانات (Database Management Systems) حيث يتبع أسلوباً مختلفاً عن الأسلوب التقليدي لقواعد البيانات ذات الجداول المترابطة (Relational Database). من أبرز أوجه الخلاف بين هذين الأسلوبين : الجداول, حيث لا يتخذها NoSQL كوحدة الأساس لبناء قواعد البيانات على عكس الــ Relational Database, لهذا السبب, تستخدم NoSQL لغة UnQL كبديل للغة SQL في التعامل مع البيانات. لعل أول سؤال يتبادر إلى ذهن القارئ هو : هل أتت NoSQL لتحل محل الــ RDBMS ؟ و الجواب ببساطة : لا ! فقد اختار مؤسسو هذه التقنية الاسم NoSQL كاختصارٍ لـــ Not Only SQL للدلالة على أن هذه التقنية لم تأتِ للقضاء على الــ RDBMS و إنما تُمثل أحد البدائل الـمـُقترحة حيث تُقدم العديد من الحلول خصوصاً في الحالات التي يكون فيها الــ RDBMS عاجزاً عن توفير حلول سهلة, فعالة و مفتوحة المصدر. أغلب التطبيقات الموزعة (distributed applications) الموجهة للإنترنت (internet-oriented) التي تعمل على قواعد بيانات عملاقة جدا, تستخدم أحد توابع NoSQL لإدارة و تسيير قواعد بياناتها. (انظر الأمثلة الموجودة في الفقرة الرابعة) **2\. متى ظهر الزعيم ؟** منذ أربع عقود مضت, أعطى العالم البريطاني المشهور Edgar Frank Codd الضوء الأخضر للأميرة المدللة Relational Database لتدخل قصر قواعد البيانات من أوسع أبوابه. منذ ذلك الوقت, بدأت أميرتنا الصغيرة بفرض سيطرتها في كافة أرجاء القصر, و بعد عشر سنوات من توليها إدارة شؤونه, بدأت حركة الــ Object Database في التمرد بقيادة CPP لتنضم إليها Java في أواخر التسعينات. لكن سرعان ما تدارك الباحثان Christopher J. Date و Hugh Darwen الأمر عندما وقَّعا اتفاق المصالحة المشهور بعنوان البيان الثالث (The Third Manifesto) في عام 1995 و الذي كان من شأنه إعادة روح الثقة بين كلا الطرفين. بعد ثلاث سنوات من عقد الاتفاق, بدأت الأصوات تتعالى من جديد و السبب هذه المرة هو الغلاء الصارخ لأسعار الـــ DBMS بالإضافة إلى القيود التي تفرضها الرخص التجارية, لذا بدأ وجهاء القصر بالبحث عن زعيم جديد يُمكنه إقناع المحتجين و تلبية طلباتهم مع الحفاظ على الأدوار التي كانت تقوم بها الأميرة المدللة. في الحادي عشر من حزيران 2009 اجتمع وجهاء القصر بقيادة الحكيم Shashank Tiwari لتجديد البيعة و انتهت الجلسة بتعيين الزعيم NoSQL ملكاً لقصر قواعد البيانات خلفاً للأميرة المدللة. بعد ساعات قليلة من توليه السلطة, عقد الزعيم اجتماعاً طارئاً حضره وكلاء الأسر التي تضررت أثناء حكم الصغيرة المدللة و تكفل بتحمل المسؤولية و إصلاح ما أفسدته الأميرة الصغيرة. كانت تلك مجرد مقدمة مختصرة أردتُ من خلالها استعراض مختلف مراحل التطور التي مرت بها أنظمة إدارة قواعد البيانات, ابتداء من نشأتها وصولاً إلى يومنا هذا. سنكتفي بهذا القدر من التنظير لننتقل إلى الجانب العملي من هذه المقالة. **3\. ACID و BASE أيهما الأفضل ؟** التعامل مع البيانات الحساسة الموجودة في قواعد البيانات يتطلب آلية محددة و دقيقة بحيث تضمن تكامل و تناسق البيانات أثناء إجراء عدة عمليات في آن واحد. يُمثل كل من ACID و BASE فلسفة في التصميم و آلية مختلفة للحفاظ على تكامل البيانات أو اتاحتها حيث يرى BASE أن الأولوية تكون دائما لإتاحة البيانات (availability) وجعلها قابلة للتصفح من طرف مختلف عُقَد النظام بينما يرى نظيره ACID أن الأولوية يجب أن تكون من نصيب تناسق البيانات فيما بينها (consistency) حسب القواعد المـُحددة. في هذه الفقرة سنتطرق إلى أوجه الخلاف بين هذين الأسلوبين و أيهما الأفضل و متى يكون ذلك ؟ يُعتبر BASE اختصاراً لــ (Basically Available, Soft-state, Eventually-consistent) و يعتمد على الخصائص الثلاث التالية : 1. Basically Available : و تعني أن النظام مُتاح بشكل دائم و يمكن الوصول إليه حتى في حالة الــ desynchronization. في هذه الحالة نجد أن الــ availability مضمونة بشكل كامل على عكس الــ consistency لأن النظام سيرد على جميع الطلبات لكن قد يحتوي الرد على بيانات غير متناسقة (inconsistent data) أو يكون الرد مثلا عبارة عن failure. 2. Soft-state : تدل هذه الخاصية على أنه يمكن لحالة النظام (state of the system) أن تتغير مع مرور الوقت حتى لو لم تكن هناك بيانات جديدة حيث يُمكن أن تحدث تغييرات في قاعدة البيانات بسبب الــ eventual consistency. 3. Eventual consistency : و تعني أن النظام لا يهتم بالـ consistency بعد إجراء transaction معينة لكن في ظرف معين من الزمن و بعد عدد غير محدد من العمليات سيستقر النظام على مجموعة من البيانات المتناسقة و التي تلتزم بالـ consistency rules مع أن هذه الأخيرة هي آخر من يعلم على عكس ما يحدث في ACID. يُعد ACID من أكبر منافسي BASE حيث يعتمده حالياً معظم الــ SGBD مثل Oracle, SQL Server و Informix. يرتكز ACID على الخصائص الأربع التالية : 1. الـ Atomicity : تنص على هذه الخاصية على أن أي transaction إما أن يتم تنفيذها بشكل كامل أو إلغائها بشكل كامل و بالتالي لا توجد half-completed transaction. 2. الـ Consistency : كل transaction يجب أن تنقل مجموعة البيانات من حالة متناسقة (consistent state) إلى أخرى مماثلة مع الحفاظ على الـ integrity constraints. 3. الـ Isolation : تضمن هذه الخاصية التنفيذ المتزامن لمجموعة من الـ requests في آن واحد حيث يتم تنفيذ كل واحدة بمعزل عن البقية و بالتالي لا يمكن لأي transaction أن تصل إلى transaction أخرى لم يكتمل تنفيذها (أي في حالة الـ unfinished). 4. Durability : بمجرد الانتهاء من تنفيذ الـ request بنجاح, لن يتم التراجع عن التعديلات الناجمة عن تلك العملية حتى لو تعطل النظام لاحقاً. [![cap-theorem](https://informatic-ar.com/wp-content/uploads/2013/11/cap-theorem-300x225.png)](https://informatic-ar.com/wp-content/uploads/2013/11/cap-theorem.png) بالنسبة للتطبيقات الموزعة (distributed computer systems) فتنص نظرية Brewer المشهورة باسم CAP Theorem بأنه يمكن فقط ضمان اثنتين من الخصائص الثلاث التالية في نفس الوقت : - Consistency : في نفس الوقت, تُشاهد كافة عُقَد النظام نفس البيانات. - Availability : تحصل جميع الطلبات على رد يُوضح نجاح تنفيذ العملية أو عدمه. - Partition Tolerance : لا يتوقف النظام عن العمل إلا في حالة تعرضه لتعطل كامل, وفي الحالة المعاكسة تظل الشبكات الفرعية تعمل بشكل مستقل. بشكل عام, عادة ما يبقى الخيار ما بين الــ Availability و الــ Consistency بافتراض وجود الــ Partition Tolerance كخاصية ثابتة في أغلب الأنظمة الموزعة. تُستخدم NoSQL في الغالب في التطبيقات الموزعة التي تلتزم بقواعد ACID و تعتمد أحد خيارات Brewer. بقي أن نُشير إلى أن NoSQL عادة ما تُستخدم في قواعد البيانات الـمـُوجهة للانترنت. **4\. أحفاد الزعيم الأربعة** للزعيم أربعة أحفاد : **4.1 - Key-values Stores** **4.1.1 - تعريف** : توجد علاقة بين المفتاح و القيمة, تماما كما هو الحال مع جدول هاش (HashMap), القيمة قد تكون سلسلة محارف أو serialized object مثلاً, غياب الــ datatyping في هذا الــ model له تأثيرٌ مهم جداً على الــ Querying لأن التواصل مع قاعدة البيانات سيقتصر على ثلاث عمليات فقط : PUT, GET و DELETE. **4.1.2 - أمثلة** : - Memcached : يقوم بتخزين البيانات فقط في الــ Random-access memory, عندما يتوقف النظام عن العمل يتم فقدان كافة البيانات لذلك يُستخدم عادة لإدارة و تسيير الــ Cache. يعمل على Windows, Linux و Mac OS وهو متوفر برخصة Permissive. ظهر الــ Memcached لأول مرة قبل عشر سنوات من الآن حيث كانت تستخدمه LiveJournal وفي عام 2010 وصل انتشاره إلى عدة مواقع عالمية نذكر منها على سبيل المثال : Orange, Wikipedia, YouTube, Facebook, Twitter. - Voldemort : تم إصدار أول نسخة منه في 2009 و قد أُخذ اسمه من الشخصية الخيالية للساحر Lord Voldemort في سلسلة Harry Potter المشهورة. تُعتبر النسخة 1.3.0 آخر إصداراته المستقرة حيث لا يزال في تطوير مستمر. اختار Voldemort الثنائي AP من نظرية Brewer. قامت بإنشائه شركة LinkedIn حيث كُتب بلغة Java لذا فهو متعدد المنصات. - Amazon DynamoDB : توفره شركة Amazon حيث يتبع للفرع الخاص بخدمات الويب, يُعتبر أحد توابع DynamoDB, والفرق بينهما يكمن في اختلاف الــ implementation حيث نجد الأول يعمل حسب أسلوب الــ multi-master بينما يعمل الآخر وفقاً لأسلوب الــ single master. استُخدمت عدة لغات برمجة لكتابة هذا النظام, منها :Java, Node.js, .NET, Perl, PHP, Python و Ruby. **4.2 - Column-Oriented** **4.2.1 -** **تعريف** : يُشبه إلى حد ما الجداول الموجودة في الــ RDBMS إلا أن عدد أعمدته ديناميكي على عكس الجداول التقليدية حيث يُمكن وجود عدة records بأعمدة مختلفة مما يسمح لك بتفادى وضع NULL في الخانات الزائدة. **4.2.2 -** **أمثلة** : - Cassandra : تمت كتابتها بلغة جافا من طرف Facebook حيث أُعلن عن اولى اصداراتها في 2008 لكن سرعان ما تخلى عنها لصالح شركة Apache, تُعتبر الحسناء Cassandra أحد أفراد عائلة الــ BigTable بشهادة العميد Jeff Hammerbacher . تم تصميمها أساسا لإدارة و تسيير قواعد بيانات عملاقة, موزعة على عدة servers مع ضمان اتاحة البيانات بشكل دائم. تحتل الحسناء Cassandra المرتبة 11 من بين أنظمة قواعد البيانات الاكثر استخداماً في العالم, حسب [احصائيات](http://db-engines.com/en/ranking?ref=informatic-ar.com) موقع DB-Engines. يستخدمها الآن كلٌّ من Twitter, Digg و Reddit بالإضافة إلى الــ Facebook طبعاً. - HBase : يُمكن من تخزين جداول ضخمة جداً بشكل منظم, كُتب بجافا, متعدد المنصات و متوفر برخصة Apache2.0, ينتمي هو الآخر إلى عائلة الــ BigTable. يُعتبر HBase أحد توابع الــ Hadoop حيث يُستخدم عادة مع HDFS لتسهيل عملية التوزيع لكن مع ذلك كما يُمكن ايضا استخدامه في مجالات أخرى متعددة. - Apache Accumulo : تم إنشائه عام 2008 من طرف وكالة الامن القومي (NSA) و استولت عليه شركة البرمجيات Apache بعد سنتين من اطلاقه. في 21 آذار 2012 خرج من حضانة Apache لينتقل إلى مستوى أعلى و بعد سنة من تمرده اعلن عن نسخته الجديدة 1.4.3 في الثامن عشر من آذار 2013\. يعتمد هذا النظام على تقنية الــ BigTable و يحتل المرتبة الثالثة ضمن أفراد عائلته حسب [احصائيات](http://db-engines.com/en/ranking?ref=informatic-ar.com) موقع DB-Engines. **4.3 - Document-Oriented** **4.3.1 - تعريف** : يعتمد هذا الــ model على نموذج الــ Key-values, القيمة في هذه الحالة هي ملف JSON أو XML, الهدف من تمثيل كهذا هو إمكانية الحصول على مجموعة بيانات مرتبة بشكل شجري من خلال مفتاح واحد فقط, نفس العملية تُقابلها مجموعة من الـــ joins في الــ RDBMS التقليدية. **4.3.2 - أمثلة** : - CouchDB : نظام لإدارة قواعد البيانات, مُوجه بشكل خاص إلى تطبيقات الويب, تم إطلاقه في 2005 و بعد ثلاث سنوات, اشترته Apache ليصبح أحد مشاريعها المنافسة على الساحة. كُتب بلغة Erlang, متوفر برخصة Apache. يستخدم ملفات JSON لتخزين البيانات و JavaScript كلغة استعلام تعتمد على خوارزمية MapReduce. - Couchbase Server : كان يُعرف سابقاً باسم Membase, يسمح بتنفيذ آلاف العمليات في آن واحد كما يضمن اتاحة البيانات لكافة المستخدمين في أي وقت. كُتب بــ C, CPP و Erlang, متعدد المنصات. تم إطلاق آخر نسخة منه في الثالث عشر من أيلول 2013. - MongoDB : تم اطلاقه في عام 2007 من طرف شركة 10gen حيث اشتُق اسمها من كلمة hu**mongo**us و تعني عملاق, فعال جداً و لايتطلب مخطط (schema) مُعرف مسبقاً, يستخدم ملفات من نوع BSON لتمثيل البيانات. كُتبت بــ CPP و متوفرة برخصة AGPL. صدرت آخر نسخها في اغسطس 2013. **4.4 - Graph Databases** [![GraphDatabase](https://informatic-ar.com/wp-content/uploads/2013/11/GraphDatabase-300x212.png)](https://informatic-ar.com/wp-content/uploads/2013/11/GraphDatabase.png) **4.4.1 - تعريف** : يعتمد هذا الـ model على الــ GraphTheory حيث تحتوي كل عقدة (على الأقل) على مؤشر يحمل عنوان العقدة الموالية و بالتالي لا حاجة في استخدام الــ indexes. يُستخدم هذا النموذج عادة في الشبكات الاجتماعية حيث يسمح بالحصول على معلومات الأعضاء بسرعة, كما يُـمـَكن أيضا من إيجاد العلاقات التي تربط الأعضاء فيما بينهم بشكل سريع جداً (مثلَ إيجادِ أصدقاءِ أصدقاءِ الأصدقاء لعضو معين). **4.4.2 -** **أمثلة** : - Neo4j : يُعد من أشهر أنظمة قواعد البيانات التي تستخدم الــ graphs, تم إطلاقه في عام 2000 من طرف شركة Neo Technology, كُتب بلغة جافا, متعدد المنصات و متوفر برخصتين هما GPLv3 و AGPLv3\. تم الإعلان عن اصدار النسخة 1.0 منه في العاشر من شباط 2010. - OrientDB : أعلنت شركة Luca Garulli عن أولى نسخه قبل ثلاث سنوات من الآن, يستخدم الــ documents لتخزين البيانات و يعتمد على الــ graphs لربط الملفات بعضها ببعض, يدعم SQL كلغة استعلام كما يدعم على التوالي كلا من schema-less, schema-full بالإضافة إلى schema-mixed. كُتب بالكامل بلغة جافا و قد صدرت آخر نسخة منه في نهاية تموز من العام الحالي. - InfiniteGraph : كُتبت نواته بـــ CPP و البقية بجافا وهو أحد منتجات شركة Objectivity حيث أعلنت عنه في عام 2010, متعدد المنصات و متوفر بنسختين إحداهما تجارية و الأخرى حرة. بطبيعة الحال, توجد أنواع أخرى من NoSQL لكنها أقل شهرة مثل ElasticSearch وهو عبارة عن محرك بحث متعدد المنصات يعمل على قاعدة بيانات NoSQL كما توجد أنواع أخرى ربما لم تسمع عنها سابقاً مثل MarkLogic, TokuMX, FleetDB بالإضافة إلى SciDB. **5\. جولة سريعة مع العملاق الرائع MongoDB** **[![mongodb_logo](https://informatic-ar.com/wp-content/uploads/2013/11/mongodb_logo-300x100.png)](https://informatic-ar.com/wp-content/uploads/2013/11/mongodb%5Flogo.png)** **5.1 - تعريف** : MongoDB عبارة عن نظام لإدارة قواعد البيانات يستخدم ملفات BSON و هي اختصار لــ Binary JSON. يتميز MongoDB بخاصية الــ ShemaLess إذ لا يلتزم بـــ schema معين لذا قد يختلف محتوى الملفات من حين لآخر. يُعتبر Mongo أيضا نظام scalable حيث يُمكنه التأقلم مع آلاف الطلبات في آن واحد دون أن يؤثر ذلك على سرعة الأداء. **5.2 - المميزات** - يُوفر لغة استعلام غنية و مليئة بالدوال الجاهزة. - يتميز بسرعة الأداء و الكفاءة العالية خصوصاً عند إدخال بيانات ذات حجم كبير جداً باستخدام الـــ batch mode حيث يُمكن إدراج مائة ألف عنصر في الثانية الواحدة. - يسمح بعمل indexing لخصائص الملفات من أجل تسريع عمليات البحث. - يُوفر العديد من الدوال التي نجدها في الــ RDBMS التقليدية (مثل count, group by, …) و يُضيف مميزات أخرى متقدمة مثل استخدام سكربتات MapReduce بلغة JavaScript لزيادة سرعة الأداء بالإضافة إلى إمكانية البحث عن بيانات معينة اعتماداً على الــ geolocation. - القدرة على عمل Replication و Partitioning في عدة instances. تتكون قواعد بيانات الــ mongo من مجموعة من الــ collections, تحتوي كل collection على عدد غير مُحدد من الـ documents حيث يعتمد حجم الــ collection على عدد الملفات الموجودة داخلها فعند إضافة ملف جديد تتم زيادة الحجم بشكل تلقائي ويتم تقليصه عند حذف أحد الملفات. أحد أهم الفروق بين الــ mongo و قواعد البيانات التقليدية هو أن بنية الملف لا تلتزم بقواعد معينة على عكس الـــ MySQL مثلا, حيث نجد أن مكونات الــ rows هي نفسها في الجدول الواحد إذ يجب أن تخضع لقواعد الــ schema الذي تم تعريفه أما الــ mongo فلا يعتمد أي مخطط و بالتالي يُمكن لبنية الملف أن تتغير من وقت لأخر داخل نفس الــ collection. ملاحظة : الملفات في الــ mongo تُقابل الصفوف في الــ MySQL. **5.3 - أمثلة على استخدام الــ mongo** المثال التالي يُوضح كيفية الاعلان عن ملفات في الــ mongo : ```json {      "Nom": "Chikoo",      "Prénom": "Banderas",      "Âge": 23 } {      "_id": "C04507",      "Nom": "Mohamed",      "Prénom": "AHMED",      "Pays": "Mauritanie",       "Adresse Electronique":       {                    "E-mail" : " Snack3R@yahoo.fr",                    "Password": " #*MonGoDb-N0$Q!_"         } } ``` في هذا المثال لدينا ملفان, يحتوي الأول منهما على 3 حقول بينما يحتوي الثاني على 5, الحقل الأخير يُمثل ملفا حيث يُسمى هذا النوع من التخزين : الملفات المتداخلة أو الــ nested documents. بالنسبة للمعرف (ID) فيجب دائما أن يكون مسبوقاً بــ underschool, إذا لم نقم بتوفير الــ ID سيقوم الــ mongo بتوليده تلقائيا. نلاحظ أيضا أن حقول الملفات تتميز بخاصية الـــ strongly-typed إذ لا نحتاج إلى تحديد نوع كل متغير حيث يقوم الــ mongo بتحديد النوع وفقا لمحتوى المتغير. يُمكنك تحميل الــ mongo من [الموقع الخاص به](http://www.mongodb.org/?ref=informatic-ar.com) كما يمكنك أيضا تجربة الـــ Shell الـمـُتاح online من خلال الضغط على [Try It Out](http://try.mongodb.org/?ref=informatic-ar.com) في الواجهة الرئيسية للموقع لتظهر لك واجهة الشل : [![try it out](https://informatic-ar.com/wp-content/uploads/2013/11/try-it-out-300x151.png)](https://informatic-ar.com/wp-content/uploads/2013/11/try-it-out.png) يُمكننا الإعلان عن كائن جديد باسم obj هكذا : ```mongodb obj = {nom:"Snack3r",age:21}; ``` الآن, obj مُخزن في الذاكرة, عندما نكتب obj ثم نضغط enter سيظهر محتواه. لتخزينه في collection داخل قاعدة البيانات التي نعمل عليها حاليا (عادة ما تكون test بشكل افتراضي) نكتب : ```mongodb db.collection.save(obj); ``` الدالة save تُخزن الكائن الممرّر كوسيط داخل الــ collection الـمُستدعاة. إذا كانت الــ collection غير موجودة مُسبقا فسيتم إنشاؤها. لتغيير قاعدة البيانات نستخدم الأمر use هكذا : ```mongodb use myDataBase ``` الآن, قمنا بتخزين الكائن obj داخل collection. للتأكد من وجوده نستخدم الدالة find بدون وسيط, هكذا : ```mongodb db.collection.find(); ``` التي تُكافئ ```sql SELECT * FROM table ``` في MySQL مثلا. عند إظهار معلومات obj سنحصل على شيء من هذا القبيل : ```json {   "nom" : "Snack3r",   "_id" : {   "$oid" : "527f800ccc9374393403c24d"   },   "age" : 21   } ``` لاحظ أنه قد تم توليد ID بشكل تلقائي من طرف الــ mongo. يُمكننا الاعلان عن كائن جديد مع تحديد الـــ ID : ```mongodb db.collection.insert({_id:100,nom : "ArabTeam",prenom:"forum",age:13}); ``` و للتأكد من جديد : ```mongodb db.collection.find(); ``` لاحظ أن محتوى الكائن الثاني يختلف عن الأول لأن الــ mongo لا يلتزم بــ schema محدد. يُمكننا أيضا البحث عن الكائن الثاني فقط, باستخدام find : ```mongodb db.collection.find({_id:100}); ``` الكتابة السابقة تُكافئ استخدام WHERE في MySQL مثلا. تغيير معلومات الكائن يتم باستخدام الدالة update : ```mongodb db.collection.update({nom:"ArabTeam"},{$set:{age:23}}); ``` تقوم الدالة find بالبحث وفقا لشرط معين على أحد المتغيرات يتم تمريره كوسيط : ```mongodb db.collection.find({nom:" ArabTeam"}) ``` في هذه الحالة, سنحصل على الكائن الثاني فقط, لكن في الحالة العامة, سنحصل على جميع الكائنات التي تحتوي على حقل nom يُخزن القيمة ArabTeam. يُمكن أيضا الإعلان عن كائن يحتوي على جدول من الـ strings : ```mongodb obj2 = {nom:"test",langue:["français","anglais","arabe"]} ``` لتخزينه : ```mongodb db.collection.save(obj2); ``` يُمكننا أيضا الإعلان عن كائن يُخزن داخله كائنا آخر (يُسمى هذا النوع من التخزين, تداخل الكائنات) : ```mongodb obj3 = {type : obj,option : "yes"}; ``` و لحذف العناصر نستخدم الدالة remove : ```mongodb db.collection.remove() ; ``` يُمكن أيضا الحذف وفقا لشروط معينة كما فعلنا سابقا مع find. توجد العديد من الدوال الأخرى مثل count, drop, sort أترك لك فرصة التعرف عليها بنفسك. **5.4 - توزيع البيانات (Data Distribution)** يدعم الــ mongo نوعين من التوزيع : 1. Replica set : حيث يتم استخدام أحد السيرفرات كــ master و الآخرين كــ slaves. في مثل هذا التوزيع, يُنصح دائما بالبدء مع 3 سيرفرات, يتم اختيار أحدهم كسيرفر رئيسي (master) و الاثنان الباقيان كسيرفرات ثانوية (slaves). تُنسخ البيانات الـمُخزنة في الـــ master إلى الـــ slaves بشكل تلقائي و في حالة تعطل السيرفر الرئيسي يتم انتخاب السيرفر الثانوي الذي يحتوي على البيانات الأحدث. السيرفر الرئيسي فقط هو من يمكنه القيام بعمليات القراءة و الكتابة أما الاثنان الباقيان فيُسمح لهم بالقراءة فقط حيث ينحصر دورهم في تخزين البيانات الموجودة في الـــ master خوفاً من ضياعها. 2. Sharding : يتم تقسيم قاعدة البيانات لضمان الـــ availability في كل وقت. مفهوم الــ shards يعني مجموعات من الــ replicaset, كل مجموعة تحتوي على جزء من البيانات و الهدف من هذا هو توزيع أو تقسيم المهام بين مختلف الــ replicasets. **6\. الخاتمة** في نهاية المطاف, نُذكر مجدداً أن NoSQL لا يُمثل حلا سحرياً لجميع مشاكل قواعد البيانات إذ يظل استخدامه مقتصراً على ميادين معينة و بالتالي يجب على المبرمج إحسان الاختيار مع الأخذ بعين الاعتبار طبيعة الـــ software architecture و تعقيد البرمجة و التطوير في حالات معينة. في الحقيقة, ما زال الزعيم NoSQL يحتاج إلى اعتراف الــ standards به بشكل أكبر, تماما كما هو الحال مع JDBC و SQL في الــ RDBMS لكن أعتقد أن الوقت ما زال مبكرا فالزعيم لا يزال في ربيع شبابه و أعتقد أن أمامه مستقبل واعد إن حافظ على سرعة الانتشار التي يتقدم بها حالياً. ### انطلاقه مشروع اعداد كتاب جافا العربي URL: https://informatic-ar.com/arabic-java-book-project/ Last updated: 2026-10-03T22:31:48.000Z **هل تود المشاركة في اعداد كتاب البرمجة بلغه الجافا العربي ؟** يوماً بعد يوم نرى الكثير من المبتدئين في البرمجة يواجهوا الكثير من المصاعب في تعلم الأساسيات والانتقال للمراحل المتقدمة التي تعطيهم الثقة بأنهم في الطريق الصحيح، وعندما ينتقل ذلك المبتدئ الى مرحلة جيدة بعد عناء الشهور في التعلم وبجهد شاق سوف يبدأ بالعمل والانشغال وقد يحاول بنفسه القيام ببعض الدورس للمتبدئين لكن لن يستطيع انهاء كل شيء بنفسه وقد يتوقف بعد فترة من العمل,, في هذا المشروع سوف نجعل عملية الكتابه متاحة للجميع من مصدر واحد وباليات محكمة حتى تنتج شيء مفيداً للقارئ العربي به كل ما يحتاجه من أساسيات وأمور متقدمة في لغه البرمجة جافا ، وهذه مجرد بدايه ان نجحت فسوف تكون بادرة حسنه لمشاريع من هذا النوع.. سوف يتم تخصيص فريق عمل في اعداد كتاب الجافا العربي ، يحيث يتم تغطية المواضيع التي يحتاجها المبرمج في جافا (حتى المتقدمة للغايه) حتى نؤهل جيل جديد من المبرمجين ويجدوا كل المعلومات باللغه العربية.. هناك الكثير من المواضيع والتي نادراً ما تجد شرحاً عربياً لها مثلاً ال Generic و ال Annotation وال JNI والتشفير Cryptography في جافا وغيرها من المواضيع التي لطالما يحتاجها أي مبرمج يريد عمل شيء حقيقي، هذا الكتاب سوف يضم اغلب هذه المواضيع بالتفصيل وبتطبيقات عملية ، بحيث اي مبرمج يقرئه يمكن أن يكون مستواه متوسط وما فوق في الجافا كلغه وك API ، وتم أختيار كتاب Core Java بسبب أنه يعتبر من أشمل وأفضل كتب الجافا المتوفرة حتى الان حتى يتم الاعتماد عليه في المادة العلمية. لن نقوم بالترجمة وانما بكتابه محتوى عربي مفهوم واحترافي من ذلك والكتاب وسوف نستخدمه كمرجع رئيسي في تقسيم المواضيع وطريقة العرض والأمثلة، وبعد ذلك بعد الانتهاء يمكن أن نستبدل أمثلة بأمثله أفضل وأكثر توجهاً للمبرمج العربي. اذا كنت قد سجلت مسبقاً أو كنت مهتم بالعمل والمشاركة في المشروع فأكمل القرائه في هذا الموضوع **حيث باب التسجيل والمشاركة مفتوح ونحتاج المزيد من المتطوعين ،** بعد قرائتك لبقية الموضوع يمكنك الاشتراك**[ عبر القروب المخصص للكتاب في الفيس بوك](https://www.facebook.com/groups/java.book/)** ### مقدمة شكراً لانضمامك أو اهتمامك في فريق عمل اعداد كتاب "**الجافا العربي**" الذي يهدف لاخراج كتاب عربي يشمل أساسيات الجافا ومواضيعها المتقدمة والذي سيفيد شريحه كبيرة من المبرمجين العرب المبتدئين وحتى المتقدمين وبالتالي يرفع من مستوى المبرمجين ويساهم في تقدم البرامج العربية وكفائتها ### الية العمل في المشروع - سيتم تقسيم المتطوعين الى مجموعات صغيرة Groups كل مجموعه سوف تعمل على موضوع واحد تقوم بالعمل فيه وسوف يرأس تلك المجموعات مبرمج له خبرة Leader يساعد مجموعته ويشرف على العمل لكي يخرج بصورة جيدة. - كل مجموعه سوف تكون بها كمية من الأعضاء تتراوح من 1 الى 10 على حسب المتطوعين المتوفرين وكلما زاد عدد المتطوعين كلما قل الجهد المطلوب حتى تنتهي المجموعه من العمل ككل - الانضمام للمجموعات أولياً سوف يكون عشوائياً ومع الوقت يمكن أن يكون الانضمام على حسب ما يفضله المتطوع ولكن بشرط أن تكون بقية المجموعات ممتلئة ولو بعضو واحد حتى تتحرك المجموعات وتنتج جميعاً. - يمكن للعضو العادي أن يصبح رئيساً للمجموعه في حال وجد أنه عمله جيد ومنضبط مع الشروط فسيتم ترقيته ليصبح مديراً للمجموعه وهكذا سوف يكتسب خبرة أكثر من التعامل مع الأعضاء وأدارتهم وسيتم ذلك من خلال تصويت الأعضاء وملاحظة انتاجيتهم بصورة مستمرة. - اذا تنازل مشرف عن دوره في العمل سيتم تعيين احد الأعضاء من مجموعته بصورة مباشرة حتى تستمر المجموعه في العمل، الفكرة ان العمل لن يعتمد على شخص واحد وانما المساهمه للجميع - سواء كنت عضواً أو مشرفاً فلا تخف من كونك مبتدئاً وأن هناك أمور لا تعرفها وجديدة عليك، فهذه فرصه جيدة لكي تتعلمها ويمكنك طلب المساعده من أحد الأعضاء في القروب المخصص له حيث يوجد بعض الأعضاء لهم خبرة أكثر من 5 سنوات في البرمجة بجافا فلا تقلق على الاطلاق. - عدم كونك مبرمجاً أو لا تعرف الجافا فهذا لا يعني أنه لا يوجد مجال لك، فيمكن المشاركة في التصميم أو المراجعه أو التدقيق اللغوي أو حتى في الاشراف والمتابعه والمجال مفتوح ### ### المجموعات في المشروع: 1. مجموعه ال Files & Streams المشرف عليه **فؤاد المالكي** 2. مجموعه ال Collections المشرف عليه **أحمد الشنقيطي** 3. مجموعه ال Multithreading المشرف عليه **وجدي عصام** 4. مجموعه ال XML المشرف عليه **خالد محمد علي** 5. مجموعه ال Networking المشرف عليه **ولاء عبدالرحيم** 6. مجموعه ال Database المشرف عليه **دعاء خليفه** 7. مجموعه ال Swing المشرف عليه **سعد رضا العنزي** 8. مجموعه ال Internationalization المشرف عليه **محمد عودة** 9. مجموعه ال Java Security 10. مجموعه ال Scripting & Annotations المشرف عليه **اسامة رضوان** 11. مجموعه ال Generic المشرف عليه **محمد بن عثمان** 12. مجموعه ال Graphic Programming المشرف عليه **أحمد عباس** 13. مجموعه ال Distributive Objects 14. مجموعه ال Native Methods المشرف عليه **علاء الصالحي** 15. مجموعه ال Exception & Logging المشرف عليه **محمد درة** 16. مجموعه ال Event Handling المشرف عليه **محمد الجبري** 17. مجموعه ال Advanced Swing المشرف عليه **حازم حمدقه** 18. مجموعه ال Advanced AWT 19. مجموعه ال Java Beans هناك مجموعات ليست لها مشرفين ، بالاضافه الى أننا نحتاج للمطوعين للعمل تحت المجموعات لأن أغلب المجموعات ليس لها الا مشرف ومتطوع واحد، فسارع الان بالانضمام في المجموعه وتابع مع المشرف لكي تعرف من أين تبدأ.. **الأعضاء المتطوعين في المجموعات**: 1. فهد اللبدي – مجموعه Files & Streams 2. حسن الحلاق - مجموعه Files & Streams 3. مصطفى حسيك - مجموعه Files & Streams 4. سيد سيد – مجموعه Collections 5. احمد عباس – مجموعه Collections 6. مصطفى - مجموعه Collections 7. محمد العلي - مجموعه Collections 8. عبدو بو زندا- مجموعه XML 9. جوان – مجموعه XML 10. مصطفى الخولي - مجموعه XML 11. فؤاد بن زينة – مجموعه Swing 12. عاصم العمراني – مجموعه Swing 13. عصام خليوة - مجموعه Swing 14. حسام مخيرز - مجموعه Swing 15. سامر الشرفا – مجموعه Multithreading 16. عبدالرحمن – مجموعه Multithreading 17. سبيل أخرس - مجموعه Multithreading 18. جمعه محمد - مجموعه Internationalization 19. أحمد عصام – مجموعه Internationalization 20. محمد اسماعيل – مجموعه Java Security 21. سفيان – مجموعه Java Security 22. غادة الطياري - مجموعه Database 23. أمل الخليل – مجموعه Database 24. نورهان محمد - مجموعه Database 25. محمد رمضان – مجموعه Exception & Logging 26. محمد فضل المولى – مجموعه Scripting & Annotations 27. بسن سنان – مجموعه Scripting & Annotations 28. محمد خلف – مجموعه Generic 29. أنيس أبو حميد – مجموعه Generic 30. ورد محمود - مجموعه Generic 31. اسلام – مجموعه Graphic Programming 32. عماد الصلاحي - مجموعه Graphic Programming 33. محمد عمر - مجموعه Graphic Programming 34. مصطفى قمحان - مجموعه Event Handling 35. أوسان عرمان - مجموعه Event Handling 36. الصاوي محمد أحمد - مجموعه Native Methods 37. على مبارك سعيد - مجموعه Native Methods 38. مصعب محمد ذكريا - مجموعه Native Methods 39. الزبير حسن - مجموعه Networking ### شروط العمل في الكتابة الشروط لم توضع الا لتنظيم العمل واخراجه بشكل افضل، فعليك ان تحاول الالتزام بها قدر الامكان : 1. يجب أن تكون الكتابه باللغه العربية الفصحى مع استخدام الفواصل للفصل بين الجمل والنقاط بأخر كل فقرة. 2. عدم ترجمة المصطلحات البرمجية وانما تكتب بالانجليزي (على سبيل المثال: عندما تكتب class يستخدم مفهوم ال Generic أو يقوم بعمل implements ل interface اسمه rules، الفرق بين ال Vector وال ArrayList هو في ال Synchronization ) وهكذا. 3. الكتابة سوف تتم على ملف MS-Word بصيغه DOCX بدون أي تنسيق في النص (بدون تلوين) فقط مع وضع Bold على العنواين الرئيسية 4. العمل سيتم من كتاب Core Java فقط بدون أي خروج عن نص الكتاب سواء في الأمثلة أو العنواين أو الصور، عندما ينتهي العمل من الفصل سوف تتم اضافة التحسينات والتغييرات بما يلزم الأمر، لكن أساسياً سوف تعمل من الكتاب وتقوم بقرائه الفقرة المسنده لك ومن ثم فهمها وكتابتها بالعربية مع وضع الأمثلة والصور الفقرة اذا وجدت 5. الامثلة البرمجية عليك بكتابتها وتشغيلها ومن ثم ارفاق ملفات الجافا .JAVA التي بها الأمثلة مع ملف الWord 6. اذا كانت هناك صور في فقرتك التي تعمل عليها فقم بحفظها لديك وضعها داخل ملف الWord في المكان التي وردت به الصورة 7. يفضل من أعضاء المجموعه بعد تحميل الكتاب أن يطلعوا على الفصل الخاص بطبيعة عمل مجموعتهم ولو بشكل سريع حتى يأخذوا الفكرة العامة منه # ### واجبات مشرف المجموعه الاشراف هو تكليف وليس تشريف فالمشرف عليه القيام ببعض الأمور حتى يضمن سير العمل على مجموعته وهي: - تقسيم العمل على اعضاء مجموعته بشكل مناسب معهم وبالتساوي ، أيضاً عليه أن يعتبر نفسه عضواً رئيسياً في العمل فقد يمكن أن تكون المجموعه تحتوى فقط على المشرف وسيتم ادخال المطوعين معه فيما بعد - أخذ المشاركات من أعضاء مجموعته ومراجعتها ويمكنه الطلب العضو من تصحيح أو تعديل بعض الفقرات حتى يلائم العمل الكلي - دمج مشاركات الأعضاء في ملف واحد وسيتم استلام العمل منه بشكل دوري - على المشرف أن يقوم بتقسيم الأعمال بشكل جيد، فاذا كانت المجموعه من 2 عضوين والفقرة المطلوبة من 40 صفحة فيستحسن أن يطلب من العضو الأول العمل على الفقرة الجزئية الأولى في الموضوع والأخر في الفقرة الجزئية التي تليها حتى لا تكون هناك مسافات كثيرة ناقصه بين الأعمال وانما تكتمل بشكل تدريجي ولو كانت بشكل بطئ - تواصل المشرف مع المجموعه أو تواصل العضو مع المشرف أو غيره يفضل أن يكون داخل [المجموعه الخاصه بالمشروع في الفيس بوك](https://www.facebook.com/groups/java.book) - مثال على طريقة التقسيم بالعنواين الفرعيه : في كل مقدمة فصل سوف تجد عناوين فرعيه داخله يمكن الاسناد على اساس عنوان لكل شخص، وفور ما ينتهي يمكنك ان تسند له عنوان أخر بعد مراجعه كتابته والتأكد منها # [![مثال على التقسيم بطريقة عنوان فرعي لكل متطوع](https://informatic-ar.com/wp-content/uploads/2013/10/example-300x193.png "مثال على التقسيم بطريقة عنوان فرعي لكل متطوع")](https://informatic-ar.com/wp-content/uploads/2013/10/example.png) ### ملاحظات عامة: - قليل دائم خير من كثير متقطع فأن تعمل يومياً ولو بفقرة واحدة أفضل من العمل كل اسبوع بعشرة فقرات لأن العمل الكثير يرهق وربما تتوقف بعد اسبوع أو اثنان - استخدم Drop box أو Git حتى تحفظ نسخ ملفاتك وتضمن أنها لن تضيع باذن الله - اذا كانت لديك ملاحظات أو أمور ترى أنها مفيدة أو حتى أسئلة فعليك بوضعها داخل المجموعه في الفيس بوك - سيتم التواصل الكلي فيما بعد عن طريق القروب في الفيس بوك <https://www.facebook.com/groups/java.book> وسيتم تحديث الصفحة متى لزم الأمر - فترة المشروع قد تكون ثلاثه أشهر ونتوقع أن نتتهي بعد ذلك منه وتبدأ مراحل أخرى ان شاء الله بخصوص التنسيق والتعديل والتصميم والخ - سوف يصدر الكتاب بصيغه حرة المصدر برخصه Creative Commons أو أحد الرخص الأخرى المناسبة ### خوارزميات غيرت العالم ( خوازرمية الرانك Page Rank وكيف تعمل) URL: https://informatic-ar.com/page_rank_algorithm/ Last updated: 2026-10-03T22:31:48.000Z ## خوازرمية الرانك Page Rank وكيف تعمل أحد أهم العوامل التي ساهمت لصعود محرك البحث Google على حساب المحركات الآخرى القديمة مثل AltaVista و Lycos هو في استخدام خوارزمية ال Page Rank (على الرغم من أن Google ظهرت بعد أربع سنوات فقط من ظهور تلك المحركات في 1998، ولكنها استطاعت في زمن وجيز اجتيازهم حتى أن مجلة PC Magazine في 1998 صنفت ذلك المحرك بأنه من أفضل 100 موقع على الانترنت). وكما أن اسم الخوارزمية يبين عملها Ranking الا أنه ايضاً كان جزء من اسم أحد المؤسيين (Larry Page) حيث نشر وهو زميله ورقه علمية بعنوان "[The Anatomy of a Large-scale Hypertextual Web Search Engine](http://infolab.stanford.edu/~backrub/google.html?ref=informatic-ar.com)" وتحدثوا عن تلك الخوارزمية وعن نظام Google في ذلك الوقت 1998 وكانت هذه أول ظهور للخوارزمية Page Rank والتي تقوم باظهار النتائج القريبة من استعلامك من بين كل النتائج التي احتوت على نتائج مقاربة Matching للاستعلام ([تحدثنا عن خوارزميات ال Matching هنا](https://informatic-ar.com/search%5Fengine%5Fbasics/)) [![Larry Page and Sergey Brin](https://informatic-ar.com/wp-content/uploads/2013/10/Larry-Page-and-Sergey-Brin.jpg "Larry Page and Sergey Brin")](https://informatic-ar.com/wp-content/uploads/2013/10/Larry-Page-and-Sergey-Brin.jpg) Larry Page and Sergey Brin الفكرة بدئت من ال Hyperlink أو الرابط وهي كما تعلم الروابط التي تنقلك من صفحة لأخرى عندما تقوم بالضغط عليها، والHyperlink بالمناسبة أحد الأفكار القديمة 1945 حيث نشر أحد المهندسين [Vannevar Bush](http://en.wikipedia.org/wiki/Vannevar%5FBush?ref=informatic-ar.com) ورقه علمية [“As We May Think"](http://www.theatlantic.com/magazine/archive/1945/07/as-we-may-think/303881/?ref=informatic-ar.com) وتحدث عن عدة تقنيات ومنها جهاز أسماه [memex ](http://en.wikipedia.org/wiki/Memex?ref=informatic-ar.com)والذي يستطيع حفظ الملفات وفهرستها وأحتوى على فكرة الفهرسه التلقائيه حتى يستطيع الفهرس الانتقال من ملف لأخر ويقوم بفهرسته كما في فكرة الروابط Hyperlink. **فكرة ال** **Hyperlink** بالرغم من بساطة فكرة ال Hyperlink والتي نراه كل يوم على المواقع، الا أنها من الأمور المهمه في محركات البحث وبالتحديد في خوارزمية ال Page Rank، لنأخذ مثالاً يوضح الفكرة: لنفرض انك تبحث عن طريقة لتحضير البيض المخفوق وقمت بالبحث عنها في محرك البحث وأن النتيجة التي ظهرت هي صفحتين واحده تحتوي على وصفحة كتبها Bert والأخرى وصفة ل Ernie ، كما تشاهده في الصورة التالية ، كل من هذه الصفحتين لها روابط قادمة اليها من صفحات أخرى، الآن السؤال ما هي الصفحة التي لها Rank أعلى؟ صفحة Bert أم صفحة Ernie ؟ [![الشكل يبين فكرة ال Hyperlink حيث هناك 6 صفحات أول صفحتين احتوت على وصفات (ل Bert و Ernie) والأربع الأخرى احتوت على روابط لتلك الوصفات، وكما تلاحظ أن وصفة Bert لها ثلاث روابط اليها وهكذا سوف نقول أن لها Rank أعلى من الأخرى.](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank1-300x185.jpg "الشكل يبين فكرة ال Hyperlink حيث هناك 6 صفحات أول صفحتين احتوت على وصفات (ل Bert و Ernie) والأربع الأخرى احتوت على روابط لتلك الوصفات، وكما تلاحظ أن وصفة Bert لها ثلاث روابط اليها وهكذا سوف نقول أن لها Rank أعلى من الأخرى.")](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank1.jpg) الشكل يبين فكرة ال Hyperlink حيث هناك 6 صفحات أول صفحتين احتوت على وصفات (ل Bert و Ernie) والأربع الأخرى احتوت على روابط لتلك الوصفات، وكما تلاحظ أن وصفة Bert لها ثلاث روابط اليها وهكذا سوف نقول أن لها Rank أعلى من الأخرى. بالنسبة لنا كمستخدمين نستطيع تقييم تلك الصفحات عن طريق قرائتهم وتقييم الطريقة الأفضل، أو حتى قرائه الروابط ومعرفة ماذا يقوله الناس عن تلك الوصفات وهذا يستطيع القيام به المستخدم، لكن الأمر يختلف بالنسبة للحواسيب فهي ليست جيدة في فهم النصوص فهي لن تستطيع تقييم تلك الوصفات ولا حتى فهم ماذا يقوله الناس عنها في تلك الروابط، لكن الشيء الذي تستطيع القيام به جيدأً هو الحساب Counting ومعرفة كم صفحة تؤدي للوصفات وفي هذه الحالة صفحة واحدة تقود لصفحة Ernie و3 صفحات تقود لصفحة Bert وهكذا يكون ال Rank على حسب الروابط القادمة للصفحات وهكذا يمكن القول أن صفحة Bert ووصفته أفضل من الأخرى. قد تكون هناك مشاكل باتباع هذه الأليه: تخيل صفحة جديدة على الويب تقول "وصفة Bert سيئة للغايه" و في تلك الحالة المفترض ان يعطي تلك الصفحة تقديراً سالباُ ولكن محرك البحث سوف ينظر لها على انها رابط وبالتالي سوف يزيد ال Rank بدلاً من أن ينقص، هذه مشكلة في صلب فكرة استخدام ال Hyperlink في عملية التقدير ولكن مع ذلك ما زالت فكرة الHyperlink مستخدمة في عملية الRanking. **فكرة ال** **Authority** قد تتسائل الآن لماذا كل الصفحات الموجهه للصفحة تعامل بالتساوي؟ فاحياناً التوصية من خبير يجب أن تكون لها وزن أكثر من التقييم بواسطة مستخدم عادي، لنكمل في المثال السابق ولكن عن طريق روابط مختلفة موجهه لصفحة الوصفات، الصورة التالية تبين الشكل الجديد فصفحة Bert و Ernie كل منهم لها رابط واحد قادم اليها وصفحة Ernie مؤشر لها من صفحة John MacCormick وهو أحد المستخدمين العاديين بينما صفحة Bert لها رابط قادم من صفحة Alice Water وهو شيف مشهور بالمجال. [![pagerank2](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank2-300x185.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank2.jpg) الآن كيف يمكن ان تفضل وصفه على أخرى؟ بكل تأكيد سوف تختار التي يشير لها الشيف الخبير بدلاً من تلك الصفحة الخاصه بالمستخدم العادي، وهذا هو المفهوم نسميه Authority Trick أي الروابط القادمة من صفحات لها شعبية عالية authority ينتج أن لها Rank أعلى من الصفحات المشارة من صفحات لها شعبية اقل. الأن السؤال الأهم كيف يعرف المحرك أن هذه الصفحة لها شعبية أعلى من هذه أو كيف يعرف أن هذا الشخص خبير بينما تلك الصفحة عائده لشخص غير خبير؟ الجواب عن طريق دمج بين فكرة ال Hyperlink وفكرة ال Authority، فكل الصفحات تبدأ ب Authority بالقيمة 1 وفي حال كان لها روابط من صفحات أخرى قادمة اليها فسوف يتم جمع الAuthority لكل تلك الصفحات وتكون هي ال Authority للصفحة، على سبيل المثال لدينا الصفحات X و Y تشيران الى الصفحة Z فإن الAuthority للصفحة Z هو حاصل جمع ال Authority للصفحات X و Y. الصورة التالية تعطي الفكرة بشكل أوضح في نفس مثال الوصفات السابق، وتكون الAuthority لأي صفحة في دائرة صغيرة بأسفل الصفحة، الأن صفحة John مشارة من قبل صفحتين ليس لهم روابط (لذلك لهم قيمة 1) وبالتالي ال Authority لصفحة John هي 2، وبنفس الأمر صفحة Alice لها القيمة 100، وبما أن صفحة Ernie لها رابط من صفحة John فهو سوف يحصل علي 2 وايضاً صفحة Bert سوف تكون القيمة 100 لأن الرابط اليها من صفحة لها القيمة 100. [![pagerank3](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank3-300x219.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank3.jpg) **فكرة ال** **Random Surfer** يبدوا أننا توصلنا لحل جيد لحساب ال Authority بشكل تلقائي بدون الحاجة لفهم النصوص في الصفحات، ولكن للأسف هناك مشكلة جلية في هذا الحل وهو أن الروابط في الصفحات يمكن أن تشكل دائرة Cycle وبالتالي يمكنك العودة لنقطة البدايه في حال تتبعت الروابط. الصورة التالية تبين الأمر، هناك 5 صفحات ويب كل منها له اسم، اذا بدئنا بالصفحة A وقمت بالضغط على رابط B ثم ذهبت الى E ثم من هذه الصفحة قد تعود للصفحة A وهذا يعني أن الصفحات A و B و E تشكل دائرة Cycle. [![pagerank4](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank4.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank4.jpg) ما المشكلة في تكوين دائرة؟ لنقوم بعملية حساب ال Authority للصفحات حتى تتبين المشكلة، ولنبدأ من الصفحات C و D حيث لا توجد لهم روابط قادمة لذلك لهم قيمة 1 لكل منهم، والصفحة A لها القيمة 2 لأنه قادم اليها رابطين كل منهم له قيمة 1، والصفحة B سوف تكون لها القيمة 2 لأنه مؤشر لها من صفحة لها القيمة 2، والصفحة E تأخذ 2 من صفحة B، الأن تبدأ المشكلة حيث A أخذ 2 من الصفحات C و D لكنه مؤشر الان بواسطة صفحة E لذلك سوف يتم جمع ذلك ايضاً ويكون الناتج ل A هو 4، وبما أن A له قيمة جديدة فB يحتاج الى ان يتحدث ايضاً لأنه في السابق A كانت 2 والان اصبحت 4 لذلك B سوف تكون 4، وE ايضاً سوف تكون 4، وهكذا A ستكون 6 بعد التحديث وB و E أيضاً وبعد تحديث B تصبح A بالقيمة 8 وتستمر العملية للأبد. [![الشكل يوضح المشكلة التي تسبب بها ال Cycle وهي أنه A و B و E دائماً يكونوا بحاجه لتحديث out of date وسوف يتم ذلك للأبد](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank5-300x90.jpg "الشكل يوضح المشكلة التي تسبب بها ال Cycle وهي أنه A و B و E دائماً يكونوا بحاجه لتحديث out of date وسوف يتم ذلك للأبد")](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank5.jpg) الشكل يوضح المشكلة التي تسبب بها ال Cycle وهي أنه A و B و E دائماً يكونوا بحاجه لتحديث out of date وسوف يتم ذلك للأبد لحسن الحظ نستطيع حل هذه المشكلة باستخدام طريقة أخرى سوف **نسميها Random Surfer** وهي تأخذ من فكرة الHyperlink والAuthority وتحل أيضاَ مشكلة الCycle في الصفحات فكرة هذه الخوارزمية هي في التنقل العشوائي في الصفحات مع البدء بصفحة جديدة اخرى يتم اختيارها عشوائياً حتى لا تقع في الدائرة ، لنأخذ مثال ولنفرض أنك تتجول في الانترنت وبدئت بصفحة قمت باختيارها عشوائياً وبعد ذلك تقوم بأخذ كل الروابط في الصفحة وأختر واحداً منهم بشكل عشوائي وادخل عليه وفي الصفحة الجديدة تقوم باختيار احد الروابط عشوائياً ومن ثم تدخل عليه وهكذا تستمر العملية وهذه فكرة الخوارزمية في التنقل العشوائي. الصورة التالية توضح المثال حيث الويب مكون من 16 صفحة وكل صفحة تتصل بالروابط مع الصفحات الأخرى، الصفحات التي تمت زيارتها له لون غامق بينما التي لم يتم زيارتها لها لون فاتح، أما الروابط المتقطعه Dashed تمثل البدايات العشوائية. [![pagerank6](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank6-300x195.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank6.jpg) الفكرة الأخرى التي تقوم بها الخوارزمية هي في البدء من جديد بصفحة اخرى وعدم اخذ الروابط من الصفحة، ففي كل مرة تقوم بزيارة صفحة فهناك احتمال أنك تزور غيرها ولا تتبع الروابط بها نسمي هذا الأمر باحتمال الاعادة Restart Probability وليكن هذا الاحتمال 15%، نأخذ مثال في الصورة التي بالأعلى: قمت بالبدء بالصفحة A وهناك ثلاث روابط بها وقمت بأخذ الرابط الذي بالمنتصف بشكل عشوائي وهكذا حتى وصلت ل B بعد ذلك قمت بتغيير رأيك وبدئت من جديد Restart بصفحة C وأنتقلت ل D ثم للصفحة التي تليها بعد ذلك بدئت بصفحة جديدة Next Restart (هذه القيمة 15% هي التي تم تحديدها في الورقة التي تم طرحها عند عمل النموذج الأولى لمحرك بحث قوقل). من السهل محاكاة هذه العملية بالحاسب، حيث تم كتابه برنامج يزور تلك الصفحات بشكل عشوائي حتى يصل عداد الزيارة ل 1000 صفحة (هذا العدد لا يعني 1000 صفحة مختلفة، ولكن الزيارات المتعدده للصفحة الواحدة تحسب ايضاً، وكما هو واضح في المثال اعلاه فسوف يتم زياره الصفحات أكثر من مرة). والنتيجة بعد عملية المحاكاة 1000 مرة هي الصورة التالية وكما تلاحظ أن الصفحة D هي أعلى صفحة تمت زيارتها بمعدل 144 زيارة. [![الشكل يبين محاكاة زيارة الصفحات 1000 مرة عشوائياً](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank7-300x198.jpg "الشكل يبين محاكاة زيارة الصفحات 1000 مرة عشوائياً")](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank7.jpg) الشكل يبين محاكاة زيارة الصفحات 1000 مرة عشوائياً وكما هو الأمر مع اي عملية محاكاة يمكن زيادة الدقة Accuracy عن طريق زيادة عدد الحالات، وهنا تم عمل العداد على أساس مليون صفحة (وبالطبع العملية لم تستغرق الا نصف ثانية طبعاً) وبهذا الرقم طبعاً من الأفضل أن يتم توضيح عدد الزيارة بالنسبة المئوية لتكون أسهل في الفهم، الصورة التالية توضيح الزيارات للصفحات وكما تلاحظ مجدداً الصفحة D هي الأكثر زيارة حيث تم زيارتها 15% من مجموع الزيارات. [![الشكل يبين نسبة زيارة الصفحاة مليون مرة عشوائياً](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank8-300x199.jpg "الشكل يبين نسبة زيارة الصفحاة مليون مرة عشوائياً")](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank8.jpg) الشكل يبين نسبة زيارة الصفحاة مليون مرة عشوائياً **السؤال الذي يجب طرحه الأن**: ما هي العلاقه بين الزيارات العشوائية هذه Random Surfer Model مع طريقة حساب ال Authority التي نقوم من خلالها بحساب الRank للصفحات؟ الجواب: لأن النسبة المحسوبة لتلك الصفحات من الزيارات العشوائية هي التي سوف نستخدمها كمقياس للAuthority في الصفحة. لنعرف اذاً ال Surfer Authority Score وهي النسبة المؤية لعدد الزيارات العشوائة للصفحة، وكما تلاحظ فقد استفدنا من فكرة ال Hyperlink وفكرة ال Authority في هذا الأمر: **أولاً استفدنا من فكرة ال Hyperlink** حيث الفكرة الأساسية هنا أن الصفحة التي لها روابط قادمة اليها تعتبر لها Rank أعلى، وهذا ايضاً في طريقة الRandom Surfer لأن الصفحة التي لها أكثر روابط قادمة اليها تعتبر لها فرصه أكبر في الزيارات، على سبيل المثال الصفحة D في الصورة التالية له 5 روابط قادمة اليه وبالتالي حصل على أعلى نسبة زيارات Surfer بنسبة 15% [![pagerank9](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank9-300x199.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank9.jpg) **ثانياً بالنسبة الى فكرة الـ Authority** حيث الفكرة بها أن الروابط القادمة من الصفحات التي لها شعبية Authoritative أعلى سوف يحسن الRank لها بخلاف الصفحات المشارة بواسطة صفحات لها شعبية أقل، كيف استفدنا من هذه الفكرة في ال Random Surfer Model ؟ السبب لأن الروابط القادمة من صفحات لها شعبية عالية لها فرصه اعلى بأن تتم زيارتها بالمقارنه مع الصفحات المشارة بوساطة صفحات أقل شعبية، الصورة اعلاه تبين الصفحتين C و A لها نفس عدد الروابط القادمة اليها (رابط واحد فقط) لكن A نسبة زيارتها أعلى 13% بالمقارنه مع 2% والسبب هو شعبية الصفحة التي قادم من ذلك الرابط. هكذا يستفيد ال Random Surfer Model من الطريقتين (Hyperlink و Authority) أي من نوعية الرابط وعددها ، لو نظرت في الصورة أعلاه ستجد B لها 10% لأن هناك رابطين قادمين اليها بنسبة 4% و 7%. **هل حل هذا النموذج مشكلة ال Cycles ؟** لنأخذ المثال الذي لم تستطيع فكرة ال Authority حله في الصورة التالية ، وبعد اجراء التجربة بطريقة ال Random Surfer حصلت A على أكبر رانك وتلتها B ثم E ثم C و D وهذا يدل على أن النموذج يعمل حتى لو كانت هناك Cycle في الصفحات بلا اي مشاكل. [![pagerank11](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank11-300x193.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/pagerank11.jpg) ## **خوارزمية ال** **Page Ranking** **في العالم الحقيقي** فكرة التجول العشوائي Random Surfer تم شرحها بواسطة مؤسسين Google في ورقتهم العملية في 1988 وهي ما زالت تستخدم في محرك البحث وغيره من المحركات لكن هناك عوامل أخرى أدت لتغيير طريقة العمل بشكل مختلف، من هذه العوامل يقع في صلب فكرة ال PageRank حيث يتم افتراض أن الروابط القادمة الى صفحتك هي صحفات للاشاره والتوصية اليك وليس العكس ، مشكلة أخرى هي أنه قد يتم استخدام الروابط بشكل سيئ حتى يحصل على رانك أعلى ، على سبيل المثال لديك الموقع booksbooksbooks.com لبيع الكتب فقد تقوم بعمل مثلاً 10000 صفحة وتجعلهم يؤشروا لموقعك وهكذا اذا قام محرك البحث بحساب ال PageRank فسوف يحصل موقعك على رانك أعلى من مواقع بيع الكتب الأخرى الموجودة، وهذا تسميه محركات البحث بال Web Spam وعملية اكتشاف تلك المواقع تعتبر جزء مهم من محركات البحث (في 2004 قام بعض الباحثين في ميكروسوفت ببحث وتوصلوا الى ان أكثر من 300 الف موقع تحتوى على روابط الى 1000 موقع تقريبا غالبيتها هي صفحات Spam حتى تحصل تلك المواقع على رانك أعلى)، لذلك فإن تطوير محركات البحث الان في صراع مع ال Spammer وفي نفس الوقت تبحث في طرق تحسين الخوارزميات بها وهذا يتطلب البحث الأكاديمي والعملي في الخوارزميات التي تعتمد على هيكلية ال Hyperlink وتسمى هذه الخوارزميات link-based ranking algorithms. أحد العوامل الأخرى هي المتعلقة بكفائة عملية حساب ال PageRank في شرحنا السابق تناولنا طريقة البحث مع محاكاه بسيطة للعملية لكن اجراء هذه العملية على الويب يكون أطول بكثير من المثال لذلك لا تقوم محركات البحث بحساب ال PageRank عن طريق محاكاة طريقة التجول العشوائي Random Surfer لكنها تستخدم طرق رياضية تعطي نتائج مشابه كما هي عملية المحاكاة لكن بجهد ووقت أقل بكثير (قمنا بشرح عملية المحاكاة حتى نعطي فكرة عن الشيء الذي يضعه محرك البحث في الحسبان). هكذا فأن محركات البحث تستخدم خوارزميات اخرى بخلاف الLink-based ranking algorithm (مثل ال PageRank) وحتى في ورقة Google المنشورة تم الاشارة الى بعض الطرق الأخرى المستخدمه في حساب الRanking للنتائج، ومع الوقت تقوم الشركات بالتطوير والبحث في تحسينها وقبل وقت قريب قامت قوقل بالاشارة الى أن المحرك يستخدم أكثر من 200 فكرة لكي يقوم بعملية حساب ال Rank للصفحات. وبغض النظر عن التعقيدات في محركات البحث، فان الخوارزمية PageRank ما زالت مستخدمه (حيث الشعبية authoritative تنقل من صفحة لأخرى عن طريق الروابط بينهم) وهي الفكرة التي جعلت Google تتفوق على AltaVista وحولتها من شركة صغيرة ناشئة الى اكبر عملاق في الانترنت في عده سنوات وبدون هذه الخوارزمية فإن محركت سوف ترجع لك الالاف الصفحات ولكن بنتيجة ليست كما تريدها انت وستظل تبحث عن الابرة في كومة القش. **المصادر**: [Nine Algorithms That Changed the Future](http://www.amazon.com/Nine-Algorithms-That-Changed-Future/dp/0691158193?ref=informatic-ar.com) ### الــ Collections في الجافا URL: https://informatic-ar.com/collections_in_java/ Last updated: 2026-10-03T22:31:49.000Z السلام عليكم و رحمة الله و بركاته البداية ستكون مع تعريف بسيط و مختصر لهياكل البيانات بشكل عام ثم نأتي بعد ذلك إلى الـ Collections و أهميتها في البرمجة لننتقل إلى التغيرات الجديدة التي أضافها الـ Collections FrameWork من خلال استعراض الـ Hierarchy الجديد و شرح دور مختلف الـ interfaces الموجودة مثل Map, Set, List, SortedSet, SotedMap و الـ Iterators المرافقة لكل interface بالإضافة إلى شرح مفصل لمختلف الدوال الموجودة في كل واحدة مع أمثلة في نهاية كل فقرة. بعد ذلك ننتقل إلى الـ views و أنواعها الموجودة و كيفية استخدامها. الفقرة الموالية تتعلق بترتيب الـ Collections مع توضيح الفرق بين Comparable و Comparator و متى نستخدم كل واحدة منهما مع مثال توضيحي شامل. الختام سيكون مع الـ algorithmes التي تُمكن من التعامل مع الـ collections من خلال وصف دقيق لكل خورارزمية على حدة بالإضافة إلى كيفية الحصول على نسخة multi-thread أو unmodifiable لـ list معينة **تعريف مختصر لهياكل البيانات** عبارة عن طريقة خاصة لتخزين وتنظيم البيانات على شكل array, list, stack .. etc, هذه الخصوصية تكمن في كمية الذاكرة المستخدمة لتخزين البيانات بالإضافة إلى الوقت المستغرق لإجراء بعض العمليات على تلك البيانات. تختلف أنواع هياكل البيانات باختلاف التطبيقات البرمجية أو نوعية الاستخدام, كل حسب ما يناسبه. فمثلا نجد أن الــ B-Tree تناسب التطبيقات الخاصة بقواعد البيانات بينما نجد الــ Hash Table يُستخدم بكثرة في برمجة الــ compilers خصوصا عند البحث عن الــ identifiers. **ما هي الــ Collections ؟** تقوم الــ collections بإدارة و تسيير مجموعة من الكائنات ذات نوع معين أو يمكننا القول بأنها عبارة عن كائن يمكنه تخزين بيانات من نوع آخر. في أولى إصدارات الجافا, اقتصر تمثيل الــ collections في Vector, Stack, Hashtable, Bitset ثم ظهر بعد ذلك الــ Collections frameWork في Java 1.2 الذي حافظ على المبادئ الأساسية لهياكل البيانات و أضاف العديد من التعديلات و التحسينات بحيث تم تصميم تلك الهياكل على شكل شجري محكم التنسيق. يمكن للــ collections تخزين العناصر أيا كان نوعها بشرط أن تكون كائنات. يمكن مثلا أن تكون String, Integer, Float, Manager أو أي كائن آخر .. لكن مع ذلك من الخطر جدا استخدام collection تحتوي على أنواع مختلفة من الكائنات لأنك ستحتاج دوما إلى استخدام الــ casting أو instanceof من أجل تحديد صنف الكائن. بالتالي يُستحسن دائما تخزين كائنات من نفس النوع في الــ collection الواحدة. عند إدراج عنصر معين في الــ collection يتم الاكتفاء بتخزين الــ reference الخاص بالــ object و بالتالي يمكننا أحيانا إدراج الــ null reference داخل collection. **مالجديد في Collections FrameWork ؟** يحتوي الإصدار الجديد على العديد من الفئات و الواجهات كما يُقدم أيضا بعض الــ abstract classes التي تقوم بعمل implementation لبعض الــ interfaces. الواجهات التي يمكن استخدامها مع كائنات للتعامل مع الــ collections هي : - Collection : معظم الفئات التي تتعامل مع الــ collections قامت بعمل implement لهذه الواجهة. - Map : هذه الواجهة تحتوي على الدوال أو الــ methods الخاصة بالكائنات التي تتعامل مع الــ associative array على شكل key/value. - Set : خاصة بالكائنات التي لا تقبل التكرار داخل المجموعة. - List : الكائنات التي تقبل التكرار داخل المجموعة و تدعم الوصول إلى العناصر بشكل مباشر. - SortedSet : هذه الواجهة ترث الواجهة Set و تسمح بترتيب العناصر. - SortedMap : ترث الواجهة Map و تسمح بترتيب العناصر. بعض الدوال (اعذرني أخي القارئ فأنا أفضل كلمة **الدوال** على مصطلح **الطرق**) الموجودة في الواجهات السابقة, اختيارية بمعنى أن تعريفها اجباري لكن إذا كانت العملية غير مدعومة فيمكن للدالة أن تُصدر exception تُوضح نوع خطأ الاستخدام. الهدف من شيء كهذا هو تقليل عدد الواجهات و تلبية أكبر عدد ممكن من الحاجات المطلوبة. أيضا, الــ framework يوفر العديد من الكائنات التي تدعم الواجهات السابقة و يمكن استخدامها بشكل مباشر: - HashSet : عبارة عن Hash table أو جدول هاش يدعم الواجهة Set. - TreeSet : شجرة (tree) قامت بعمل implement للواجهة SortedSet. - ArrayList : مصفوفة ديناميكية قامت بعمل implement للواجهة List. - LinkedList : قائمة متصلة مزدوجة قامت بعمل implement للواجهة List. - HashMap : جدول هاش (Hash table) قام بعمل implement للواجهة Map. - TreeMap : شجرة (tree) قامت بعمل implement للواجهة SortedMap. بالنسبة للواجهات التي تُمكن من المرور على عناصر الــ collections و ترتيبها فهي : - Iterator : للتجول بين مختلف عناصر الــ collection. - ListIterator : تسمح بالمرور على عناصر List في الاتجاهين كما تسمح بتغيير عناصر المجموعة. - Comparable : لتعريف نوع الترتيب الطبيعي لكائن معين. - Comparator : لتعريف ترتيب كيفي (لا على التعيين). هذا بالإضافة إلى فئتين كانتا توجدان في النسخة السابقة و أجريت عليهما بعض التعديلات لدعم بعض الواجهات الجديدة: - Vector : مصفوفة ديناميكية قامت بعمل implement للواجهة الجديدة List - HashTable : Hash table قام بعمل implement للواجهة الجديدة Map **المخطط الجديد لــ Collections FrameWork** 1 - Collection Hierarchy **[![1](https://informatic-ar.com/wp-content/uploads/2013/10/118-300x177.png)](https://informatic-ar.com/wp-content/uploads/2013/10/118.png)** **1.1 - الواجهة Collection** ```java public interface Collection { // Basic Operations int size(); boolean isEmpty(); boolean contains(Object element); boolean add(Object element); // Optional boolean remove(Object element); // Optional Iterator iterator(); int hashCode(); boolean equals(Object element); // Bulk Operations boolean containsAll(Collection c); boolean addAll(Collection c); // Optional boolean removeAll(Collection c); // Optional boolean retainAll(Collection c); // Optional void clear(); // Optional // Array Operations Object[] toArray(); Object[] toArray(Object a[]); } ``` كما قلنا سابقا, أغلب الواجهات الجديدة تحتوي على دوال اختيارية, هذه الخاصية تسمح لك بالتعامل مع هياكل البيانات الخاصة دون أن تحتاج إلى تعريف دوال جديدة. الدوال الاختيارية تُستعمل عند الحاجة فقط. على سبيل المثال إذا كان لدينا Set غير قابلة للتعديل يمكننا إعادة تعريف الدالة add بحيث نمنع التعديل أو الإضافة. تختلف الدوال باختلاف مهامها فهناك بعض الدوال ينفذ العملية بشكل فردي (أي على كائن واحد) و هناك أخرى تنفذها بشكل جماعي أي على مجموعة كائنات في آن واحد. - الدالتان add و remove تسمحان بإضافة أو حذف عنصر معين بينما تقوم addAll أو removeAll بحذف أو إضافة مجموعة كائنات بشكل جماعي. - الدالة contains تتحقق من وجود عنصر داخل المجموعة. إذا أردنا التحقق من وجود مجموعة عناصر في آن واحد نستخدام containsAll. - size, isEmpty و clear يمكنهم (على التوالي) معرفة حجم الــ collection, التحقق من أن الــ collection فارغة و الأخيرة تُمكن من حذف محتوى الــ collection. - retainAll تُستعمل لإيجاد تقاطع الــ collections, فمثلا إذا كانت A={1,2,5,8} و B={3,8} فإن A ستصبح {8}. - equals تتحقق من تطابق الــ collections. - hashCode تُعيد كود الــ hash code الخاص بالــ collection. - toArray تُعيد عناصر الــ collection على شكل مصفوفة. - toArray(Object a\[\])l تسمح بتحديد نوع المصفوفة المعادة. للمرور على عناصر collection نستخدم الواجهة Iterator : ```java public interface Iterator { boolean hasNext(); Object next(); void remove(); // Optional } ``` - hasNext : تتحقق من وجود عنصر قادم. - next : تُشير إلى العنصر القادم. - remove : تحذف العنصر الحالي. **ملاحظة:** الدالة next تُصدر NoSuchElementException عند استدعائها بعد انتهاء عناصر الــ collection. لتفادي هذه الـــ Exception يكفي تطبيق hasNext على الكائن المُعاد من طرف الدالة next, هكذا : ```java Iterator iter = objetCollection.iterator(); while (iter.hasNext()) { Object o = iter.next(); System.out.println("Objet : " + o); } ``` **1.2 - الواجهة Set** هذه الواجهة عبارة عن Collection غير مرتبة, لا تسمح بتكرار العناصر. يوجد نوعان من الــ implementation, الاختيار بينهما يكون حسب ضرورة ترتيب العناصر: - HashSet : العناصر غير مرتبة, عملية الإضافة سريعة جدا. - TreeSet : العناصر مرتبة, عملية الإضافة تكون ابطأ. مالفرق بين الاثنين ؟ - HashSet : تستخدم تقنية الــ hashing لتحديد تكرار العنصر و تعتمد على الدالة hashCode في الترتيب. - TreeSet : تستخدم binary tree لترتيب العناصر و تعتمد على الدالة compareTo في الترتيب. **ملاحظات:** 1. نوع الترتيب المُستخدم يتم تحديده من طرف الكائنات المضافة إلى TreeSet من خلال عمل implementation للواجهة Comparable إذا أردنا الترتيب حسب natural sort order أو يمكننا تمرير كائن من Comparator لدالة بناء TreeSet إذا أردنا تحديد الــ sort order. 2. للتأكد من وجود عنصر معين داخل Set استخدم الدالة equals. مع العلم طبعا بأنه يجب إعادة تعريف equals حتى تستطيع التأكد من وحدانية أو عدم تكرار الكائن المضاف. انظر المثال: ```java import java.util.HashSet; import java.util.Set; import java.util.TreeSet; public class SetExample { public static void main(String args[]) { Set set = new HashSet(); // Une table de Hachage set.add("Bernadine"); set.add("Elizabeth"); set.add("Gene"); set.add("Elizabeth"); set.add("Clara"); System.out.println(set); Set SetTrie = new TreeSet(set); // Un Set trié System.out.println(SetTrie); } } ``` **1.3 - الواجهة List** List عبارة عن collection مرتبة تسمح بتكرار العناصر, تحتوي هذه الواجهة على دوال تُمكن من إضافة أو حذف عنصر في أي مكان من المجموعة. كما تسمح بالتعامل مع القوائم المتداخلة, في أغلب الحالات نستخدم ArrayList إلا إذا كانت هناك عمليات إضافة في المنتصف, عندها يُستحسن استخدام LinkedList لتفادي عمليات الإزاحة المتكررة. ```java public interface List extends Collection { // Positional Access Object get(int index); Object set(int index, Object element); // Optional void add(int index, Object element); // Optional Object remove(int index); // Optional boolean addAll(int index, Collection c); // Optional // Search int indexOf(Object o); int lastIndexOf(Object o); // Iteration ListIterator listIterator(); ListIterator listIterator(int index); // Range-view List subList(int fromIndex, int toIndex); } ``` دوال هذه الواجهة تسمح بإجراء بعض العمليات على عنصر يتم تحديد موقعه أو على مجموعة عناصر ابتداء من index معين. - الدالة get تُعيد العنصر الموجود في الموقع index. - set تُغير العنصر الموجود في الموقع index. - indexOf (على التوالي lastIndexOf) تبحث عن وجود العنصر في المجموعة و تُعيد رقم موقعه (على التوالي رقم آخر ظهور له) في المجموعة. - subList تسمح بإنشاء قائمة داخل أخرى. للمرور على عناصر List تم إنشاء Iterator خاص : ```java public interface ListIterator extends Iterator { boolean hasNext(); Object next(); boolean hasPrevious(); Object previous(); int nextIndex(); int previousIndex(); void remove(); // Optional void set(Object o); // Optional void add(Object o); // Optional } ``` حيث يسمح بالمرور على عناصر List في الاتجاهين بالإضافة إلى إمكانية تغيير عنصر معين أو إضافة آخر جديد : ```java List list = ...; ListIterator iterator = list.listIterator(list.size()); while (iterator.hasPrevious()) { Object element = iterator.previous(); // traitement d'un élément } ``` nextIndex تُعيد رقم موقع العنصر الحالي و بقية الدوال رأيناها سابقاً. **1.4 - الكلاس ArrayList** هذا الكلاس يرث الكلاس AbstractList مما يعني أنه قام بعمل implement للواجهة List يُمثل مصفوفة ديناميكية من الكائنات. طريقة عمله مطابقة تماما لتلك الخاصة بــ Vector, الفرق بينهما هو أن هذا الأخير يدعم الــ multi-thread (جميع الدوال synchronized). اذا اردنا استخدام thread واحد فالــ synchronization تكون مكلفة و عديمة الفائدة لذا من الأفضل استخدام ArrayList في مثل هذه الحالات. اضافة إلى الدوال السابقة توجد: - ensureCapacity : تسمح بزيادة حجم المصفوفة لضمان تخزين العناصر الجديدة. - trimToSize : تسوية سعة المصفوفة على حجمها الحالي. مثال أول: ```java import java.util.ArrayList; public class TestArrayList01 { public static void main(String[] args) { ArrayList c = new ArrayList(); /* * ajout de dix element de type Integer */ for (int i = 0; i < 10; i++) { c.add(new Integer(i)); } /* * affiche des element par accès direct */ for (int i = 0; i < c.size(); i++) { System.out.print(c.get(i) + " "); //1 } System.out.println(); /* * suppression d'element aux positions indiquées */ c.remove(2); c.remove(4); c.remove(6); //c.remove(8) ; /* * affiche de la liste */ System.out.print(c.toString()); //2 /* * ajout de dix element de type Integer */ c.add(2, "100"); c.add(2, "200"); c.add(5, "300"); /* * réaffiche de la liste */ System.out.print(c.toString()); //3 } } ``` مُخرجات الكود ستكون بهذا الشكل: [![3](https://informatic-ar.com/wp-content/uploads/2013/10/36-300x105.png)](https://informatic-ar.com/wp-content/uploads/2013/10/36.png) بعد حذف 2, 5 و 8 سيكون طول المصفوفة يساوي 7 و بالتالي عندما نحاول حذف العنصر الثامن سيتم رفع استثناء من نوع IndexOutOfBoundsException. مثال ثاني: ```java import java.util.ArrayList; import java.util.ListIterator; public class TestArrayList02 { public static void main(String[] args) { ArrayList c = new ArrayList(); /* * ajout de dix element de type Integer */ for (int i = 0; i < 10; i++) { c.add(new Integer(i)); } /* * affiche toute la liste */ System.out.println(c.toString()); /* * iterateur sur le tableau */ ListIterator iter = c.listIterator(); while (iter.hasNext()) { Integer k = (Integer)iter.next(); int i = k.intValue(); if (i % 2 == 0) { iter.set(new Integer(500)); } } /* * affiche toute la liste */ System.out.print(c.toString()); } } ``` مُخرجات الكود ستكون على النحول التالي: [![4](https://informatic-ar.com/wp-content/uploads/2013/10/46-300x52.png)](https://informatic-ar.com/wp-content/uploads/2013/10/46.png) 2 - Map Hierarchy [![2](https://informatic-ar.com/wp-content/uploads/2013/10/26-300x92.png)](https://informatic-ar.com/wp-content/uploads/2013/10/26.png) **2.1 – ماهو الــ Map ؟** الــ Map عبارة عن مجموعة أزواج, كل زوج يحتوي على مفتاح و قيمة key/value مع العلم أن المفتاح لا يمكن تكراره. (في الحقيقة يمكننا اسناد عدة قيم لمفتاح واحد, في هذه الحالة سنكون أمام multimap). الهدف الأساسي من الــ Map هو الوصول بسرعة إلى المفتاح للحصول على القيمة المقابلة له. ```java public interface Map { // Basic Operations Object put(Object key, Object value); Object get(Object key); Object remove(Object key); boolean containsKey(Object key); boolean containsValue(Object value); int size(); boolean isEmpty(); // Bulk Operations void putAll(Map t); void clear(); // Collection Views public Set keySet(); public Collection values(); public Set entrySet(); // Interface for entrySet elements public interface Entry { Object getKey(); Object getValue(); Object setValue(Object value); } } ``` **2.2 - عرض سريع لمختلف الدوال الموجودة داخل الواجهة Map** - الدالة clear تسمح بحذف جميع عناصر الــ Map. - containsKey تُحدد ما إذا كان المفتاح key موجود أو لا. - containsValue تُحدد ما إذا كانت القيمة value موجودة داخل الــ Map. - entrySet تُعيد Set تحتوي على جميع قيم الــ Map. - get تُعيد القيمة المرافقة للمفتاح key. - isEmpty تتحق من فراغ الــ Map. - keySet تُعيد Set تحتوي على كافة مفاتيح الــ Map. - put تضيف المفتاح key و القيمة المرافقة له value داخل الــ Map. - putAll تضيف كافة المفاتيح و القيم داخل الــ Map. - values تُعيد قيم الــ Map على شكل collection. - remove يحذف العنصر المقابل للمفتاح key. - size تُعيد عدد عناصر الــ Map. **2.3 - ماهي انواع الــ implementation الموجودة ؟** يوجد نوعان من الــ Map : - HashMap, Hashtable : يستخدمان جدول هاش. - TreeMap : تستخدم شجرة ثنائية. **ملاحظات :** 1. الفئة HashMap ليست synchronized. للتعامل مع الــ concurrent access, قم بتغليف الكائن داخل كائن من Map باستخدام الدالة synchronizedMap الموجودة في الواجهة Collection. 2. إذا كان المفتاح المُمرر إلى put موجود مسبقا سيتم استبدال القيمة القديمة بالجديدة. put تُعيد القيمة القديمة إذا كان المفتاح موجود مسبقا و إلا فستعيد null. 3. TreeMap قامت بعمل implement لــ SortedMap, ترتيب العناصر يتم تحديده عن طريق كائن من نوع Comparable. **2.4 - ماهي الــ view ؟** - تسمح الــ view بالمرور على عناصر الـــ Map. - نظريا, الفئتان HashMap و TreeMap لا يملكان أي Iterator. - الدالة entrySet تسمح برؤية Map كمجموعة ازواج. - الزوج عبارة عن كائن من نوع الواجهة Map.entry حيث يجمع كائنين في آن واحد. - الدالتان getKey و getValue الموجودتان في Map.entry تسمحان باستخراج مفتاح و قيمة زوج معين. انظر المثال: ```java import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class TestHashMapView { public static void main(String[] args) { HashMap table = new HashMap(); /* * ajout d'éléments dans la table */ table.put(new Integer(1), "Livre Java"); table.put(new Integer(2), "Livre Oracle"); table.put(new Integer(3), "Livre C++"); table.put(new Integer(4), "Livre Reseaux"); Set entrees = table.entrySet(); // entrees est un ensemble de paires Iterator iter = entrees.iterator(); // un iterateur sur les paires while (iter.hasNext()) { Map.Entry entree = (Map.Entry)iter.next(); // on est sur la paire courante Object cle = entree.getKey(); Object valeur = entree.getValue(); if (cle != null && cle.toString().equals(new Integer(2).toString())) { table.put(new Integer(2), "Revue Technical Report"); } } System.out.println(table.get(new Integer(2))); } } ``` بالإضافة إلى الــ view السابقة توجد أخرى, للحصول على كافة المفاتيح نستخدم الدالة keySet : ```java import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class ExampleKeySet { public static void main(String[] args) { HashMap map = new HashMap(); map.put("1", "stock"); map.put("2", "livres"); map.put("3", "sucre"); map.put("4", "huile"); //ensemble des cles Set cles = map.entrySet(); System.out.println("Aff1 " + cles); Iterator iter = cles.iterator(); while (iter.hasNext()) { Map.Entry entree = (Map.Entry)iter.next(); String s = (String)entree.getValue(); if (s.equals("huile")) { entree.setValue("riz"); } System.out.println("Element courant " + s); } System.out.println("Affiche " + cles); } } ``` للحصول على كافة القيم نستخدم الدالة values : ```java import java.util.Collection; import java.util.HashMap; import java.util.Iterator; public class ExampleValues { public static void main(String[] args) { HashMap map = new HashMap(); map.put("1", "stock"); map.put("2", "livres"); map.put("3", "sucre"); map.put("4", "huile"); //collection des valeurs Collection valeurs = map.values(); System.out.println("Affichage 1 " + valeurs); Iterator iter = valeurs.iterator(); while (iter.hasNext()) { Object o = iter.next(); if (o.toString().equals("huile")) { iter.remove(); } System.out.println("Element courant " + o); } System.out.println("Affichage 2 " + valeurs); } } ``` **ملاحظات:** 1. أي تغيير يحدث في الـــ Map سيتم اجراؤه أيضا على الــ view المقابلة. 2. حذف الزوج أو العنصر الحالي (باستخدام remove) في الــ view يقتضي أيضا حذف العنصر المقابل له في الــ Map. 3. لا يُسمح بإضافة عناصر مباشر من داخل الــ view. مثال جامع : ليكن لدينا الكلاس التالي: ```java public class Produit { private String idProduit, libelle, quantite; //نفترض أن الكلاس يحتوي على دالة بناء بالإضافة إلى //getters and setters } ``` نُعطي الكلاس EnregistrementProduit المعرف كما يلي: ```java import java.util.ArrayList; import java.util.HashMap; public class EnregistrementProduit { private static HashMap<String, Produit> prod = new HashMap<String, Produit>(); public static HashMap<String, Produit> enreg(Produit e) { //أكمل الكود } public static ArrayList<Produit> chercherProduits(HashMap<String, Produit> prod) { //أكمل الكود } } ``` 1. قم بتعريف الدالة enreg بحيث تقوم بتخزين الــ Produit الذي تم تمريره كوسيط داخل جدول الهاش prod. نعتبر أن idProduit هو المفتاح. 2. قم أيضا بتعريف الدالة chercherProduits بحيث تقوم بإعادة مجموعة الــ Products ذو quantite أقل من 15000. الحل: ```java import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; class Produit { private String idProduit, libelle, quantite; public Produit(String id, String libel, String qnt) { idProduit = id; libelle = libel; quantite = qnt; } public String getId() { return idProduit; } public String getQnt() { return quantite; } } public class EnregistrementProduit { private static HashMap<String, Produit> prod = new HashMap<String, Produit>(); public static HashMap<String, Produit> enreg(Produit p) { prod.put(p.getId(), p); return prod; } public static ArrayList<Produit> chercherProduits(HashMap<String, Produit> prod) { Collection c = prod.values(); ArrayList<Produit> array = new ArrayList<Produit>(); Iterator<Produit> iter = c.iterator(); while (iter.hasNext()) { Produit p = iter.next(); if (Integer.parseInt(p.getQnt()) > 15000) { array.add(p); } } return array; } } ``` **ترتيب الــ Collections** يتم تحديد نوع الترتيب المُستخدم في الــ Collections باستخدام احدى الواجهتين : Comparable Or Comparator **1 - الواجهة java.lang.Comparable** جميع الكائنات التي تستخدم natural sort order يجب أن تقوم بعمل implement لهذه الواجهة: ```java interface Comparable { int compareTo(Object o); } ``` بعض الفئات مثل String, File, Date, Integer, Float الخ, عملت implement لهذه الواجهة. Comparable تحتوي على دالة واحدة فقط هي compareTo التي تعيد احدى القيم التالية: - قيمة اكبر من الصفر, إذا كان الكائن الحالي (current object) اكبر من الكائن الذي تم تمريره. - اصغر من الصفر, إذا كان الكائن الحالي (current object) اصغر من الكائن الذي تم تمريره. - صفر, اذا تطابق الكائنان. مثال: قم بكتابة كلاس باسم TestComparable يقوم بعمل implement للواجهة Comparable و يسمح بترتيب عناصر موجودة في HashMap على النحو التالي: 1. الدالة void tri (Object \[ \]o)l تسمح بترتيب جدول من Object. 2. الدالة ArrayList triHashMap (HashMap map)l تستقبل HashMap كوسيط و تُعيد ArrayList يحتوي على قيم الــ HashMap بشكل مرتب. 3. مع العلم أنه يجب اعادة تعريف الدالة CompareTo حتى توافق المتطلبات. الحل: ```java import java.util.*; public class TestComparable implements Comparable { public int compareTo(Object o) { if (o instanceof String) { String d = (String)o; return this.compareTo(d); } else { new ClassCastException(); return -1; } } ArrayList triHashMap(HashMap map) { int i = 0; ArrayList liste = new ArrayList(); Set c = map.entrySet(); Object tab[] = new Object[c.size()]; Iterator it = c.iterator(); Map.Entry mp = null; while (it.hasNext()) { mp = (Map.Entry)it.next(); tab[i] = mp.getValue(); i++; } tri(tab); for (int ii = 0; ii < tab.length; ii++) { liste.add(tab[ii]); } return liste; } public void tri(Object[] tab) { for (int i = 0; i < tab.length - 1; i++) { if (tab[i].toString().compareTo(tab[i + 1].toString()) > 0) { String temp = tab[i].toString(); tab[i] = tab[i + 1]; tab[i + 1] = temp; tri(tab); } } } public static void main(String[] args) { TestComparable tt = new TestComparable(); HashMap map = new HashMap(); map.put("1", "stock"); map.put("2", "livres"); map.put("3", "sucre"); map.put("4", "huile"); map.put("5", "riz"); map.put("6", "or"); Collection val = map.values(); System.out.println("aff 1 " + val); ArrayList h = tt.triHashMap(map); System.out.println("aff 2 " + h.toString()); } } ``` **2 - الواجهة java.util.Comparator** هذه الواجهة تُمثل sort order كيفي, أي أن المبرمج هو من يحدده. حيث تسمح بترتيب الكائنات التي لم تقم بعمل implement للواجهة Comparable أو تعريف sort order مختلف عن الذي تقدمة الواجهة السابقة. ```java interface Comparator { int compare(Object o, Object v); boolean equals(Object object); } ``` الدالة compare تعيد: - قيمة اكبر من الصفر, إذا كان o اكبر من v. - اصغر من الصفر, إذا كان o اصغر من v. - صفر, اذا كان o و v متطابقان. قم بعمل المثال السابق باستخدام الواجهة Comparator. **الـــ Algorithmes :** مجموعة الـــ algorithmes التي تُمكن من التعامل مع الـــ collections موجودة في الكلاس Collections (انتبه جيداً, الكلاس Collections يختلف تماما عن الواجهة Collection). تم تعريف جميع تلك الدوال على أنها static. نبدأ بالـــ algorithmes الخاصة بــ List : **الترتيب:** - sort(List list)l : ترتيب List - sort(List list,Comparator comp)l : ترتيب List باستخدام comparator **الــ Merge :** shuffle(List liste)l : يخلط العناصر بطريقة عشوائية **البحث:** - binarySearch(List list, Object element)l : البحث عن عنصر باستخدام خوارزمية البحث الثنائي - binarySearch(List list, Object element, Comparator comp)l : البحث عن عنصر باستخدام خوارزمية البحث الثنائي اعتمادا على comparator. **بعض العمليات الأخرى:** - reverse(List liste)l : قلب عناصر List - fill (List liste, Object element)l : تقوم بعمل initialization لـ List باستخدام element - copy(List dest, List src)l : نسخ src داخل dest أيضا هناك خوارزميات أخرى يمكن تطبيقها على جميع الــ collections مثل : - min (Collection)l : للبحث عن أصغر عنصر - max (Collection)l : للبحث عن أكبر عنصر **ملاحظات:** - لاستخدام الدالة sort(List)l يجب أن تقوم كافة عناصر الـــ List بعمل implement للواجهة Comparable وإلا سيتم رفع استثناء من نوع ClassCastException. - الكلاس Collections يحتوي على دوال خاصة تسمح بالحصول على نسخة multi-thread و Unmodifiable لمعظم الــ interfaces الموجودة مثل Collection, List, Map,Set, SortedMap, SortedSet : 1. synchronizedXXX ( XXX)l : للحصول على نسخة multi-thread من الكائنات التي قامت بعمل impelement للواجهة XXX 2. unmodifiableXXX (XXX)l : للحصول على نسخة Unmodifiable من الكائنات التي قامت بعمل impelement للواجهة XXX مثال على خاصية الــ Unmodifiable : ```java public class TestUnmodifiable { public static void main(String args[]) { java.util.List list = new java.util.ArrayList(); list.add(new Integer(100)); list.add(new Integer(200)); list.add(new Integer(300)); java.util.List listun; listun = java.util.Collections.unmodifiableList(list); list.add(new Integer(400)); listun.add(new Integer(400)); // tentative illégale de modification de la liste l System.out.println(list.toString()); } } ``` محاولة تغيير Unmodifiable List تقتضي اصدار استثناء من نوع java.lang.UnsupportedOperationException. مثال على خاصية الــ synchronization : ```java import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class Testsynchronized { public static void main(String[] args) { List list = new LinkedList(); list.add(new Integer(800)); list.add(new String("livres")); list.add("revue"); list.add("articles"); list.add(new Integer(122)); list.add(new Object()); list = Collections.synchronizedList(list); synchronized (list) { Iterator iter = list.iterator(); while (iter.hasNext()) { if (iter.next() instanceof Integer) { iter.remove(); } list.add(null); // illegal } } synchronized (list) { Iterator it = list.iterator(); while (it.hasNext()) { if (it.next() instanceof String) { it.remove(); } } } System.out.println(list.toString()); } } ``` استدعاء الدالة synchronizedXXX يُعيد كائن يدعم الــ synchronization لبعض العمليات مثل الاضافة و الحذف. أما بالنسبة لعملية المرور على العناصر باستخدام Iterator فيجب وضع الكود المخصص داخل synchronized bloc. يجب أيضا وضع استدعاء الدالة داخل الجزء المخصص. محاولة اضافة عنصر جديد أثناء المرور على عناصر المجموعة, تقتضي اصدار استثناء من نوع java.util.ConcurrentModificationException. **مراجع:** Développons en Java - Jean Michel DOUDOUX Notes de cours du mon Prof Joseph NDONG Cours Java - Bernard Caylux ### الـ Queues في لغة C URL: https://informatic-ar.com/queues_in_c/ Last updated: 2026-10-03T22:44:58.000Z السلام عليكم و رحمة الله و بركاته كنا قد تحدثنا في [الجزء الأول](https://informatic-ar.com/singly%5Flinked%5Flist%5Fin%5Fc/) من هذه السلسلة عن القوائم المتصلة البسيطة و تحدثنا في [الجزء الثاني](https://informatic-ar.com/doubly%5Flinked%5Flist%5Fin%5Fc/) عن القوائم المزدوجة و انتقلنا بعد ذلك إلى [الجزء الثالث](https://informatic-ar.com/stacks%5Fin%5Fc/) لنشرح المكدسات و يسرني أن أبدأ معكم في شرح الجزء الرابع الذي يتعلق بالطوابير (Queues). **الفهرس** - تعريف - المحاكاة باستخدام المصفوفات - المحاكاة باستخدام القوائم البسيطة - المحاكاة باستخدام القوائم المزدوجة - اختبر قدراتك **تعريف** الطابور أو الــ Queue مُشابه جدا للمكدس (Stack) فقط الفرق الوحيد بينهما يكمن في أن الإضافة في الطابور تكون في النهاية بينما تكون في البداية عند الحديث عن المكدس, في بقية العمليات (الحذف و الإظهار) لا يوجد فرق يُذكر. أقرب مثال على الطابور هو قوائم الانتظار أو الــ Waiting List حيث نجد أن أول من يدخل هو أول من يخرج و آخر من يدخل هو آخر من يخرج و هذا النوع من الترتيب يُعرف اختصاراً بــ FIFO أي First In First Out. كما هو الحال مع المكدس, الطابور لا يملك نوع بيانات خاص به و إنما هو مجرد تركيبة يُمكن محاكاتها باستخدام المصفوفات أو القوائم (سواء كانت بسيطة أو مزدوجة) أو حتى الأشجار (Trees). **تنويه:** في بقية المقالة : 1. سأقوم بتقسيم الكود الكامل إلى مجموعة دوال للتوضيح. 2. كل جزء من الكود سيكون مُرتبطاً بالجزء السابق له. 3. الأخطاء التي شرحناها سابقا (مثل فشل عملية الحجز) لن نتعرض لها هذه المرة من أجل الاختصار. **المحاكاة باستخدام المصفوفات** لمحاكاة الطابور بالمصفوفات سنحتاج إلى 3 متغيرات: الأول عبارة عن المصفوفة التي ستُحاكي الطابور و المتغير الثاني يحمل رقم أول خانة من الطابور و المتغير الثالث يحمل رقم آخر خانة, هكذا: [![1](https://informatic-ar.com/wp-content/uploads/2013/10/116-300x42.png)](https://informatic-ar.com/wp-content/uploads/2013/10/116.png) بالنسبة لتهيئة المتغيرات, قمنا بحجز 20 خانة للمصفوفة Queue ثم أسندنا القيمة -1 لكل من front و rear كدلالة على فراغ الطابور. نأتي الآن إلى دالة الإدراج : [![2](https://informatic-ar.com/wp-content/uploads/2013/10/25-300x156.png)](https://informatic-ar.com/wp-content/uploads/2013/10/25.png) إذا تساوت قيمة rear مع SIZE-1 فهذا يعني أننا وصلنا إلى آخر خانة و بالتالي لا يمكننا إضافة المزيد من العناصر لذا قمنا بإظهار الرسالة OverFlow كدلالة على حدوث فيض عند محاولة الإدراج. في الحالة المعاكسة, سنقوم بالانتقال إلى الخانة الموالية ثم نقرأ العدد المُدخل و نخزنه في الخانة الحالية. بالنسبة لدالة الحذف فهي كالتالي : [![3](https://informatic-ar.com/wp-content/uploads/2013/10/35-300x77.png)](https://informatic-ar.com/wp-content/uploads/2013/10/35.png) إذا تساوت قيمة rear مع front فهذا يعني أنه لا يوجد عنصر للسحب, هذا من جهة. من جهة أخرى, يتساوى rear مع front عند -1 فقط و من المعروف أن -1 لا يمكن أن تكون index لأحد عناصر المصفوفة لأن الترقيم يبدأ من 0 لذا قمنا بإظهار الرسالة UnderFlow كدلالة على أن index المصفوفة أقل من الصفر. في الحالة المعاكسة, سنقوم بإظهار قيمة الخانة الحالية و الانتقال إلى الخانة الموالية (لذا أجد أن هذه المحاكاة سيئة جداً لأنه لا يتم حذف العناصر بصفة حقيقية كتحرير الذاكرة كما يحدث في القوائم). دالة الإظهار أعتقد أنها واضحة, إذا تساوت قيمة rear مع front فهذا يعني أن الطابور فارغ. في الحالة المعاكسة, سنقوم بإظهار كافة عناصر الطابور : [![4](https://informatic-ar.com/wp-content/uploads/2013/10/45-300x108.png)](https://informatic-ar.com/wp-content/uploads/2013/10/45.png) **المحاكاة باستخدام القوائم** **\-** **مُقدمة** كما قلنا سابقا, توجد عدة صفات مشتركة بين الطابور و المكدس و بالتالي يمكننا اعتبار أن الطابور ما هو إلا حالة خاصة من القوائم المتصلة حيث لا يُمكن إضافة عنصر إلا في نهاية القائمة أما عملية الحذف فتظل كما كانت. بالرغم من تشابه الــ Queue و الــ Stack إلا أن التغييرات ستكون مُعقدة نسبيا نظرا لأن الطابور يُلزمنا بالانتقال إلى بداية أو نهاية القائمة حسب نوع العملية المطلوب إجرائها (إضافة أو حذف). **المحاكاة باستخدام القوائم البسيطة** الإعلان عن الطابور سيكون هكذا: [![5](https://informatic-ar.com/wp-content/uploads/2013/10/55.png)](https://informatic-ar.com/wp-content/uploads/2013/10/55.png) الطابور يحتوي على قيمة واحدة من نوع int بالإضافة إلى المؤشر ptrNext الذي يُمثل مفتاح الدخول للعقدة الموالية. المؤشر front يُشير إلى قمة الطابور بينما يُشير rear إلى مؤخرة الطابور. نبدأ مع دالة الإضافة : [![6](https://informatic-ar.com/wp-content/uploads/2013/10/66-300x127.png)](https://informatic-ar.com/wp-content/uploads/2013/10/66.png) في البداية, قمنا بحجز مساحة جديدة للمؤشر q ثم طلبنا من المستخدم إدخال عدد صحيح ليتم تخزينه في المتغير value داخل العقدة الجديدة. إذا كان أحد المؤشرين front أو rear يُشير إلى NULL فهذا يعني أن الطابور فارغ لذلك ستلعب دالة الإضافة في هذه الحالة دور دالة التهيئة, في الحالة المعاكسة سنجعل المؤشر اللاحق لــ rear يُشير إلى الطابور ثم نقوم بتحديث المؤشر rear. أما دالة الحذف فهي كالآتي : [![7](https://informatic-ar.com/wp-content/uploads/2013/10/75-300x123.png)](https://informatic-ar.com/wp-content/uploads/2013/10/75.png) إذا كان حمل أحد المؤشرين front أو rear القيمة NULL فهذا يعني أن الطابور فارغ و بالتالي لا توجد عناصر للسحب لذا قمنا بإظهار رسالة تُفيد بذلك. في الحالة المعاكسة سنقوم بنسخ front داخل q و نُظهر القيمة التي سيتم حذفها ثم نحرر المؤشر q بعد أن ننتقل إلى العقدة الموالية. بالنسبة لدالة الإظهار فلن تتغير كثيرا (سبق و أن شرحنا فكرتها في الجزء الأول) : [![8](https://informatic-ar.com/wp-content/uploads/2013/10/85-300x155.png)](https://informatic-ar.com/wp-content/uploads/2013/10/85.png) **المحاكاة باستخدام القوائم المزدوجة** لتمثيل الطابور باستخدام القوائم المزدوجة يكفي أن نتذكر معا كيفية الإعلان عن قائمة مزدوجة بسيطة تحوي عنصر واحد مثلا: [![9](https://informatic-ar.com/wp-content/uploads/2013/10/95.png)](https://informatic-ar.com/wp-content/uploads/2013/10/95.png) المؤشر head يُشير إلى قمة الطابور بينما يُشير tail إلى مؤخرة الطابور أما المتغير length فيُمثل طول الطابور. دالة الإضافة ستكون كالآتي : [![10](https://informatic-ar.com/wp-content/uploads/2013/10/105-300x233.png)](https://informatic-ar.com/wp-content/uploads/2013/10/105.png) في البداية, قمنا بالتحقق من أن الطابور لم يمتلئ بعد, إذا كان قد امتلأ نُظهر رسالة تفيد بذلك و يتم الخروج من الدالة. إذا تجاوزنا مرحلة التحقق فهذا يعني أن الطابور لم يمتلأ بعد لذا قمنا بالإعلان عن مكدس جديد باسم temp و قمنا بحجز المساحة المطلوبة له ثم أسندنا قيمة الوسيط إلى المتغير value و جعلنا المؤشر اللاحق يُشير إلى NULL. إذا كان طول الطابور يُساوي صفر فهذا يعني أن العقدة المُراد إضافتها هي أول عقدة لذا جعلنا المؤشر السابق يُشير إلى NULL و كذلك اللاحق ثم جعلنا المؤشر head يُشير إلى بداية الطابور. في الحالة المعاكسة (هذا يعني أنه توجد أكثر من عقدة) نجعل المؤشر السابق للعقدة الحالة يُشير إلى tail ثم نجعل المؤشر اللاحق لــ tail يُشير إلى العقدة الحالية. في النهاية نقوم بتحديث الطابور ثم نزيد طول الطابور بواحد. بالنسبة لدالة الحذف فستكون كالتالي : [![11](https://informatic-ar.com/wp-content/uploads/2013/10/117-300x179.png)](https://informatic-ar.com/wp-content/uploads/2013/10/117.png) إذا كانت قيمة المتغير length أقل أو تُساوي صفر فهذا يعني أن الطابور فارغ و بالتالي سيتم إظهار رسالة تُفيد بذلك و الخروج من الدالة. في الحالة المعاكسة, سنقوم بحفظ نسخة من المؤشر المُراد تحريره ثم ننتقل إلى العقدة الموالية و نحرر المؤشر ثم نجعله يُشير إلى NULL (هذه الخطوة مهمة جدا و هي عادة حسنة عند التعامل مع المؤشرات) و أخيرا ننقص طول الطابور بواحد. دالة الإظهار لن تتغير كثيراً (سبق و أن شرحناها في الجزء الثاني) : [![12](https://informatic-ar.com/wp-content/uploads/2013/10/126-300x204.png)](https://informatic-ar.com/wp-content/uploads/2013/10/126.png) **اختبر قدراتك** اكتب برنامج يطلب من المستخدم إدخال جملة ثم يُخبره ما إذا كانت تلك الجملة تُمثل Palindrome أو لا. ملاحظة : قم بتخزين الجملة في Stack ثم خزن نسخة أخرى من الجملة في Queue و قارن بين محتوى البنيتين. إلى هنا أصل بك أخي القارئ إلى نهاية هذا الدرس, آمل أن تكون استفدت من هذا الدرس و تعلمتَ أشياء جديدة :) تحياتي. ### الــ Stacks في لغة C URL: https://informatic-ar.com/stacks_in_c/ Last updated: 2026-10-03T22:31:50.000Z السلام عليكم و رحمة العلام و بركاته كنا قد تحدثنا في [الجزء الأول](https://informatic-ar.com/singly%5Flinked%5Flist%5Fin%5Fc/) من هذه السلسلة عن القوائم المتصلة البسيطة و تحدثنا في [الجزء الثاني](https://informatic-ar.com/doubly%5Flinked%5Flist%5Fin%5Fc/) عن القوائم المزدوجة و يسرني أن أبدأ معكم في شرح الجزء الثالث و الذي يتعلق بالمكدسات (Stacks). **الفهرس** - تعريف - المحاكاة باستخدام المصفوفات - المحاكاة باستخدام القوائم البسيطة - المحاكاة باستخدام القوائم المزدوجة - اختبر قدراتك **تعريف** يُمكن تشبيه المكدس أو الــ Stack بمجموعة الصحون حيث يُمكننا إضافة صحن في القمة لكن عندما نريد سحب أحد الصحون, يلزمنا سحب كافة الصحون الموجودة فوقه و هذا النوع من الترتيب يُعرف اختصاراً بــ LIFO أي Last In First Out. المكدس لا يملك نوع بيانات خاص به و إنما هو مجرد تركيبة يُمكن محاكاتها باستخدام المصفوفات أو القوائم (سواء كانت بسيطة أو مزدوجة) أو حتى الأشجار (Trees). **تنويه:** في بقية المقالة : - سأقوم بتقسيم الكود الكامل إلى مجموعة دوال للتوضيح. - كل جزء من الكود سيكون مُرتبطاً بالجزء السابق له. - الأخطاء التي شرحناها سابقا (مثل فشل عملية الحجز) لن نتعرض لها هذه المرة من أجل الاختصار. **المحاكاة باستخدام المصفوفات** لمحاكاة المكدس بالمصفوفات سنحتاج إلى : - عدد ثابت يُمثل العدد الأقصى لعناصر المكدس. - مصفوفة لتخزين العناصر. - و متغير صحيح يُمثل الــ index الخاص بقمة المكدس. إذا الإعلان عن العناصر السابقة سيكون هكذا: [![1](https://informatic-ar.com/wp-content/uploads/2013/10/114.png)](https://informatic-ar.com/wp-content/uploads/2013/10/114.png) نأتي الآن إلى دالة الإدراج : [![2](https://informatic-ar.com/wp-content/uploads/2013/10/24-300x195.png)](https://informatic-ar.com/wp-content/uploads/2013/10/24.png) إذا كانت قيمة top أكبر أو تساوي من MAXSIZE فهذا يعني أن المكدس قد امتلأ لذا قمنا بإظهار رسالة تفيد بذلك. في الحالة المعاكسة, إذا كانت قيمة top أقل من الصفر, نقوم بإعادتها للصفر ثم نقرأ القيمة التي أدخلها المستخدم و نخزنها في قمة المكدس ثم نجعل المتغير top يُشير إلى الخانة الموالية. بالنسبة لدالة الحذف فهي كالتالي : [![3](https://informatic-ar.com/wp-content/uploads/2013/10/34.png)](https://informatic-ar.com/wp-content/uploads/2013/10/34.png) إذا كان الــ index الذي يُشير إلى قمة المكدس أكبر أو يساوي صفر نقوم بعمل decrement له مما يعني الرجوع إلى الخلف بخطوة واحدة. دالة الإظهار هي التي تُظهر حقيقة المحاكاة : ) [![4](https://informatic-ar.com/wp-content/uploads/2013/10/44-300x145.png)](https://informatic-ar.com/wp-content/uploads/2013/10/44.png) القيمة الابتدائية للمتغير top هي 0 و بالتالي إذا كان top أقل أو يُساوي صفر فهذا يعني أن المكدس فارغ. في الحالة المُعاكسة, سنقوم بإظهار العناصر ابتداء من قمة المكدس وصولا إلى الصفر. (في الحقيقة, ما يحدث هو أن المكدس عبارة عن مصفوفة و إظهار العناصر يتم بالمقلوب أي من الأخير إلى الأول) **المحاكاة باستخدام القوائم - مُقدمة** يمكننا اعتبار أن المكدس ما هو إلا حالة خاصة من القوائم المتصلة حيث لا يُمكن إضافة أو حذف عنصر إلا من بداية القائمة لذا فإن العمليات ستقتصر أساسا على دالتين أساسيتين هما: - دالة اسمها Push تقوم بإدراج عنصر في بداية المكدس. - دالة اسمها Pop تقوم بحذف عنصر من بداية المكدس. **المحاكاة باستخدام القوائم البسيطة** الإعلان عن المكدس سيكون هكذا: [![5](https://informatic-ar.com/wp-content/uploads/2013/10/54.png)](https://informatic-ar.com/wp-content/uploads/2013/10/54.png) المكدس سيحتوي على قيمة واحدة من نوع int بالإضافة إلى المؤشر ptrPrev الذي يُمثل مفتاح الدخول. المؤشر top يُشير إلى قمة المكدس و المؤشر temp عبارة عن مؤشر مؤقت. نبدأ مع دالة التهيئة : [![6](https://informatic-ar.com/wp-content/uploads/2013/10/65-300x78.png)](https://informatic-ar.com/wp-content/uploads/2013/10/65.png) في البداية, قمنا بحجز مساحة جديدة للمؤشر top ثم طلبنا من المستخدم إدخال عدد صحيح ليتم تخزينه في المتغير value داخل العقدة الجديدة. المؤشر ptrPrev جعلناه يُشير إلى NULL و temp إلى top. دالة الإضافة مُشابهة جدا لدالة التهيئة : [![7](https://informatic-ar.com/wp-content/uploads/2013/10/74-300x79.png)](https://informatic-ar.com/wp-content/uploads/2013/10/74.png) فقط, الفرق يكمن في كيفية ربط المؤشر ptrPrev بالمؤشر الموالي. دالة الحذف ستكون كالآتي : [![8](https://informatic-ar.com/wp-content/uploads/2013/10/84-300x130.png)](https://informatic-ar.com/wp-content/uploads/2013/10/84.png) إذا كان temp يُشير إلى NULL فهذا يعني أن المكدس فارغ لذا قمنا بإظهار رسالة تُفيد بذلك. في الحالة المعاكسة سنقوم بنسخ temp داخل top و نُظهر القيمة التي سيتم حذفها ثم نحرر المؤشر top بعد أن ننتقل إلى العقدة السابقة. بالنسبة لدالة الإظهار فلن تتغير (سبق و أن شرحناها في الجزء الأول) : [![9](https://informatic-ar.com/wp-content/uploads/2013/10/94-300x135.png)](https://informatic-ar.com/wp-content/uploads/2013/10/94.png) **المحاكاة باستخدام القوائم المزدوجة** الإعلان عن مكدس باستخدام القوائم المزدوجة سيكون قريبا جدا من الإعلان السابق, فقط نُضيف مؤشر لاحق : [![10](https://informatic-ar.com/wp-content/uploads/2013/10/104.png)](https://informatic-ar.com/wp-content/uploads/2013/10/104.png) دالة الإضافة ستكون كالآتي : [![11](https://informatic-ar.com/wp-content/uploads/2013/10/115-300x176.png)](https://informatic-ar.com/wp-content/uploads/2013/10/115.png) في البداية, قمنا بالإعلان عن مكدس جديد باسم newStack و قمنا بحجز المساحة المطلوبة له ثم طلبنا من المستخدم إدخال قيمة و قمنا بتخزينها في المتغير value. إذا كان newStack يُشير إلى NULL فهذا يعني أن المكدس فارغ و بالتالي العقدة الجديدة ستُحاط بعناوين NULL من كلا الجانبين. في الحالة المعاكسة سنجعل المؤشر اللاحق لــ newStack يُشير إلى myStack و المؤشر السابق لــ myStack يُشير إلى newStack و المؤشر السابق لــ newStack يُشير إلى NULL ثم نقوم بتحديث المكدس. بالنسبة لدالة الحذف فستكون كالتالي : [![12](https://informatic-ar.com/wp-content/uploads/2013/10/125-300x195.png)](https://informatic-ar.com/wp-content/uploads/2013/10/125.png) إذا كان المكدس فارغ سيتم إظهار رسالة تُفيد بذلك و الخروج من الدالة. في الحالة المعاكسة, سيتم إظهار العنصر الذي سيتم حذفه و التقدم إلى العقدة الموالية و حذف المؤشر الذي يُشير إلى العقدة السابقة. عند الانتهاء من عملية الحذف, نتحقق من ما إذا كان المكدس يحتوي على عقدة واحدة أم لا ؟ إذا كان الجواب نعم, نقوم بربط العقدة الوحيدة بالمؤشر NULL كمؤشر سابق لها. دالة الإظهار لن تتغير (سبق و أن شرحناها في الجزء الثاني) : [![13](https://informatic-ar.com/wp-content/uploads/2013/10/135-300x149.png)](https://informatic-ar.com/wp-content/uploads/2013/10/135.png) **اختبر قدراتك** قم بعمل برنامج يحول infix إلى postfix باستخدام الــ Stacks. إلى هنا أصل بك أخي القارئ إلى نهاية هذا الدرس, آمل أن تكون استفدت من هذا الدرس و تعلمتَ أشياء جديدة : ) تحياتي. ### خوارزمية ID3 URL: https://informatic-ar.com/id3_algorithm/ Last updated: 2026-10-04T04:35:44.000Z *بسم الله الرحمن الرحيم ،* والصلاة والسلام على اشرف المرسليين سيدنا محمد صلى الله علية وسلم # ***خوارزمية* *ID3*** **مقدمة**: تتحدث المقالة عن احد خوارزميات تصنيف البيانات وهى خوارزمية ID3 ونستعرض فيها شرح للخوارزمية وكيفية تطبيقها والقصور فيها **تعريفها** هى عبارة عن خوارزمية تستخدم لتصنيف البيانات –حيث مدخل الخوارزمية مجموعة من البيانات والناتج هو قاعدة classifier تستطيع من خلالة تصنيف بيانات جديدة لم تستخدم من قبل كنوع من التنبؤ لهذة للبيانات الجديدة - هذا الclassifier يكون على شكل شجرة tree structure لذا سميت decision tree ويمكن تحويلها الى مجموعة من القواعد rules لذا اطلق عليها ايضا اسم decision rules **استخدامها** كل ما يخص مشاكل التصنيف (من تحليل الدخل income Analysis-تصنيف امراض –تصنيف صور---) **مبدأ الخوارزمية** تستخدم الخوارزمية مبدأ Divide and Conquer فكرة المبدا تقسيم المشكلة الى اجزاء ويحل كلها منها على حدة ثم يتم تجميع الحل هناك قصة فى نهاية المقالة عن المبدأ ( لا تنسون قرأتها والاستفادة منها) **كيفية استخدامها** لنفترض ان لدى مجموعة من البيانات كبيرة –كمثال مصغر كما بالجدول (1) [![جدول(1): مجموعة بيانات عبارة عن Attributes ,class](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1-300x140.png "جدول(1): مجموعة بيانات عبارة عن Attributes ,class")](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1.png) جدول(1): مجموعة بيانات عبارة عن Attributes ,class البيانات فى العادة تكون كبيرة لذا سوف نمثلها على هيئة جدول بة مجموعة من الامثلة instances كل مثال فى صف record و يتم تقسيمها من حيث الاعمدة columns الى:- 1) مجموعة خصائص Attributes وكل Attribute لها مجموعة من القيم Values فمثلا Travel Cost هى خاصية Attribute وتأخذ اى من القيم الثلاثة( Cheap-Standard-Expensive )، وهكذا لباقى خصائص الجدول . 2) مجموعة الكلاسات Classes وهى ايضا خاصيةAttribute ولكنها تعتبر ناتج تصنيف مجموعة من Attributes مع بعضها وفى العادة مشاكل التصنيف تكون بها كلاس واحد لكن يمكن ان تكون اعقد وبها اكثر من كلاس. فمثلا جدول (1) عبارة عن بيانات لتحديد وسيلة الموصلات المناسبة لكل شخص حسب خصائص Attributes معينة . **انواع الخصائص Attributes:** هناك انواع لل Attributes يتم تصنيفها من حيث القيم Values التى تأخذها فمثلا: - اذا كانت القيم لخاصية Attribute ما -يمكن التعبير عنها على صورة (0,1) True or False فتسمى Binary Attributes مثل الخاصية Gender فتأخذ قيم اما male, female - اذا كانت الخاصية تأخذ قيم يمكن التعبير عنها دون ان يوجد ترابط بين قيمها تسمى Ordinal Attribute مثل الخاصية الكلاس Transportation mode وتأخذ القيم Bus-Train-Car فلا يوجد علاقة بينهم هناك انواع كثيرة اخرى للخصائص يمكن البحث عنها . **كيفية استخدام ال** **Decision Tree** الجدول السابق يمكن اختصارة الى قاعدة تصنيف classifier كما بالصورة [![شكل (1) Decision tree example](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled2-300x140.png "شكل (1) Decision tree example")](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled2.png) شكل (1) Decision tree example ومن هذة القاعدة classifier يمكن ان استنتج قيم لبيانات لم نراها من قبل فنفترض ان لدينا مجموعة من الاشخاص واريد التنبؤ لهم ما هو طريقة الانتقال المناسبة لهم Transportation mode كما بالجدول (2) فلدى مجموعة بيانات جديدة لمجموعة من الاشخاص واريد ان اعطى كل منهم نوع وسيلة المواصلات الملائمة لة (هذة البيانات الجديدة نطلق عليها Test Set اى بيانات اختبارية حيث استطيع بها تحديد واختبر مدى جودة القاعدة الclassifier التى انتجتها) [![جدول (2): مجموعة من البيانات الجديدة التى ليس لها كلاس واريد التنبؤ بة](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled21-300x54.png "جدول (2): مجموعة من البيانات الجديدة التى ليس لها كلاس واريد التنبؤ بة")](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled21.png) جدول (2): مجموعة من البيانات الجديدة التى ليس لها كلاس واريد التنبؤ بة لكن كيف هذة القاعدة classifier تعبر عن الجدول وكيف يمكن استخدامها فى التنبؤ بوسيلة الموصلات الملائمة اذا كانت مفقودة لاستخدام القاعدة سوف ابدأ ب Root node فى الشكل (1) وهى Travel cost/km ثم ابدأ التفرع منها حسب قيمة كل خاصية Attribute الى ان انتهى الى Leaf node والتى تعتبر الكلاس للبيانات التى تتبعت قيمها فى القاعدة. فمثلا لو اردت التنبؤ للشخص Alex عن طريقة المواصلات الملائمة لة سوف ابدأ ب Root node فى الشكل (1) وهى Travel cost/km ثم التفرع سوف اجد ثلاث خيارات امامى اما expensive او standardاو cheap لكن Alex من جدول (2) قيمة ال Travel cost/km لة هى stranded لذا سوف يكون تصنيف المواصلات لة هو Train هكذا الحال عندما اريد التنبؤ لنوع المواصلات لدى بقية الاشخاص. يمكن تحويل decision tree الى مجموعة من القواعد Rules Set باستخدام if –else فالشكل (1) يمكن كتابتة كالاتى: ```pseudocode Rule 1 : If Travel cost/km is expensive then mode = car Rule 2 : If Travel cost/km is standard then mode = train Rule 3 : If Travel cost/km is cheap and gender is male then mode = bus Rule 4 : If Travel cost/km is cheap and gender is female and she owns no car then mode = bus Rule 5 : If Travel cost/km is cheap and gender is female and she owns 1 car then mode = train ``` طبقا لمجموعة القواعد السابقة والdecision tree فى شكل (1) فيمكن التنبؤ لبقية Test set كما بالجدول (3) [![جدول(3):نتيجة تطبيق Rule Set and decision tree](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled11-300x70.png "جدول(3):نتيجة تطبيق Rule Set and decision tree")](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled11.png) جدول(3):نتيجة تطبيق Rule Set and decision tree **كيف انشىء** **Decision Tree** يتم انشاء الdecision tree اعتمادا على اختيار افضل خاصية Attribute يمكن ان تقسم الTraining set بحيث يقل عمق الشجرة فى نفس الوقت الذى يتم فية تصنيف للبيانات بشكل صحيح كيف يتم اختيار افضل Attribute للتفرع منها هذا يجعلنا نتطرق لطرق لحساب كيمة التشوية فى البيانات ويعرف على انة Entropy اذا لدى مجموعة من البيانات فى جدول عبارة عن امثلة instanceوكل مثال لة Attributes وكلاس class فأننا نقول على جدول ما انة Pure او homogenous اذا كان كل الامثلة فى الجدول تأخذ نفس قيمة الكلاس كما بالشكل التالى [![Untitled11](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled111-300x69.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled111.png) اما اذا كانت الامثلة فى الجدول تاخذ قيم مختلفة للكلاس فيعرف الجدول على انة impure or heterogeneous [![Untitled11](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled112-300x177.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled112.png) **هناك اكثر من طريقة لحساب** **impurity** **منها:** Entropy : ويحسب بالمعادلة الاتيه ****[![Untitled11](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled113.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled113.png)** حيث pj هو احتمالية اختيار الكلاس j نبدأ نتعلم كيف نحسب الاحتمالية لكل كلاس اولا ثم نتعلم كيفية حساب Entropy بداية نرجع للجدول علشان التطبيق [![Untitled11](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled114-300x103.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled114.png) لحساب احتمالية( Attribute( class والتى هى Transportation mode انظر الى الجدول واحدد عدد القيم التى تأخذها هذة الخاصية سوف نجد انها تأخذ ثلاث قيم ( car-bus-train) سوف احسب عدد حدوث كل قيمة نسبة لعدد الامثلة الكلى فى الجدول والذى هو 10 لذا فاحتمال probability لكل قيمة تكون ```plain P (Bus) = 4 / 10 = 0.4 P (Car) = 3 / 10 = 0.3 P (Train) = 3 / 10 = 0.3 ``` [![Untitled11](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled119-300x61.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled119.png) ```plain Entropy (Transportation mode) = – 0.4 log (0.4) – 0.3 log (0.3) – 0.3 log (0.3) = 1.571 )      ``` Gini Index: وهى طريقة اخرى لحساب impurity يحسب بالمعادلة **[![Untitled11](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled115.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled115.png)** بالتطبيق على مثالنا نجد ```plain Gini Index = 1 – (0.4^2 + 0.3^2 + 0.3^2) = 0.660 ``` Classification error: طريقة اخرى ايضا لحساب impurityيحسب بالمعادلة **[![Untitled11](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled116.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled116.png)** ```plain Classification Error Index = 1 – Max{0.4, 0.3, 0.3} = 1 - 0.4 = 0.60 ``` الان بعد معرفة كيفية حساب impurity نستطيع ان ندخل على كيفية انشاء ال decision tree فى البداية قلنا مسبقا ان مبدأ الخوارزمية هو divide and conquer لذا سوف نبدأ نتقسيم الTraining Set فى الجدول حسب كل Attribute حتى استطيع حساب افضل Attribute ابدأ بها ك Root node : **نبدأ اول خطوة فى الخوارزمية :** نبدأ نحسب لكل Attribute فى Training set قيمة ال information gain ما هذة القيمة وكيف يتم حسابها ؟هذة القيمة التى تحدد لنا افضل Attribute يمكن ان نقسم بها Training set كيف يمكن حسابها سوف نستخدم impurity measure التى تحدثنا عنها مسبقا لحسابها لنفترض اننا بدأنا بخاصية Attributeالتى هى Travel cost فأننا سوف نأخذها على حدة مع الكلاس ونقسمها حسب كل قيمة لها فى جدول مصغر –بشكل مبسط اكثر نحن لدينا ثلاث قيم يأخذها Travel cost وهىCheap- Standard-Expensive لذا سوف يكون لدينا ثلاث جداول مصغريين كما بالشكل التالى [![Untitled11](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled117-300x232.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled117.png) لكل جدول مصغر سوف نحسب impurity measure بأى طريقة منهم سواء classification error Entropy, Gini index سوف احسب وليكن Entropy لكل جدول مصغر ```plain Entropy(Cheap) = –( 4/5)log (4/5) – (1/5) log (1/5)=0 .722 Entropy(Expensive) = –( 3/3)log (1) =0 Entropy(Travel Cost) = –( 2/2)log (1) =0 ``` [![Untitled00](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled00-300x164.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled00.png) [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled101-300x26.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled101.png) شرح كل ترم وقيمتة [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1011-300x126.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1011.png) الان بعدما قمنا بحساب information gain لل Travel Cost Attribute نكرر حسابة لكل Attributes الاخرى نحصل على الشكل التالى [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1012-300x288.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1012.png) والتى تلخص قيم information gain لكل Attribute على حدة [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1013-300x59.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1013.png) الان نحصل على الAttribute ذات الinformation gain الاعلى وتكون هى افضل Attribute بعدما حصلنا على افضل Attribute وهى Travel Cost نضعها هى Root Node فى الشجرة [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1014.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1014.png) ثم نبدأ تقسيم الجدول طبقا لها لنحصل على الاتى [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1015-300x140.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1015.png) نجد بعد استخدام الTravel cost لتقسيم الجدول ان هناك ثلاث تفرعات فاذا كانت القيمة Expensive لا يوجد الا كلاس واحدوهو car كذلك اذا كانت القيمة Standard فايضا لا يوجد الا كلاس واحد وهو Train اما اذا كان القيمة Cheap فلدينا اكثر من كلاس لذا سوف نبدأ نكرر مرة اخرى الخطوات السابقة . [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1016-300x73.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1016.png) الان نبدأ iteration جديدة ونعتبر ايضا اننا سوف نبدأ ببيانات جديدة لان الامثلة التى غطتها الrule السابقة لل Travel cost ==expensive && Travel cost ==standard سوف تلغى من training set الان. اذن كما بالرسمة لم يتبقى لدينا الا الجدول الاول [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1017-300x141.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1017.png) اذن لنبدأ ننظر فية : بداية نلغى منة Travel cost ==cheap سوف يتبقى لدينا ثلاث خصائص Gender, car ownership, income level نكرر حساب افضل Attribute يمكن ان نقسم بها الجدول عن طريق حساب information gain لكل خاصية كما سبق **[![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1018-300x182.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1018.png)** اولا :نحسب Entropy للجدول الجديد [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1019-300x136.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled1019.png) ثانيا: نحسب الinformation gain لكل Attribute على حدة Gender,car ownership,income level [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled10110-300x204.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled10110.png) سوف نجد ان افضل information gain هى للAttribute Gender لذا سوف نقسم الجدول من خلالها لنحصل على الاتى [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled10111-300x101.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled10111.png) اذن يمكن تقسم الجدول الى تفرعيين لو Gender= male يوجد لدينا كلاس واحد حصلنا على pure Table تبقى لنا لو Gender=female سوف نتفرع مرة اخرى الى ان نصل الى pure table ايضا ونحصل على القاعدة classifier [![Untitled101](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled10112-300x85.png)](https://informatic-ar.com/wp-content/uploads/2013/10/Untitled10112.png) **اخيرا Main Algorithm الذى يلخص الخطوات السابقة:** ```pseudocode ID3 (Examples, Target_Attribute, Attributes) Create a root node for the tree If all examples are positive, Return the single-node tree Root, with label = +. If all examples are negative, Return the single-node tree Root, with label = -. If number of predicting attributes is empty, then Return the single node tree Root, with label = most common value of the target attribute in the examples. Otherwise BeginEnd A = The Attribute that best classifies examples. Decision Tree attribute for Root = A. For each possible value, , of A, Add a new tree branch below Root, corresponding to the test A = . Let Examples() be the subset of examples that have the value for A If Examples() is empty Then below this new branch add a leaf node with label = most common target value in the examples Else below this new branch add the subtree ID3 (Examples(), Target_Attribute, Attributes – {A}) Return Root ``` **عيوب** **decision tree** **:** رغم سهولة decision tree الا ان لها عيوب كثيرة 1.عند كثرة الامثلة والبيانات ما مدى عمق الشجرة how deep to grow the decision tree 2\. لا تسطيع التعامل مع البيانات المستمرة handling continuous attributes 3\. ايجاد طريقة لقياس الخاصية الانسب التى تقلل عمق الشجرة وفى نفس الوقت تصنف للبيانات بشكل جيد ليس خاطىء choosing an appropriate attribute selection measure 4\. لا تستطيع التعامل اذا هناك قيم لخصائص مفقودة فى اى مثال handling training data with missing attributes 5\. لا يمكن التنبؤ اذا لدى اكثر من كلاس handling attributes with different costs **قصة مبدأ Divide and conquer** *وفكرة المبدا تقسيم المشكلة الى اجزاء ويحل كلها منها على حدة ثم يتم تجميع الحل* *حقيقة مبدا جميل لحل المشاكل وهكذا يفعلون الاعداء معنا للاسف* *علشان نوضح المبدا راح احكى لكم *قصة صغيرة* وهى قصة الثور الابيض وثلاث من اخواتة ولكن باللون الاسود* *وهم كانوا صحبة لا يتفرقون ابدا ونتيجة لتجمعهم لم يستطع ابدا اى عدو اسد او ذئاب ان يقتربوا منهم* *للاسف فى يوم من الايام اجتمع الاخوة " الثور" الذين باللون الاسود وقالوا ان الثور الذى باللون الابيض يظهر فى الظلام وبذلك فهو يعرضنا للخطر بالليل ونحن لوننا اسود فلا نظهر لذلك نفترق عنة حتى لا نتعرض للخطر وفعلا اجمعوا على ان يتركوة لوحدة وفى يوم من الايام وجد الذئاب الثور الابيض لوحدة فلم يخافوة وبدؤا بالهجوم علية وياكلون لحمة والاخوة الذين باللون الاسود يشاهدون ويراقبون ولا يدافعون عن اخوهم وفى اليوم الذى يلية هجم الذئاب على الثلاث باللون الاسود لان عددهم قل وكانت النتيجة ان احدهم مات وفى اليوم الذى يلية اصبح اسهل لان العدد اصبح اثنين وهكذا حتى اخر واحد* *وكانت *المقوله الشهيرة لاخر واحد فيهم عندما هاجمه الذئاب لقد اوكلت عندما اوكل الثور الابيض---- والتى تعنى لقد مت ليس الان ولكن عندما كنت اراقب اخى يموت امام عيينى واشاهدة ولم افعل شيئا* *"** **المراجع:** المرجع الاول هو اساس كتابة هذة المقالة مقتبس منة معظم الشرح -في نهايتة اكواد ومراجع بأسماء الكتب مرجو للمهتميين زيارتة للتعلم منة [http://people.revoledu.com/kardi/tutori ... n-tree.htm](http://people.revoledu.com/kardi/tutorial/DecisionTree/what-is-decision-tree.htm?ref=informatic-ar.com) [ http://en.wikipedia.org/wiki/ID3\_algorithm](http://en.wikipedia.org/wiki/ID3%5Falgorithm?ref=informatic-ar.com) ****{رب انى لما انزلت الى من خير فقير}** **{رَبِّ ابْنِ لِي عِنْدَكَ بَيْتًا فِي الْجَنَّةِ}** الحمد الله رب العالميين اللهم دومها نعمة واحفظها من الزوال -امين امين امين ### القوائم المزدوجة في لغة C URL: https://informatic-ar.com/doubly_linked_list_in_c/ Last updated: 2026-10-03T22:44:58.000Z السلام عليكم ورحمة الله و بركاته كنا قد تحدثنا في [الحلقة الأولى من هذه السلسلة](https://informatic-ar.com/singly%5Flinked%5Flist%5Fin%5Fc/) عن القوائم المتصلة البسيطة و يسرني أن أبدأ معكم في شرح الجزء الثاني و الذي يتعلق بالقوائم المزدوجة (Doubly Linked List). **الفهرس** - تعريف - ͏الإعلان عن القائمة - تهيئة القائمة (إنشاء أول عقدة) - إضافة عقدة جديدة. - حذف عقدة معينة. - حساب طول القائمة. - دمج قائمتين في قائمة واحدة - حذف قائمة - اختبر قدراتك **تعريف** تتميز القوائم المزدوجة بوجود مؤشرين في كل عُقدة, المؤشر الأولى يُشير إلى العُقدة السابقة و المؤشر الثاني يُشير إلى العقدة الموالية. [![1](https://informatic-ar.com/wp-content/uploads/2013/10/110-300x96.png)](https://informatic-ar.com/wp-content/uploads/2013/10/110.png) بشكل عام, المؤشرات هي ما يميز طبيعة الحركة في القوائم, أياً كان نوعها, و في حالتنا هذه فإن القوائم المزدوجة تتميز بالقدرة على الحركة في كلا الاتجاهين نظرا لوجود مؤشر سابق و آخر لاحق في كل عقدة. **تنويه** في بقية المقالة : - سأقوم بتقسيم الكود الكامل إلى مجموعة دوال للتوضيح. - إذا اجتمعت في الكود عدة دوال تشترك في نوعية الخطأ (فشل عملية الحجز مثلاً) سيتم التحقق من دالة واحدة فقط و ذلك تجنباً للتكرار. - كل جزء من الكود سيكون مُرتبطاً بالجزء السابق له. **الإعلان عن القائمة** عند التعامل مع القوائم, يُستحسن دائما استخدام قائمة مُساعدة تأتي فائدتها في تخزين بعض المعلومات التي تخص القائمة الأصلية, مثل عناوين العُقد الرئيسية (الأولى و الأخيرة مثلاً) و طول القائمة و ما إلى ذلك .. لتبسيط الأمور, نفترض أن القائمة التي سنعمل عليها تحوي عنصرا واحدا عبارة عن متغير من نوع int. الإعلان عن القائمة سيكون هكذا: [![2](https://informatic-ar.com/wp-content/uploads/2013/10/23-300x122.png)](https://informatic-ar.com/wp-content/uploads/2013/10/23.png) لا شيء جديد, فقط قمنا بالإعلان عن قائمة تحوي 3 عناصر : متغير من نوع int ومؤشر على العقدة السابقة و آخر على العقدة اللاحقة. ثم قمنا بإعطاء اسم مستعار لمؤشر القائمة. بالنسبة للقائمة الـمُساعدة فستكون هكذا: [![3](https://informatic-ar.com/wp-content/uploads/2013/10/33.png)](https://informatic-ar.com/wp-content/uploads/2013/10/33.png) القائمة الـمُساعدة تحتوي على متغير من نوع int يُمثل طول القائمة و مؤشرين, الأول يُشير إلى بداية القائمة الأصلية و الثاني يُشير إلى نهايتها. **تهيئة القائمة (إنشاء أول عقدة)** نأتي الآن إلى كيفية تهيئة القائمة من خلال إنشاء عقدة جديدة و إسناد قيم ابتدائية لكافة البيانات: [![4](https://informatic-ar.com/wp-content/uploads/2013/10/43-300x123.png)](https://informatic-ar.com/wp-content/uploads/2013/10/43.png) الدالة init تستقبل وسيط واحد عبارة عن مرجع (Reference) للقائمة المزدوجة, في السطر الأول قمنا بحجز ذاكرة للمؤشر الذي سيُشير إلى أول عقدة في القائمة, إذا فشلت عملية الحجز ستعيد الدالة false و ينتهي الأمر, أما إذا مرت عملية الحجز بسلام فهذا يدل على أن المؤشر argDbll أصبح يُشير إلى منطقة من الذاكرة تحوي 3 عناصر (المتغير الصحيح, المؤشر السابق و المؤشر اللاحق) و في هذه الحالة سنقوم بإسناد قيمة ابتدائية لكل عنصر. بعد تهيئة العناصر الثلاثة تقوم الدالة بإعادة true كإشارة إلى نجاح العملية. **ملاحظات هامة:** - في حالة عدم وجود العُقدة السابقة يُشير المؤشر السابق إلى NULL. - في حالة عدم وجود العُقدة الموالية يُشير المؤشر اللاحق إلى NULL. - إذا كانت القائمة تحتوي على عُقدة واحدة فهذا يعني دمج الملاحظتين السابقتين. - العمليات المختلفة (تهيئة, إضافة, حذف, ..) سيتم تطبيقها على القائمة الأصلية من خلال القائمة الـمُساعدة. نأتي الآن إلى تضمين المكتبات اللازمة بالإضافة إلى شرح مختصر لمحتوى الدالة الرئيسية : [![5](https://informatic-ar.com/wp-content/uploads/2013/10/53-300x117.png)](https://informatic-ar.com/wp-content/uploads/2013/10/53.png) الدالتان printf و fprintf موجودتان في المكبتة stdio.h و الماكرو EXIT\_FAILURE و EXIT\_SUCCESS موجود في المكتبة stdlib.h و النوع bool مُعرف في المكتبة stdbool.h لذا قمنا باستدعاء المكتبات الثلاثة معاً. في بداية الدالة الرئيسية قمنا بالإعلان عن قائمة جديدة و أسندنا لها العنوان NULL وهذه الخطوة ضرورية جدا في تهيئة المؤشرات بغض النظر عما تُشير إليه. بعد ذلك, قمنا بتمرير القائمة myDoubleList إلى الدالة init كوسيط ثم تحققنا من القيمة الـمُعادة من طرف الدالة, إذا كانت false سيتم إظهار رسالة تنبيه على الشاشة و إلا فسيتم إظهار البيانات الابتدائية للعقدة الجديدة. و هذه صورة لـمُخرجات الكود : [![6](https://informatic-ar.com/wp-content/uploads/2013/10/64.png)](https://informatic-ar.com/wp-content/uploads/2013/10/64.png) لاحظ أن طول القائمة يُساوي 0, البعض يرى أن الصفر غير مناسب في هذه الحالة لأنه على الأقل تحتوي القائمة حالياً على عُقدة واحدة و بالتالي يجب أن يكون طول القائمة يُساوي واحد و ليس صفر, هذه وجهة النظر الأولى. وجهة النظر الثانية (و هي التي أميل إليها) يقول أصحابها أنه حتى لو كانت القائمة تحتوي حالياً على عُقدة واحدة فإن هذه العُقدة خالية من المعلومات و لا تُشكل عُقدة حقيقة تُؤثر على طول القائمة و بالتالي يُمكن تجاهلها. في معظم الحالات تتم العودة لاحقاً إلى هذه الُعقدة لتعديل بياناتها إلى بيانات حقيقية عن طريق إدراج عدد جديد كقيمة للمتغير الصحيح الموجود في العقدة, و بعدها تتم زيادة طول القائمة بواحد. بالنسبة للعناوين فمن الطبيعي جداً أن تظهر الأصفار كقيم لعناوين المؤشرات لأن المؤشر NULL يملك العنوان 0x00000000 وفي العادة يكون هذا العنوان Invalid Memory Address في أغلب نظم التشغيل. يُستخدم الـ Null Pointer للدلالة على أن المؤشر فارغ أي لم يتم حجز ذاكرة له بعد. **إضافة عقدة جديدة** لإضافة عقدة جديدة إلى القائمة, يجب أن نمر بالخطوات التالية: - حجز مساحة من الذاكرة للعقدة الجديدة. - تهيئة كافة عناصر العقدة. - المؤشر السابق للعقدة الجديدة سيُشير إلى NULL. - المؤشر اللاحق للعقدة الجديدة سيُشير إلى بداية القائمة. - إذا كانت القائمة غير فارغة نجعل المؤشر السابق لبداية القائمة يُشير إلى العُقدة الجديدة. - في الحالة الـمُعاكسة نجعل المؤشر last يُشير إلى العُقدة الجديدة. - نقوم بتحديث القائمة من خلال جعل العُقدة الجديدة هي الأولى. - نزيد طول القائمة بواحد. و بالتالي, دالة الإضافة ستكون هكذا: [![7](https://informatic-ar.com/wp-content/uploads/2013/10/73-300x159.png)](https://informatic-ar.com/wp-content/uploads/2013/10/73.png) كالعادة, إذا لم تنجح عملية الحجز ستُعيد الدالة false و إلا فالقيمة الـمُعادة ستكون true, هذا من جهة. من جهة أخرى, قمنا بتطبيق الخطوات التي ذكرناها آنفا, لا أكثر و لا أقل :) نأتي الآن إلى تضمين المكتبات اللازمة بالإضافة إلى تعليق موجز حول الــ main : [![8](https://informatic-ar.com/wp-content/uploads/2013/10/83-300x150.png)](https://informatic-ar.com/wp-content/uploads/2013/10/83.png) في البداية, قمنا بتهيئة القائمة المزدوجة ثم استدعينا دالة الإضافة و تحققنا من القيمة الـمُعادة كما فعلنا سابقا مع دالة التهيئة, إذا تمت إعادة false نُظهر رسالة الخطأ و إلا فسنستدعي الدالة display المسئولة عن إظهار بيانات القائمة (سنشرح هذه الدالة لاحقاً). مُخرجات الكود ستكون على هذا النحو: [![9](https://informatic-ar.com/wp-content/uploads/2013/10/93.png)](https://informatic-ar.com/wp-content/uploads/2013/10/93.png) لاحظ أنه لم يتم إظهار بيانات العقدة الفارغة لأنها مُحاطة بعناوين فارغة و بالتالي ستتوقف الدالة عن الإظهار عندما تصل إليها. من ناحية أخرى, إدراج العُقدة الجديدة حدث قبل الُعقدة الأولى و هذا ما يُسمى بالإضافة في بداية القائمة, يُمكننا أيضا أن نُضيف العُقدة في نهاية القائمة أو في أي مكان آخر و هنا تكمن أحد أبرز نقاط القوة لدى القوائم و هي المرونة. بالنسبة لإضافة العُقدة في نهاية القائمة فستكون هكذا: [![10](https://informatic-ar.com/wp-content/uploads/2013/10/103-300x159.png)](https://informatic-ar.com/wp-content/uploads/2013/10/103.png) نفس الخطوات السابقة مع تعديل طفيف يتمثل في : - جعل المؤشر السابق للعقدة الجديدة يُشير إلى نهاية القائمة. - إسناد القيمة NULL إلى المؤشر اللاحق للعقدة الجديدة. - إذا كانت القائمة غير فارغة نجعل المؤشر اللاحق لنهاية القائمة يُشير إلى العُقدة الجديدة. - في الحالة الـمُعاكسة نجعل المؤشر first يُشير إلى العُقدة الجديدة. - نقوم بتحديث القائمة من خلال جعل العُقدة الجديدة هي آخر عُقدة. - نزيد طول القائمة بواحد. الصورة التالية تُوضح مفهوم الإضافة في نهاية القائمة المزدوجة: [![11](https://informatic-ar.com/wp-content/uploads/2013/10/113-300x157.png)](https://informatic-ar.com/wp-content/uploads/2013/10/113.png) يُمكننا أيضا إضافة عقدة جديدة في أي مكان من القائمة, لنفترض مثلا أننا نريد إدراج عقدة جديدة بعد عقدة أخرى يتم تحديد رقهما في القائمة. في هذه الحالة ستكون الخُطوات الـمُتبعة هي: - الانتقال إلى العُقدة المحددة, توجد حالتان: - إذا وصلنا إلى NULL فهذا يعني أن رقم العقدة غير موجود, حينها نقوم بإعادة false. - في الحالة المعاكسة نقوم بالآتي: - حجز مساحة من الذاكرة للعقدة الجديدة. - تهيئة كافة عناصر العقدة. - جعل المؤشر اللاحق للعقدة المحددة يُشير إلى المؤشر السابق للعقدة الجديدة. - جعل المؤشر السابق للعقدة الجديدة يُشير إلى المؤشر اللاحق للعقدة المحددة. - جعل المؤشر اللاحق للعقدة الجديدة يُشير إلى المؤشر الذي يُشير إليه المؤشر اللاحق للعقدة المحددة. - جعل المؤشر السابق للعقدة التي تلي العقدة المحددة يُشير إلى المؤشر اللاحق للعقدة الجديدة. - المؤشر first لن يتغير. - المؤشر last يتغير فقط إذا كانت العُقدة المحددة هي آخر عقدة. - زيادة طول القائمة بواحد. و بالتالي دالة الإضافة ستكون هكذا: [![12](https://informatic-ar.com/wp-content/uploads/2013/10/124-300x190.png)](https://informatic-ar.com/wp-content/uploads/2013/10/124.png) إذا لم تنجح عملية الحجز أو لم يتم العثور على رقم العقدة ستُعيد الدالة false و إلا فستعيد الدالة true بعد أن تتم إضافة العُقدة الجديدة و تحديث القائمة. الآن أصبح من السهل جداً كتابة دالة تقوم بتعديل بيانات عقدة مُعينة, فقط بدلا من إدراج عُقدة جديدة, نقوم بتغيير بيانات العُقدة التي توقفت عندها الحلقة for ما لم تكن تلك العقدة فارغة. هذا مثال على استدعاء الدالة addAfterACertainNode في الــ main : [![13](https://informatic-ar.com/wp-content/uploads/2013/10/134-300x153.png)](https://informatic-ar.com/wp-content/uploads/2013/10/134.png) يُمكننا جعل الدالة addAfterACertainNode تعيد int و نخصص قيمة لكل خطأ فمثلا يمكننا اعتبار أن الصفر يدل على عدم وجود العقدة و 1 يدل على فشل عملية الحجز و 2 تدل على نجاح عملية الإدراج كما يُمكننا أيضا جعل الدالة تعيد void و في هذه الحالة يُستحسن إضافة رسائل الخطأ داخل جسم الدالة. على كل حال, الـمُخرجات ستكون هكذا: [![14](https://informatic-ar.com/wp-content/uploads/2013/10/145.png)](https://informatic-ar.com/wp-content/uploads/2013/10/145.png) نأتي الآن إلى شرح كيفية عمل دالة الإظهار التي تقوم بعرض محتويات القائمة : [![15](https://informatic-ar.com/wp-content/uploads/2013/10/153-300x124.png)](https://informatic-ar.com/wp-content/uploads/2013/10/153.png) حتى لا نفقد بيانات القائمة, قمنا بتخزين نسخة من عنوان العقدة الأولى داخل المؤشر currentPointer و مادام هذا الأخير غير فارغ, سيتم إظهار العدد الموجود في العقدة الحالية و الانتقال إلى العقدة الموالية. بالنسبة للمتغير i فتأتي فائدته في ترقيم عناصر العقدة. **حذف عقدة مُعينة** كما فعلنا سابقا مع عملية الإضافة, يمكننا أيضا حذف عُقدة من البداية, النهاية أو أي مكان آخر من القائمة. فمثلا, الدالة التالية تقوم بحذف آخر عُقدة من القائمة : [![16](https://informatic-ar.com/wp-content/uploads/2013/10/163-300x141.png)](https://informatic-ar.com/wp-content/uploads/2013/10/163.png) الخُطوات الـمُتبعة هي: - نُخزن عنوان آخر عقدة في متغير مؤقت. - إذا كانت القائمة فارغة, تتم إعادة false و ينتهي الأمر. - في الحالة المعاكسة نقوم بالخطوات التالية: - نجعل المؤشر اللاحق للعقدة القبل الأخيرة يُشير إلىNULL. - نقوم بتحديث القائمة من خلال جعل العُقدة القبل الأخير هي آخر عقدة. - ننقص طول القائمة بواحد. - نُحرر العنوان الذي كان يُشير إلى العُقدة الأخيرة. هذا مثال على استدعاء الدالة removeTheLast في الــ main : [![17](https://informatic-ar.com/wp-content/uploads/2013/10/173-300x262.png)](https://informatic-ar.com/wp-content/uploads/2013/10/173.png) إذا كانت القائمة فارغة سيتم إظهار رسالة الخطأ, في الحالة المعاكسة سيتم إظهار عناصر القائمة قبل و بعد الحذف. الـمُخرجات ستكون هكذا: [![18](https://informatic-ar.com/wp-content/uploads/2013/10/182.png)](https://informatic-ar.com/wp-content/uploads/2013/10/182.png) الصورة التالية تُوضح مفهوم حذف آخر عُقدة في قائمة مزدوجة: [![19](https://informatic-ar.com/wp-content/uploads/2013/10/192-300x208.png)](https://informatic-ar.com/wp-content/uploads/2013/10/192.png) أما إذا أردنا حذف عقدة من بداية القائمة فستكون الخطوات كما يلي - تخزين عنوان العقدة الأولى داخل متغير مؤقت. - جعل مؤشر القائمة يُشير إلى العقدة الثانية. - تحرير المؤشر الذي كان يُشير إلى العقدة الأولى. و بالتالي دالة الحذف في هذه الحالة ستكون هكذا: [![20](https://informatic-ar.com/wp-content/uploads/2013/10/201-300x136.png)](https://informatic-ar.com/wp-content/uploads/2013/10/201.png) لاحظ أنه تم التأكد أن القائمة غير فارغة قبل إجراء عملية الحذف و هذا مُهم جدا إذْ يجب التأكد من مثل هذه الحالات قبل القدوم على تنفيذ العملية. نأتي الآن إلى كيفية الحذف عن طريق تحديد قيمة المتغير elem في العقدة المُراد حذفها : [![21](https://informatic-ar.com/wp-content/uploads/2013/10/212-300x164.png)](https://informatic-ar.com/wp-content/uploads/2013/10/212.png) هذه المرة قمنا بالحذف على أساس العقدة التي تحمل قيمة معينة لــ elem و ليس على أساس رقم العقدة في القائمة. الدالة السابقة بإمكانها حذف أي عُقدة من القائمة باستثناء العقدة الأولى و الأخيرة, يُمكننا إضافة شروط للتحقق من مكان العقدة فإذا كانت العقدة المراد حذفها هي العقدة الأولى نقوم باستدعاء الدالة removeTheFisrst و removeTheLast إذا كانت آخر عقدة. بالنسبة لتفاصيل الدالة فعند الخروج من while سنكون أمام خيارين, الخيار الأول يعني أنه لا توجد عقدة تحمل القيمة elem التي تم إرسالها للدالة, حينها سنقوم بإعادة false و تنتهي المهمة, إذا تجاوزنا الــ if بسلام فهذا يعني أنه توجد عُقدة تحمل قيمة elem داخلها, لذا سنقوم بتخزين العقدة التي تُحقق الشرط في المؤشر tmpNode ثم نُحرر عنوان تلك العُقدة بعد أن نربط بين العقدتين اللتين يحيطان بالعقدة المراد حذفها. **حساب طول القائمة** هذه المرة لن نحتاج إلى كتابة دالة منفردة لحساب الطول, لأن طول القائمة مُخزن في المتغير lengthOfTheList و يتم تحديث قيمته كلما تمت إضافة أو حذف عقدة معينة. **دمج قائمتين في قائمة واحدة** دالة الدمج تستقبل وسيطين يُمثلان القائمتين الـمُراد دمجهما : [![22](https://informatic-ar.com/wp-content/uploads/2013/10/222-300x90.png)](https://informatic-ar.com/wp-content/uploads/2013/10/222.png) إذا كانت القائمة الأولى فارغة, فهذا يعني أن دمج القائمتين يُعطي القائمة الثانية لذا تمت إعادتها. في الحالة الـمُعاكسة نقوم بربط آخر عُقدة من القائمة الأولى بأول عُقدة من القائمة الثانية. **حذف القائمة** دالة الحذف فكرتها كالآتي : في كل مرة نقوم بتخزين عنوان العقدة الحالية في مؤشر مؤقت ثم ننتقل إلى العقدة الموالية و نحرر المؤشر و هكذا حتى نصل إلى عنوان فارغ (NULL) مما يعني أن القائمة انتهت و بالتالي نخرج من الحلقة while. ثم نقوم بتحديث عناصر المصفوفة المساعدة (نجعل المؤشرات تُشير إلى NULL و نُسند القيمة صفر إلى المتغير الذي يحوي طول القائمة). [![23](https://informatic-ar.com/wp-content/uploads/2013/10/231-300x150.png)](https://informatic-ar.com/wp-content/uploads/2013/10/231.png) **اختبر قدراتك** لتكن لدينا زجاجة تحتوي على 7 كرات, واحدة باللون الأحمر و اثنتين باللون الأصفر و أربعة باللون الأخضر. يقوم اللاعب بسحب كرة عشوائيا من الزجاجة, إذا كانت الكرة حمراء سيربح اللاعب 10 دولار و يخسر 5 دولار إذا كانت صفراء أما إذا كانت خضراء فسيحظى اللاعب بسحب كرة أخرى من الزجاجة (دون أن يعيد الكرة الأولى التي سحبها), إذا كانت الكرة الجديدة حمرءا سيربح 8 دولار و إلا سيخسر 4 دولار. تنتهي اللعبة عندما يصبح رصيد اللاعب يساوي صفر علما أن الرصيد الابتدائي لكل لاعب يُساوي 6 دولار. قم بعمل برنامج صغير يُحاكي هذه اللعبة باستخدام القوائم المزدوجة. إلى هنا أصل بك أخي القارئ إلى نهاية هذا الدرس, آمل أن تكون استفدت من هذا الدرس و تعلمتَ أشياء جديدة :) تحياتي. ### خوارزميات غيرت العالم (محركات البحث وكيف يستخرج النتائج المطابقة) URL: https://informatic-ar.com/search_engine_basics/ Last updated: 2026-10-03T22:31:52.000Z جميعنا نستخدم محركات البحث عشرات المرات في اليوم الواحد، وقد نضجر في حال لم نستطيع الحصول على ما نريد في أول ثواني بعد عملية البحث، ولكن هل تسائلت يوماً كيف يعمل هذا المحرك وكيف يستخرج النتائج المطابقة التي تريدها؟ هل تسائلت كيف يمكن أن يرتب محرك البحث تلك النتائج بحيث يظهر ذلك المحرك النتائج الأقرب Relevant لاستعلامك؟ [![searchengine](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine-300x170.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine.jpg) # كيف تعمل محركات البحث اذا نظرت للصورة اعلاه سوف تجد مثال لترسانه من ال Data Center التي توجد في اي شركة تختص بالعمل في مجال محركات البحث، كل من هذه الداتا سنتر تحتوى على مئات أو الالاف السيرفرات بالاضافه الى تجهيزات الشبكة وغيرها من الاجهزة، ولكن كل هذه التجهيزات والأجهزة **بدون خوارزمية لاسترجاع تلك المعلومات Information Retrieval سوف تكون بلا قيمة أو فائده تذكر.** في هذه المقالة سوف نتحدث عن الخوارزمية المستخدمه في كل مرة تقوم بعملية البحث، وهي تبدأ بعد كتابتك للكلمة التي تريد البحث عنها وضغط زر البحث لكي تبدأ أول مرحلتين هما مرحلة البحث عن الصفحات التي احتوت على ما تريد Matching ومرحلة ترتيب تلك النتائج واظهار الصفحات الأكثر صلة بموضوعك Ranking سيتم الحديث عن خوارمية Page Rank والتي رفعت قوقل للقمة في مقالة أخرى. ## البحث والمطابقة Matching والترتيب Ranking لنبدأ في طرح مثال وهو البحث عن الجملة London bus timetable ولننظر للصورة ادناه وستجد أن مرحلة المطابقة Matching ما هي الا للإجابه على السؤال "**ما هي الصفحات التي تحتوى على تلك الجمل**" بمعنى كل الصفحات التي احتوت العبارة London bus timetable. [![searchengine1](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine1-300x106.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine1.jpg) الشكل أعلاه يبين المرحلتين عند البحث: مرحلة ال matching وقد ترجع هذه العملية ملايين الصفحات Hits التي احتوت على تلك العبارة والتي تحتاج لترتيب على حسب اهميتها وتقاربها لما يريده المستخدم Relevance وتسمى هذه المرحلة بال Ranking. بشكل عملي أغلب محركات البحث تقوم بهذه الخطوتين في خطوة واحدة لكن كل منهم له هدف لوحده عن الثاني لذلك يمكن أن يدرسوا بشكل مفصول على أساس مرحلتين وتبدأ الثانية بعد أن تنتهي الاولى من عملها. أغلب الاستعلامات التي تقوم بها لها مئات أو الالاف النتائج Hits والمستخدم يفضل أن ينظر لمجموعه من الصفحات في النتيجة مثلاً أول 10 صفحات في النتائج، لذلك على محرك البحث أن يختار أفضل نتائج من العدد الكلي الضخم الناتج من المرحلة الاولى بالاضافة الى عرضهم بطريقة مرتبة بحيث تكون النتيجة الاولى هي التي تحتوى على أقرب ما يريده المستخدم والتي تليها ثم التي تليها وهكذا.. مرحلة اختيار الصفحات تسمى Ranking وهي مرحلة مهمه تاتي بعد مرحلة ال Matching. وفي عالم شركات محركات البحث فإن خوارزمية الRanking قد تحدد مصير ذلك المحرك هل سوف ينجح أم لأ (لو رجعنا للوراء في 2000 سنجد أن استحواذ السوق في محركات البحث كان متساوياً للشركات الثلاثه Google و Yahoo و MSN بنسبة 30% تقريباً في السوق الامريكي، لكن بعدها قامت Google بتحسين محركها مما أدى لسقوط ياهو و MSN لنسبة 20% وهذا التحسين كان بسبب التحسين في خوارزمية ال Ranking). لذلك خوارزمية ال Ranking تعتبر من المؤثرات القوية في محركات البحث. ## **ماذا تعرف عن الفهرسة** **Indexing** شركة Google لم تكن الأولى في خدمة محركات بحث، بل سبقتها عده شركات مثل Infoseek و Lycos في منتصف التسعينيات وتبعتهم AltaVista والتي كانت رائجه ذلك الوقت بسبب قوة محركها الذي استطاع البحث على كل الصفحات في الويب وسرعة استخراجه للمعلومات، والسؤال كيف تم ذلك، الجواب بكلمة واحدة هي **الفهرسة Index**. مفهوم الفهرسه Indexing هو أحد أهم الأساسيات وراء محركات البحث وهو مفهوم قديم من قبل ظهور محركات البحث وعلوم الحاسب، وفي الغالب عندما نقول الفهرسه فهذا يذكرنا بالفهرس الموجود في اواخر الكتب والذي من خلاله تستطيع الرجوع للصفحة التي فيها الكلمة التي تبحث عنها. الفهرس Index في محركات البحث يعمل بنفس الفكرة، فالصفحة هنا لن تكون صفحة في الكتاب وانما في الويب وسيتم اسناد رقم لكل صفحة في الويب (حتى ان كانت بلايين الأرقام فالحواسيب جيدة عندما نتعامل بلغه الأرقام). حتى نعرف كيف يتم عمل الفهرسه ، لنفترض أن الويب مكون من 3 صفحات كما في الصورة التالية ، وتم اعطاء رقم لكل صفحة [![searchengine2](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine2-300x161.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine2.jpg) يتم بناء الفهرس عند طريق عمل قائمة الكلمات التي تكررت في كل الصفحات ومن ثم ترتيب تلك الكلمات أبجدياً (وسوف نسميها Word List) وهي التي تتكون من الكلمات a و cat و dog الخ من الكلمات التي تكررت في الصفحات الثلاثه، وبجانب كل من الكلمات سيتم وضع رقم الصفحة التي تكررت في هذه الكلمة، مثلاً الكلمة on تكررت في الصحفة 1 و 2 ، وهكذا للجميع. بهذه الفكرة البسيطة يستطيع محرك البحث ايجاد الصفحات لاستعلامك، مثلاً قمت بالبحث عن cat سوف يبدأ المحرك بالذهاب لل word list وايجاد الكلمة cat سريعاً لأن القائمة مرتبة ابجدياً وبعدها يتم ايجاد الصفحات التي تكررت فيها الكلمة وهي هنا 1 و 3، والمحركات الحديثة بالطبع تقوم باظهار النتيجة بشكل جيد عن طريق اظهار مختصر سطر أو اثنين من كل صفحة في النتيجه لكن سوف نتجاهل هذا الامر ونركز عن كيف يعرف محرك البحث أن الصفحة هذه هي التي تريد. نأخذ مثال أخر للبحث عن جملة من كلمتين cat dog وهذا يعني الصفحات التي احتوت على cat و dog وبنفس الأسلوب سيتم البحث عن الصفحات التي تكرر فيها cat وهي 1 و 3 والتي تكرر فيها dog هي 2 و 3 والان سيتم التظر لهذه النتيجة والبحث عن اي صفحة تكررت في القائمتين وهي الصفحة رقم 3\. وبنفس الفكرة الاستعلامات عن الجمل من أكثر من كلتين مثلاً cat the sat والنتيجة هي الصفحة 3 و 1 (يمكنك اكتشاف ذلك بنفسك كتمرين). قد تتسائل بأن هذا الأمر بسيط وهذا يعني أنه يسهل بناء محركات البحث؟ الفكرة اعلاه تجدي نفعاً مع البحث في الاستعلام من كلمة word query أو أكثر multi-words query ولكن لا تنفع لكي تكون محرك بحث مثل المحركات التي نراها اليوم، والسبب ونحصره الان في نقطة واحدة ونركز عليها وهي انه لا يدعم البحث عن الجمل phrase queries. ال Phrase Query هي عندما تبحث عن وجود نص بعينه في الصفحة وليس مجرد البحث عن كلمة هل موجودة في الصفحة أم لأ، وفي أغلب المحركات يكون من خلال كتابة النص بين علامتي التنصيص "هنا"، على سبيل المثال البحث عن "cat dog" يختلف عن البحث cat dog فالأول يبحث عن وجود cat dog بنفس الشكل أم الثاني يبحث عن وجود cat dog في الصفحة بغض النظر عن مكان وجودهم في الصفحة (بأي ترتيب). لو طبقنا هذه الاستعلامات على الصفحات اعلاه سنجد أن الاستعلام cat sat يعطي الصفحات 1 و 3 بينما الاستعلام “cat sat” يعطي الصفحة رقم 1 فقط. كيف حصل محرك البحث على النتيجة عندما تم الاستعلام عن “cat sat” ؟ كما يبدوا اننا سنستخدم نفس الطريقة ونقوم بالبحث عن cat (وتخرج الصفحات 1 و 3) لها وللكلمة sat (تخرج النتيجة 1 و 3) ، هنا سيقع محرك البحث البدائي في المشكلة ، فهو الآن يعلم أن الكلمتين تكررتا في الصفحات 1 و 3 ولكن كيف يعلم أنهم جائا متتابعات وفي الترتيب الصحيح؟ قد تقول يمكن للمحرك أن يذهب وينظر للصفحات نفسها ويتأكد من أن الجملة موجودة بها وقد يعمل هذا ولكن بكل تأكيد الحل غير عملي اطلاقاً لأن المحرك سوف يبحث عن الجملة في كل صفحة كانت تحتوى على عباره من العبارات في الاستعلام ، وتذكر أننا نعمل على مثال صغير مكون من 3 صفحات أي بشكل طبيعي سوف تكون هناك الالاف من الصفحات يحتمل أن ان تحتوى على العبارة كاملة. ## **فكرة اضافة موقع الكلمة في ال** **Index** حل المشكلة السابقة كان مفتاحاً لجعل المحركات تدعم البحث الذي يحتوى على النصوص، فالفهرس لن يحتوى على ارقام الصفحات فقط ولكن ايضاً موقع الكلمة في تلك الصفحات، الصورة التالية تبين الفهرس المكون من 3 صفحات مع مواقع indexes الكلمات في تلك الصفحات. [![الشكل يبين ال3 صفحات بعد فكرة اضافة موقع الكلمة للفهرس](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine3-300x159.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine3.jpg) الشكل يبين ال3 صفحات بعد فكرة اضافة موقع الكلمة للفهرس لاحظ الجدول اسفل الصورة، وستجد a بجانبها 3-5 وهذا يعني أن الكلمة a تكررت في الصفحة الثالثة في الخانه 5\. انظر للكلمة the ستجد أنها تكررت في الصفحة 1 الخانه 1 والصفحة 1 الخانه 5 وهكذا. بما أنه تم اضافة فكرة موقع الكلمة word location trick لكي يتم دعم البحث بالجملة phrase queries، فلنعيد المثال مرة اخرى بهذا التصميم الجديد للفهرس، ونبحث عن الكلمة “cat sat” ؟ اول خطوة كما في السابق هي معرفة كل مواقع كل كلمة على حدة في الفهرس، والكلمة cat تكررت في 1-2 و 3-2 أما الكلمة sat فهي 1-3 و 3-7، والان نحن نعلم ان الصفحات في النتيجة هي 1 و 3، ولننظر للصفحة 1 وستجد أن الكلمة cat تكررت في الخانه 2 والكلمة sat تكررت في الخانه 3 وهكذا تعلم أنها جائت بعدها وهو كما تبحث عنه، هكذا الصفحة 1 احتوت على النتيجة الصحيحة. هكذا بهذه الفكرة word location trick استطعنا دعم ال phrase queries بدون الحاجة لاعادة قرائة الصفحات والبحث مجدداً، اضافة الى أنها تفيد في أمور أخرى مثل ايجاد الكلمات القريبة من بعضها عن طريق الكلمة NEAR (وهي كانت مدعومة في محرك بحث Altavista)، في مثالنا السابق لو بحثنا عن cat NEAR dog ستخرج 1-2 و 3-2 لل cat و 2-2 و 3-6 لل dog وهكذا تعلم أن الصفحة 3 هي النتيجة الوحيدة لهذا الاستعلام والفرق هو 4 اذاً الكلمة dog تأتي بعد 3 كلمات من cat. هكذا بهذه الفكرة دعمنا ال Phrase Queries وال Near Queries والتي لم تعد مهمه هذه الايام في محركات البحث ولا توجد محركات تدعمها هذا بالنسبة لنا كمستخدمين، ولكن من الداخل فان عملية ال Near Queries مهمه خلف الستار ولمعرفة لماذا نحتاج لتسليط الضوء حول المهمه الثانية من مهام محرك البحث وهي ال Ranking. ## **ترتيب النتيجة** **Ranking** **والاستعلام** **NEAR** قليل قليل كنا نتحدث عن مرحلة البحث Matching (أيجاد كل النتائج hits من الاستعلام) وذكرنا أن عملية ال Ranking هي الأساس لأي محرك بحث يحترم نفسه هي هنا يتم ختيار بعض من ال hits حتى يتم عرضها للمستخدم. لكي نقوم بنتاولها بشكل جيد دعنا نبدأ بالحديث عن ال Rank وعلى ماذا يعتمد ؟ والسؤال هو ليس "هل هذه الصفحة تحتوى على الاستعلام" وانما "هل هذه الصفحة مقاربة relevant للاستعلام"؟ في علم الحاسب يتم استخدام Relevance لوصف كيف تبدوا الصفحة متعلقه باستعلام معين. مثال: لتكن انت مهتم بالبحث عن مسببات مرض الملاريا وقمت بالبحث عنها في المحرك بالعبارة malaria cause ولنفرض أن هناك صفحتين احتوت على تلك العبارات موجودة في الصورة أسفل [![الشكل يبين الصفحتين الناتجة من البحث عن مرض الملاريا مع جزء من الفهرس التي تم بنائه](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine4-300x180.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine4.jpg) الشكل يبين الصفحتين الناتجة من البحث عن مرض الملاريا مع جزء من الفهرس التي تم بنائه لو تمعنت النظر في هذه الصفحات ستجد أن الاولى تتحدث عن مرض الملاريا أما الثانية فهي لا علاقه لها بالأمر وانما صدف أن احتوت على تلك الكلمتين وهكذا الصفحة الأولى هى اكثر صلة لما يريده المستخدم relevant من الصفحة الثانية. لكن المشكلة ان الحواسيب ليسوا كالبشر ولا توجد طريقة سهله لكي يفهم هذا النص، فهل هذا يعني انه يستحيل ان يقدر كل من هذه الصفحات Ranking بشل صحيح ؟ لحسن الحظ سوف نستخدم طريقة سهله لعمل ذلك وفكرتها هي أن الصفحة التي تحتوى على الكلمات بجانب بعضهم NEAR تكون اكثر صلة relevant من الصفحات التي تحتوى على الكلمات متباعدة. في مثال الملاريا الصفحة الأولى كانت الكلمتان cause و malaria متتابعتين أما الصفحة الثانية الفرق هو 17 (وتذكر سوف يتم ايجاد ذلك من خلال النظر للindex وليس للصفحات والسبب باستخدام فكرة موقع الكلمة في الفهرس). هكذا حتى لو لم يستطيع الحاسب فهم المحتوى يمكنه تخمين ان الصفحة 1 أقرب للاستعلام من الصفحة 2 لأنها احتوت على العبارات بشكل متقارب. الى هذه اللحظة كنا نتحدث عن صفحات بسيطة جداً، وكما تعلم أن الصفحات في الويب تحتوى تكون بلغه معينة أو لها structure معين مثل العنوان والمحتوى والروابط والصور وغيرها، وسوف ننظر الآن كيف ينظر المحرك لتلك الامور في الصفحة، ولتبسيط الامر سوف نفترض أن الصفحة لها عنوان title ولها محتوى body، الصورة التالية تبين 3 صفحات لها structure. [![searchengine5](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine5-300x37.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine5.jpg) اي صفحة في الويب مكتوبة في الغالب بلغة HTML والتي لها tags معينة لتحديد العنوان والروابط والخ، وسوف نسميها بشكل عام metawords ولننظر للصفحات اعلاه بعد أن قمنا بتحديد تلك ال metawords حتى تكون أقرب للواقع. [![searchengine6](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine6-300x55.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine6.jpg) عندما تنبي محرك البحث فيمكنك اضافه كل ال metawords ايضاً في الفهرس، الشكل التالي يبين اضافه ال metawords في الفهرس للصفحات السابقة وسوف نطلق عليها "فكرة اضافة الmetawords" [![searchengine7](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine7.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine7.jpg) قد تظن أن فكرة اضافة ال metawords ليست ذا فائده ولكنها في الحقيقة عكس ذلك، حيث هي مهمه لتقديم خدمات بحث جيدة . سوف نأخذ مثال ولكن قبله سوف نفرض أن المحرك يدعم البحث من خلال IN ك IN TITLE للبحث في العنوان مثلاً boat IN TITLE للبحث عن الصفحات التي بها boat في العنوان. مثال: اننا نبحث عن dog IN TITLE في الصفحات اعلاه، فأول خطوة هي معرفة نتيجة dog وهي 2-3 و 2-7 و 3-11 وبعدها أخذ نتيجة <titleStart> وهي 1-1, 2-1, 3-1 ونتيجة <titleEnd> وهي 1-4,2-4,3-4 ، النتيجة توجد في الصورة التالية وتجاهل الدوائر والمربعات حالياً: [![searchengine9](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine9.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/searchengine9.jpg) سوف يبدأ المحرك بالنظر لنتيجة dog وينظر هل هي داخل العنوان أم لأ، النتيجة الأولى 2-3 عليها دائرة في الصورة اعلاه وهي أن الكلمة dog تكررت في الصفحة 2 الخانه 3 بعدها يتم فحص <titleStart> عن اي صفحة تبدأ ب 2 وسيجد المحرك أن العنوان في الصفحة 2 وايضاً يبحث في <titleEnd> عن الصفحة 2 وسيجد أنها انتهت في الخانه 4 (عليهم دوائر في الصورة السابقة). الخطوة الاخيرة هي سهله لأن 3 أكبر من 1 وأصغر من 4 اذاً نحن نعلم ان الكلمة dog موجودة في العنوان في الصفحة 2. قم بالنظر الان في الصفحة 3 وستجد أن كلمة dog موجودة في الخانه 11 بينما العنوان بدأ في 1 وانتهي في 4 اذاً الكلمة dog ليست موجودة في عنوان صفحة 3. هكذا نجد أن ال metawords تسمح لمحرك البحث بأن يبحث داخل تركيبة الملف بسهولة، وفي الغالب المستخدمين لا يقوموا بذلك مباشرة ولكن يمكن للمحرك أن يقوم بها داخلياً، مثلاً قمت بالبحث عن dog فيمكن للمحرك أن يقوم بعمل dog IN TITLE من الداخل والسبب أن الصفحات التي تحتوى على dog في العنوان قد تكون أقرب وأكثر صلة لاستعلامك من الصفحات التي تحتوي على dog في الbody. ## خلاصة الIndexing وال Matching هي ليست كل شي في محرك البحث فبناء محركات البحث أمر شاق والمنتج النهائي يحتوي على الكثير من التفاصيل الأخرى مثل الCrawler وغيرها، ما تم استعراضه هنا فكرة word location trick و metawords trick ما هم الا اجزاء من محركات البحث حول كيفية البحث واستخدام الفهرس، وال metawords كانت مساعدة لمحرك البحث AltaVista وقت ظهوره ولكن كما ذكرنا ان البحث والMatching ما هي الا نصف الطريق في محرك البحث وتبقى التحدى الاخر وهو ال Ranking والذي ابدعت في قوقل واخرج محرك Altavista خارج اللعبة وهو ما سيتم تناوله ولكن في المقالة التالية ان شاء الله. ## **المصادر** [Nine Algorithms That Changed the Future](http://www.amazon.com/Nine-Algorithms-That-Changed-Future/dp/0691158193?ref=informatic-ar.com) ### القوائم المتصلة في لغة C URL: https://informatic-ar.com/singly_linked_list_in_c/ Last updated: 2026-10-03T22:31:53.000Z السلام عليكم و رحمة الله و بركاته هذه هي الحلقة الأولى من سلسلة **هياكل البيانات المتقدمة في لغة C** حيث سنتطرق للمواضيع التالية تباعا : Singly Linked List Doubly Linked List Stacks Queues و إذا وجدتُ متسعا من الوقت سأكتب عن : Trees Binary Trees Hash tables Graphs هذه هي المقالة الأولى من هذه السلسلة و البقية تأتي .. **الفهرس** - **الجانب النظري** - تعريف - نبذة تاريخية - قالوا عن الــ Linked List - أيهما أفضل, المصفوفة الديناميكية أم القائمة المتصلة ؟ - مفهوم القوائم المتصلة - **الجانب التطبيقي** (العمليات الأكثر استخداما في القوائم) - مدخل - تهيئة القائمة (إنشاء أول عقدة) - إضافة عقدة جديدة. - حذف عقدة معينة. - حساب طول القائمة. - دمج قائمتين في قائمة واحدة - حذف قائمة - **اختبر قدراتك** **لمن هذه الدروس ؟** هذه الدروس مُوجهة إلى كل من لديه معرفة بأساسيات السي مثل المصفوفات, التراكيب و المؤشرات و يتطلع إلى دراسة هياكل البيانات المتقدمة في لغة السي. **الجانب النظري** **تعريف** القائمة المتصلة عبارة عن مجموعة من العُقد, مُخزنة في الذاكرة بشكل متصل و غير متسلسل و هذا أحد أبرز أوجه الخلاف بين القوائم المتصلة و المصفوفات. **نبذة تاريخية** كانت تُعرف القوائم المتصلة باسم NSS memory و تم تصميمها خلال السنتين 1955-1956, من طرف الثلاثي Allen Newell, Cliff Shaw و Herbert Simon برعاية المؤسسة الأمريكية للبحوث RAND Corporation. كانت القوائم المترابطة هي البُنية الأساسية في لغتهم Information Processing Language (IPL) و كان المخترعون الثلاثة يستخدمون IPL لتطوير مجموعة من برامج الذكاء الاصطناعي, مثل Logic Theory Machine, General Problem Solver بالإضافة إلى Chess (لعبة الشطرنج). نُشرت أعمال الفريق حول القوائم المتصلة في الــ IRE Transactions on Information Theory في سنة 1956 و عُقدت العديد من المؤتمرات خلال الفترة 1957 – 1959 (1). أما التمثيل الحالي للقوائم المتصلة (حيث تتكون القائمة من مجموعة عقد مُرتبطة فيما بينها بواسطة أسهم) فقد تم تنشره في شهر فبراير من عام 1957 تحت عنوان Programming the Logic Theory Machine 2. في عام 1975 حصل الثنائي Allen Newell و Herbert Simon على جائزة Turing لمساهمتهم الفعالة في علم الذكاء الاصطناعي و التعامل مع القوائم. **قالوا عن الــ Linked List** بطبيعة الحال, يُمكن تعريف القوائم المتصلة بأكثر من طريقة لذا اقتطفت لكم بعض التعريفات التي وردت في أهم الكتب المتعلقة بهياكل البيانات: - يقول Seymour Lipschutz في كتابه The data structures (Courses and problems) : "القائمة عبارة عن مجموعة خطية من عناصر البيانات". - بينما يرى John Rast Hubbard في كتابه Java Data Structures أن "القائمة عبارة عن حاوية متسلسلة العناصر و قادرة على إدراج و إزالة العناصر بشكل مطرد محليا, أي بغض النظر عن حجم الحاوية". - أما Alain-Bernard Fontaine فيرى في كتابه The C++ Standard Template Library أن "القوائم عبارة عن حاويات مُخصصة للقيام بعمليات معينة (مثل الإدراج و الإزالة) حيث تتم هذه العمليات في وقت ثابت مهما كان موقع العنصر داخل الحاوية". - و يعتبر الثلاثي Thomas Cormen, Charles Leiserson و Ronald Rivest في كتابهم Introduction to Algorithms أن "القائمة المتصلة عبارة عن هيكل بيانات يتم فيه ترتيب الكائنات بشكل خطي ولكن بخلاف المصفوفات التي تُحدد فيها العناصر عن طريق ترقيم الخانات, يتم تحديد عناصر القائمة المتصلة عن طريق مؤشر في كل كائن" **أيهما أفضل, المصفوفة الديناميكية أم القائمة المتصلة ؟** عادة ما يكون السؤال الذي يطرح نفسه عند المقارنة بين هياكل البيانات, هو: ما مدى سهولة الوصول إلى عناصر المجموعة ؟ و كذا التنقل بين مختلف العناصر و إجراء العمليات الأكثر استعمالا ؟ للإجابة على هذا السؤال قُمنا بحساب التعقيد الزمني للعمليات الأكثر استعمالا: [![1](https://informatic-ar.com/wp-content/uploads/2013/10/19-300x142.png)](https://informatic-ar.com/wp-content/uploads/2013/10/19.png) لإضافة عنصر معين في الخانة رقم i من المصفوفة X يجب إزاحة size-i عنصر حيث size هو طول المصفوفة و بالتالي عملية الإضافة في المصفوفات تستغرق وقتاً لا يُستهان بها و يزداد الأمر سوءا كلما كثُرت العناصر. نفس الشيء يحدث مع عملية الحذف. أما في القوائم المتصلة فعملية الحذف أو الإضافة تأخذ نفس الوقت بغض النظر عن طول القائمة أو المكان الـمُراد إضافة (حذف) العُقدة فيه (منه). نلاحظ أن القوائم المتصلة تكون أسرع في عمليتي الإضافة و الحذف بينما تكون المصفوفة الـمُرتبة أسرع في عملية البحث و يتساوى الاثنان عند المرور على جميع العناصر. **مفهوم القوائم المتصلة** [![2](https://informatic-ar.com/wp-content/uploads/2013/10/22-300x96.png)](https://informatic-ar.com/wp-content/uploads/2013/10/22.png) ذكرنا آنفا أن طريقة تخزين عناصر المصفوفة تختلف عن طريقة تخزين عناصر القائمة إذْ أن عناصر الأولى تُخزَّن في أماكن متتابعة في الذاكرة أما القوائم فلا يُشترط تتابع عناصرها لأن الوصول إلى أي عُقدة من القائمة يتم عن طريق مؤشر في العُقدة السابقة أما المصفوفات فيتم الوصول إلى خاناتها عن طريق الترقيم لذا لزمها تتابع الخانات. القوائم المتصلة تنتمي إلى عائلة الــ Dynamic Data Structures لذا تجد أن عملية حجز الذاكرة تتم أثناء عمل البرنامج باستخدام malloc مثلا. قبل أن ننتقل إلى الجانب التطبيقي, تذكر النقاط التالية جيدا لأنك ستحتاجها لفهم العمليات الأكثر استخداما في القوائم : - تتكون القائمة المتصلة من مجموعة عُقد ترتبط فيما بينها عن طريق المؤشرات. - كل عُقدة تحتوي على جزئين, الجزء الأول يحتوي على بيانات العقدة و الجزء الثاني عبارة عن مؤشر يُشير إلى العقدة الموالية. - للوصول إلى عُقدة معينة, يجب الذهاب دائماً من أول عُقدة. و يتم الانتقال من العقدة الموالية عن طريق المؤشر الموجود في العُقدة السابقة. - دائما ما يُشير المؤشر الموجود في آخر عقدة إلى NULL. - عندما تكون أول عقدة هي آخر عُقدة, نقول أن القائمة فارغة. - في القوائم المتصلة البسيطة (Singly Linked List) تتم الحركة في اتجاه واحد, انطلاقا من العُقدة الأولى باتجاه العقدة الأخيرة. - إذا كنتَ تريد التحرك في كلا الاتجاهين, يُمكنك استخدام القوائم المتصلة الـمُضاعفة (Doubly Linked List). **الجانب التطبيقي** **مدخل** في بقية هذه الفقرة, سنعتبر أن القائمة التي نعمل عليها تحتوي على بيانات مجموعة من الموظفين في شبكة محلية تابعة لشركة مسابقات, كل مُوظف يملك رقم دخول و بريد الكتروني, نفترض أن رقم الدخول يُميز كل موظف عن الآخر بمعنى أنه لا يُمكن أن نجد مُوظفيْن يملكان نفس رقم الدخول. كما أن أرقام الدخول يجب أن تكون أكبر تماماً من الصفر. قامت الشركة بتنظيم المسابقة التالية لموظفيها على النحول التالي: كل مُوظف سيختار حرف عشوائي من A إلى Z. يفوز الموظف إذا تطابق الحرف الـمُختار مع أول حرف من مُعرَّفه (الجزء الموجود قبل @ من البريد الالكتروني) و يخسر في الحالة المعاكسة. الإعلان عن القائمة التي ستضم بيانات الموظفين سيكون هكذا: [![3](https://informatic-ar.com/wp-content/uploads/2013/10/32.png)](https://informatic-ar.com/wp-content/uploads/2013/10/32.png) كلما في الأمر أننا قمنا بالإعلان عن بنية باسم singlyLinkedList تحتوي على 4 عناصر, العناصر الثلاثة الأولى تُمثل بيانات الموظف و العنصر الرابع عبارة عن المؤشر الذي يُشير إلى الموظف الموالي. الخطوة التالية تتمثل في بناء القائمة عن طريق الإعلان عن المؤشر الذي سيُشير لاحقا إلى أول عُقدة, قُمنا بإعطاء اسم مستعار لمؤشر القائمة من أجل تسهيل و تنظيم الكتابة: [![4](https://informatic-ar.com/wp-content/uploads/2013/10/42-300x18.png)](https://informatic-ar.com/wp-content/uploads/2013/10/42.png) **تهيئة القائمة (إنشاء أول عقدة)** نأتي الآن إلى كيفية تهيئة قائمة الموظفين من خلال إنشاء حساب لموظف جديد و إسناد قيم ابتدائية لكافة البيانات: [![5](https://informatic-ar.com/wp-content/uploads/2013/10/52-300x145.png)](https://informatic-ar.com/wp-content/uploads/2013/10/52.png) **ملاحظات هامة:** - إذا كانت P عبارة عن بنية تحوي (مثلاً) عنصرين x و y فإن الوصول إلى عناصر البنية يكون هكذا P.x Or P.y. - إذا كان Q مؤشر لبنية من نوع P, فإن الوصول إلى عناصر البنية يكون هكذا : Q->x Or Q->y. - لغة C لا تدعم التمرير بالمرجع (Pass by reference) على عكس C++. - تستخدم C تمرير المؤشرات الثابتة بدلا من تمرير المراجع لكنني أفضل دائما التمرير بالمرجع و لحسن الحظ, معظم مترجمات C الحالية (الـمُحتكة بــ CPP) تدعمه. بالنسبة للدالة init فتستقبل وسيط واحد عبارة عن مرجع (Reference) لقائمة الموظفين, في السطر الأول قمنا بحجز ذاكرة للمؤشر الذي سيُشير إلى أول عقدة في القائمة, إذا فشلت عملية الحجز ستعيد الدالة false و ينتهي الأمر, أما إذا مرت عملية الحجز بسلام فهذا يدل على أن المؤشر sll أصبح يُشير إلى منطقة من الذاكرة تحوي 4 عناصر (رقم الدخول, الحرف العشوائي, البريد الالكتروني و المؤشر) و في هذه الحالة سنقوم بإسناد قيمة ابتدائية لكل عنصر على النحو التالي: إسناد القيمة 0 إلى المتغير Login و تخزين المسافة البيضاء داخل المتغير randomCharacter و إسناد المؤشر NULL إلى ptrNext أما المتغير email فله حالته الخاصة, لأنه عبارة عن نوع مُركب و ليس نوع بسيط مثل (int, float, char, bool, ..). الأنواع الـمُركبة (مثل المصفوفات و التراكيب ,..) يتم التعامل معها بصفة مختلفة لذا قُمنا باستدعاء الدالة memset لتصفير الذاكرة التي يُشير إليها المؤشر email, الدالة memset تستقبل 3 معاملات, المعامل الأول هو المؤشر الـمُراد تصفير ذاكرته و المعامل الثاني هو القيمة المراد وضعها داخل المنطقة التي يشير إليها المؤشر و المعامل الثالث هو عدد البايتات أو كمية البيانات الـمُؤشر عليها. بعد تهيئة العناصر الأربعة تقوم الدالة بإعادة true كإشارة إلى نجاح العملية. نأتي الآن إلى تضمين المكتبات اللازمة بالإضافة إلى شرح مختصر لمحتوى الدالة الرئيسية : [![6](https://informatic-ar.com/wp-content/uploads/2013/10/63-300x107.png)](https://informatic-ar.com/wp-content/uploads/2013/10/63.png) الدالة printf موجودة في المكبتة stdio.h و الماكرو NULL موجود في المكتبة stdlib.h و الدالة memset موجودة في المكتبة string.h لذا قمنا باستدعاء المكتبات الثلاثة معاً. في بداية الدالة الرئيسية قمنا بالإعلان عن قائمة جديدة و أسندنا لها العنوان NULL وهذه الخطوة ضرورية جدا في تهيئة المؤشرات أيا كانت. بعد ذلك, قمنا بتمرير القائمة mySimpleList إلى الدالة init كوسيط ثم تحققنا من القيمة الـمُعادة من طرف الدالة, إذا كانت false سيتم إظهار رسالة تنبيه على الشاشة و إلا فسيتم إظهار البيانات الابتدائية للموظف. و هذه صورة لمُخرجات الكود : [![7](https://informatic-ar.com/wp-content/uploads/2013/10/72-300x75.png)](https://informatic-ar.com/wp-content/uploads/2013/10/72.png) **إضافة عقدة جديدة** لإضافة عقدة جديدة إلى القائمة, يجب أن نمر بالخطوات التالية: - حجز مساحة من الذاكرة للعقدة الجديدة. - تهيئة كافة عناصر العقدة بما في ذلك المؤشر. - إضافة العقدة و تحديث القائمة. و بالتالي, دالة الإضافة ستكون هكذا: [![8](https://informatic-ar.com/wp-content/uploads/2013/10/82-300x148.png)](https://informatic-ar.com/wp-content/uploads/2013/10/82.png) كالعادة, إذا لم تنجح عملية الحجز ستُعيد الدالة false و إلا فالقيمة الـمُعادة ستكون true, هذا من جهة. من جهة أخرى, تستقبل الدالة 3 وسائط, الأول عبارة عن مرجع (Reference) لقائمة الموظفين و الوسيط الثاني عبارة عن رقم دخول الموظف الجديد و الوسيط الثالث عبارة عن البريد الالكتروني. بطبيعة الحال, من غير المنطقي أن نضع الحرف العشوائي ضمن وسائط الدالة لأن قيمته تعتمد على توليد عشوائي. في البداية, أسندنا قيمة الوسيط L إلى المتغير login ثم قمنا باختيار حرف عشوائي من المصفوفة aplphabet و أسندناه إلى المتغير randomCharacter. بعد ذلك, استخدمنا الدالة strcpy لنسخ محتوى الوسيط em داخل المصفوفة email ثم جعلنا المؤشر الحالي يُشير إلى أول عُقدة في القائمة ثم قمنا بتحديث القائمة من خلال جعل العُقدة الجديدة هي الأولى. نأتي الآن إلى تضمين المكتبات اللازمة بالإضافة إلى تعليق موجز حول الــ main : [![9](https://informatic-ar.com/wp-content/uploads/2013/10/92-300x185.png)](https://informatic-ar.com/wp-content/uploads/2013/10/92.png) **تنويه:** قمتُ بتقسيم الكود الكامل إلى مجموعة فقرات للتوضيح, فمثلا لم أقم بتضمين المكتبات التي قمتُ بتضمينها سابقا للاختصار, لذا تجد أن كل جزء من الكود يكون مُرتبطاً بالجزء السابق. في البداية, قمنا بتضمين المكتبة time.h لوجود كلا من rand و srand. ثم قمنا بالإعلان عن بريد الكتروني باسم mail و استدعينا دالة الإضافة ثم تحققنا من القيمة الـمُعادة كما فعلنا سابقا, إذا تمت إعادة false نُظهر رسالة الخطأ و إلا فسنستدعي الدالة display المسئولة عن إظهار بيانات الـمُوظفين (سنشرح هذه الدالة لاحقاً). مُخرجات الكود ستكون على هذا النحو: [![10](https://informatic-ar.com/wp-content/uploads/2013/10/102-300x129.png)](https://informatic-ar.com/wp-content/uploads/2013/10/102.png) لاحظ أنه تم إدراج العُقدة الجديدة قبل الُعقدة الأولى و هذا ما يُسمى بالإضافة في بداية القائمة, يُمكننا أيضا أن نُضيف العُقدة في نهاية القائمة أو في أي مكان آخر و هنا تكمن أحد أبرز نقاط القوة لدى القوائم و هي المرونة. الصورة التالية تُوضح مفهوم الإضافة في بداية القائمة: [![11](https://informatic-ar.com/wp-content/uploads/2013/10/112-300x195.png)](https://informatic-ar.com/wp-content/uploads/2013/10/112.png) بالنسبة لإضافة العُقدة في نهاية القائمة فستكون هكذا: [![12](https://informatic-ar.com/wp-content/uploads/2013/10/123-300x158.png)](https://informatic-ar.com/wp-content/uploads/2013/10/123.png) لا شيء جديد, سوى إضافة الحلقة while من أجل المرور على كافة العقد وصولا إلى العقدة الأخيرة ثم ربط العقدة الجديدة بنهاية القائمة. بشكل عام, الخطوات الـمُتبعة عند إضافة عُقدة في نهاية القائمة تكون كالآتي: - حجز مساحة من الذاكرة للعقدة الجديدة. - تهيئة كافة عناصر العقدة بما في ذلك المؤشر. - الانتقال إلى آخر عُقدة من القائمة. - ربط آخر عُقدة بالعقدة الجديدة. يُمكننا أيضا إضافة عقدة جديدة في أي مكان من القائمة, لنفترض مثلا أننا نريد إدراج مُوظف جديد بعد موظف آخر يتم تحديد رقمه. في هذه الحالة ستكون الخُطوات الـمُتبعة هي: - البحث عن العُقدة الـمُرافقة لرقم الموظف, توجد حالتان:حجز مساحة من الذاكرة للعقدة الجديدة. - إذا وصلنا إلى NULL فهذا يعني أن رقم الموظف غير موجود, حينها نقوم بإعادة false. - إذا وصلنا إلى العُقدة المطلوبة نقوم بالآتي: - تهيئة كافة عناصر العقدة بما في ذلك المؤشر. - تخزين عنوان العقدة الموالية في متغير مؤقت. - جعل العُقدة الحالية تُشير إلى العُقدة الجديدة. - ربط عنوان العُقدة الجديدة بالمتغير المؤقت. و بالتالي دالة الإضافة ستكون هكذا: [![13](https://informatic-ar.com/wp-content/uploads/2013/10/133-300x159.png)](https://informatic-ar.com/wp-content/uploads/2013/10/133.png) إذا لم تنجح عملية الحجز أو لم يتم العثور على رقم الموظف ستُعيد الدالة false و إلا فستعيد الدالة true بعد أن تتم إضافة العُقدة الجديدة و تحديث القائمة. الآن أصبح من السهل جداً كتابة دالة تقوم بتعديل بيانات مُوظف مُعين, فقط بدلا من إدراج عُقدة جديدة, نقوم بتغيير بيانات العُقدة التي توقفت عندها الحلقة while ما لم تكن تلك العقدة فارغة. نأتي الآن إلى شرح كيفية عمل دالة الإظهار التي تقوم بعرض محتويات قائمة الـمُوظفين: [![14](https://informatic-ar.com/wp-content/uploads/2013/10/144-300x89.png)](https://informatic-ar.com/wp-content/uploads/2013/10/144.png) الفكرة بسيطة جداً و هي كالتالي: حتى لا نفقد بيانات الـمُوظفين نقوم بإنشاء نُسخة من القائمة الـمُرسلة كوسيط, ما دامت العُقدة الحالية غير فارغة نقوم بالتقدم خطوة إلى الأمام بعد أن نُظهر كافة بيانات العُقدة. فقط هذا كل شيء. يُمكننا حساب طول القائمة بنفس الفكرة, فقط بدلا من إظهار البيانات نقوم بزيادة العداد ثم نُعيد قيمة العداد عند الوصول إلى آخر عُقدة. توجد أيضا طريقة أخرى لإظهار محتوى القائمة باستخدام التراجع, سنشرحها لاحقاً. **حذف عقدة مُعينة** [![15](https://informatic-ar.com/wp-content/uploads/2013/10/152-300x281.png)](https://informatic-ar.com/wp-content/uploads/2013/10/152.png) كما فعلنا سابقا مع عملية الإضافة, يمكننا أيضا حذف عُقدة من البداية, النهاية أو أي مكان آخر من القائمة. فمثلا, الدالة التالية تقوم بحذف آخر عُقدة من القائمة : [![16](https://informatic-ar.com/wp-content/uploads/2013/10/162-300x133.png)](https://informatic-ar.com/wp-content/uploads/2013/10/162.png) الخُطوات الـمُتبعة هي: - إنشاء نُسخة من القائمة حتى لا نفقد بيانات الموظفين. - الانتقال إلى العُقدة القبل الأخيرة (نتقدم بخطوة واحدة إذا كانت العقدة الموالية للعقدة الموالية غير فارغة). - تخزين عنوان العقدة الأخيرة في متغير مؤقت وجعل العُقدة القبل الأخيرة تُشير إلى NULL. - تحرير العنوان الذي كان يُشير إلى العُقدة الأخيرة. أما إذا أردنا حذف عقدة من بداية القائمة فستكون الخطوات كما يلي: - تخزين عنوان العقدة الأولى داخل متغير مؤقت. - جعل مؤشر القائمة يُشير إلى العقدة الثانية. - تحرير المؤشر الذي يُشير إلى العقدة الأولى. و بالتالي دالة الحذف في هذه الحالة ستكون هكذا: [![17](https://informatic-ar.com/wp-content/uploads/2013/10/172.png)](https://informatic-ar.com/wp-content/uploads/2013/10/172.png) لاحظ أنه تم التأكد أن القائمة غير فارغة قبل إجراء عملية الحذف و هذا مُهم جدا إذْ يجب التأكد من مثل هذه الحالات قبل القدوم على تنفيذ العملية. لم أتحقق من أشياء كهذه في الدوال السابقة نظراً لأنني افترضتُ أن استدعاء الدوال يجب أن يكون بشكل تتابعي, في هذه الحالة لن نحصل على أي خطأ. أيضاً لم أُرد إرباك القارئ من خلال مُعالجة عدة أخطاء في نفس الوقت. نأتي الآن إلى كيفية حذف عُقدة عن طريقة تحديد رقم الـمُوظف: [![18](https://informatic-ar.com/wp-content/uploads/2013/10/181-300x150.png)](https://informatic-ar.com/wp-content/uploads/2013/10/181.png) إذا كان رقم المُوظف موجود في العُقدة الأولى نقوم باستدعاء الدالة removeTheFirst لأن الانتقال في while يكون بمقدار خطوتين إلى الأمام و بالتالي سيتم تجاوز العُقدة الأولى, هذا من جهة. من جهة أخرى, عند الخروج من while سنكون أمام خيارين, الخيار الأول هو عدم وجود رقم الـمُوظف حينها سنقوم بإعادة false و تنتهي المهمة, إذا تجاوزنا الــ if بسلام فهذا يعني أن رقم الـمُوظف موجود داخل القائمة, لذا سنقوم بتخزين العقدة التي يوجد فيها الرقم في المتغير delNode ثم نُحرر عنوان تلك العُقدة بعد أن نتقدم خطوة واحدة إلى الأمام. **حساب طول القائمة** الفكرة بسيطة جداً وهي كالآتي: إذا وصلنا إلى العقدة الأخيرة نقوم بإعادة الصفر و إلا فهذا يعني أنه توجد عقدة أخرى (و هي العُقدة الحالية) بالإضافة إلى الُعقد الموجودة في بقية القائمة (إن وُجدت). و بالتالي دالة حساب الطول ستكون هكذا: [![19](https://informatic-ar.com/wp-content/uploads/2013/10/191-300x31.png)](https://informatic-ar.com/wp-content/uploads/2013/10/191.png) قبل أن ننتقل إلى الفقرة المُوالية, ستقوم بكتابة الدالة التي من خلالها نستطيع معرفة فوز أو خسارة مُوظف مُعين: [![20](https://informatic-ar.com/wp-content/uploads/2013/10/20-300x45.png)](https://informatic-ar.com/wp-content/uploads/2013/10/20.png) ينجح الـمُوظف إذا تساوى أول حرف من بريده الالكتروني مع الحرف العشوائي الخاص به و يخسر في الحالة الـمُعاكسة. **دمج قائمتين في قائمة واحدة** دالة الدمج تستقبل وسيطين يُمثلان القائمتين الـمُراد دمجهما : [![21](https://informatic-ar.com/wp-content/uploads/2013/10/211-300x115.png)](https://informatic-ar.com/wp-content/uploads/2013/10/211.png) إذا كانت القائمة الأولى فارغة, فهذا يعني أن دمج القائمتين يُعطي القائمة الثانية لذا تمت إعادتها. في الحالة الـمُعاكسة نقوم بالانتقال إلى آخر عُقدة من القائمة الأولى ثم نربطها بأول عُقدة من القائمة الثانية. **حذف القائمة** دالة الحذف فكرتها كالآتي : ما دامت القائمة غير فارغة, نقوم بحذف العقدة الأولى. فقط ! [![22](https://informatic-ar.com/wp-content/uploads/2013/10/221-300x89.png)](https://informatic-ar.com/wp-content/uploads/2013/10/221.png) **اختبر قدراتك** قررت الشركة كتابة برنامج صغير يُساعدها في تسيير الـمُوظفين على مرحلتين: - ترتيب الـمُوظفين تصاعدياً حسب أرقام الدخول. - تقسيم قائمة الـمُوظفين إلى قائمتين, الأولى تحتوي على كافة الـمُوظفين الذين فازوا في المسابقة و القائمة الثانية تحتوي على بقية الـمُوظفين. قم بكتابة دالة خاصة بكل مرحلة ثم قم باختبار الدوال في برنامج رئيسي موجود في ملف مستقل. إلى هنا أصل بك أخي القارئ إلى نهاية هذا الدرس, آمل أن تكون استفدت من هذا الدرس و تعلمتَ أشياء جديدة. تحياتي. --- (1) Proceedings of the Western Joint Computer Conference en 1957 et 1958 et Information Processing en 1959 (première réunion de l'International Conference on Information Processing de l'UNESCO) (2) Programming the Logic Theory Machine de Allen Newell et Cliff Shaw, Proceedings of the 1957 Western Joint Computer Conference, février 1957. ### نظرة سريعة حول Lucene URL: https://informatic-ar.com/lucene_basics/ Last updated: 2026-10-03T22:44:57.000Z العديد من البرامج التي نستخدمها بشكل يومي تستخدم خوارزميات سريعة في البحث قد لا ندري عنها، فبدئاً من محرك البحث في على الويب Google أو MSN Search والتي تخرج لك النتائج في غضون ثانية واحدة وأيضا برامج البحث في نظام التشغيل لديك Desktop Search Engine والتي تبحث في الملفات الموجودة لديك على النظام وتخرج لك نتيجة بسرعة جيدة ، وإنتهائاً ببرامج البحث في التفاسير والمعاجم وفي ارشيف المكتبات ، وغيرها كثير من التطبيقات تتطلب الحصول على النتيجة في اسرع وقت ممكن.. هذه الخوارزميات نتطوي تحت مجال كبير يسمى [Information Retrieval](http://en.wikipedia.org/wiki/Information%5Fretrieval?ref=informatic-ar.com "رابط خارجي") (اختصارا IR) وهو يهدف للبحث عن المعلومات سواء داخل الملفات أو البحث عن الملفات نفسها والبحث في شبكة الانترنت World Wide Web وأيضا البحث داخل قواعد البيانات العلائقية RBDMS، وأشهر الأمثله على الIR هي محركات البحث في الويب Web Search Engine. الفكرة من وراء السرعة في جميع تلك البرامج ومحركات البحث هي ال **index** ، فعند بناء للIndex تستطيع من خلاله الوصول لما تريد من الملفات التي قمت بعمل index لها بسرعه كبيرة، لنأخذ مثالا في الحياة اليومية على استخدام الindex، فعندما تريد البحث عن كلمة معينه داخل كتاب ورقي كبير مكون من 1000 صفحة، فهل ستقوم بالبحث سطر بسطر من أول صفحة في الكتاب آخر صفحة؟ بالطبع حل غير عملي.. ولكنك يمكنك الحصول على الصفحة التي وجدت فيها هذه الكلمة من خلال ال index الموجود في نهاية كل كتاب. [![ir_1](https://informatic-ar.com/wp-content/uploads/2013/10/ir_1-300x155.png)](https://informatic-ar.com/wp-content/uploads/2013/10/ir%5F1.png) هكذا اذا أردت البحث عن كلمة Ant ستنتقل الى الصفحة التي تحتوي جميع الكلمات التي بدأت بالحرف A ، وبنظره سريعه ستجد الكلمة Ant والصفحات التي تتكررت فيها، فهنيئاً لك . الindex في عالم الحوسبة لا يختلف كثيرا عن index الموجود في الكتب، فهو أيضا يتكون من جميع الكلمات التي تتكررت وعدد تكرارها والمسارات التي تتكرت فيها تلك الكلمات، وبالتالي عند بحثك على الكلمة فسوف تخرج لك مباشره النتيجة عن طريق البحث في الجدول الذي يحتوي على الكلمة واستخراج الموقع الذي وجدت فيه تلك الكلمة.. الجميل في الcomputer indexing هو في أنك يمكنك تخزين أي معلومات تريدها في الindex وليس فقط مسار الملف، يمكنك تخزين معلومات الملف metadata ، يمكنك تخزين حجمه size والامتداد وأي معلومات تريدها.. وسوف تستفيد من ذلك حتما في البحث، حيث يمكنك البحث عن الملفات التي ألفها محمد بالامتداد txt والتي احتوت كلمها سي++ في التاريخ ما بين 2004 الى 2006 ، استعلام جميل أليس كذلك! وسوف تحصل على نتائج سريعة جدا ربما أقل من نصف ثانية! تركيبة الindex هي ما تجعل التطبيقات الأخرى قابلة للبحث فيه بكل سرعه، لحسن الحظ أو سوء الحظ (تختلف باختلاف حالتك) لن نتطرق هنا الى بناء هذه الdata structure ولا تفاصيلها، ولكن سنقوم بالاستفاده من جميع خصائصها من خلال المكتبة Lucene، وقتا ممتعاً . **ما هي [Lucene](http://lucene.apache.org/?ref=informatic-ar.com "رابط خارجي") ؟** هي مكتبة IR عالية الأداء، مفتوحة المصدر، من خلالها تستطيع اضافة خاصية البحث السريع لبرامجك ، بدأت كغيرها في من المشاريع على sourceforge وانتهت أيضا كغيرها من المشاريع القوية تحت مظلة Apache Software Foundation والتي لا تألوا جهدا في ضم أي مشروع تحت لوائها تتشم فيه رائحة التميز . Lucene (وهي اسم زوجة مبرمج المكتبة Doug Cutting والأخير معروف بخبرته في مجال الIR عموما، حاليا اطلق أول محرك بحث مفتوح المصدر [Nutch](http://nutch.apache.org/?ref=informatic-ar.com "رابط خارجي") لا تتفاجئ حين تعرف ان المحرك يستخدم لوسين أيضا :) ). كما في بقية المكتبات فإن Lucene تقدم لك Abstraction جيد في عملية الindexing والSearching وتقدم لك API سهله الاستخدام، هذا الAbstraction يعني أنه بامكانك استخدام lucene في برامجك بدون أن تكون لديك معرفة بألية عمل الindexing والsearching وسوف تحصل على أداء عالي منها. العديد من المشاريع كتبت بالاعتماد على lucene منها Nutch و SearchBox و jSearch وغيرها الكثير الكثير والقائمة طويلة: [http://wiki.apache.o...-java/PoweredBy](http://wiki.apache.org/lucene-java/PoweredBy?ref=informatic-ar.com "رابط خارجي") اضافة للعديد من الporting للغات الأخرى ، فسوف تستطيع استخدام lucene الان اذ كنت مبرمج java , C++, .NET, python , Perl .الجميل في الأمر انك تستطيع الوصول لindex كتب بلغة أخرى من خلال لغتك المختلفة عن اللغة التي كتب بها الIndex. الصورة التالية توضح عن كيف سيتفاعل تطبيقك مع lucene. [![ir_2](https://informatic-ar.com/wp-content/uploads/2013/10/ir_2-300x239.png)](https://informatic-ar.com/wp-content/uploads/2013/10/ir%5F2.png) كما هو من الصورة فانك تستطيع ان تقوم بعمل Index للملفات من جهازك أو بيانات مدخله من المستخدم أو صفحة ويب أو حتى قاعدة بيانات وبعد ذلك يمكنك البحث عنها بالطريقة التي تريد، حيث تدعم lucene استعلامات متقدمة سواء بالعلامات المنطقية and or أو حتى باستبعاد كلمة معينة من خلال - + وغيرها من أنواع البحث مثلا Fuzzy Search. لكن يجب ملاحظة أن جميع ما يمكن وضعه في الindex يجب أن يكون على صيغه نصوص Textual information، واذا أردت عمل index لملفات بصيغة ثنائية مثلا pfd, word, pst, ppt يجب ان تقوم باستخراج النصوص من الملف أولا بنفسك (Lucene لن تقوم بعمل parsing لك) وبعدها يمكنك ان تدخله في الindex . أيضا ملفات الhtml عليك باستخراج النصوص من الtags اذا أردت أن يكون البحث من خلال النصوص داخل الtags (بمعنى تستخدم html parser لكي تستخرج النصوص).. وهكذا لجميع الصيغ عليك أن تستخدم parser مناسب من خلال تحصل على الtextual information حتى تضعه داخل الindex. لنأخذ مثال على البحث في جميع الملفات النصية txt داخل القرص G ، قم أولا بتحميل lucene وضع ملف الjar في مسارك الذي تعمل عليه، ثم شغل البرنامج التالي: ```java // indexer example in java lucene import org.apache.lucene.index.IndexWriter; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import java.io.File; import java.io.IOException; import java.io.FileReader; import java.util.Date; public class Indexer { public static void main(String[] args) { try { if (args.length != 2) { System.out.println("error in usage , use : java Indexer <indexDir> <dataDir>"); return; } File indexDir = new File(args[0]); File dataDir = new File(args[1]); long start = new Date().getTime(); int numIndexed = index(indexDir, dataDir); long end = new Date().getTime(); System.out.println("Indexing : " + numIndexed + " take : " + (end - start) + " Millisecond(s)"); } catch (IOException e) { e.printStackTrace(); } } public static int index(File indexDir, File dataDir) throws IOException { if (!dataDir.exists() || !indexDir.exists()) throw new IOException("not found one of the arguments"); IndexWriter writer = new IndexWriter(indexDir, new StandardAnalyzer(), true); writer.setUseCompoundFile(false); indexDirectory(writer, dataDir); int numIndexed = writer.docCount(); writer.optimize(); writer.close(); return (numIndexed); } public static void indexDirectory(IndexWriter writer, File indexDir) throws IOException { File[] files = indexDir.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { File f = files[i]; if (f.isDirectory()) { indexDirectory(writer, f); } else if (f.getName().endsWith(".txt")) { indexFile(writer, f); } } } } public static void indexFile(IndexWriter writer, File f) throws IOException { if (f.isHidden() || !f.exists() || !f.canRead()) return; System.out.println("indexing: " + f.getCanonicalPath()); Document doc = new Document(); doc.add(Field.Text("contents", new FileReader(f))); doc.add(Field.Keyword("filename", f.getCanonicalPath())); writer.addDocument(doc); } } ``` لاحظ عليك تمرير قيمتين للبرنامج الأولى المجلد الذي سيحتوي على الindex ، والثاني على مسار المجلد الذي تود عمل indexing له حتى تستطيع البحث فيه ، عن نفسي نفذت الأمر: ```java java indexer I :\muindex G :\ ``` هكذا سيتم عمل index للقرص G ويتم وضع تلك البيانات في I:\\myindex . بعد انتهائك من عمل الindex والتي قد تأخذ وقتا طويلا (هذا يعتمد على حجم الملفات التي تود عمل index لها ) ولكنها ستكون بالطبع لمرة واحدة هذه العملية.. بالنسبة لي انتهت عملية البحث في 6010 ميلي ثانية ل76 ملف نصي. بالرغم من أن عملية الIndex طويلة نوعا ما (لكن قد تسرع باستخدام أكثر من thread) ، الا أن عملية البحث تكون سريعه للغاية ، شغل المثال التالي لكي تقوم بالبحث: ```java // Searcher in Index Files import org.apache.lucene.document.Document; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.Query; import org.apache.lucene.search.Hits; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.Directory; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.analysis.standard.StandardAnalyzer; import java.io.File; import java.util.Date; public class Searcher { public static void main(String[] args) throws Exception { try { if (args.length != 2) { System.out.println("error in usage , use: java Searcher indexDir QueryString"); return; } } catch (Exception e) { e.printStackTrace(); } File indexDir = new File(args[0]); String queryString = args[1]; if (!indexDir.exists() || !indexDir.isDirectory()) throw new Exception("directory is not found"); search(indexDir, queryString); } public static void search(File indexDir, String queryString) throws Exception { Directory fsDir = FSDirectory.getDirectory(indexDir, false); IndexSearcher is = new IndexSearcher(fsDir); Query query = QueryParser.parse(queryString, "contents", new StandardAnalyzer()); long start = new Date().getTime(); Hits hits = is.search(query); long end = new Date().getTime(); System.out.println("Found: " + hits.length() + " document(s) (in " + (end - start) + " Millisecond(s) ) that matched Query" + " '" + queryString + "':"); for (int i = 0; i < hits.length(); i++) { Document doc = hits.doc(i); System.out.println(doc.get("filename")); } } } ``` عليك بتمرير معاملين للدالة main ، الاولى مكان الindex والاخرى الكلمة التي تريد البحث عنها ولاحظ النتيجة تكون سريعه جدا، في تجربتي استغرقت 6 ميلي ثانية: [![ir_3](https://informatic-ar.com/wp-content/uploads/2013/10/ir_3-300x151.png)](https://informatic-ar.com/wp-content/uploads/2013/10/ir%5F3.png) يمكنك أن تقوم الأن بالبحث في هذا الindex ملايين المرات والسرعة كما هي بالطبع.. في البداية قد تدفع ثمن عمل index خصوصا للأقراص الضخمة ، لكن حينها البحث سيكون سريع جدا جدا. هكذا يعمل أغلب محققي الجرائم الالكترونية والبرامج التي يستخدموها، حيث يقوموا بأخذ نسخه من الهارديسك image (يقوموا بأخذ أكثر من نسخه في الحقيقة).. بعد ذلك يقوموا بعمل index لجميع الملفات (txt,pdf,html,,,etc) ومن ثم يتم البحث فيها باستخدام keywords معينة أو باستخدام predefined list .. هذه احدى تطبيقات الindex الجيدة بالمناسبة :). المشكلة الان أن الindex لا يتعرف الا على النصوص فقط، هذه ليست مشكلة ففي جافا توجد مكتبات لأي شيء.. html parser و pdf parser و xml parser ، بل في الحقيقة يوجد الكثير جدا من المكتبات، جرب أن تبحث حول HTML parsers في جافا وستجد على الأقل 8 مكتبات! فهم المكتبة المناسبة واستخدامها أمر مهم أيضا سنتطرق له ان شاء الله فيما بعد.. حاليا lucene النسخة الجديدة اصبحت تدعم ملفات pdf (تستخدم pdfparser نيابة عنك) .. وربما كانت الخطة لمطوري lucene أن يتم استخدام جميع تلك الparsers حتى يتم التسهيل على المطورين. ولكن هنا حدث تطور جيد حيث انبثق مشروع جديد داخل Lucene اطلق عليه [Tika](http://tika.apache.org/?ref=informatic-ar.com "رابط خارجي") ، من خلال هذه المكتبة يمكنك ان تقوم بعمل index في lucene لأي ملف تريد ، حيث تدعم هذه المكتبة أكثر من 15 format للملفات.. بالتالي عند استخدامك للTika فهي تقوم بمعرفة نوع الملف ثم اختيار الparser المناسب (الجميل في الأمر أن التحقق من الملف يتم عن طريق الFile Signature وليس امتداد الملف).. المكتبة Tika حديثة نسبيا ولا يوجد حتى ملف jar جاهز للاستخدام.. فعليك ببناء المكتبة بنفسك باستخدام Meaven اذا أردت.. Tika كانت مجرد البداية، تولدت الأن من lucene المكتبة [solr](http://lucene.apache.org/solr/?ref=informatic-ar.com "رابط خارجي") والتي تستطيع استخدامها من خلال تطبيق الويب ، حيث تقدم لك خدمات lucene على طبق من ذهب (indexing + searching) وخدمات Tika الوليدة أيضا على طبق من فضة (indexing many files foramt) .. أعتقد أن مبرمجي الويب سعيدين جدا بهذه الاضافة! النهاية لم تكن بSolr ، فالملكة [mahout](http://mahout.apache.org/?ref=informatic-ar.com "رابط خارجي") قد نضجت الان تحت lucene وهي مكتبة data mining و Collaborative Filtering ، فمن خلالها تستطيع عمل عمل clustering للبيانات الموجودة في الindex ،و يمكنك فلترتها بطرق الذكاء الاصطناعي.. كانت هذه لمحة سريعه حول مشروع lucene وبناتها، أمل ان تكون قد استحوذت هذه المكتبة على انتباهك.. > كتب الموضوع في نسخه لوسين 3 والا وصلنا لنسخه 4.5 ولكن المفهوم والفكرة واحدة. ### خوارزمية Sieve of Eratosthenes URL: https://informatic-ar.com/sieve_of_eratosthenes/ Last updated: 2026-10-03T22:31:54.000Z تعتبر خوارزمية Sieve of Eratosthenes من أسهل الخوارزميات لإيجاد الأعداد الأولية Prime Numbers ضمن مجال معين من الأعداد الصحيحة n ، حيث تقوم فكرتها على حذف مضاعفات الأعداد داخل هذا المجال ، حيث أن تعريف الأعداد الأولية يستبعد وجود الأعداد التي يمكن إيجادها كحاصل ضرب عددين. الخوارزمية من [wikipedia](http://en.wikipedia.org/wiki/Sieve%5Fof%5FEratosthenes?ref=informatic-ar.com) 1\. Create a list of consecutive integers from two to n: (2, 3, 4, ..., n). 2\. Initially, let p equal 2, the first prime number. 3\. Strike from the list all multiples of p greater than p. 4\. Find the first number remaining on the list greater than p (this number is the next prime); let p equal this number. 5\. Repeat steps 3 and 4 until p^2 is greater than n. 6\. All the remaining numbers on the list are prime. سنوضح الخوارزمية عبر مثال بسيط لإيجاد الأعداد الأولية من 1 إلى 100. (**ملاحظة**: العدد 1 لا يعتبر عدد أولي حيث أنه لا يوجد عددين مختلفين يقسما العدد 1) : في البداية سننشئ مصفوفة من الأعداد : [![eratosthenes-ex-0](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-0-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-0.png) سنبدأ من العدد الأولي 2 ، وسنقوم بإزالة كل مضاعفاته ابتداءا من 4 : [![eratosthenes-ex-1](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-1-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-1.png) ملاحظة: اللون الاخضر يعني عدد أولي ، أما الأحمر فهو ليس أولي. العدد 3 هو أولي ، وسنحذف كل مضاعفاته ابتداءا من 9 : [![eratosthenes-ex-2](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-2-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-2.png) نفس الفكرة مع العدد 5 : [![eratosthenes-ex-3](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-3-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-3.png) وكذلك العدد 7: [![eratosthenes-ex-4](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-4-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-4.png) العدد التالي الآن هو 11 ، لكن سنتوقف هنا لأن مربع العدد 11 أكبر من المجال المطلوب 100. [![eratosthenes-ex-5](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-5-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-5.png) إذاً الأعداد الأولية من 1 إلى 100 هي : [![eratosthenes-ex-6](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-6-269x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/eratosthenes-ex-6.png) مثال آخر : [![Animation_Sieve_of_Eratosth-2-ca](https://informatic-ar.com/wp-content/uploads/2013/10/Animation_Sieve_of_Eratosth-2-ca-300x248.gif "الصورة متحركه قم بالضغط عليها لمشاهدتها")](https://informatic-ar.com/wp-content/uploads/2013/10/Animation%5FSieve%5Fof%5FEratosth-2-ca.gif) الصورة متحركه قم بالضغط عليها لمشاهدتها **تطبيق الخوارزمية :** هذا تطبيق بسيط للخوارزمية باستخدام سي++ ، والفكرة مشابهة تماما للمثال في الأعلى حيث سأنشئ مصفوفة من 100 عنصر ، وسأضع جميع القيم فيها مساوية للواحد دلالة على أن كل العناصر هي أولية ، وبعدها عندما نجد عدد مركب Composite number وليكن i سنجعل قيمة العنصر i هي الصفر دلالة على أنه تم حذف العنصر. ```cpp #include #include #include using namespace std; void sieveOfEratosthenes(int N) { int *array = new int[N + 1]; for (int i = 2; i <= N; ++i) array[i] = 1; array[1] = 0; for (int i = 2; i <= sqrt(N); ++i) { if (array[i] == 0) continue; for (int j = i * i; j <= N; j += i) { array[j] = 0; } } for (int i = 2; i <= N; ++i) if (array[i]) cout << i << " "; cout << endl; delete[] array; } int main(int argc, char *argv[]) { if (argc == 2) sieveOfEratosthenes(atoi(argv[1])); else sieveOfEratosthenes(100); return 0; } ``` **شرح بسيط :** ```cpp int *array = new int[N + 1]; for (int i = 2; i <= N; ++i) array[i] = 1; array[1] = 0; ``` هنا تم انشاء المصفوفة وتعيين القيمة 1 لكل الأعداد دلالة على أنها أولية ، أما العدد 1 فتم تعيين القيمة 0 له دلالة على أنه ليس عدد أولي. ```cpp for (int i = 2; i <= sqrt(N); ++i) { if (array[i] == 0) continue; for (int j = i * i; j <= N; j += i) { array[j] = 0; } } ``` لا يوجد داعي لأن نمر على جميع الأعداد من 1 الى 100 ! سنبدأ من العدد 2 لأن العدد 1 قد ذكرنا بأنه ليس أولي ، وسننتهي عند العدد 10 (تأتي من تطبيق الجذر التربيعي على n) لأنه عنما تعمل الخوارزمية على العدد 2 و 3 و 5 و 7 و9 ستجد أننا قد مررنا على كل الأعداد (المضاعفة) في هذا المجال. في البداية سيتم اختبار هل العدد ليس أولي ؟ إذا كانت الاجابة لا فهذا يعني أن العدد أولي ويجب معرفة مضاعفاته لكي نقوم بازالتها . أما إذا كانت الإجابة نعم فهذا يعني أن هذا العدد مضاعف Composite number وبالتأكيد مضاعفات هذا العدد هي ليس أولية وقد تم ازالتها من قبل. **نتيجة البرنامج :** 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 **وهذا تطبيق آخر للدالة :** ```cpp void sieveOfEratosthenes(int N) { int *array = new int[N + 1]; for (int i = 2; i <= N; ++i) array[i] = 1; array[1] = 0; for (int i = 2; i <= N / 2; ++i) for (int j = 2; j <= N / i; ++j) { cout << i * j << endl; array[i * j] = 0; } for (int i = 2; i <= N; ++i) if (array[i]) cout << i << " "; cout << endl; delete[] array; } ``` **مصادر:** [Wikipedia](http://en.wikipedia.org/wiki/Sieve%5Fof%5FEratosthenes?ref=informatic-ar.com) [Prime\_number](http://en.wikipedia.org/wiki/Prime%5Fnumber?ref=informatic-ar.com) [Sieve\_of\_Eratosthenes](http://www.algolist.net/Algorithms/Number%5Ftheoretic/Sieve%5Fof%5FEratosthenes?ref=informatic-ar.com) كتاب Algorithms in C. الكود بالمرفقات. [printprime.cpp.tar](https://informatic-ar.com/wp-content/uploads/2013/10/printprime.cpp%5F.tar.gz) ### جافا دائماً Passing by Value URL: https://informatic-ar.com/java_passing_by_value/ Last updated: 2026-10-03T22:31:54.000Z ## طريقة تمرير المتغيرات في جافا Java من المفاهيم الأساسية في مفهوم الدوال Functions/Methods في لغات البرمجة هو تمرير المتغيرات أو المعاملات للدوال Parameter Passing، وبشكل عام هناك طريقتين للتمرير المعاملات الأولى هي التمرير بالقيمة Pass by Value والثانية هي Pass by Reference. هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا - [الإسلوب الصحيح لكتابه Utility Classes](https://informatic-ar.com/utility%5Fclasses/) - [هل مللت من دالة البناء Constructor ؟](https://informatic-ar.com/constructor-vs-factory/) - [استخدام ال Builder Pattern في دالة البناء](https://informatic-ar.com/builder%5Fpattern/) - [لا تستخدم Public الا وقت الحاجة](https://informatic-ar.com/inaccessible%5Fclass/) - [كيف تصمم الPackages جيداً في تطبيقات جافا](https://informatic-ar.com/packages%5Fin%5Fjava/) - جافا دائماً Passing by Value (المقال الحالي) - [نظرة حول الدالة Equals](https://informatic-ar.com/java-object-equals/) - [خارطة طريق لتعلم الجافا](https://informatic-ar.com/how%5Fto%5Flearn%5Fjava/) ## الفرق بين التمرير بالقيمة By Value والتمرير بالمؤشر By Reference عند استخدام النوع الأول Pass by Value سوف يتم نسخ القيمة من المتغير أو الكائن المرسل الى المعامل الموجود في الدالة التي تم استدعائها ، وأي تغيير يتم اجرائه على هذا المعامل فلن يتأثر المتغير الأول والسبب أنه تم تمرير القيمة فقط . أما عند استخدام النوع الثاني Pass by Reference فسوف يتم ارسال عنوان Pointer/Reference المتغير أو الكائن الى المعامل الموجود في الدالة ، وسوف يكون هذا المعامل يؤشر للمتغير أو الكائن وبالتالي أي تغيير يتم اجرائه على المعامل سوف يتغير المتغير أو الكائن تبعا لذلك ، لأنهم الإثنين يؤشران لنفس المنطقة في الذاكرة . تقريبا أي مبرمج ولو حتى مبتدئ يعرف هذا الكلام بشكل جيد ، **ولكن هناك فهم غير دقيق misconception خاصه عند خلط الحديث بين التمرير في لغه مثل سي/سي++ مع الجافا** ، ومصدر هذه المشكلة هو أنه في لغه سي++ يمكنك تنشيء كائن object يوجد في المكدس Stack أو يمكنك أنشاء الكائن في الHeap وتتعامل معه من خلال Pointer أو Reference . بينما في الجافا دائما الكائنات تتواجد في ال Heap وتتعامل معها من خلال الReference. مثال : ```java // in C++ Student st("Ahmed", 15, 80); // this object created in stack Student *st2 = new Student("Ahmed", 15, 80); // this object created in heap // in Java Student st3 = new Student("ahmed", 15, 80); // this object created in heap ``` بالنظر الى المثال السابق سنجد في مثال سي++ أن st يسمى كائن Object وst2 يسمى مؤشر لكائن Pointer to Object ، بينما في مثال الجافا فإن الst3 يسمى Reference to Object حيث أن الكائن موجود في الذاكرة Heap و st3 هو مجرد مؤشر لتلك المنطقة . نأتي الأن لموضوع التمرير Passing ، ففي لغات سي\\سي++ فهي تسمح بأن يتم ارسال المتغير أو الكائن من خلال طريقتين Value or Reference ، بينما في الجافا يتم دائما الإرسال بالقيمة سواء لمتغير أو لReference to Object . بالتالي وبسبب هذا النوع من التمرير فإنه: 1. لا يمكن أن تغير الدالة قيمة أي primitive مرسل . 2. تستطيع الدالة تغيير أحد المتغيرات الموجودة في الكائن المرسل (أقصد المؤشر للكائن المرسل) . 3. لا تستطيع الدالة تغيير المؤشر المرسل وجعله يؤشر لكائن أخر . الأمثله التالية خير برهان للنقاط أعلاه ،، والمثال الأول سوف يثبت صحة النقطة الأولى ### **لا يمكن أن تغير الدالة قيمة أي primitive مرسل** ```java // Test Passing primitive varaible in Java public class TestPassing { public static void main(String[] args) { int x = 4; System.out.println("Before Calling ChangeX x = " + x); ChangeX(x); System.out.println("After Calling ChangeX x = " + x); } public static void ChangeX(int x) { x = x * 2; System.out.println("in ChangeX x = " + x); } } ``` وكما هو واضح من المخرج التالي : [![passing_1](https://informatic-ar.com/wp-content/uploads/2013/10/passing_1-300x121.png)](https://informatic-ar.com/wp-content/uploads/2013/10/passing%5F1.png) فإن قيمة x لم تتغير بعد استدعاء دالة التغيير ، وهكذا تم اثبات النقطة الأولى . ### **تستطيع الدالة تغيير أحد المتغيرات الموجودة في الكائن المرسل (أقصد المؤشر للكائن المرسل)** ```java // Test Change Object state through object reference public class TestPassing2 { public static void main(String[] args) { Student st = new Student("Ahmed", 20); System.out.println("Before Call ChangeStudent st : " + st); ChangeStudent(st); System.out.println("Before Call ChangeStudent st : " + st); } private static void ChangeStudent(Student st) { st.setAge(100); System.out.println("in ChangeStudent st : " + st); } } class Student { public Student(String name, int age) { this.name = name; this.age = age; } public void setAge(int age) { this.age = age; } public String toString() { return String.format(name + " , " + age); } private String name; private int age; } ``` وكما هو واضح من المخرج التالي: [![passing_2](https://informatic-ar.com/wp-content/uploads/2013/10/passing_2-300x119.png)](https://informatic-ar.com/wp-content/uploads/2013/10/passing%5F2.png) ### **لا تستطيع الدالة تغيير المؤشر المرسل وجعله يؤشر لكائن أخر (مصدر الخلل)** ```java // Test Swap reference and varaible public class TestPassing3 { public static void main(String[] args) { int x = 5, y = 10; System.out.println("Before Calling SwapVar : x = " + x + " y = " + y); SwapVar(x, y); System.out.println("After Calling SwapVar : x = " + x + " y = " + y); System.out.println("\n\n"); Student s1 = new Student("Ahmed", 10); Student s2 = new Student("Ali", 20); System.out.println("Befor Calling SwapReference : " + s1 + " " + s2); SwapReference(s1, s2); System.out.println("After Calling SwapReference : " + s1 + " " + s2); } public static void SwapVar(int x, int y) { int tmp = x; x = y; y = tmp; System.out.println("in SwapVar : x = " + x + " y = " + y); } public static void SwapReference(Student st1, Student st2) { Student tmp = st1; st1 = st2; st2 = tmp; System.out.println("in SwapReference : " + st1 + " " + st2); } } class Student { public Student(String name, int age) { this.name = name; this.age = age; } public void setAge(int age) { this.age = age; } public String toString() { return String.format(name + " , " + age); } private String name; private int age; } ``` وكما هو واضح من المخرج التالي : [![passing_3](https://informatic-ar.com/wp-content/uploads/2013/10/passing_3-300x143.png)](https://informatic-ar.com/wp-content/uploads/2013/10/passing%5F3.png) فأنه لا يمكن عمل swap بين المتغيرات والسبب أننا من الأساس لا يمكن تغيير قيمة أي Primitive type (النقطة الأولى) ، بالنسبة للSwap الثانية بين الكائنات فالذي حصل هو أنه تم تمرير عنواين الكائنات بالقيمة pass by value للدالة swapReference ، وبعدها تم تغيير قيم المعاملات وجعل كل منها يؤشر للأخر ولكن هذا لن يؤثر في المؤشرات في الدالة main والسبب أنه تم ارساله العنواين بالقيمة . ومن هنا كانت العبارة > Java Always passing by Value ### منع تشغيل أكثر من نسخه من برنامجك URL: https://informatic-ar.com/no_multiple_instance_java/ Last updated: 2026-10-03T22:31:55.000Z في بعض الأحيان قد نجبر المستخدم على عدم فتح أكثر من نسخه من البرنامج ، وخاصه في البرامج التي تتصل مع قاعده البيانات والتي لا تقوم بعمل Commit الا مثلا عند الخروج من البرنامج أو أي برنامج أخر مثلا مثل برامج المنبهات والتي تعمل كل مده معينه .. أو لأي سبب أخر يريده المبرمج. ولأن أي برنامج نقوم بتشغليه فسوف يعمل على VM خاصه به ، لذلك لا توجد طريقه مباشره لهذا الأمر وسوف نستعرض اشهر طريقتين ، أحدهما عن طريق socket ، والأخرى عن طريق FileLocking . **الطريقه الأولى The socket technique :** هذه الطريقه فكرتها كالأتي: هو عندما نشغل البرنامج لأول مره نقوم بتشغيل Server برقم بورت معين مثلا 2000 ، ويكون هذا السيرفر دليل على أن هناك نسخه تعمل . في حال شغلت نسخه أخرى من البرنامج نقوم بالاتصال بالسيرفر فاذا أتصل فهذا معناه أن هناك نسخه تعمل وهنا سوف أغلق البرنامج الجديد .أي الخوارزميه كالأتي/ \- قم بالأتصال بالسيرفر بالمنفذ 2000 ، في حال أتصل فمعناه أنه هناك نسخه تعمل ، فقم الأن باغلاق النسخه الجديده . \- في حال لم يتصل البرنامج بالسيرفر (معناه أنه لا يوجد سيرفر يعمل ) سوف ينفذ جزء Exception وهنا سوف نقوم بتشغيل السيرفر من داخل الException .كود بسيط للسيرفر : ```java // This MonitorServer will start when you open myApp // Wajdy Essam - 2008 import java.net.ServerSocket; import java.net.Socket; import java.io.IOException; public class MonitorServer extends Thread { private ServerSocket serverSocket; private Socket socket; private static final int PORT = 5000; public void run() { try { serverSocket = new ServerSocket(PORT); while (true) { socket = serverSocket.accept(); // get client socket.close(); } } catch (IOException e) { e.printStackTrace(); } } } ``` وهذا كود البرنامج : ```java // Wajdy Essam - 2008 import java.net.Socket; import java.io.IOException; import javax.swing.JOptionPane; public class MyApp { public static void main(String args[]) { try { Socket socket = new Socket("localhost", 5000); // if get connection in mean that server is open , it mean that is client is opening JOptionPane.showMessageDialog(null, "Their is Opening Vesrion of this Program", "Cannot Open More Than One Version", JOptionPane.ERROR_MESSAGE); System.exit(1); } catch (IOException e) { // here the server is not opening , so we open it now MonitorServer server = new MonitorServer(); server.start(); } // here application code // good Luck } } ``` نأخذ مثال لتطبيق الفكره ، وهو لبرنامج مفكره بسيط يقوم بحفظ وقرائه ملفات txt وبه خاصيه نسخ ولصق ... المهم أنظر الصوره التاليه وستجد أننا نستطيع تشغيل أكثر من نسخه .. نريد الأن اضافه الطريقه التي تعلمناه قبل قليل عليه [![uqucs_HflN9G88W](https://informatic-ar.com/wp-content/uploads/2013/10/uqucs_HflN9G88W-300x248.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/uqucs%5FHflN9G88W.jpg) ننظر الى الداله الرئيسيه : ```java public class JNotePad { public static void main(String args[]) { JNotePadFrame app = new JNotePadFrame(); app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); app.setVisible(true); } } ``` نقوم الأن باضافه كود الأتصال في البدايه -أول سطر في البرنامج- : ```java public static void main(String args[]) { try { Socket socket = new Socket("localhost", 5000); // if get connection in mean that server is open , it mean that is client is opening JOptionPane.showMessageDialog(null, "Their is Opening Vesrion of this Program", "Cannot Open More Than One Version", JOptionPane.ERROR_MESSAGE); System.exit(1); } catch (IOException e) { // here the server is not opening , so we open it now MonitorServer server = new MonitorServer(); server.start(); } JNotePadFrame app = new JNotePadFrame(); app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); app.setVisible(true); } ``` وها هو برنامجنا الأن يصيح عندما تقوم بتشغيل أكثر من نسخه : [![uqucs_D1HvrMYdQ](https://informatic-ar.com/wp-content/uploads/2013/10/uqucs_D1HvrMYdQ-300x122.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/uqucs%5FD1HvrMYdQ.jpg) على العموم ، الطريقه ليست سريعه كما في الطريقه التاليه ، ولكنها تفي بالغرض !! **الطريقه الثانيه The file lock technique :** تعتمد هذه الطريقه على java.nio.channels وطريقتها هي انشاء ملف في مجلد user.home وعمل lock عليه channel.tryLock() ، وفي حاله لم يستطع البرنامج عمل lock وذلك بسبب وجود نسخه تعمل سوف يحصل Exception من نوع OverlappingFileLockException . وعند اغلاق البرنامج يجب أن نزيح هذا المقبض على الملف وذلك باستخدام shutdown hook . هذا الكود ، قم بتشغيله وستجد أنه أسرع من الطريقه الأولى .. الملف الأول JustOneLock وهو الخاص بوضع الLock . ونستطيع استخدامه مع أي برنامج أخر بلا تغيير . ```java import java.io.*; import java.nio.channels.*; public class JustOneLock { private String appName; private File file; private FileChannel channel; private FileLock lock; public JustOneLock(String appName) { this.appName = appName; } public boolean isAppActive() { try { file = new File(System.getProperty("user.home"), appName + ".tmp"); channel = new RandomAccessFile(file, "rw").getChannel(); try { lock = channel.tryLock(); } catch (OverlappingFileLockException e) { // already locked closeLock(); return true; } if (lock == null) { closeLock(); return true; } Runtime.getRuntime().addShutdownHook(new Thread() { // destroy the lock when the JVM is closing public void run() { closeLock(); deleteFile(); } }); return false; } catch (Exception e) { closeLock(); return true; } } private void closeLock() { try { lock.release(); } catch (Exception e) { } try { channel.close(); } catch (Exception e) { } } private void deleteFile() { try { file.delete(); } catch (Exception e) { } } } ``` برنامجنا الذي نريده أن يعمل مره واحده : ```java public class JustOneTest { public static void main(String[] args) { new JustOneTest().test(); } void test() { JustOneLock ua = new JustOneLock("JustOneId"); if (ua.isAppActive()) { System.out.println("Already active."); System.exit(1); } else { System.out.println("NOT already active."); try { while (true) { try { System.out.print("."); Thread.sleep(5 * 60); } catch (Exception e) { e.printStackTrace(); } } } catch (Exception e) { } } } } ``` نقوم بالتعديل الأن في برنامج المفكره لكي نستخدم هذ الطريقه ، وسوف تكون الداله الرئيسيه بهذا الشكل : ```java public static void main(String args[]) { JustOneLock ua = new JustOneLock("JustOneId"); if (ua.isAppActive()) { JOptionPane.showMessageDialog(null, "Their is Opening Vesrion of this Program", "Cannot Open More Than One Version", JOptionPane.ERROR_MESSAGE); System.exit(1); } JNotePadFrame app = new JNotePadFrame(); app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); app.setVisible(true); } ``` [هذا الرابط يحتوى على البرنامج بطريقة File Locking](https://informatic-ar.com/wp-content/uploads/2013/10/JNotePad-Using-FileLocking.rar) [وهذا الرابط يحتوى على البرنامج بطريقة ال Socket](https://informatic-ar.com/wp-content/uploads/2013/10/JNotePad-Using-Socket-Technique.rar) ### الخوارزميات الجينية Genetic Algorithm URL: https://informatic-ar.com/genetic_algorithm/ Last updated: 2026-10-03T22:46:28.000Z **بسم الله الرحمن الرحيم** **السلام عليكم ورحمة الله وبركاتة** تتناول المقالة بأذن الله شرح للخوارزميات الجينية: تاريخها مبدأها الخوارزمية اساسها تطبيق على مشكلة من مشاكل الحياة وضع الكود اللازم لكل جزء ::ان شاء الله:: **\~تاريخها History** الgenetic algorithm تم اخترعها بواسطة جون هولند John Holland عام 1960وطورها هو وطلابة وزملائه وكانت فكرة الخوارزمية مبينة على نظرية التطور لداروين وما فيها من عملية الانتخاب الطبيعى **\~مبدأ الخوارزمية ( البقاء للاصلح او الاقوى )** [![saveLife.png](https://informatic-ar.com/wp-content/uploads/migrated/external-873cca3674578896db6c5e9e8de411d607dc910a312088fbcac7e17d80377b1d.png "saveLife.png (72.5 KiB) شوهد 2522 مرات")](http://www.sudancs.com/download/file.php?id=796&mode=view&ref=informatic-ar.com) **\~قصة صغيرة من عالم العيش فى الغابة** كان لدينا مجموعة من الارانب سريعة وذكية واخرى بطيئة وغبية وكان الجيل الاول من الارانب يعيش بسعادة وامان حتى اتت مجموعة من الثعالب لتأكلة وفى هذة الحاله للاسف لم ينجو الا كثير من الارانب السريعة وقد نجت بعض الارانب البطيئة نتيجة الحظ ثم عاش الجيل المتبقى بعد ذلك الهجوم الشديد مع بعضهم فى امان وتم التزاوج بين هذة الارانب المتبقية والتى سوف تنقسم الى: تزاوج ارانب سريعة مع اخرى مثلها--وارانب بطيئة مع سريعة--وبطيئة مع بطيئة ومعنى ذلك ان الجيل الناتج سوف يأخذ جينات من الاباء والامهات ونتج جيل جديد ولكن استمرت هجوم الثعالب وتكررت القصة ومع مرور الوقت فأن معدل الارانب السريعة يزيد ويقل معدل الارانب البطيئة و مع مرور زمن اكبر فلمفلم يتبقى الا مجموعة من الارانب الاصلح للمعيشة(الاسرع) وانتهت القصة بموت الضعفاء !!!! بمعنى اصح ان البقاء للاقوى دائما ( الاقوى هنا تحسب حسب مقياس المشكلة التى سوف تعمل على حلها) **\~تعالوا نطبق قصة العيش فى الغابة لعمل خوارزمية بسيطة!!!!** اذا كان لدى مشكلة ما ولها عدد لا حصر لة من الحلول واريد ان اجد او ابحث عن افضل حل لها ماذا افعل؟؟ باستخدام نظرية الافتراض؟ نفترض ان مجموعة الحلول solution هو مجموعة الارانب جودة الحل(دالة الهدف) fitness function هى مقياس سرعة الارانب او بطئها عملية التزاوج هى عملية سوف ننشأها لتبادل اجزاء الحلول وسوف نطلق عليها crossover فرصة المعيشة للارنب نتيجة الحظ سوف نجعلها بطرق متعددة للاختيار للحلول من جيل لاخر ونسميها selection نضيف جزء الطفرة؟؟حيث انة يمكن ان يحدث بتغيير فى جين ما لحل ان ينتج عنة حل افضل وتسمى mutation **\~اية رأيكم نكتب ما افترضنا على صورة خطوات :** اذا بداية لدى مجموعة من الارانب initial population( الحلول لمشكلة ما ) نطلق عليها (P(t اى P هى المجموعة الاولى( الجيل الاول) فى اللحظة الزمنية t سوف نقوم بعمل فرز لهم وذلك عن طريق مقياس لخصائص فيهم ونسمية fitness function هذة الخطوة تسمى (evaluate(t ثم نبدا من هذا الجيل بحلقة تكراية تنتهى او تتوقف حتى تحقق بشرط ما (نضعة نحن طبقا للمشكلة النى نعمل على حلها) ما الذى سوف يحدث فى هذة الحلقة 1-سوف نختار الافضل منهم (select P(t ليخضعوا لخطوتين التاليتين 2- عملية التزاوج بين افراد المجموعة لينتج جيل جديد crossover 3- يحدث بعض الطفرات وتسمى mutation وتحدث على مستوى الفرد (الارنب-الحل الواحد) ثم نرجع ونعمل للجيل الجديد الحالى تقيم (evaluate P(t ويخضع للخطوات السابقة حتى ينتهى الشرط للحلقة التكرارية 4-نجد ان ما سوف نحصل علية افضل ارنب ( او بمعنى اصح افضل حل للمشكلة) **\~نعيد كتابتها بطريقة رياضية اكثر وبأسلوب افضل** CODE: [تحديد الكل](http://www.sudancs.com/viewtopic.php?f=11&t=1171&ref=informatic-ar.com) ` 1.[Start] Generate random population of n chromosomes (suitable solutions for the problem) 2. [Fitness] Evaluate the fitness f(x) of each chromosome x in the population 3. [New population] Create a new population by repeating following steps until the new population is complete 1.[Selection] Select two parent chromosomes from a population according to their fitness . 2.[Crossover] With a crossover probability cross over the parents to form a new offspring (children). 3.[Mutation] With a mutation probability mutate new offspring at each locus (position in chromosome). 4.[Accepting] Place new offspring in a new population 4.[Replace] Use new generated population for a further run of algorithm 5.[Test] If the end condition is satisfied, stop, and return the best solution in current population 6.[Loop] Go to step 2 ` **\~الان لفهم خوارزمية الجينات الوراثية** لدينا اربع نقاط مهمة اذا اردنا تطبيق خوارزمية الجينات الوراثية **problem representation :: encoding :**عملية التمثيل لحل المشكلة ؟كيف سيتم تمثيل الحل لمشكلة ما **How to select best solution:: selection ::** كيف يتم اختيار مجموعة من الحلول ليتم عليها عمليات الcrossover ,mutation **crossover::**كيف يتم تبادل اجزاء من الحلول ليتم الحصول على حلول جديدة ترث من الحليين الابويين **mutation::**كيف يمكن تغيير فى جزء من حل ليتم الحصول على حل جديد طفرة ربما يكون افضل وربما يكون اسوء \~ سوف نبدأ بالتطبيق على مشكلة ونشرح كل خطوة كيف يتم تطبيقها افضل بأذن الله عز وجل \~ **مشكلة البائع المتجول** **\~\~\~\~\~\~Traveling Salesman Problem\~\~\~\~\~\~\~** "The traveling salesman problem, or TSP for short, is this: given a finite number of 'cities' along with the cost of travel between each pair of them, find the cheapest way of visiting all the cities and returning to your starting point." **ما هى مشكلة البائع المتجول:** لنفترض في دولة ما .. هنالك بائع لاغراض ما .. يتجول بين مدن هذه الدولة .. والمدن مبعثرة أي لا تقع على خط مستقيم لكنه يعرف الطرق التي تربط بين هذه المدن كلها .. فكر هذا البائع بان يمر بكل هذه المدن .. لكن بشروط :- 1\. لا يمكن ان يكون البائع فى مدينتيين فى وقت واحد"زمن واحد" 2\. المدينة يمر منها مرة واحدة فقط ولا يعود اليها ثانية . 3\. اخيرا هو يجب علية ان يجد اقصر الطرق التي تجعله يمر بكل المدن ويعود الى مدينتة [![tsp.png](https://informatic-ar.com/wp-content/uploads/migrated/external-1f45712a509a8d70402dd965ff20ff6af7d0f445b1bedbb882b9132cd0f9d534.png "tsp.png (281.81 KiB) شوهد 2504 مرات")](http://www.sudancs.com/download/file.php?id=802&mode=view&ref=informatic-ar.com) هذا فلاش توضيحى اكثر [http://www.alaws2006.com/vb/showthread.php?t=46236](http://www.alaws2006.com/vb/showthread.php?t=46236&ref=informatic-ar.com) [http://en.wikipedia.org/wiki/Travelling ... an\_problem](http://en.wikipedia.org/wiki/Travelling%5Fsalesman%5Fproblem?ref=informatic-ar.com) **ما هى الصعوبة فى مشكلة البائع المتجول وما هى اهميتها وتطبيقتها فى الحياة؟؟؟** **\~اولا ::الصعوبة :** نتخيل لو لدنيا اربع مدن يجب على البائع زيارتهم هم A,B,C,D فان عدد احتمالات الطرق التى ممكن ان يسلكها تحسب بالقانون CODE: [تحديد الكل](http://www.sudancs.com/viewtopic.php?f=11&t=1171&ref=informatic-ar.com) `number of paths=n(n-1)/2 ` حيث n هو عدد المدن لو افترضنا لدينا 4 مدن فأن عدد المسارات التى يمكن ان يسلكها هى 6 لو افترضنا لدينا 100 مدينة فأن عدد المسارات التى يمكن ان يسلكها هى 4950 لو افترضنا لدينا 1000 مدينة فأن عدد المسارات التى يمكن ان يسلكها هى 499500 اذن نجد كلما زاد عدد المدن كلما زاد عدد المسارات الممكنة (الحلول الممكنة للمشكلة والذى اريد ايجاد افضل حل من بينهم) اى انة لدى عدد هائل من الحلول واريد ايجاد الافضل من بينهم --اذا كملخص صعوبة المشكلة انة كلما زاد حجم المشكلة كلما زاد الفضاء للبحث عن افضل حل لها **\~ثانيا::اهمية المشكلة وتطبيقتها :** طبعا مشاكل كثيرة يمكن ان تكون جزء من مشكلة البائع المتجول مثلا \~فى الانتقالات (transportation) ترتيب المسارات للباصات التى توصل الطلبة الى المدارس او توزيع المنتجات الزراعية بين مجموعة من المدن \~فى صناعة الدوائر المنطقية وترتيبها \~فى routing ايصال الباكت data عبر اجهزة شبكة ما (حالة خاصة حيث لا يلزم المرور على كل الاجهزة) بعدما تعرفنا على مشكلة البائع المتجول الان نريد ان نطبق الخوارزميات الجينية عليها لحلها **سوف نطبق خطوة خطوة STEP BY STEP\~ كطفل صغير يحبو!** **\~الخطوة الاولى::تمثيل المشكلة problem representation :: encoding** :عملية التمثيل لحل المشكلة ؟كيف سيتم تمثيل الحل لمشكلتنا بداية تمثيل اى مشكلة هو الاصعب فى الخوارزميات الجينية لانة يعتمد بشكل كبير على فهم المشكلة واختلاف التمثيل يؤثر تماما فى الخطوات الاخرى للخوارزمية كما يؤثر فى النتيجة والوقت عندما نقول تمثيل اى مشكلة:فيجب عليكم اعطائى شيئين :: اولا :طريقة صياغة الحل الواحد للمشكلة ثانيا :دالة الهدف التى تعتمد على شكل هذا الحل اولا:طريقة صياغة المشكلة هناك عدة طرق لصياغة اى مشكلة او عمل تمثيل لها وهذا يجعلنا نتطرق الى kind of encoding **\~انواع التمثيل kind of encoding:** **Binary Encoding** : ويتم فية تمثيل الحل باستخدام 0 و 1 حيث الحل عبارة عن مجموعة من الاصفار والوحايد التى تعبر عن حل للمشكلة \~نطبق على مشكلتنا البائع المتجول:لو اننا استخدمنا مصفوفة ثنائية الابعاد وافترضنا ان كل صف يمثل مدينة وكل عمود يمثل ترتيبها فى الزيارة (الرحلة ) للبائع المتجول فاننا حصلنا على تمثيل ثنائى للمشكلة -ننظر للصورة [![binary.png](https://informatic-ar.com/wp-content/uploads/migrated/external-6593dc4a5184f71bcbb0555037c5fe62029436cef3007bc5635fec0d4fcbbc55.png "binary.png (86.74 KiB) شوهد 2463 مرات")](http://www.sudancs.com/download/file.php?id=809&mode=view&ref=informatic-ar.com) للتوضيح اكثر نفترض كما فى الصورة لدينا سبع مدن نرمز لهم {A,B,C,D,E,F,G} لو كما بالصورة قمنا بانشاء مصفوفة ثنائية الابعاد كل بعد طولة يبلغ عدد المدن لدى فى المشكلة 7 فاننا نستطيع ان نقول المدينة A تم زيارتها السابعة المدينة C تم زيارتها الاولى المدينة D تم زيارتها الثالثة وهكذا فان الحل النهائى يكون CODE: [تحديد الكل](http://www.sudancs.com/viewtopic.php?f=11&t=1171&ref=informatic-ar.com) `Solution s={C-F-D-G-E-B-A}` هذا هو التمثيل طيب ماذا عن دالة الهدف التى قلت عليها حتى يكون تمثيل صحيح -دالة الهدف سوف تكون عبارة عن شروط للتأكد من ان الحل صحيح [![func1.png](https://informatic-ar.com/wp-content/uploads/migrated/external-ff464ab253356aa1e1213132c9302429914e2e2a9ece7227ee81bf0f3b0f79be.png "func1.png (17.76 KiB) شوهد 2468 مرات")](http://www.sudancs.com/download/file.php?id=806&mode=view&ref=informatic-ar.com) حيث الجزء الاول شرط ان عدد الوحايد فى الصفوف تكون n هو عدد المدن -الجزء الثانى شرط ان عدد الوحايد فى الاعمدة مساوى n عدد المدن -الجزء الثالث هو شرط ان عدد الوحايد فى الصفوف والاعمدة(فى المصفوفة ككل )مساوى n الجزء الاخير هو لحساب المسافة بين المدن نلاحظ ان دالة الهدف ما هى الا تطبيق لشروط نجاح رحلة البائع المتجول فيجب ان يكون كل عمود بة واحد فقط لانة لا يمكن ان يزور مدينيتن وقت واحد ويجب ان يكن كل صف بة واحد فقط لانة لا يمكن ان يزور المدينة اكثر من مرة ويجب ان يكون عدد المدن التى يزورها فى النهاية هى n من المدن اخيرا يجب ان تكون رحلة امنة وليس فيها مشقة فنحسب اقل مسافة بين المدن لكن هذا التمثيل لة عيب كبير انة كلما زاد حجم المشكلة كلما زاد حجم الذاكرة المستخدمة غير ان طرق الcrossover ,mutation تكون سهلة فى تطبيقها على هذا النوع من التمثيل لكن تؤدى دائمة الى كارثة وهى ان الحل الناتج يكون غير صحيح ويحتاج الى دوال للتصحيح validation "\~سوف نتطرق لهذا مرة اخرى فيما بعد\~" **Permutation Encoding ::** **وفية يتم التمثيل "بارقام صحيحة او حروف "وهو سهل جدا والاغلب استخداما لدى المبرمجيين** **\~**نطبق على مشكلتنا البائع المتجول: لو لدينا 7 مدن يمكن ان يكون الحل هو CODE: [تحديد الكل](http://www.sudancs.com/viewtopic.php?f=11&t=1171&ref=informatic-ar.com) `Solution s={ 1 4 7 3 5 6 2}` طبعا سهل لا يحتاج لشرح **طيب دالة الهدف ايضا بسيطة باعتبار ان π هى الرحلة فان الجزء الاول يحسب المسافة بين المدينة i التى تليها فى الرحلة -اما الجزء الثانى فلحساب المسافة للعودة الى المدينة رقم البداية** ![fun2.png](https://informatic-ar.com/wp-content/uploads/migrated/external-c8433a80a962d93e230b6d224217efc8a30750ffc7f2fd3b6f7efb3f6f97a868.png) طبعا هناك انواع اخرى للتمثيل ويمكن ان يصمم اى منا تمثيل خاص لة بالمشكلة حسب وجهة نظرة لكن ايضا يجب ان يوجد دالة هدف لهذا التمثيل الجديد لحساب قيمة الحل النهائى **انواع اخرى للتمثيل encoding هنا** **[http://www.obitko.com/tutorials/genetic ... coding.php](http://www.obitko.com/tutorials/genetic-algorithms/encoding.php?ref=informatic-ar.com)** **\~الخطوة الثانية :** **How to select best solution:: selection :: كيف يتم اختيار مجموعة من الحلول ليتم عليها عمليات الcrossover ,mutation** **\~طرق الselection** :Roulette Wheel Selection\~ وفية يتم اختيار الحلول اعتمادا على جودة هذا الحل (قيمة دالة الهدف تكون كبيرة لو المشكلة maximization-وتكون صغيرة لو minimization ![rwsgraph.gif](https://informatic-ar.com/wp-content/uploads/migrated/external-e541c27ffb35cecbb14707a5fd109f3d0047aa5725abe373b370eabaa3d40f55.gif) لو تخيلنا كما بالشكل اننا مثلنا جودة كل حل بمقدار ما اخذة من مساحة الدائرة فالحل الاول باللون الازرق اخذ مساحة كبيرة وهذا يعنى انة سوف يتم اختيارة لانة جودتة كبيرة (قيمة دالة الهدف للمشكلة جيدة) اما الحل باللون الاصفر فنسبة اختيارة اقل لان جودة الحل ليست كبيرة اذا فى roulette wheel يتم اختيار الحلول حسب جودتها فالحل ذو قيمة افضل نسبة اختيارة ليكون فى الجيل الذى ستتم علية عملية crossover ,mutation كبيرة هذا pseudo code لها ```java [Sum] Calculate sum of all chromosome fitnesses in population - sum S.[Select] Generate random number from interval(0, S) - r.[Loop] Go through the population and sum fitnesses from 0 - sum s.When the sum s is greater then r, stop and return the chromosome where you are. ``` الكود طبعا بيختلف حسب المشكلة -لكن هذا كود كتبتة ```java public final static LinkedList<Solution> roulewheelSelection(LinkedList<Solution> pop, int numToSelection) { LinkedList<Solution> selected = new LinkedList<Solution>(); double curr = 0; double sum = 0; // step 1- sum all fitness in population// for (Solution s : pop) sum = sum + s.fit; while (selected.size() < numToSelection) { //select number of solution equal numToSelection// step 2- generate random number double q = Math.random(); //q = [0,1) //step 3 loop through solution to find the one with firness greater than q for (Solution s : pop) { curr = curr + s.fit / sum; if (curr >= q) { selected.add(s); break; } } } return selected; } ``` Tourment Selection\~ وهى طريقة لاختيار مجموعة من الحلول من population (الجيل الحالى) حيث نحدد بدايةَ عدد الحلول التى نريد اختيارها من population ثم نحدد عدد tournament size ونكرر لعدد الtournament size اختيار عدة حلول عشوائية من population ومنهم نختار الافضل فى كل مرة -نلاحظ ان يمكن ان يتم اختيار حل اكثر من مرة (تكرار اختيارة) لذا يمكن عندما يتم اختيار حل يتم الغاؤة من population [http://en.wikipedia.org/wiki/Tournament\_selection](http://en.wikipedia.org/wiki/Tournament%5Fselection?ref=informatic-ar.com) elitistSelectionStrategy\~ وهى طريقة لاختيار جزء الحلول الافضل من الpopulation والغاء باقى الحلول منة هذة الاسهل على الاطلاق هناك ايضا طرق كثيرة للselection ويمكن ان تعمل طريقة خاصة بكم وليس لها شروط هذة المرة **\~الخطوة الثالثة : وهى خطوة انشاء جيل جديد وتتم بواسطة خطوتين crossover -mutation نبدأ بخطوة خطوة** **\-crossover::كيف يتم تبادل اجزاء من الحلول ليتم الحصول على حلول جديدة ترث من الحليين الابويين** اذن من التعريف عملية الcrossover هى عملية سوف تكون مسئولة عن تبادل بعض اجزاء من الحلول (بين ابويين -حليين) لينتج حلول جديدة ترث من هذين الحليين الاوليين (عملية الcrossover يجب ان تتم بين حليين على عكس عملية الmutation ) يوجد لدينا اكثر من طريقة لcrossover **Single point crossover\~** فيها يتم اختيار **نقطة عشوائية** ويتم قطع الحليين عندها ويتبادل الاجزاء بينهما -ننظر للصورة لو لدينا حليين ابويين parent 1, parent 2 وافترضنا اختيار النقطة رقم 5 فيكون الناتج كما بالصورة child 1, child2 [![SPC.png](https://informatic-ar.com/wp-content/uploads/migrated/external-f2e3df7d5f2f3fd4192a9a43c52cfff355f29333999c234a6b5a1bcb1a837124.png "SPC.png (31.41 KiB) شوهد 2448 مرات")](http://www.sudancs.com/download/file.php?id=810&mode=view&ref=informatic-ar.com) **\~Two point crossover** فيها يتم اختيار **نقطتين عشوائيتين** ويتم قطع الحليين عندهما ويتبادل الاجزاء بينهما [![tpc.png](https://informatic-ar.com/wp-content/uploads/migrated/external-4474c4d152ca5003a35cc0e976e90f770b50cf87519f186e33d09a17a7d1608b.png "tpc.png (44.58 KiB) شوهد 2448 مرات")](http://www.sudancs.com/download/file.php?id=811&mode=view&ref=informatic-ar.com) هناك طرق اخرى للcrossover يمكن البحث فى google طيب هذا الcrossover لو كان الحل ممثل بbinary encoding ماذا لو كان الحل ممثل بPermutation Encoding | هو نفس المبدأ لا يتغير طيب اية رايكم نطبق شوية على البائع المتجول : سوف نطبق ونحن نمثل الحل باستخدام الPermutation Encoding ننظر للصورة لو اردنا تطبيق الSingle point crossover على الحليين الابويين فى الصورة ماذا سوف يحدث؟؟ [![Untitled.png](https://informatic-ar.com/wp-content/uploads/migrated/external-9dde5c39080c5a9edb0e0ae7244079fbbdb5382bf423fb26c091316d3d62a5df.png "Untitled.png (29.02 KiB) شوهد 2448 مرات")](http://www.sudancs.com/download/file.php?id=812&mode=view&ref=informatic-ar.com) ما هذا الحلول الناتجة عن عملية الcrossover حلول غير صحيحة حيث تكرر زيارة المدينة 2 مرتيين فى الرحلة وهذا غير محقق لشروط المشكلة؟؟ اذن ماذا نفعل ؟؟ اذن ما الحل الان ابدا بوضع طرق من فهمك للمشكلة - اقصد اذا كنت تحل مشكلة ما باستخدام الخوارزميات الجينية وطرق الcrossover ,mutation المعروفة والمعتادة لا يمكن تطبيقها فيمكن من فهمكم للمشكلة ان تصنعوا طرق جديدة وتحسب جودة فعليتها على برنامجكم طيب كيف اصنع طريقة crossover تضمن لى عدم تكرار المدن وان الحلول الناتجة تكون صحيحة --نعطى لمحة بسيطة من ضمن الطرق التى ىسوف نستخدمها عند التطبيق **\~Partially-Mapped Crossover** حقيقة اجد الكثير يسأل عن شرحها وطريقة عملها ونقدمة لكم بأذن الله Partially-Mapped Crossover ومن اسمها فيها يتم عمل mapping جزئى اى لاجزاء من الحل نفترض ان لدينا حليين : ```java Parent1 : 2 5 0 3 61 4 7 Parent2 : 3 4 0 7 25 1 6 ``` لو افترضنا اننا اخذنا مجموعة من المدن وقمنا بعمل **mapping** لهم ماذا سوف يحدث؟ \~ فى parent1 نفترض اننا سوف نقوم بعمل map للمدينة رقم 3 ونضع ما يقابها مدينة رقم 7 فى الparent 2 \~ فى parent1 نفترض اننا سوف نقوم بعمل map للمدينة رقم 6 ونضع ما يقابها مدينة رقم 2 فى الparent 2 \~ فى parent1 نفترض اننا سوف نقوم بعمل map للمدينة رقم 1 ونضع ما يقابها مدينة رقم 5 فى الparent 2 غير واضح ممكن توضح اكثر -نعم انظروا للرسمة فما اريدة هو عمل mapping بين المدن فى الحليين الابويين ![mapping.png](https://informatic-ar.com/wp-content/uploads/migrated/external-e91845d1989208fc83e3107df5f5f8802347f92887f6b61d42983e66ac91af4d.png) **لعمل mapping :اتبع الخطوات الاتية** الخطوة الاولى :قم بعمل تبديل بين المدن 3و7 فى الابويين بمعنى كلما تجد المدينة 3ضع مكانها 7 والعكس الناتج ```java Child1 : 2 5 0 7 6 1 4 3 Child2 : 7 4 0 3 2 5 1 6 ``` الخطوة الثانية :قم بعمل تبديل بين المدن 2و6 فى الابويين بمعنى كلما تجد المدينة 2 ضع مكانها 6 والعكس الناتج ```java Child1 : 6 5 0 7 2 1 4 3 Child2 : 7 4 0 3 6 5 1 2 ``` الخطوة الاخيرة :قم بعمل تبديل بين المدن 5و1 فى الابويين بمعنى كلما تجد المدينة 1ضع مكانها 5 والعكس الناتج ```java Child1 : 6 1 0 7 2 5 4 3 Child2 : 7 4 0 3 6 1 5 2 ``` هذا ينتج لنا الحلول الابناء الاتية : ```java Parent1 : 2 5 0 3 6 1 4 7 Parent2 : 3 4 0 7 2 5 1 6 Child1 : 6 1 0 7 2 5 4 3 Child2 : 7 4 0 3 6 1 5 2 ``` **mutation\~::** وتعرف بالطفرة من علم الوراثة وفيها يتم تغيير جين ما بطريقة عشوائية لينتج صفة ليست فى الاب او الام -بالنسبة لنا نقصد بها كيف يمكن تغيير فى جزء من حل ليتم الحصول على حل جديد " ربما يكون افضل وربما يكون اسوء من الحل قبل الطفرة" طرق الmutation كثيرة وسوف نعرض منها مجموعة كثيرة حقيقة اجد لها تأثير افضل من crossover فى معظم البرامج التى اعمل عليها **\~Exchange Mutation**: وفية يتم تبادل بين two genes تم اختيارهم بشكل عشوائى نطبق على البائع المتجول :لو لدينا حل متمثل بالرحلة الاتية path =5 3 2 1 7 4 0 6 فيمكن الحصول على حل جديد باختيار مدينتين بشكل عشوائى ويتم تبديل المواقع لهم فى الرحلة-فمثلا اذا قلما اننا تم اختيار المدينيتن 7 و5 فأننا نحصل على حل جديد ويتمثل ب path =7 3 2 1 5 4 0 6 **Displacement Mutation\~:** وفية يتم اختيار two genes بشكل عشوائى ويتم اخذ مجموعة الgenes التى بينهم كمجموعة ويعاد وضعهم فى الحل من اى نقطة عشوائية مثال :على البائع المتجول ايضا لو افترضنا لدينا الحل الاتى p1=0 1 2 3 4 5 6 7 وتم اختيار الموقعين رقم 4 و6 فان مجموعة الجين 345 سوف يتم اعادة وضعهم فى الحل فى اى نقطة عشوائية من البداية سوف نحصل على c1=0 3 4 5 1 2 6 7 **Insertion Mutation\~:** وفية يتم اختيار gene عشوائى ويتم ازالتة من الحل ويعاد وضعة فى موضع عشوائى فى الحل نطبق على البائع المتجول : لو لدينا الحل الاتى: p1=0 1 2 3 4 5 6 7 وافترضنا اننا تم اختيار المدينة رقم 2 فاننا لنطبق الinsertion سوف نزيل المدينة 2 من الحل ليصبح p1=0 1 3 4 5 6 7 ثم نحدد نقطة عشوائية لاعادة وضعها ليصبح الحل كالاتى c1=0 1 3 4 5 2 6 7 هناك العديد والعد من الطرق لعمل طفرة ليتكون حل جديد الى هنا انتهينا من شرح جميع الخطوات -الحمد الله رب العالميين التطبيق العملى هناك اكواد كثيرة جدا فى google متاحة عن المشكلة وحلها بالخوارزميات الجينية سوف اعرض مقاطع من الاكواد **\~التطبيق العملى لمشكلة البائع المتجول \~** اولا: قبل البدء فى الخوارزمية نحدد مدخلات المشكلة ومخرجاتها (المدخلات مجموعة من المدن- المخرجات اقصر رحلة يمكن ان يسلكها السائق) اذن الخطوة الاولى : قراءة ملف المدن التى سوف يزورها السائق(لكن من اين احصل على مجموعة مدن ومسافات بينها احد الحلول توليد مجموعة عشوائية من المدن -حل اخر هناك موقع يعرض قاعدة بيانات عن مجموعة من المدن ) [http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/](http://comopt.ifi.uni-heidelberg.de/software/TSPLIB95/?ref=informatic-ar.com) فلنعمل من هذا الموقع لماذا ؟ لان اى احد يقوم بعمل بحث للنشر فى هذة المشكلة يستخدم standard data ويقارن نتائجة مع الاخريين لذا احببت ان اقرأ الملفات منة لنستخدم نفس data-سوف نحمل ملف ونعمل على قرائتة \~نكتب كود لميثود لقراءة الملف ```java public static void readData() { BufferedReader file; try { file = new BufferedReader(new FileReader("cities.txt")); String text = null; int i = 0; try { while ((text = file.readLine()) != null) { String[] line = text.split(" "); x[i] = Double.parseDouble(line[1]); y[i] = Double.parseDouble(line[2]); i++; } } catch (NumberFormatException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } catch (FileNotFoundException ex) { ex.printStackTrace(); } } ``` بعد ما قمنا بقراءة ملف المدن نبدأ بخطوات الخوارزمية **\~ تمثيل حلencoding: سوف نستخدم permutation encoding اى ان الحل عبارة عن مجموعة من الارقام التى تعبر عن رقم المدينة** **الان نريد ان ننشىء حل عبارة عن مجموعة من الارقام ليست متكررة لان السائق لا يمكن ان يزور المدينة الواحدة اكثر من مرة** **هذة ميثود لتوليد ارقام عشوائية ليست متكررة** ```java public static int[] createPermutation(int start, int end) { int minValue = start; int maxValue = end; int numInts = end; int range = maxValue - minValue; int[] randomInts = new int[numInts]; int nextRandom; for (int i = 0; i < numInts; i++) { nextRandom = r.nextInt(range + 1) + minValue; randomInts[i] = nextRandom; // now check the digits we already have for (int j = 0; j < i; j++) { if (nextRandom == randomInts[j]) { i--; // duplicate, try again j = i; // short-circuit inner-loop } } } return randomInts; } ``` يمكنكم تكملة اجزاء الكود بأذن الله تعالى **ملحوظات** من المشاكل التى تقابلنا فى الخوارزمية الجينية هى ان بعد مرور فترة من التكرار للعمليات selection- crossover-mutation نجد الpopulation بقى حل واحد (وقع فى local minimum ) لا تؤثر فية الcrossover ولا muation ترى كيف احل هذة المشكلة : بعض الاقتراحات: 1-الحل الاول :**امنع الحلول المتشابهة فى population لكن عيبها** \-اولا :وقت اطول للحصول على حلول جديدة \-ثانيا-هل فى المشكلة التى نحن نحلها طريقة سهلة لنعلم بها كيف نحصل على دالة للتشابهة بين الحلول 2- الحل الثانى:**اغير فى طرق الcrossover ,mutation** \-ممكن لكن سوف نعود لنفس النقطة ان بعد فترة من تكرارها نحصل على حلول متشابهة 3**\-الحل الثالث :احاول وضع مجموعة من الحلول عشوائية فى الpopulation** **4-الحل الرابع:احاول عدم رفض كل الحلول الغير جيدة ربما عند تطبيق crossover ,mutation مع حلول جيدة ينتج حلول افضل من الاثنيين (وكأننا نهرب مثل الsimulation anneling من local optimall** \------------------------------------------ **مراجع:** [http://gaul.sourceforge.net/](http://gaul.sourceforge.net/?ref=informatic-ar.com) [http://www.c-sharpcorner.com/UploadFile](http://www.c-sharpcorner.com/UploadFile?ref=informatic-ar.com) ... rithm.aspx [http://www.codeguru.com/cpp/misc/misc/a](http://www.codeguru.com/cpp/misc/misc/a?ref=informatic-ar.com) ... php/c3795/ [http://www.obitko.com/tutorials/genetic](http://www.obitko.com/tutorials/genetic?ref=informatic-ar.com) ... coding.php [http://mnemstudio.org/genetic-algorithms-mutation.htm](http://mnemstudio.org/genetic-algorithms-mutation.htm?ref=informatic-ar.com) [http://www.scribd.com/doc/53306810/30/UNIFORM-CROSSOVER](http://www.scribd.com/doc/53306810/30/UNIFORM-CROSSOVER?ref=informatic-ar.com) [http://en.wikipedia.org/wiki/Crossover\_ ... gorithm%29](http://en.wikipedia.org/wiki/Crossover%5F%28genetic%5Falgorithm%29?ref=informatic-ar.com) الحمد الله رب العالميين حمدا كثيرا مباركا فية ### توليد الأرقام العشوائية URL: https://informatic-ar.com/random_numbers/ Last updated: 2026-10-03T22:31:56.000Z السلام عليكم و رحمة الله و بركاته في هذه المقالة سنتعرف على النقاط التالية: 1. ما هو العدد العشوائي ؟ وهل هو موجود ؟ 2. لماذا نحتاج لتوليد الأرقام العشوائية ؟ 3. كيف يمكننا توليد أرقام عشوائية (باستخدام الدوال الجاهزة) ؟ 4. كيف ننشئ دالة عشوائية ؟ 5. مثال عملي 1 - ما هو العدد العشوائي ؟ وهل هو موجود ؟ أولا .. عندما نقول "عدد عشوائي" فهذا لا يعني بالضرورة أنه عشوائي بالمعنى العام .. العدد العشوائي هو العدد المولد من طرف دالة مجهولة السلوك (نسبيا) و عندما أقول نسبيا فأنا أقصد "المستخدم" , أي أن المستخدم عندما يقوم بتشغيل الدالة ستقوم بإرجاع عدد عشوائي كما يتصور "هو" و لكن في الحقيقة .. هذا العدد العشوائي يخضع لقانون معين ..المبرمج فقط هو من يعرفه ! ألا ترى بعض التناقض في كلامنا السابق ! قلنا أن العدد "عشوائي" ثم قلنا بأنه يخضع لقانون معين ! كيف ؟ بداية .. أحب أن ألفت انتباهك إلى الجدل الدائر حول العدد العشوائي ! فالمبرمجون أرادوا برمجة دالة تمكن من توليد أرقام عشوائية ! وكانت المشكلة تكمن في برمجة الدالة العشوائية .. فنحن حين نقول "دالة مبرمجة" فهذا يعني أن الدالة تخضع لقانون محدد .. أما العدد العشوائي فلا يخضع لأي قانون .. وإلا فسيكون عددا محددا و سيفقد عشوائيته .. هنا تكمن المشكلة. السؤال المطروح الآن هو: إذا استطعنا إخضاع كل شيء لقانون محدد فهل هذا يعني بأن العشوائية موجودة أصلا ؟ في الحقيقة .. لا يوجد شيء اسمه (العشوائية) لأن الكون لا يمكنه الاستمرار لأكثر من مليار عام و الأرض لأكثر من 60 مليون عام إذا كان هناك شيء "عشوائي" و لو كان صغيرا ! هناك تأثير يسمي (تأثير الفراشة) تم تجسيده في فيلم أجنبي بنفس الاسم و هو ببساطة يقول أنه لو تم تغيير شيء صغير جدا جدا في الماضي و لو كان مجرد " فراشة " فإن هذا سيؤثر قطعاً بشكل ضخم جدا في الحاضر و المستقبل. لو طبقنا هذا التأثير على (كلمة عشوائي) لوجدنا أنه لن يصبح الكون كوناً إذا كان هناك شيء اسمه العشوائية . أذكر هنا أيضاً (دالة ليمان) التي لم يجد العلماء لها حلا منذ 150 عاماً و هي دالة رياضية حاول العالم (ليمان) أن يربط فيها بين عدد الأرقام الأولية في مجموعة معينة من الأرقام تبدأ بالصفر و بين نهاية أو آخر رقم في هذه المجموعة . الدارس لهذه الدالة يجد أول الأمر أنها أرقام عشوائية و لكن إصرار العلماء على وضعها كدالة و لها حل و يحاولون بكل جهدهم إيجاد هذا الحل, يؤكد على عدم اقتناع العلماء أصلاً بوجود العشوائية . و يرى البعض أن كلمة "عشوائي" هي مرادف لكلمة "مجهول" فنحن عندما نعجز عن تفسير ظاهرة معينة نقول أنها عشوائية, مثلا "النرد", فهناك من يعتقد أنها ظاهرة عشوائية بشكل مطلق ولكنني أرى أنه لو توفرتْ لنا بعض البارمترات فيمكننا حينها توقع نتيجة الرمي. فمثلا, لو عرفنا كتلة النرد والسرعة التي قُذف بها والوضعية التي كان عليها فإننا قد نتوقع الرقم الذي سنحصل عليه بعد رمية ! 2 - لماذا نحتاج لتوليد الأرقام العشوائية ؟ تُستخدم الأعداد العشوائية في الكثير من الأمور .. نذكر منها على سبيل المثال لا الحصر: a- علم الإحتمالات. b- برمجة الألعاب. c- الهندسة العكسية. d- بروتوكولات الإتصال (مثل TCP) **a- علم الإحتمالات** سأعطي مثالا بسيطا لكي تتضح الفكرة, لدينا مجموعة من الكرات في صندوق و نريد سحب إحدى هذه الكرات بشكل عشوائي. كلما ما علينا فعله هو ترقيم هذه الكرات و أخذ عدد عشوائي من بين الأعداد المتاحة. **b- برمجة الألعاب** في الحقيقة. فإن الألعاب مجال خصب للبحث عن خوارزمية تولد أرقام عشوائية. فمثلا, لكي لا يُصر اللاعب على أن يبرمج شكل واحد للعبة معينة فيمكنه أن يعيد نفس اللعبة بعدة أشكال. لنأخذ مثال : حين نلعب مثلا لعبة "Tick-Tack" فالنجمة تظهر كل مرة في مكان عشوائي وهذا مما يجعل اللعبة أكثر واقعية. نفس الفكرة يمكننا تطبيقها في الكثير من الألعاب الأخرى .. **c- الهندسة العكسية** أما في مجال الهندسة العكسية فنحن نحتاجها مثلا من أجل برمجة "الكيجن". للذين لا يعرفون ما هو "الكيجين": هناك طرق كثيرة لحماية البرامج و من أسهلها استخدام أرقام سرية معينة يتم تحديدها من قبل الشركة المنتجة للبرنامج .. وهذه الأرقام قد تعتمد في تحديدها على اسم المستخدم أو بريده الالكتروني أو الرقم التسلسلي للجهاز أو أي معلومة أخرى أو مجموعة من هذه المعلومات معا!! و عادة تحصل على هذا الرقم عند شراء المنتج من الشركة و تعطيهم بياناتك فيعطونك الرقم السري (السيريال) الذي يتيح لك استخدام البرنامج بشكل كامل. نأتي حاليا لكيفية كسر هذه الحماية .. وأشهر طريقة هي استخدام "الكيجين". ما هو "الكيجين" ؟ الكيجين (keygen):هو اختصار لـ key generator أي مولد المفاتيح و هو عبارة عن برنامج صغير يطلب منك نفس المعلومة التي تعتمد عليها الشركة في إعطاء الرقم السري !! و يولد لك الرقم الذي تستطيع فتح البرنامج به .. عادة ما يستطيع بعض المبرمجين معرفة الخوارزميات التي تعتمد عليها هذه الشركات في توليد الأرقام السرية و ينشئون برنامج ليستخدمه الناس!! و طريقته سهلة (نسبيا) فهي مثلا تطلب منك الاسم الموجود في جهازك و من ثم تعطيك الرقم الذي تنسخه في البرنامج فيعمل معك بشكل كامل. نعود مرة أخرى لموضوعنا السابق و هو فائدة الأعداد العشوائية من أجل برمجة الكيجين .. تخيل أن خوارزمية التحقق من سيريال برنامج ما .. تخضع للشروط التالية: الشرط الأول : السيريال يجب أن يتكون من 10 حروف. الشرط الثاني : يقارن الحرف الأول مع A والحرف الثامن مع S والحرف العاشر مع D. إذا لحل هذه الخوارزمية يكفي أن نأخذ سيريال عشوائي من 10 حروف بحيث يحقق الشروط .. ليكن هذا السيريال مثلا: AFGMRTWSOD هذا السيريال يحقق الخوارزمية ولكن يبقى السؤال هو: لماذا لا نكتفي بنشر سيريال واحد يحقق الخوارزمية . السبب هو : مثلا .. أنت تعبت حتى استنتجت الخوارزمية ثم نشرت السيريال و من ثم قام شخص آخر بسرقة هذا السيريال وقال بأنه ملك له ! من نصدق ؟؟؟ طبعا سنصدق من فهم الخوارزمية. لذلك وجب علينا برمجة "الكيجن" الذي سيولد عدة سيريالات تحقق خوارزمية التحقق. الكلام السابق يكون صحيحا إذا كان الشخص لا يعرف الخوارزمية أما إذا كان يعرفها فأنتم إذا سواء ! بالمناسبة فإن الأعداد العشوائية تستخدم بشكل كبير في خوارزميات التشفير لأن أغلب هذه الخوارزميات تحتاج إلى تزويدها بأرقام عشوائية لتبدأ عملها بحيث لا يمكن التكهن بنتائج الخوارزمية و لا مُدخلاتها الأصلية حتى لمن يعرف جيدا طريقة عملها. **d- بروتوكولات الإتصال,مثل بروتوكول TCP** الحصول على أرقام عشوائية أمر مهم جدا في علوم الحاسوب خاصة في مجال بروتوكولات الإتصالات .. مثلا في بروتوكول TCP هناك رقم تسلسلي لكل حزمة يسمح لطرف الإتصال الآخر بترتيب الحزم عند وصولها و بمعرفة ما ضاع منها و إعادة إرساله .. و هناك هجوم كلاسيكي يتمكن خلاله طرف ثالث من الدخول في الإتصال و تحويله إذا كان بإمكانه التكهن بالرقم التسلسلي المستعمل. باختصار إذا كنت تعرف نوع الخوارزمية و كانت الدالة العشوائية التي تزودها ضعيفة بحيث يمكن التكهن بالأرقام العشوائية التي تنتجها فيمكنك نظريا كسر هذه الخوارزمية. الحصول على أرقام حقيقة عشوائية صعب جدا في الحواسيب ! لأن هذه الأخيرة صممت كي تكون محددة في عملها (deterministic) و بالتالي أي دالة رياضية تستخدم لتوليد أرقام عشوائية سيتم كسرها و لو طالت دورتها .. لذلك عندما يحتاج برنامج ما إلى أرقام عشوائية تصلح في عمليات التشفير ( crypto grade random numbers) فيجب تزويد الحاسوب ببطاقة خاصة تقوم بإنتاج هذه الأعداد انطلاقا من "ظاهرة فيزيائية عشوائية" و ليس "دوال رياضية" و نظام FreeBSD يدعم هذا النوع من العتاد. 3- كيف يمكننا توليد أرقام عشوائية ؟ يمكن توليد الأرقام العشوائية بأكثر من (قانون,خوارزمية,طريقة) و كلما كان القانون أكثر تعقيدا يكون العدد أقرب ما يكون للعشوائية .. يعني يصبح من المستحيل (بالنسبة للمستخدم) معرفة سلوك الدالة التي تتولى المهمة .. فنحن عندما نقوم بإنشاء دالة عشوائية ترجع قيمة العدد المُدخل قسمة على 2. الآن أصبح من السهل اكتشاف سلوك هذه الدالة .. فعندما يقوم المستخدم بتشغيل هذه الدالة 10 مرات (مثلا) سيتمكن من استنتاج سلوك هذه الدالة بكل سهولة .. هنا يكمن احتياجنا في قانون رياضي يكون معقد بعض الشيء ..مما يربك المستخدم أثناء محاولته معرفة سلوك الدالة. الكلام السابق يكون أكثر دقة إذا كان العدد العشوائي عبارة عن عدد "كسري" وكلما كانت الفاصلة غير منتهية كان الكلام أدق .. أما إذا كان العدد العشوائي عبارة عن عدد صحيح .. فسيكون الكلام السابق غير دقيق تماما ! لأن المستخدم أصبح بإمكانه اكتشاف سلوك الدالة التي تولد الأعداد العشوائية. فيمكنه (مثلا) توليد 1000 عدد بين 1 و 10 مما يقوده إلى استنتاج سلوك الدالة .. وهكذا .. يعني يختار مجال ضيق ويقوم بتوليد الكثير من الأعداد العشوائية في المجال المُختار .. مُستنتجا الملامح الأولى لسلوك هذه الدالة. الآن .. سنقوم باستعمال دالة تقوم بتوليد رقم عشوائي. الدالة التي سنستعملها هي الدالة rand وتوجد في المكتبة cstdlib , هذه الدالة تتبع خوارزمية مُعقدة بعض الشيء, مما يجعل عملية تتبع الرقم المُولد من طرف الدالة "عملية صعبة" إن لم تكن "مستحيلة" , هذا في حالة استخدام البذرة أما إذا لم نستخدمها فستقوم الدالة بتوليد أرقام عشوائية "شبه منتظمة" و السبب هو أنها تتبع خوارزمية ثابتة لتوليد الأرقام العشوائية مثلا .. إذا قام المستخدم بأخذ مجال ضيق للأعداد الصحيحة و قام بتوليد الكثير من الأرقام العشوائية ثم قام بتكرار العملية عدة مرات .. فسيلاحظ انتظام هذه الأعداد بشكل أو بآخر .. لذلك قام المبرمجون بإنشاء الدالة srand التي تعتمد على توليد الأرقام العشوائية حسب البذرة (seed) والبذرة عبارة عن عدد صحيح , هذه البذرة يجب أن تكون متغيرة في كل وقت .. لكي يكون العدد المولد "شبه عشوائي" , و عادة ما يستخدم المبرمجون "الزمن" كبذرة للدالة srand , يعني نُسند قيمة الثواني إلى الدالة srand فنحصل على رقم عشوائي و عندما نُسند قيمة الزمن مرة أخرى سنحصل على عدد عشوائي آخر ! لأن الوقت يتغير من لحظة إلى أخرى. و لكن كيف يمكننا إسناد قيمة الثواني ؟ يتم ذلك باستخدام الدالة time الموجودة في المكتبة ctime , هذه الدالة تستقبل الوسيط المعدوم NULL أو يمكن أن نكتب العدد 0 ولكنني أفضل الكتابة الأولى. الدالة time تستقبل الوسيط NULL و تعيد عدد الثواني التي مرت من الساعة: 00:00 في اليوم 1 جانفي في عام 1970. طبعا يمكننا الآن أن نستنتج عدد الدقائق و الساعات والأيام و الشهور ..إلخ انظر الكود: ```cpp long seconds, minutes, days, hours; seconds = time(NULL); minutes = seconds / 60; hours = minutes / 60; days = hours / 60; ``` و يمكننا أن نكتب كلمة time\_t بدل الكلمة long لأن المكتبة ctime تحتوي على المتغير time\_t المعرف كما يلي: ```cpp typedef long time_t; ``` الآن سنقوم بكتابة كود يخرج عددا عشوائيا: ```cpp #include <iostream> #include <ctime> #include <cstdlib> using namespace std; int main() { srand(time(NULL)); int n = rand(); cout << n << endl; system("pause"); return 0; } ``` لاحظ أننا قمنا أولا بتجهيز الدالة srand عن طريق البذرة (seed) و من ثم قمنا بإسناد القيمة التي تعيدها الدالة rand إلى المتغير n. حسنا .. الآن فهمنا كيفية توليد رقم عشوائي باستخدام الدالة rand و بالإعتماد على الدالة srand التي تحتاج إلى البذرة seed و التي عادة ما تكون "الزمن" أو لنقل:عدد الثواني المُعاد من طرف الدالة time. الآن يمكننا استنتاج كيفية توليد أرقام عشوائية محصورة بين عدد معين يُدخله المستخدم و الصفر .. كيف ذلك؟ كل ما في الأمر أننا سنستخدم الرمز % و الذي يعني باقي القسمة و يخضع للقانون التالي: باقي قسمة عدد a على b يكون دائما أصغر تماما من b و أكبر أو يساوي 0 لنسمي r باقي قسمة a على b و q ناتج القسمة , إذا : a = bq + r حيث : r محصور بين 0 و b و تسمى هذه النظرية بنظرية "اقليدس" وهو العالم الرياضي المشهور. مثال: ```cpp #include <iostream> #include <ctime> #include <cstdlib> using namespace std; int main() { int n; srand(time(NULL)); cout << "n="; cin >> n; cout << rand() % n << endl; system("pause"); return 0; } ``` و يمكننا توليد العديد من الأعداد العشوائية باستخدام الحلقة for. طيب .. كيف يمكننا توليد أرقام عشوائية محصورة بين عددين يُدخلهما المستخدم ؟؟؟ قلنا سابق أنه يجب علينا إتباع قانون معقد بعض الشيء .. و القانون الذي سأستعمله هو: ملاحظة:القانون مكتوب باللغة الفرنسية ولكني شرحته باللغة العربية. ```cpp Le nombre aléatoire = la valeur initiale + rand() % (la valeur scalaire); valeur scalaire = valeur finale - valeur initiale + 1; ``` حيث: العدد العشوائي : Le nombre aléatoire العدد الأول أو القيمة الإبتدائية : La valeur initiale القيمة الموجهة : La valeur scalaire العدد الثاني أو القيمة النهائية : La valeur finale طريقة توليد حروف عشوائية: ```cpp #include <iostream> #include <ctime> #include <string> using namespace std; int main() { string str = "ABCDEFGHIJKLMNOPQRST"; srand(time(NULL)); cout << str[rand() % 20] << endl; system("pause"); return 0; } ``` في الحقيقة .. فإن هذا المثال بسيط و لا يحتاج إلى شرح و لكن سنمر عليه مر الكرام. في البداية قمنا بالإعلان عن سلسلة حرفية باسم str ثم قمنا بتوليد رقم عشوائي يقع في النطاق المسموح به .. أي ما بين 0 و 19 و سبب ذلك هو أنه إذا كان العدد العشوائي يأخذ قيمة أكبر من 19 أو أصغر من الصفر فسننتقل إلى حدود خارج نطاق المصفوفة مما يسبب ظاهرة الفيض الحسابي او ما يعرف بـ overflow لأن عناصر المصفوفة تمتلك الدليل من 0 و حتى 19 , لاحظ أن باقي القسمة على 20 يأخذ نفس المجال الذي يأخذه دليل عناصر المصفوفة. الفكرة هنا تكمن في توليد رقم عشوائي يقع ضمن المجال الذي يتحرك فيه دليل عناصر المصفوفة ثم نقوم بطباعة العنصر الموافق للرقم العشوائي المُوَّلَد .. مثلا إذا كان الرقم المولد هو 2 فسيظهر الحرف C لأنه يأخذ الترتيب 2 في المصفوفة. 4- كيف ننشئ دالة عشوائية ؟ رأينا سابقا كيفية توليد أرقام عشوائية باستخدام دوال جاهزة .. ولكن كيف يمكننا إنشاء دالة تقوم بتوليد الأعداد العشوائية ؟ و ما هي أهم الخوارزميات المستخدمة في هذا المجال ؟ في الحقيقة هناك بعض الأمور التي صنفها علماء الفيزياء على أنها عشوائية مثل تغير المقاومة واحتمال وجود "فوتون" في مكان معين ولكن دمجها في المعلوماتية في غاية الصعوبة .. إن لم يكن مستحيــل لذلك تم اللجوء إلى التفكير في الخوارزميات وأول خوارزمية طُرحت كانت من طرف العالم الشهير: "Von Neuman" وهو أحد رواد المعلوماتية. الخوارزمية التي اكتشفها "فون نيمان" تعتمد على اختيار عدد معين و رفعه للقوة 2 واختيار رقمين من الوسط في الناتج. مثال:نختار الرقم 35 , بعد رفعه للقوة 2 سنجد العدد 1225 نختار رقمين من وسط العدد 1225 وهما 22 , نعاود نفس العملية .. نرفع 22 للقوة 2 فنحصل على العدد 484 نلاحظ أن العدد يتكون من 3 أرقام , نضيف 0 من اليسار ليصبح عدد أرقام العدد عبارة عن 4 كالتالي 0484 الآن نختار رقمين من الوسط و هما 48 , إذا استمرينا على نفس الحال فسنصل إلى هده المتتالية التي تشكل أرقاما عشوائية: 35 22 48 30 90 10 ولكن إذا جربنا الآن أن نقوم برفع 10 إلى القوة 2 واختيار رقمين فإننا دائما نعود إلى الرقم 10 وهذه مشكلة .. ولكن لا بأس بانطلاقة كهذه ... و لكي تكون الخوارزمية أكثر تعقيدا, يقوم المبرمجون بالإعتماد على الوقت كبذرة لتوليد الأعداد العشوائية (كما قلنا سابقا). بلا شك هده ليست أفضل خوارزمية ممكنة .. ولكنها مهمة لأنها أعطت الانطلاقة .. فظهرت عدة خوارزميات .. مثل الخوارزمية التالية: ```cpp #include <iostream> #include <ctime> using namespace std; int main() { unsigned int a = time(NULL), b = time(NULL), m = 100; unsigned int nombre = time(NULL); for (int i = 0; i < 20; i++) { nombre = (a * nombre + b) % m; cout << nombre << endl; } system("pause"); return 0; } ``` هذا البرنامج سوف يطبع 20 رقما عشوائيا وبالنسبة للخوارزمية فهي تعتمد على أخذ رقم معين يتم الحصول عليه من الزمن وبعد ذالك يتم ضربه في أحد الأرقام العشوائية التي اخترناها في البداية ثم إضافته إلى رقم عشوائي آخر .. و أخيرا نقوم بحساب باقي القسمة على الرقم 100 لأنني أردت إظهار أعداد عشوائية أقل من 100 و يمكنك تغيير العدد m كيفما تشاء .. كما يمكنك أن تجعله متغير عشوائي! كأن تسند إليه القيمة المعادة من طرف الدالة time مثلا و لكن ستظهر لكَ أعداد ضخمة. 5 - مثال عملي في المرفقات, كيجن بسيط كتبته قبل مدة بالجافا. [![KeyGen](https://informatic-ar.com/wp-content/uploads/2013/10/KeyGen-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/KeyGen.png) للتحميل : [KeyGen v 1.0.2](https://informatic-ar.com/wp-content/uploads/2013/10/KeyGen-v-1.0.2.rar) تحياتي. ### تحليل الفايروس Perlovga وكتابة المضاد للفايروس URL: https://informatic-ar.com/perlovga_virus/ Last updated: 2026-10-03T22:31:57.000Z في هذا الموضوع سوف نتحدث بشكل عملي على تحليل أحد الفايروسات الحقيقيةو هو فايروس Perlovga كما يسمية مضاد Mcafee وهناك أسماء أخرى لهذا الفايروس مثل TROJ\_PERLOVGA.A (من تسمية شركة Trend Micro) ، باستخدام اداوت التحليل ومن ثم نتعلم كيف يتم القضاء عليها بواسطة برنامج بسي++ ، والقائمه التالية هي الأسماء الأخرى لهذا الفايروس Aliases من مضادات الفايروسات الأخرى: - Mcafee: W32/Perlovga - F-Secure: Trojan-Dropper.Win32.Small.apl - Eset: Win32/TrojanDropper.Small.APL - Bitdefender: Trojan.Dropper.Small.APL هذا الفايروس يصيب أنظمه الويندوز Win32 وتم اكتشافه في منتصف 2006 وما يزال الى الأن يتواجد في الكثير من الأجهزه وينتشر عن طريق النقل بالوسائط مثل الفلاش ديسك Flash Disk ، وحينها يقوم الفايروس بعمل ملفين يضعهم في مجلد النظام System32 (الملف الأول بالإسم temp1.exe ويقوم بانشاء ملف بالإسم svchost.exe ويضعه في مجلد C:\\Windows أما الملف الثاني temp2.exe فيقوم بالإتصال عبر المنفذ 8888 الى الموقع 211.69.242.91 وربما لغرض تحميل تروجان أو فايروس أخر ). هذا كل ما يتعلق بعمل الفايروس Payload ، وهو انشاء هذين الملفين وتشغيل الملف svchost.exe عند بدء التشغيل وذلك من خلال التعديل في قيم الريجستري ووضعها في الStartup ، وكما يتضح من الPayload فالفايروس يعتبر من الفايروسات البسيطة قليلة الخطر على المستخدم Low Risk ، أي أن ضرر الفايروس في وضع الملفين في الجهاز لذلك يطلق على هذا النوع من الفايروسات Dropper Virus. ***التحليل المبدئي*** ***Static Analysis*** ***:*** دعنا الأن نأخذ نظره مقربه أكثر على الفايروس وطريقه عمله ونبدأ بتحليل الفايروس بدون تشغيله Static Analysis وهنا يمكن استخدام العديد من الأدوات والتي نريد من خلالها التعرف على بعض الأمور المتعلقة بالفايروس ، مثلا يمكن من خلال بعض هذه الأدوات معرفه جميع النصوص في البرنامج string ، أومعرفة هل البرنامج مضغوط أو مشفر Packed أم لأ ، ومعرفة الدوال والمكتبات التي يتعامل معها ال [![الشكل يبين استخدام برنامج Virtual Machine لعمل الجهاز التخليي](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_1-300x230.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F1.png) الشكل يبين استخدام برنامج Virtual Machine لعمل الجهاز التخليي فايروس Imported Function وغيرها من الأمور التي ستتوضح بعد قليل. وأكثر طريقه يمكن من خلالها التعرف على طريقة العمل بالتفصيل هي من خلال أستخدام أي Disassembler لأخذ نظرة مقربة على الكود ومعرفة تفاصيله . وقبل البدء بأي عملية جراحية يفضل ان تكون على نظام أو جهاز أخر بخلاف الجهاز العادي التي يتم العمل عليه وتصفح الانترنت وما الى ذلك حتى لا تتسبب في اصابة الملفات في حالة القيام بفتح الفايروس عن طريق الخطأ وبالتالي تخسر ملفاتك بلمح البصر، لذلك يفضل دائما حفظ البيانات في مكان أخر Backup ، كما يفضل أخذ صوره Image للنظام وبالتالى عند عند اصابة الجهاز بأحد الفايروسات أو تم التغيير في أحد إعدادات النظام فيتم ارجاع النظام للصوره السابقة له Image في عده دقائق. أحد أشهر برامج حفظ واستعادة هذه الImage هو Norton Ghost. حاليا سنعمل على جهاز أفتراضي وليس على الجهاز العادي ، وسنستخدم أي برنامج Virtualization مثل VMware و VirtualPC من مايكروسوفت وهناك غيرها الكثير ،، في هذا المثال سوف نستخدم برنامج Virtual Box المجاني من Sun نظرأ لصغر حجمه وسرعته في العمل وسهوله التعامل معه. بالإضافة الى برنامج الVirtualization سوف يتم استخدام برنامج Deepfreeze والذي سوف يعود النظام الى حالته الأصليه بعد عمل أي تغييرات عند اعادة التشغيل Restart، وهو مفيد بعد أن تقوم بتشغيل الفايروس وتلاحظ مهامه حينها يمكنك اعادة التشغيل وسيرجع الجهاز الى حالته الأصلية من قبل الإصابة بالفايروس. الشكل التالي يبين استخدام برنامج الVirtual Box والDeepfreeze . نقوم بعد ذلك بجلب الفايروس الذي نود تحليله الى الجهاز التخيلي ، وذلك من خلال نسخه بواسطة Flash Disk الى النظام التخيلي أو تحميله من الأنترنت ، أو باستخدام أي طريقة أخرى مثل نقله من الجهاز المضيف Host الى التخيلي وذلك بعمل قرص تخيلي في الجهاز المضيف ومن ثم يتم الوصول لهذا القرص من خلال الجهاز التخيلي (باستخدام برنامج UltraISO) ، والشكل التالي يوضح الفايروس بعد جلبه الى الجهاز التخيلي و الذي سوف نقوم بتحليله : [![الشكل يبين ملف الفايروس الذي نريد تحليله](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_2-300x247.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F2.png) الشكل يبين ملف الفايروس الذي نريد تحليله بعد ذلك نبدأ بجمع معلومات أولية مثل اسم الملف وهو كما يتبين الأن في الصوره malware.exe ولكن بكل تأكيد لن تفيد هذه المعلومه كثيرا حيث يمكن تغيير اسم الملف لذلك لن يتم الإعتماد على هكذا معلومة ، أيضا يتم ملاحظه حجمه وهو 68.5 KB ومن ثم نرى هل هناك Attribute (للقرائه Read-only - مخفي Hidden ) على هذا الملف وحاليا كما هو واضح من الصوره أن الملف هو ملف عادي غير مخفي hidden وليس read-only . هذه المعلومات يتم جلبها من خلال الضغط على الفايروس بالزر الأيمن وعرض الخصائص للفايروس . [![الشكل يبين معلومات أولية عن الملف](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_3-274x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F3.png) الشكل يبين معلومات أولية عن الملف نقوم الأن بمعرفه هل الفايروس مشفر أو مضغوط وفي حال لم يكن كذلك سوف يتم معرفة لغه البرمجه التي استخدمت في كتابة الفايروس وهناك الكثير من البرامج للتعرف على هذا الأمر ، وسنستخدم الأن برنامج PEiD لمعرفه ذلك : [![الشكل يبين استخدام برنامج PEiD لمعرفة هل البرنامج مشفر أم لا](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_4-300x200.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F4.png) الشكل يبين استخدام برنامج PEiD لمعرفة هل البرنامج مشفر أم لا بعد ذلك يمكن الإطلاع علي محتويات البرنامج من خلال أي محرر هكس Hex Editor ولكن ستكون هناك كميه من البيانات والتي تتطلب وقتا لقرائتها وفهمها لذلك يمكن استخدام برامج صغيره مثلا BinText والذي سيقوم باستخراج النصوص من البرنامج ومعرفه الstring سواء كانت نصوص عاديه أم دوال يستدعيها البرنامج ، كما يوضح ذلك الشكل التالي : [![الشكل يبين استخدام برنامج BinText لاستخراج النصوص من البرنامج](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_5-300x238.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F5.png) الشكل يبين استخدام برنامج BinText لاستخراج النصوص من البرنامج ويفضل أخذ الChecksum للملف التنفيذي (الفايروس) من قبل اجراء أي تجارب عليه وبعد اجراء أي تجربة يتم اعادة أخذ الChecksum لمعرفه أي تغيير حصل على الفايروس لأن هناك برامج تقوم بتغيير نفسها بعد العمل Self Modified Code ، ويمكن أن يكون الChecksum عباره عن CRC أو Hash ، وحاليا سوف يتم أخذ الMD5 Hash للملف ، ويمكن كتابه برنامج لهذا الأمر أو الإستعانه بأحد الأدوات مثلا md5sum ، والشكل التالي يوضح الهاش الناتج وهو 1d4c07370babee309401a73ebaa64f58 باستخدام البرنامج md5sum : [![الشكل يبين استخراج الهاش للملف التنفيذي](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_6-300x147.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F6.png) الشكل يبين استخراج الهاش للملف التنفيذي نكمل الأن التحليل الستاتيكي ونصل لأهم طريقة لتحليل الفايروس وهي عن طريق استخدام الDisassembler ، وسوف نستخدم الDisassembler الذي يأتي مع المنقح OllyDB ، وسوف نلقى نظره على أهم الدوال بشكل سريع فنحن نريد أخذ فكره عن عمل الفايروس بشكل سطحي وليس فهم أدق التفاصيل بشكل 100% ، كما يتبين من الصور التاليه : [![الشكل يبين فتح الفايروس لملف للقرائه](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_7-300x152.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F7.png) الشكل يبين فتح الفايروس لملف للقرائه هنا في الشكل السابق يقوم الفايروس بالبحث عن مجلد النظام باستخدام الدالة GetSystemDirectory والتي أخرجت له مجلد النظام وسوف يستخدمه لكي يضع المخلفات فيها وسيقوم بالقرائه من الفايروس والكتابه في الملفات الجديدة . [![الشكل يبين قيام الفايروس بالكتابه في الملفين temp](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_8-300x179.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F8.png) الشكل يبين قيام الفايروس بالكتابه في الملفين temp الأن واضح أن الفايروس قام بانشاء الملف الأول temp1.exe وفي نفس الحلقة سوف ينشئ الملف الثاني temp2.exe ويقوم بالكتابه في هذه الملفين (طريقة الكتابة غريبه بعض الشيء) . بعد ذلك سيقوم بتشغيل كل من هذه الملفين باستخدام الدالة CreateProcess كما يوضحه الشكل التالي : [![الشكل يبين تشغيل الفايروس للملقين باستخدام الدالة CreateProcess](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_9-300x154.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F9.png) الشكل يبين تشغيل الفايروس للملقين باستخدام الدالة CreateProcess ***التحليل الديناميكي*** ***Dynamic Analysis*** ***:*** الطريقة الأخرى لتحليل الفايروسات وهي الطريقة الديناميكية وهنا سوف نقوم بتشغيل الفايروس وملاحظه وظيفته أثناء عمله أي أننا سنقوم بتشغيل الفايروس بأنفسنا ولن يضر الجهار التخيلي ذلك بسبب وجود برنامج الDeep Freeze والذي سيعيد الجهاز الى حالته الأصليه عند اعادة تشغيل الجهاز. ويمكن الإستفاده في هذا التحليل من أدوات الMonitoring لملاحظه تغييرات الريجيستري التي يغيرها الفايروس وملاحظه العمليات على الملفات في القرص I/O وملاحظه التعامل مع الشبكة سواء بارسال أو استقبال بيانات ، وأشهر الأدوات هي أدوات Sysinternal. وقبل أن نقوم بتشغيل الفايروس يجب أخذ حالة مسجل النظام الحاليه Registry ومن ثم بعد تشغيل الفايروس يتم أخذ حالة الـ Registry مره أخرى وبعدها تتم عملية المقارنه واكتشاف أي تغييرات حدثت على الريجستري، وسوف نستخدم الأداه الصغيره RegShot كما توضحه الصوره التاليه : [![الشكل يبين استخراج حالة مسجل النظام قبل وبعد تشغيل الفايروس](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_10-300x185.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F10.png) الشكل يبين استخراج حالة مسجل النظام قبل وبعد تشغيل الفايروس الأن سوف يتم تشغيل الفايروس ، وبعد تشغيل الفايروس خرجت الرسالة التاليه وعلى ما يبدوا هناك خطأ ما في الفايروس : [![الشكل يبين ما حصل بعد تشغيل الفايروس](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_11-300x145.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F11.png) الشكل يبين ما حصل بعد تشغيل الفايروس بعد تشغيل الفايروس بدأ الملف temp1.exe بالعمل في الذاكرة أم الملف الأخر فلم يعمل بسبب مشكلة ما ، على العموم يفضل حاليا بعد تشغيل الفايروس مباشره أخذ صوره أخرى لحالة مسجل النظام ومن ثم عمل مقارنه ما بين قيم الريجستري الجديده والقديمه واكتشاف التغيير والشكل التالي يوضح النتيجة ، ونلاحظ أن الفايروس يضيف برنامج جديد باسم svchost.exe يعمل مع بدء التشغيل ويكون هذا الملف موجود في المسار C:\\windows . ولكن بعد الذهاب لذلك المسار لم يتم ايجاد الملف وهو دليل على أن هناك مشكله في الفايروس ولم يستطع انشاء الملف من الأساس! [![الشكل يبين اظهار التغييرات التي حصلت في مسجل النظام ويوضح القيمة التي يضيفها](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_12-300x154.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F12.png) الشكل يبين اظهار التغييرات التي حصلت في مسجل النظام ويوضح القيمة التي يضيفها وهذه الملفات التي قام الفايروس بانشائها وهي temp1.exe و temp2.exe وتكون موجوده في مجلد النظام system32 : [![الشكل يبين الملفات التي قام الفايروس بانشائها](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_13-300x153.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F13.png) الشكل يبين الملفات التي قام الفايروس بانشائها حاليا بعد اكتشاف التغييرات التي يحدثها الفايروس باستخدام الأدوات أعلاه فيمكن القيام بها مره أخرى ولكن باستخدام أدوات أخرى وذلك لاستخراج أكبر قدر ممكن من المعلومات ، فبالتالي يمكن اعاده تشغيل الجهاز التخيلي ومن ثم البدء بتحليل الفايروس باستخدام أداوت أخرى ولتكن أدوات الMonitoring مثل Process Monitor حيث تقوم هذه الأداوت بمراقبه كل العمليات في الProcess لكل العمليات Processes الموجودة في النظام. وعند التعامل مع البرنامجProcess Monitor فسوف يقوم باخراج جميع المعلومات المتعلقه بأي عملية في النظام وهذا يؤدي لكثره المعلومات وصعوبه قرائتها وملاحظة الفايروس ، لذلك يجب عمل Filter واختيار مراقبة على عمليات معينه في الProcess المعين ، وسنقوم باختيار العمليات التي نريدها للعملية Process الذي نريد ، الشكل التالي يبين مراقبه العملية Malware.exe وسيتم مراقبة فقط عمليات الكتابة والقرائه على الملفات ومسجل النظام وبضعه أمور أخرى يبينها الشكل التالي: [![الشكل يبين استخدام برنامج File Monitor لمراقبة العمليات](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_14-300x234.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F14.png) الشكل يبين استخدام برنامج File Monitor لمراقبة العمليات هكذا ستكون النتيجة كلها عن العملية malware.exe وبالتالي يمكن ملاحظه التغييرات بكل سهوله ، الشكل التالي يوضح ذلك: [![الشكل يبين بعد استخدام ال Filter لمراقبة عملية واحدة](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_15-300x224.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F15.png) الشكل يبين بعد استخدام ال Filter لمراقبة عملية واحدة لنلقى نظره على معلومات الملفات التي أنشأها الفايروس ، والحصول على هذه المعلومات تكون بنفس الطريقة التي استخدمناها لمعرفة معلومات الفايروس الأصلي وسوف نستخدمها الأن لمعرفه تفاصيل كل من الملفات الجديده سولء بمعرفة الحجم و الهاش و اللغه التي يعمل بها ويفضل عمل قرائه سريعه للكود ، الشكل التالي يوضح الهاش الناتج لكل من الملفين : [![الشكل يبين استخراج الهاش لكل من الملفين الذي انشأئهما الفايروس](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_16-300x151.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F16.png) الشكل يبين استخراج الهاش لكل من الملفين الذي انشأئهما الفايروس وهم كالتالي: temp1.exe: a5f8018df4209ae978b0907ce1664ff2 temp2.exe: 6350faf65f311c04cb4808ea3cad7ce7 لنلقى نظره على كل منهم على حده ، بالنسبة للملف الثاني temp2.exe فأهم ما فيه وهو محاولة الإتصال بالموقع (hnmy.3322.org) عن طريق السوكت TCP Socket ، الشكل التالي يوضح الكود المسؤول عن انشاء الإتصال: [![الشكل يبين أن الفايروس بتصل بالموقع عن طريق TCP Socket](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_17-300x184.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F17.png) الشكل يبين أن الفايروس بتصل بالموقع عن طريق TCP Socket أما الملف الأول temp1.exe فهو مشفر ببرنامج MEW 11 SE 1.2 ويحتاج للفك التشفير Unpacked ، وتمت تجربة برنامج Qunpacker ولكن لم ينجح في الفك لذلك لن نقوم بفكه حاليا ونكتفى بالمعلومات التي توفرت وهو أن الملف يكون موجود بالذاكرة temp1.exe فور عمل الفايروس. ملخص للفايروس الذي قمنا بتحليله هو أنه يقوم بانشاء ملفين temp1.exe وهي يقوم بمهمه ما لا نعرف ماهيتها الى الأن ولكن لم تظهر لنا اي نتيجة بعد تشغيل الفايروس (لا يوجد ملفات ينشئها أو مهمه يقوم بها) ، الملف الأخر يقوم بالإتصال لأحد المواقع بالسوكت عن طريق المنفذ 8888 وتحصل مشكلة ويتوقف عن العمل كما وضحت أحد الصور السابقة. موقع مكافي وضح عمل الفايروس W32/Perlovga كالتالي [![الشكل يبين تحليل مكافي للفايروس](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_18-300x244.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F18.png) الشكل يبين تحليل مكافي للفايروس وموقع Bitdefender وضح عمل الفايروس Trojan.Dropper.Small.APL كالتالي: [![الشكل يبين تحليل Bitdefender للفايروس](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_19-300x258.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F19.png) الشكل يبين تحليل Bitdefender للفايروس ولكن للأسف الملفات الموضحه بالمربع الأحمر في الشكل أعلاه لا توجد في الجهاز بعد أن أصبناه ، وهذا يعود لإحتمالين لا ثالث لها ، الأول هو أن يكون الفايروس الذي قمنا بتحليله يختلف عن الموجود لدى Bitdefender والإحتمال الأخر وهو الإحتمال الاكبر وجود مشكله ما أدت الى أن يتوقف الفايروس عن العمل ولم يكمل انشاء هذه الملفات ! **كتابة المضاد للفايروس Perlovga Removal Tool** **:** بعد أن وضحنا عمل الفايروس بشكل سطحي ، يمكن الأن حذفه بسهوله من النظام وذلك من خلال البحث في المسارات التي تم كشفها والتي يستخدمه الفايروس ومن ثم القيام بتعديلها يدويا، أو كتابه بريمج صغير يقوم بالمهمه وهو ما سنفعله الأن ، أي كتابه أداه بسيطه للتخلص من هذا الفايروس Anti Perlovga وتقوم هذه الأداه بعد حذف الفايروس بتصليح المفاتيح في مسجل النظام والتي قام الفايروس بتغييرها. ***الخوارزمية لهذا المنظف*** ***Removal*** ***تكون كالتالي :*** - البحث في الذاكرة عن وجود الملف temp1.exe ، واذا وجد هذا الملف يتم اغلاقه . - البحث في المسار system32 عن الملفين temp1.exe و temp2.exe ويتم حذفهم. - البحث في المسار windows عن الملفات autorun.inf و svchost.exe و xcopy.exe ويتم حذفهم. - تعديل القيمه في الريجستري وارجاعها الى القيمه الأصليه . لتطبيق الخطوه الأولى سنحتاج لطريقة نمر من خلالها على جميع العمليات في النظام Enumerate Process ومن ثم نختبر كل عملية على حده ونرى هل أسم العملية هو نفس أسم الملف الذي نريد غلقه (في حال تغير الأسم يجب تغيير الإسم هنا أيضا ، ويمكن أن تكون المقارنة على أساس الHash للملفين) . وسوف يتم استخدام الدوال Process32First/Process32Next لإيجاد كل العمليات في النظام ومن ثم اغلاق العمليه باستخدام الداله TerminateProcess . الكود التالي يوضح العملية : [![الشكل يبين طريقة اغلاق عملية من العمل من خلال اسم العملية](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_20-300x199.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F20.png) الشكل يبين طريقة اغلاق عملية من العمل من خلال اسم العملية الخطوه الثانيه وهي حذف الملفات التي يخلفها الفايروس وهي temp1.exe و temp2.exe في مجلد الC:\\WINDOWS\\system32 ، بالإضافة الى الملفات autorun.inf و svchost.exe و xcopy.exe في المسار C:\\WINDOWS . وسيتم استخدام الدالة DeleteFile للقيام بهذه المهمه ولكن بعد تعديل خصائص الملف وجعل الملف ملف عادي . الكود التالي يقوم بالمهمه : [![الشكل يبين حذف الملفات التي ينشئها الفايروس](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_21-300x136.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F21.png) الشكل يبين حذف الملفات التي ينشئها الفايروس الخطوه الأخيره وهي حذف قيمه الريجستري load الموجوده في المسار : HKEY\_CURRENT\_USER\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows . وسيتم استخدام دوال الريجستري للتعامل معها ، والكود التالي يوضح حذف القيمه : [![الشكل يبين حذف قيمة مسجل النظام التي يضيفها الفايروس](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_22-300x80.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F22.png) الشكل يبين حذف قيمة مسجل النظام التي يضيفها الفايروس وكهذا يكون الكود الكامل بعد تجميع تلك الأجزاء على الشكل التالي: [![الشكل يبين تطبيق الدوال السابقة للقضاء على الفايروس](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_23-300x126.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F23.png) الشكل يبين تطبيق الدوال السابقة للقضاء على الفايروس نقوم الأن بعمل أعاده تشغيل الجهاز التخيلي ومن ثم اصابة الجهاز التخيلي بالفايروس ، وبعدها يتم نقل المضاد للفايروس الى الجهاز التخيلي ومن ثم تشغيله وتخرج الرسالة التاليه : [![الشكل يبين مخرج البرنامج بعد تنفيذه](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga_24.png)](https://informatic-ar.com/wp-content/uploads/2013/10/perlovga%5F24.png) الشكل يبين مخرج البرنامج بعد تنفيذه وهكذا يتم القضاء على الفايروس وحذف كل المخلفات التي يتركها Dropper ويعدل قيم الريجستري التي يغيرها الفايروس ، ويمكن عمل اعاده تشغيل بالضغط على ok . > هذه المقالة قمت بنشرها في 2009 في احد مواقع الهندسة العكسية وفي احد كتب الخاصه بالقضاء على الفايروسات ### تحليل البرامج الخبيثة Malware Analysis URL: https://informatic-ar.com/malware_analysis/ Last updated: 2026-10-03T22:31:58.000Z تتحدث هذه المقالة عن موضوع تحليل البرامج الخبيثة وأهمية ذلك المجال لمختصي أمن المعلومات وبشكل عام لأقسام أمن المعلومات في المنشآت المختلفة، ثم تتدرج المقالة في الحديث عن طرق تحليل هذه البرامج الخبيثة لمعرفة ما تقوم به في نظام المستخدم بدون علمه وتتناول الأدوات الأساسية التي يمكن لأي فرد الدخول والبدء في مجال تحليل الفيروسات. البرامج الخبيثة Malware هي برامج عادية مثل التي نستخدمها بشكل دوري، ولكن لها طابع خبيث لا نعلم عنه شيئاً حيث تختفي في بعض الأحيان خلف شاشات مبهرجة أو ألعاب قد تحبها (Trojans) وأحياناً سوف تعمل وأنت لا تعلم بأنها تسرح وتمرح على حاسوبك (Root kits) وهذا ما يجعلها خبيثة وما يجعل برامج الأنتي فيروس في حالة بحث دائم عنها. هذا الطابع الخبيث قد يكون مجرد طباعة رسالة على الشاشة كل فترة معينة، وقد يكون مجرد سرقة كل ما تكتبه على لوحة مفاتيحك وإرساله لجهة معينة، وقد يكون حذف وتدمير جميع محتويات القرص D في جهازك. كل هذه الأدوار هي مجرد أدوار تلعبها البرامج الخبيثة بأشكالها وألوانها المتعددة والتي يحدد هدفها قبل إطلاقها وكتابتها ونشرها بين العموم. فالبرنامج الخبيث الذي ينتظر لوحة مفاتيحك بفارغ الصبر لكي يلتقط كل ما تقوم بكتابته (Key loggers) يختلف هدفه عن البرنامج الخبيث الذي يقوم كل فترة بإصدار رسائل إعلانية مزعجة لك (Spyware) ويختلف هدفه عن البرنامج الخبيث الذي لا يفعل شيئاً سوى استقبال الأوامر من جهاز بعيد لكي يستفيد من موارد حاسوبك ويقوم بتنفيذ خوارزمية على معالجك الذي لا يقوى على منع تلك الهجمات ولا التصدي لها بأي شكل من الأشكال. هذه الأيام ومع انتشار الفيروسات كانتشار النار في الهشيم فمعظم المستخدمين إن لم يكن جميعهم فقد أصيبوا بإحداها وسببت لهم مشاكل جمة أقلها كان نتيجته إعادة تهيئة الأقراص لديه وإعادة تحميل نسخة جديدة من النظام. لكن مثل هذه العادات والتي قد تؤثر على القرص على المدى البعيد لا يمكن تطبيقها في جميع الحالات كأجهزة السيرفرات والتي تحتوي على بيانات كثير من المواقع المستضافة لديها، أو الأجهزة التي تحتوي على قواعد البيانات لجميع الموظفين في شركة ما، أو حتى الأجهزة التي تحتوي على ملفات ومستندات سرية والأمثلة كثيرة . فإعادة التهيئة في مثل هذه الحالات قد يكون وبالاً من ناحية البيانات إذا ضاعت خصوصاً إذا لم تتوفر نسخ احتياطية من هذه البيانات ومن ناحية أخرى وهي في غاية الأهمية هي ضياع المعلومات عن ذلك الفيروس، ففي بعض الحالات قد يكون معرفة معلومات ذلك الفيروس أهم من المعلومات التي ضاعت. الأنظمة الآمنة أو التي تريد الوصول لهذه الغاية عندما تخترق أو يصيبها أحدى الفيروسات فهي لا تعتمد على المضادات فقط والتي طبيعة عملها إذا وجدت ذلك البرنامج الخبيث هي حذفه من النظام وإعادة النظام إلى حالته السابقة قبل الإصابة، ولكنها تريد التعرف عن طبيعة عمل الفيروس عندما أصاب الجهاز، ومعرفة المعلومات التي كتبها على الجهاز ومعرفة المعلومات التي أخذها من الجهاز. كل هذه الأجوبة وللأسف لا يجيب عليها مضاد الفيروسات ويتركك مبتسماً ابتسامة واسعة لأنه قد حذف بمساعدتك (الضغط على زر الحذف) البرنامج الخبيث الذي أصاب الجهاز وأنت لا تدري أنه بمجرد إعادتك لتشغيل الجهاز فقد تعمل 10 نسخ أخرى من الفيروس (أضافها الفيروس بعد الإصابة مباشرة وبطريقة مشفرة بطرق أخرى لا يعلمها مضاد الفيروسات الذي تستخدمه). معرفة طبيعة عمل الفيروس أثناء إصابته للجهاز وكيف أصاب الجهاز والأضرار التي سببها هي من المعلومات المهمة جداً والتي تبحث عنها أي منظمة تم إصابتها بأحد الفيروسات، ولهذا زاد الطلب على المختصين بهذه المهنة "محلل البرمجيات الخبيثة" وهذا مقال منظمة SANS (بعنوان " Coolest Jobs in Information Security") خير دليل على ذلك والذي يحتوي على قائمة أكثر الوظائف جذباً في مجال أمن المعلومات حيث حازت مهنة "محلل البرمجيات الخبيثة" على المهنة السادسة في قائمة المهن \[1\]. أحد المواضع الأخرى والتي تتجلي فيه فائدة تخصص "تحليل البرمجيات الخبيثة" هو في الحالات التي لا يمكن لبرنامج مضاد الفيروسات كشف ذلك الفيروس، وهي حالات كثيرة جدأ وذلك بسبب تزايد عدد تلك الفيروسات بشكل رهيب يومياً وصعوبة تحديث الأنتي فيروس لجميع هذه الفيروسات، إضافة إلى أن الفيروسات الحديثة يصعب اكتشافها بالطرق التقليدية (كالفحص عن تواجد توقيع الفيروس) وذلك بسبب استخدام الفيروسات لطرق متقدمة تجعله مشفر عن أعين برامج الحماية ويقوم بفك تشفيره بنفسه عندما يعمل بصورة آلية. كل هذا يجعل برامج الحماية دائماً تأتي متأخرة وبشكل بطئ أيضاً ، وقد تحدثت مجلة hakin9 في عددها Mobile Malware عن موت برامج الحماية في مقالة بعنوان "Is Anti-virus Dead, The answer is YES" وان كان ما زال لبرامج الحماية دور في الحماية وتقليص الأضرار بشكل كبير خصوصاً في البرامج الخبيثة القديمة أو التي تعدل من البرمجيات الخبيثة المعروفة، إلا أن الاعتماد عليها بشكل كلي في الحماية لا يكفي خصوصاً في حال معرفة الأسباب والحوادث التي سببها وللأسباب الأخرى التي ذكرت قبل قليل \[2\]. ## **البداية في تحليل البرمجيات الخبيثة** قبل البدء في تحليل البرمجيات الخبيثة يجب أن تكون لدى المحلل فكرة أساسية عن لغة التجميع "**أسمبلي**" حيث أنه في بعض الحالات سوف يستوجب الرجوع إلى هذه الصيغة الأولية حتى يتم معرفة بعض التفاصيل، ويفضل أن تكون لدى المحلل فكرة أساسية عن أنظمة الويندوز وتركيبتها خصوصاً في التعامل مع الذاكرة والملفات التنفيذية وأخيراً معرفة عامة في كيفية عمل البرمجيات والمترجمات والأنظمة التخيلية . هذه المتطلبات لا يعني أن يكون المحلل خبيراً بها ولكن الأساسيات في كل من هذه المجالات سوف يوسع مدارك المحلل وتقوى مهارات التحليل لديه ، وفي بعض البرمجيات الخبيثة المتقدمة سوف تحتاج لبعض من الخبرة من طرف المحلل. بعد ذلك تأتي مرحلة الأدوات التي سوف يستخدمها المحلل في عملية تحليل البرمجيات الخبيثة، ويمكن تقسيم هذه الأدوات بشكل عام إلى ثلاثة أقسام وهي: 1) أدوات توفير بيئة عمل أمنة Safe Environment 2) أدوات متابعة سلوك النظام بشكل دائم System Monitoring. 3) أدوات تحليل البرنامج التنفيذي نفسه Software Analysis ## أدوات توفير بيئة العمل الآمنة The Safe Environment تعتبر خطوة مهمة جداً أثناء تحليل البرمجيات الخبيثة، فإذا عمل المحلل على جهازه الشخصي وقام بفتح الفيروس والذي قام بدوره بمسح بيانات الجهاز فقد يخسر المحلل الكثير ولذلك يجب أن تتم عملية التحليل في بيئة آمنة تماماً لا يخاف من ضرر تشغيل الفيروس بها. يمكن أن تتم هذه العملية على أجهزة مخصصة كأجهزة حاسب قديمة ولا يستفاد منها. ولكن في الغالب فإن كثير من المحللين قد لا يتوفر لديهم جهاز آخر لغرض تحليل الفيروسات وذلك في هذه الحالة فعلى المحلل استخدام البرامج التخيلية Virtualization Systems والتي يمكن من خلالها اقتلاع جزء صغير من القرص الخاص بالمحلل ويتم تخصصيها للتجربة وأي أمر يتم تشغيله في هذه البيئة التخيلية يكون معزول على القرص المضيف ، أي يمكن تخيل هذه الأنظمة على أنها توفر للمحل جهاز كامل بمساحة معينة وذاكرة أيضا. من فوائد هذه الأنظمة هي أنه يمكنك إعادتها إلى لحظتها الأولية Rollback (وقت تحميلك للنظام) بكل سهولة، وهذا يعني انه يمكنك العمل في البيئة التخيلية مع الفيروسات وتشغيلها واختبارها وبعد ذلك يمكنك إعادة النظام لحالته الأصلية حتى تقوم بعمل الاختبار مرة أخرى أو لكي تختبر مجموعة جديدة من الفيروسات. من المهم تذكر أنه يجب فصل الجهاز التخيلي عن الشبكة الداخلية حيث إذا كان البرنامج الخبيث من النوع worms فمكن الممكن أن تنتشر الدودة في جميع الأجهزة عبر الشبكة، لذلك يجب أن يكون الجهاز التخيلي معزول عن الشبكة ومن الممكن عمل شبكة من الأجهزة التخيلية وكل من هذه الأجهزة تعمل بنظام تشغيل مختلف وذلك لاختبار مدى قدرة ال worms على الانتشار في الأنظمة المختلفة. من أشهر البرامج التي تقدم خدمة الVirtualization هي VirtualPC من شركة مايكروسوفت و VMWare أو VirtualBox من شركة أوراكل(صن سابقاً). الجدير بالذكر هنا أن بعض الفيروسات تقوم بالكشف على بيئة النظام الحالية عند تشغيلها وقد يتعرف على أنه يعمل ثم يصيب النظام المضيف host وذلك بالاستفادة من إحدى أماكن الضعف أو الثغرات الموجودة في البرنامج الذي يقدم خدمة الVirtualization، لذلك على المبتدئين في مجال التحليل التعامل في بادئ الأمر مع البرامج الخبيثة البسيطة والتي تم تحليلها سابقاً حتى اكتساب الخبرة. الشكل رقم (1 ) يبين برنامج خبيث يعمل تحت النظام التخيلي (برنامج VirtualBox). [![الشكل رقم (1) يبين الجهاز التخيلي أثناء العمل](https://informatic-ar.com/wp-content/uploads/2013/10/VirtualBox-300x187.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/VirtualBox.jpg) الشكل رقم (1) يبين الجهاز التخيلي أثناء العمل ## أدوات متابعة سلوك النظام System Monitoring فهذه الأدوات تريك ماذا يحصل تحت الخفاء أثناء وقبل وبعد عمل البرنامج الخبيث، فعندما يعمل البرنامج الخبيث فقد يقوم بتغيير بعض الإعدادات في مسجل النظام Registry لكي يجعل الفيروس قابل للإقلاع كل مرة عند تشغيل الجهاز أو قد يقوم البرنامج الخبيث بفتح مجموعة من الملفات والكتابة بها أو حتى قد يتصل بموقع أخر لأخذ أو إعطاء مجموعه من الأوامر و الملفات. أدوات شركة مايكروسوفت SysInternals وهي مجموعه من أشهر الأدوات (اشترتها الشركة قبل سنوات هي ومبرمج هذه البرامج) التي تساعد في فهم سلوك النظام فمنها ما يراقب اتصالات الشبكة عبر بروتوكول TCP/UDP (مثل برنامج TCPView) ومنها ما يراقب مسجل النظام (مثل برنامج Registry Monitoring) ومنها ما يراقب البرامج والعمليات في النظام (مثل برنامج Process Explorer و Process Monitor) ومنها ما يوضح لك قائمة جميع البرامج التي تعمل عند بدء التشغيل (Autoruns). وفيما يلي توضيح لبعض أشهر هذه البرامج التي تفيد محللي البرامج الخبيثة. - الأداة FileMon: وهذه الأداة تراقب أي عملية تجري على الملفات والمجلدات في النظام ويمكن أن تشاهد كل عمليات القراءة والكتابة التي تتم على الملفات من خلال البرامج والعمليات الموجودة على النظام. وبالتالي باستخدامك هذه الأداة يمكنك معرفة الملفات التي يقوم البرنامج الخبيث بفتحها أو الكتابة فيها أو لقرائه منها أيضاً يمكنك معرفة الملفات التي يقوم البرنامج الخبيث بحذفها. الشكل رقم (2) يبين الأداة FileMon . [![الشكل رقم (2) يبين أداة مراقبة العمليات على الملفات التي تقوم بها جميع العمليات على النظام](https://informatic-ar.com/wp-content/uploads/2013/10/FileMon-300x166.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/FileMon.jpg) الشكل رقم (2) يبين أداة مراقبة العمليات على الملفات التي تقوم بها جميع العمليات على النظام - الأداة TCP View: هذه الأداة تراقب كل الاتصالات الشبكية (TCP or UDP) التي تجريها العملية أو البرنامج وتقوم بعرض عنوان الجهة الأخرى التي قام البرنامج بالاتصال بها وعلى أي منفذ تم ذلك ولكنها لا توضح لك البيانات المرسلة أو القادمة. الشكل (3) يبين الأداة TCPView تحت العمل. [![الشكل رقم (3) يبين الأداة TCPView لمشاهدة جميع الاتصالات التي تقوم بها البرامج](https://informatic-ar.com/wp-content/uploads/2013/10/TCPView-300x179.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/TCPView.jpg) الشكل رقم (3) يبين الأداة TCPView لمشاهدة جميع الاتصالات التي تقوم بها البرامج - الأداة RegMon: هي أداة تراقب مسجل النظام Registry وتقوم بعرض جميع العمليات التي تحاول الوصول لمسجل النظام سواء كانت عمليات قراءة أو كتابة أو حذف كما توضح المفاتيح التي تستخدمها العملية، وهكذا يمكن معرفة المفاتيح التي يستخدمها البرنامج الخبيث أثناء عمله. الشكل رقم (4) يبين الأداة تحت العمل. [![الشكل رقم (4) يبين جميع المفاتيح التي تتعامل معها جميع العمليات على النظام](https://informatic-ar.com/wp-content/uploads/2013/10/RegMon-300x143.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/RegMon.jpg) الشكل رقم (4) يبين جميع المفاتيح التي تتعامل معها جميع العمليات على النظام - الأداة Process Explorer: يمكن تشبيه هذه الأداة ببرنامج ال Task Managerالموجود في أنظمة ويندوز حيث يعرض جميع العمليات التي تعمل في النظام ولكنه يوضح تفاصيل أخرى عن العملية مثلاً ملفات الDLL التي تستخدمها هذه العملية والمقابض Handle واتصالات الشبكة والذاكرة المستخدمة ونسبة استخدام العملية من المعالج وغيرها من المعلومات الأخرى. الشكل (5) يبين الأداة تحت العمل. [![الشكل رقم (5) يبين جميع العمليات والبرامج التي تعمل على النظام](https://informatic-ar.com/wp-content/uploads/2013/10/Process_explorer-300x186.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/Process%5Fexplorer.jpg) الشكل رقم (5) يبين جميع العمليات والبرامج التي تعمل على النظام من البرامج الأخرى التي تفيد محللي البرامج الخبيثة والتي برمجت بواسطة جهات مختلفة، منها: - أداة Regshot: ومن خلال هذه الأداة يمكن أخذ نسخة من حالة مسجل النظام ومقارنتها بأحد النسخة السابقة وسوف تستخرج لك الأداة جميع التغييرات التي حصلت بين هاتين النسختين. وتعتبر هذه الأداة من أهم الأدوات لمحلل الفيروسات حيث يقوم المحلل بأخذ حالة مسجل النظام قبل البدء بتحليل الفيروس وبعد أن يقوم المحلل بتشغيل الفيروس يعود مرة أخرى لهذه الأداة حتى يستخرج الأحداث التي فعلها الفيروس على مسجل النظام وبالتالي سوف يعرف محلل الفيروسات أثر الفيروس على مسجل النظام وكيف يمكن حذف هذه الآثار بعد حذف الفيروس، حيث برامج الحماية لا تقوم بإزالة بقايا الفيروسات وخصوصاً المفاتيح التي ينشئها في مسجل النظام. ## أدوات فهم وتحليل البرنامج نفسه Software Analysis وباستخدام هذه الأدوات يمكنك معرفة محتوى البرنامج التنفيذي ومعرفة النصوص التي فيه ويمكنك قرائه كود البرنامج بلغة الأسمبلي وذلك بعد تحويل البرنامج التنفيذي إلى هذه الصيغة باستخدام ما يعرف ب Disassembly فيما يلي توضح لأهم البرامج المستخدمة في تحليل البرامج الخبيثة وغير الخبيثة: - الأداة BinText: وعن طريق هذه الأداة يمكنك الحصول على جميع النصوص String الموجودة داخل الملف التنفيذي، وهي أداة مهمة حيث تستخرج لك جميع الأسماء والمواقع والمكتبات التي تستخدم في الملف التنفيذي وفي ملفات ال Resources التابعة للملف التنفيذي وبالتالي يستطيع المحلل أخذ فكرة جيدة عن هذا البرنامج الخبيث. الشكل رقم (10) يبين استخدام هذه الأداة. - أدوات الHex Editor : ومن خلال هذه الأدوات يمكن معرفة محتوى البرنامج التنفيذي أو أي ملف أخر، حيث تعرض هذه البرامج محتوى البرنامج الذي يراد تحليله بترميز السادس عشر وتقوم بمحاولة تحويل أي ASCII String إلى شكل يمكن للمستخدم قراءته. ومن أشهر هذه الأدوات Hex workshop و HxD. الشكل رقم (6) يوضح استخدام الHex Workshop. - أدوات تحليل البرمجيات Disassembler/Debugger: وهذه الأدوات تحول البرنامج التنفيذي إلى كود بلغة الأسمبلي ومن خلال هذا الكود يمكن للمحلل معرفة ماذا يفعل البرنامج الخبيث أثناء عمله ولكن في العادة يصعب الاعتماد على كود لغة الأسمبلي في فهم البرامج حيث أن الكود قد يكون معقداً ويحتوي على الكثير من التفرعات والجمل الشرطية لذلك يستخدم المحللون الDebuggers حيث يقوموا بتشغيل البرنامج تحت ال Debugger والذي سيقوم بتنفيذ البرنامج سطر بسطر وسوف يشاهد المحلل تأثير كل سطر على حده، إضافة إلى الاستفادة من إمكانيات الDebuggers وذلك بوضع نقاط معينة في البرنامج يتم التحليل منها. أشهر هذه البرامج هو برنامج IDA pro التجاري و برنامج OllyDbg المجاني والغني بالكثير من الإضافات المفيدة. الشكل رقم (13) يوضح استخدام OllyDbg. هذه أشهر البرامج والأدوات التي سوف تساعد المحلل في أداء عمله بشكل جيد، الفقرة التالية تتناول تحليل أحد البرمجيات الخبيثة والتي تم برمجتها لغرض توضيح كيفية استخدام هذه الأدوات في التحليل . ## **مثال مبسط لتحليل أحد البرامج الخبيثة:** من أحدى الأساسيات في أنظمة الويندوز هي أن أي ملف تنفيذي يكون مكون من عدة أقسام لكل منها هدف ما (تسمى بنية الملفات التنفيذي في أنظمة ويندوز ب Portable Executable Format واختصارا PE Format) . لنأخذ أحد الأمثلة على برنامج الآلة الحاسبة (أو أي برنامج تنفيذي آخر) في جهازك ولنقوم بفتحها بمحرر الهكس Hex Editor وليكن HxD ، وستجد أن أول حرفين في هذا الملف هي MZ (جميع الملفات التنفيذية يجب أن تبدأ بهذين الحرفين)، ويعود الحرفين MZ إلى اسم مصمم بنية الملفات PE. الشكل (6) يبين ذلك باستخدام أحد برامج الHex Editors . [![ الشكل رقم (6) يوضح استخدام محرر الهكس لمشاهدة ترويسة الملف التنفيذي](https://informatic-ar.com/wp-content/uploads/2013/10/Hex_Editors-300x154.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/Hex%5FEditors.jpg) الشكل رقم (6) يوضح استخدام محرر الهكس لمشاهدة ترويسة الملف التنفيذي إذا قمت بتغيير هذين الحرفين إلى أي قيم أخرى فلن يعمل البرنامج والسبب أن محمل الملفات التنفيذي في نظام الويندوز لن يستطيع التعرف عليها كملفات تنفيذيه وبالتالي لن تعمل هذه الملفات إلى أن تعود القيمتين إلى MZ كما كانت. الشكل التالي يبين أحد البرامج InfectTestFile.exe (وتم كتابته من أجل تجربة تحليل البرامج الخبيثة ) والذي يقوم بتغيير هذين الحرفين إلى أي قيم أخرى في برنامج تنفيذي اسمه Test.exe موجود على نفس مسار البرنامج الخبيث، وعند فتح البرنامج الخبيث والضغط على زر إصابة برنامج التجربة Test.exe سوف يصاب برنامج الاختبار ولن تستطيع تشغيله مجدداً إلا بعد إصلاحه. البرنامج لا يعد أي خطراً على الجهاز أو الملفات سوى أنه يغير في الملف الذي يأتي معه. ولكنه استخدم كمثال على أن مثل هذه التغييرات قد تشكل خطراً إذا وسع مجال العمل فيها وسوف يتبين بعد قليل نتيجة فحص الملف بعدة من برامج الأنتي فيروس. [![الشكل (7) يبين الأدوات التي سيتم استخدامها في المثال](https://informatic-ar.com/wp-content/uploads/2013/10/InfectTestFile-300x108.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/InfectTestFile.jpg) الشكل (7) يبين الأدوات التي سيتم استخدامها في المثال بعد فتحك للبرنامج الخبيث وإصابة ملف الاختبار فلن يمكنك تشغيل ملف الاختبار وإذا حاولت ذلك فلن يعمل ولن تخرج لك شاشة البرنامج الرئيسية والسبب انه تم إصابته وتغيير بعض من البيانات الهامة فيه. ولن يمكنك من إعادة تشغيل برنامج الاختبار إلا من خلال الضغط على زر الإصلاح في البرنامج الخبيث. [![الشكل (8) يبين البرنامج الخبيث والذي يحتوى على زر الإصابة وزر الإصلاح](https://informatic-ar.com/wp-content/uploads/2013/10/InfectTestFile1-300x174.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/InfectTestFile1.jpg) الشكل (8) يبين البرنامج الخبيث والذي يحتوى على زر الإصابة وزر الإصلاح هذا البرنامج الخبيث InfectTestFile.exe يمكن اعتباره على انه أحد البرامج الخبيثة والسبب في ذلك هو أن هذا البرنامج يغير في الملفات التنفيذية حتى لا تعمل مرة أخرى. هذا من وجه نظرنا المستخدمين فهذا البرنامج ضار وقد يؤثر على عملك حيث يمنع جميع الملفات التنفيذية من العمل وبالتالي يمنع نظام التشغيل من العمل. ولكن من وجه نظر المضادات فقد يعتبر هذا البرنامج على أنه برنامج حميد والسبب أن قاعدة بيانات مضاد الفيروسات لا تحتوي على توقيع هذا البرنامج. الشكل (9) يبين رأي برامج الأنتي فيروس في هذا البرنامج وأن هذا الملف ملف نظيف (يمكنك استخدام هذه المجموعة من خلال موقع virustotal.com). [![الشكل (9) يبين نتيجة فحص البرنامج الخبيث على مجموعه من برامج الأنتي فيروس والنتيجة أن الملف سليم](https://informatic-ar.com/wp-content/uploads/2013/10/virustotal_result-300x256.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/virustotal%5Fresult.jpg) الشكل (9) يبين نتيجة فحص البرنامج الخبيث على مجموعه من برامج الأنتي فيروس والنتيجة أن الملف سليم تخيل ماذا لو كان هذا البرنامج الخبيث يصيب جميع الملفات التنفيذية على الجهاز بمجرد فتحه وبدون تدخل المستخدم في ذلك، فما هي ردة فعلك عندما تعلم أن برامج الأنتي فيروس لن تكون قادرة على كشفه ولا حتى إصلاح الملفات المعطوبة. لذلك تحليل الفيروس هو الحل الوحيد في هذه الحالة حتى يمكن معرفة طريقة عمله وماذا وكيف يصيب ومن ثم يمكن كتابة برنامج يقوم بتطهير الملفات المصابة، فعملية تطهير الملفات من الفيروسات عملية صعبة ومكلفة وأغلب برامج الأنتي فيروس تستخدم الأسلوب الأسهل وهو حذف الملف بالكامل أو حجر الملف في الحجر الصحي وذلك لأن عملية تطهير الفيروسات تختلف من فيروس لأخر ويستوجب ذلك دراسة الفيروس دراسة دقيقة ومعرفة الجزئية التي يصيب بها الملف التنفيذي حتى يمكن اقتلاع ذلك الجزء من الملفات المصابة. كما يتضح من الشكل أن البرنامج الخبيث يحتوي على زر لتصليح الملف المعطوب بعد الإصابة، لكن ماذا لو لم يكن هذا الزر موجود، كيف يمكن أن تصلح ذلك الخلل، وكيف يمكنك أن تعرف ذلك الخلل من الأساس، هذا تطبيق عملي لأحد أساسيات تحليل البرامج الخبيثة وسنحتاج إلى تحميل البرنامج ونقله إلى داخل النظام التخيلي (أو يمكن العمل على الجهاز العادي، فضرر البرنامج هو تغيير برنامج اختبار Test.exe ولا يتعدى عمله ذلك، سوف تكتشف ذلك بعد قليل). بعد ذلك تبدأ خطوات التحليل الحقيقة، ويمكن تقسيم تحليل البرامج الخبيثة إلى قسمين، الأول عن طريق تحليل سلوك عمل البرنامج Behavior Analysis وهنا سوف يتم استخدام برامج مراقبة السلوك مثل مراقبة الملفات ومراقبة مسجل النظام ومراقبة الشبكة، القسم الآخر وهو تحليل كود البرنامج Code Analysis أثناء عمله وهنا سوف نحتاج لبرامج التنقيح Debuggers. في هذه المقدمة من الموضوع، لن نتطرق لجميع هذه البرامج وسنستخدم ما سنحتاجه فقط وذلك لأن البرنامج الخبيث بسيط في مهامه ولا نحتاج لبقية البرامج حيث لا توجد في البرنامج الخبيث وصول لمسجل النظام أو الشبكة إضافة إلى انه سيتم التطرق لها في الأجزاء الأخرى من المقالة فيما بعد إن شاء الله. أولاً يمكن استخدام الأداة BinText ولذلك لكي يتم معرفة النصوص التي وردت في البرنامج الخبيث، والقصد من ذلك هي أخذ فكرة عامة عن الأسماء التي توجد في البرنامج، سواء كانت أسماء لأشخاص أو أسماء لمواقع أو حتى ملفات موجودة على القرص. الشكل (10) يبين استخدام الأداة BinText. [![الشكل رقم (10) يبين استخدام الأداة BinText لاستخراج النصوص من الملف التنفيذي](https://informatic-ar.com/wp-content/uploads/2013/10/BinText-300x248.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/BinText.jpg) الشكل رقم (10) يبين استخدام الأداة BinText لاستخراج النصوص من الملف التنفيذي بعد ذلك يفضل أخذ حفظ نسخة من الهاش MD5/SHA1 لهذا الملف التنفيذي، وسيتم أخذها مرة أخرى بعد تشغيل الملف والغرض من ذلك هو اكتشاف أي تعديلات أجراها الملف على نفسه، ويمكن استخدام برنامج md5sum لحفظ MD5 للملف. ولكنها حالياً غير مهمة نظراً لبساطة البرنامج. الآن لنقوم بتشغيل البرنامج الخبيث وبرنامج الProcess Monitor لكي نلاحظ ماذا يفعل هذا البرنامج أثناء عمله، وعند فتح الProcess Monitor يمكن فلتره النتائج بحيث لا تخرج نتائج إلا النتائج المتعلقة بالبرنامج الذي نريد اختباره، وذلك من خلال القائمة Filter ثم اختيار filter وهذا ما يتضح من الشكل (11). [![ الشكل (11) يبين تحديد فلتر معين في برنامج Process Monitor وتم تحديد البرنامج الخبيث فقط لكي تتم مراقبته](https://informatic-ar.com/wp-content/uploads/2013/10/Process_Monitor-300x179.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/Process%5FMonitor.jpg) الشكل (11) يبين تحديد فلتر معين في برنامج Process Monitor وتم تحديد البرنامج الخبيث فقط لكي تتم مراقبته بعد ذلك سوف يصبح البرنامج في حالة مراقبة لكل العمليات التي يقوم بها البرنامج الخبيث، ويمكن تطبيق العديد من الفلاتر الأخرى وذلك لتقليل المعلومات وتقليصها إلى المعلومات التي يريدها المحلل، وذلك من خلال شريط الأيقونات أو من قائمة الأدوات، وكما يتضح من الشكل أنه تم حصر جميع النتائج التي يصدرها البرنامج الخبيث والمتعلقة بالتعامل مع الملفات، وبالتالي عند الضغط على زر الإصابة في البرنامج سوف يخرج برنامج المراقبة جميع أوامر الملفات التي أجراها البرنامج الخبيث وهكذا سوف يعرف المحلل ما هيه الملفات التي سوف يصيبها البرنامج الخبيث وفي مثالنا هذا اتضح أنه سوف يتعامل مع الملف Test.exe. [![ الشكل (12) يبين النتيجة بعد تطبيق الفلتر وتم تحديد أن العملية تكتب وتقرأ من الملف Text.exe](https://informatic-ar.com/wp-content/uploads/2013/10/scan_result_after_filtering-300x119.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/scan%5Fresult%5Fafter%5Ffiltering.jpg) الشكل (12) يبين النتيجة بعد تطبيق الفلتر وتم تحديد أن العملية تكتب وتقرأ من الملف Text.exe نأتي بعد ذلك إلى تحليل البرنامج نفسه ولننظر ماذا يفعل بالضبط أثناء عمله، يمكن أن نقوم بالنظر إلى كود البرنامج بالاسمبلي Disassembler وهو يتطلب معرفة جيدة في اللغة إضافة إلى معرفة في دوال نظام الويندوز حتى تسهل للمحلل معرفة الغرض من كل بلوك من الكود، ويمكن أن نقوم بتشغيل البرنامج تحت المنقح Debugger ويتم متابعة تأثير الأسطر خطوة بخطوة، ولكن بما أن البرنامج بسيط فيمكن تحليل الكود وقراءته بدون الحاجة إلى تشغيل المنقح Debugger ، الشكل (13) يبين البرنامج يعمل تحت المنقح OllyDbg ويوضح الكود المسئول عن الكتابة داخل الملف التنفيذي حيث يتم التحقق من وجود الأحرف AD (وهو الحرف M) و 5A (الحرف Z) فإذا وجدها يقوم بكتابة القيمة 30 (وهي تعادل صفر) مكان هذه الأحرف. هكذا نكون قد عرفنا وظيفة البرنامج بشكل مفصل، فهو يقوم بفتح الملف Test.exe ويقوم بتغيير الحرفين MZ إلى الحرفين 00 وهكذا يتعطب الملف عن العمل ولا يمكن إعادة تشغيله إلا من خلال إعادة الحرفين MZ مكان الحرفين 00 مرة أخرى. [![الشكل (13) يوضح البرنامج الخبيث تحت المنقح وتم الكشف عن البيانات التي يكتبها](https://informatic-ar.com/wp-content/uploads/2013/10/OllyDbg-272x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/OllyDbg.jpg) الشكل (13) يوضح البرنامج الخبيث تحت المنقح وتم الكشف عن البيانات التي يكتبها هذه المقالة تحتوي على مثال لفايروس حقيقي وطريقة تحليله ([تحليل الفايروس Perlovga وكيفية كتابه المضاد له](https://informatic-ar.com/perlovga%5Fvirus/)). ## خاتمة: تناولت هذه المقالة عن أهمية علم تحليل الفيروسات والهندسة العكسية للبرامج الخبيثة، كما تناولت أهم الأدوات والبرامج التي يفضل استخدامها عند البدء في عملية تحليل البرامج سواء كانت عادية أو برامج خبيثة، ثم تناولت هذه المقالة برنامج خبيث يقوم بعملية تعديل على احد البرامج الأخرى (كمثال على طريقة عمل الفيروسات) ثم تبينت أن برامج الأنتي فيروس لم تستطيع الكشف على البرنامج الخبيث، بعد ذلك تناولت المقالة بشكل مبسط طريقة تحليل البرنامج الخبيث واستخراج المعلومات المتعلقة منه. ## المراجع: 1\. Coolest Jobs in Information Security [http://www.sans.org/20coolestcareers/](http://www.sans.org/20coolestcareers/?ref=informatic-ar.com) 2\. Mobile Malware – the new cyber threat [http://hakin9.org/magazine/1464-mobile-malware-the-new-cyber-threat](http://web.archive.org/web/20111222014706/http://hakin9.org/magazine/1464-mobile-malware-the-new-cyber-threat) 3\. The Real World Clickjacking [http://hakin9.org/magazine/810-the-real-world-clickjacking](http://web.archive.org/web/20111222014706/http://hakin9.org/magazine/810-the-real-world-clickjacking) 4\. مقالتي في مركز التميز لأمن المعلومات ### الاستخدامات المتقدمة للدالة system URL: https://informatic-ar.com/system_function/ Last updated: 2026-10-03T22:31:59.000Z السلام عليكم و رحمة الله و بركاته رابط النسخة الفرنسية من المقالة على موقعي الخاص في منتديات developpez : [Apprendre à utiliser la fonction system](http://ahmed-mohamed.developpez.com/tutoriels/c-plus-plus/fonction-system/?ref=informatic-ar.com) سنتعرف في هذه المقالة على كيفية استخدام الدالة system في سي++. لتوضيح أهمية هذه الدالة سنقدم عدداً من الأمثلة التي تعمل في بيئة الويندوز مثل التعامل مع حسابات المستخدمين (User Accounts) و إصلاح إدارة المهام (Task Manager) بالإضافة إلى كيفية إصلاح أداة الريجستري (register). بعد ذلك, سننتقل إلى المعيار POSIX لنناقش الحالات التي تكون فيها system غير آمنة و قد تؤدي إلى فتح ثغرات لسباب معينة, قبل أن نتحدث عن البدائل المقترحة لهذه لدالة في أنظمة Unix-like. [![couv](https://informatic-ar.com/wp-content/uploads/2013/10/couv-200x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/couv.jpg) لنلق نظرة على الكود التالي : ```cpp #include <cstdlib> #include <iostream> int main(int argc, char **argv) { std::cout << "Apprendre à utiliser la fonction system" << std::endl; int value_returned = std::system("PAUSE"); std::cout << "La valeur retournée est : " << value_returned << std::endl; return EXIT_SUCCESS; } ``` قمنا بالتحقق من القيمة المُعادة من طرف الدالة system هذه المرة, لكن في بقية المقالة لن نقوم بمثل هذا التحقق اختصاراً للوقت و تجبنا للتكرار. عند ترجمة الكود و تنفيذ البرنامج سيتم إظهار الجملة التالية في شاشة الـ console : Apprendre à utiliser la fonction system لنعد قليلا إلى السطر السادس : ```cpp int value_returned = std::system("PAUSE"); ``` لماذا كتبنا العبارة السابقة ؟ الهدف من العبارة السابقة هو إيقاف البرنامج حتى نَتَمَكن مِن مُشاهدة مخرجات البرنامج .. فكما تعلم أن الحاسوب بإمكانه القيام بآلاف العمليات الحسابية و المنطقية للدقيقة الواحدة بل أكثر! و نظرا لبطء الإنسان مقارنة مع الكومبيوتر فإننا نحتاج إلى إيقاف البرنامج حتى نشاهد النتائج. ما هي الدالة system ؟ الدالة system هي دالة تعمل كوسيط بين المستخدم و نظام التشغيل أيا كان. **ملاحظة:** الأمر PAUSE يعمل في بيئة الـ Windows فقط, إذا كنت تعمل على Linux فيمكنك استخدام الأمر read كالتالي : ```bash read -rsp $'Appuyer sur une touche pour continuer ...\n' ``` ما هي فائدة هذه الدالة؟ لنفرض أنك تستعمل نظام تشغيل لينكس أو ويندوز و تريد إدخال بعض أوامر DOS أو Shell في برنامجك, عندها ستتولى الدالة system هذه المهمة. لكن كيف؟ إليك المثال: لنفترض أننا نريد مسح شاشة البـرنامج, طبعا بإمكاننا استخدام الدالة clrscr الموجودة في المكتبة conio.h لكننا هذه المرة سنستخدم الدالة system مع الأمر المناسب و هو cls إذا كُنتَ في الويندوز أو clear إذا كُنتَ في لينكس كالتالي: ```cpp system("cls"); system("clear"); ``` **ملاحظة:** conio.h هو أحد الـ header files الخاصة بلغة سي, كان يُستخدم في التسعينيات من القرن الماضي حيث كان يتواجد أساسا في المترجمات التي تعمل في بيئة الويندوز غير أنه لم يرد في الكتاب الأب The C Programming Language و لا ينتمي إلى المكتبة القياسية للغة السي كما أن المعيار POSIX أيضا لا يدعمه. ما هي وسائط هذه الدالة ؟ الدالة system تستقبل وسيط واحد عبارة عن الأمر (system command) المُراد تنفيذه, الــ prototype الخاص بها هو : ```cpp #include <cstdlib> int system(const char *command); ``` مثلا في المثال الأول قمنا بإدخال الأمر PAUSE و هو أحد أوامر DOS مسؤول عن إظهار الجملة : ```cpp Appuyer sur une touche pour continuer... ``` حيث يظل البرنامج ينتظر... حتى يضغط المستخدم على زر من لوحة المفاتيح حينها يتم الانتقال إلى الخطوة القادمة وهي: ```cpp return 0; ``` و تعني الخروج من البرنامج. الآن أصبح بإمكاننا كتابة الكود المكافئ للعبارة الموجودة في السطر السادس من المثال الأول : ```cpp std::cout << "Appuyer sur une touche pour continuer ..." << std::endl; std::cin.ignore(std::numeric_limits<int>::max(), '\n'); ``` **باختصار:** تلعب الدالة system دور الوسيط بين المبرمج و أوامر الـ console حيث تُعتبر مسؤولة عن تنفيذ أوامر النظام. لا شك أن الاستخدامات البدائية لهذه الدالة بسيطة جدا لكن مع ذلك, من الضروري جدا الانتباه جيدا أثناء استخدام هذه الدالة و التحقق من الأخطاء التي قد تنتج أثناء استخدامها. من ضمن الملاحظات الهامة التي تجب مراعاتها عند استخدام هذه الدالة : - يجب التحقق من أن الـ control processor مُتاح و على أتم الاستعداد.ا (1) - بشكل عام, يُؤدي استخدام الـ system functions إلى كود لا يتوافق مع مختلف المنصات لأن أوامر النظام تختلف من نظام لآخر و قد تختلف أيضا في نفس النسخة من نفس النظام.ا (2) - في الأصل, لا تُتيح مثل هذه الدوال تنفيذ عدة أوامر في آن واحد. - القيمة المُعادة من طرف الدالة ليست سوى تلك التي أعادها الأمر الذي تم تنفيذه, أيضا القيمة المُعادة تختلف باختلاف الـ implementation. - لا تسمح الدالة system بالتأكد من أن الأمر المُراد تنفيذه تم تنفيذه بنجاح أو لا. - أثناء تنفيذ الأمر, يتم تعطيل SIGCHLD و تجاهل كلا من SIGINT و SIGQUIT. ا (3) - لا تسمح system بفصل الـ controller عن الـ view إذا أردنا العمل وفقا لـ MVC.ا (4) - احذر من استخدام system في برنامج بصلاحيات set-user-ID أو set-group-ID.ا (5) **توضيحات :** - (1) : باستخدام system(NULL)l : إذا كان الوسيط المُمرر عبارة عن مؤشر NULL, تقوم الدالة system بالتحقق من استعداد معالج الأوامر (control processor) دون أن تستدعي أي أمر آخر للتنفيذ. - (2) : على سبيل المثال, يمكن للمستخدم إذا كان root تغيير كافة أوامر النظام بأخرى أو تعريف alias جديدة. - (3) : SIGCHLD, SIGINT و SIGQUIT عبارة عن مجموعة signals مُعرفة في الملف signal.h. هذه الـ signals أو الإشارات توجد في الأنظمة التي تدعم المعيار POSIX. - (4) : على سبيل المثال, سيتم الإنتقال مباشرة من الـ execution أو التنفيذ (controller) إلى إظهار الناتج (view) دون إمكانية تغيير الأخير أو تغييره بشكل محدود جداً. - (5) : أي برنامج يعمل بصلاحيات set-user-ID , من المُحتمل جداً أن يكون سبب في التعرض لهجمات معينة أو الوقوع في ثغرات خصوصا إذا كان البرنامج يتفاعل (و لو يسيراً) مع مستخدم يختلف عن الذي قام ببرمجته و بالتالي يجب دائما أخذ مثل هذه الإعتبارات عند التعامل مع برنامج بصلاحيات مرتفعة. سنقوم الآن بإعطاء بعض الأمثلة (الخاصة بالـ Windows) توضيح كيفية استخدام هذه الدالة حتى تتعرف أكثر على مدى أهميتها. **المثال الأول:** اكتب برنامج يُظهر كلا من الوقت و التاريخ الحاليين مع إمكانية تغيـيـرهما. في هذا المثال سنستعمل الأمرين time و date, الأمر الأول مسؤول عن الوقت أما الثاني فمسؤول عن التاريخ و الكود الآتي يوضح ما سبق: ```cpp #include <cstdlib> #include <iostream> using namespace std; int main(int argc, char **argv) { system("TIME"); system("DATE"); system("CLS"); return EXIT_SUCCESS; } ``` أما إذا أردنا عدم تغيير الوقت فيمكننا استخدام الــ switch المخصص وهو /t هكذا: ```batch time /t ``` **ملاحظة:** السي++ القياسية لا تحتوي على دوال لمعالجة الوقت و التاريخ لكن مع ذلك, إضافة إلى الدالة system توجد عدة طرق أخرى لمعرفة الوقت و التاريخ, نذكر منها على سبيل المثال **(1)**: - الدالة time الموجودة في الملف الرأسي ctime. - الدالة GetSystemTime الموجودة في الملف الرأسي windows.h. - الفئتين CTime و CTimeSpan بالنسبة لمبرمجي Visual CPP و MFC. - الفئتين TDate و TDateTime بالنسبة لمبرمجي C++ Builder و VCL. - المكتبة boost::date\_time بالنسبة لمن يريد الحفاظ على الــ code portability. **المثال الثاني:** اكتب برنامج يُظهر أربع خيارات للمستخدم على الشكل التالي: 1. إيقاف تشغيل الجهاز 2. إعادة التشغيل 3. إنهاء جلسة المستخدم 4. الخروج من البرنامج وعند الضغط على 1 يتم إيقاف الكومبيوتر, 2 تتم إعادة التشغيل و هكذا بالنسبة لبقية الأوامـر.. في هذا المثال سنحتاج إلى الأوامر التالية : ```batch SHUTDOWN /S /T 00 SHUTDOWN /R /T 00 SHUTDOWN /L EXIT ``` ماهو الأمر SHUTDOWN ؟ الأمر SHUTDOWN يسمح بالانتقال بين مختلف حالات نظام التشغيل (shutdown, restart, standby, hibernate) و يملك العديد من الــ switches منها على سبيل المثال: - /i : التعامل مع مختلف وضعيات نظام التشغيل باستخدام واجهة رسومية. - /l : اغلاق جلسة المستخدم. - /s : ايقاف نظام التشغيل. - /r : إعادة التشغيل. - /h : وضع في حالة hibernate - /t xxx : تحديد الفترة الزمنية قبل تنفيذ الأمـر حيث تُحسب هذه الفترة بالثواني. - /a : الغاء ايقاف الجهاز. - /f : يُجبر جميع التطبيقات القيد التشغيل على التوقف و يقوم بإغلاق الجهاز بشكل فوري دون تحذير المستخدمين. لمعرفة المزيد استعمل الأمر **SHUTDOWN /HELP** **.** لم يبقى إلا الأمر EXIT و أعتقد أن الجميع يعرفه. الآن صار بإمكاننا كتابة الكود: ```cpp #include <cstdlib> #include <iostream> using namespace std; int main(int argc, char **argv) { char choix; cout << endl << "\t\t***************************************" << endl << endl << "\t\t ******* La commande SHUTDOWN ******* " << endl << endl << "\t\t***************************************" << endl; cout << endl << "1 - Arreter l'ordinateur." << endl << endl << "2 - Redermarrer l'ordinateur." << endl << endl << "3 - Fermer la session." << endl << endl << "1 - Quitter le programme." << endl; cout << endl << "Donnez votre choix : " << endl; cin >> choix; switch (choix) { case '1': system("SHUTDOWN /S /T 00"); break; case '2': system("SHUTDOWN /R /T 00"); break; case '3': system("SHUTDOWN /L"); break; case '4': system("EXIT"); break; default: cout << "Erreur, choix incorrect !" << endl; } return EXIT_SUCCESS; } ``` الآن سنقوم بإضافة بسيطة للكود السابق... وهي تغيير لون الكتابة و لون الخلفية أيضا. سنستخدم الدالة system و بالضبط مع الأمر color الذي يملك وسيطين يُمثلان لون الخلفية و لون الكتابة على التوالي, هذين الوسيطين يُكتبان على شكل ست عشري أو Hexadecimal و الجدول التالي يوضح كل رقم مع اللون الذي يقابله : [![tab](https://informatic-ar.com/wp-content/uploads/2013/10/tab.jpg)](https://informatic-ar.com/wp-content/uploads/2013/10/tab.jpg) طيب الآن سنقوم بإضافة السطر: ```cpp system("COLOR 2E"); ``` ليصبح الكود هكذا: ```cpp char choix; system("COLOR 2E"); cout << endl << "\t\t***************************************" << endl << endl << "\t\t ******* La commande SHUTDOWN ******* " << endl << endl << "\t\t***************************************" << endl; ``` بالنسبة لي, اخترت اللونين الأصفر و الأخضر لأنهما يمثلان علم دولتي: موريتانيا و أنت يمكنك اختيار ما يعجبك, فقط راجع الجدول.و هذه واجهة البرنامج : [![6](https://informatic-ar.com/wp-content/uploads/2013/10/62-300x149.png)](https://informatic-ar.com/wp-content/uploads/2013/10/62.png) **ملاحظة:** في معظم الأحيان ستلاحظ أن استخدام الدالة system لتلوين الواجهة غير مجدي لأنها تسمح بظهور لون واحد فقط و بالتالي باستخدامها لا يمكنك تلوين عدة أسطر بألوان مختلفة. لفعل هذا الشيء يمكنك استخدام الدالة **SetConsoleTextAttribute**الموجودة في المكتبة windows.h و تأخذ وسيطين, الأول هو الــ HANDLE و الثاني هو الرقم الست عشري الذي يمثل اللون. و هذا مثال بسيط على استدعاء الدالة: ```cpp SetConsoleTextAttribute(GetStHandle(STD_OUTPUT_HANDLE), 14); ``` **المثال الثالث :** اكتب برنامج يقوم بتغيير باسوورد المستخدم. في هذا المثال سنحتاج إلى الأمر net user و هذا الأمر في الحقيقة هو أحد وسائط الأمر net لاي يسمح بإرسال أوامر عن طريقة الشبكة مع العلم أن الأمر NET يملك العديد من subcommands منها: - **NET SEND** : لارسال رسالة إلى جهاز عن بعد. - **NET START** لتشغيل خدمة. - **NETSTAT** : تعرض اتصالات الشبكة الواردة والصادرة و جداول التوجيه وعدد من الاحصاءات الأخرى. - **NET TIME** : عرض الوقت و التاريخ الحالي حسب توقيت الــ time server domain. - **NET FILE** : اغلاق ملف تم اشتراكه على الشبكة و تحرير مصادر النظام التي كان يحجزها. لنعد الآن إلى الأمر **NET USER**الذي يسمح بالتعامل مع حسابات المستخدمين في الويندوز. هذه بعض الــ switches الاكثر استعمالا : - /ADD : اضافة حساب جديد - /DELETE : حذف حساب معين - ACTIVE:{YES | NO : تعطيل أو تفعيل الحساب - EXPIRES:date | NEVER : امكانية تحديد تاريخ انتهاء صلاحية الحساب. - PASSWORDCHG:YES | NO : تحديد ما إذا كان المستخدم يمكنه تغيير كلمة المرور الخاصة به. - PASSWORDREQ:YES | NO : تحديد ما إذا كان المستخدم يمكنه تعريف كلمة مرور لحسابه أو لا. - LOGONPASSWORDCHG:YES|NO : هل يجب يجب على المستخدم تغيير كلمة مروره عند تسجيل الدخول القادم. للمزيد من المعلومات أدخل الأمر **NET USER /HELP** **.** بالنسبة للتابع user فبإمكانه إضافة مستخدم جديد أو حذفه أو تغيير كلمة المرور الخاصة به لكننا هنا سنقتصر على تغيير الباسوورد . الشكل العام للأمر هكذا: ```batch NET USER [user_name] [password] ``` في هذه الحالة فإن الأمر يستقبل واسطين الأول هو اسم الحساب و الثاني هو كلمة المرور الجديدة. الآن صار بإمكاننا الآن تغيير الباسوورد.. كما في الكود الآتي: ```cpp #include <cstdlib> #include <iostream> using namespace std; int main(int argc, char **argv) { system("USER NET %USERNAME% 123456"); return EXIT_SUCCESS; } ``` **ملاحظة:** الحصول على **system error 5** عند تنفيذ الكود يعني أنك لا تملك الصلاحيات الكافية لتنفيذ الأمر NET USER من أجل تغيير كلمة المرور لذا قم بتشغيل الملف التنفيذي كــ administrator. **المثال الرابع:** معظم الفيروسات الصغيرة المنتشرة الآن تقوم بتعطيل إدارة المهمام حتى لا يتمكن المستخدم من الوصول إلى الــ process الخاص بالفيروس و توقيفه, نفس الشيء يحدث مع registry خوفا من حذف القيمة التي تُمكن الفيروس من العمل عند اقلاع الجهاز. طبعا توجد فيروسات أكثر ذكاء حيث تقوم بإخفاء الــ process من إدارة المهام و بالتالي قد يصعب التعامل معها يدويا في هذه الحالة. في هذا المثال, سنقوم بكتابة كود يقوم بتفعي أو تعطيل Task Manager و registry و الهدف من هذا المثال هو امكانية اصلاح هذه الأدوات عند تعرضها للهجوم الذي ذكرناه سابقا. الأمر الذي يُمكن من اصلاح ادارة المهام هو: ```batch REG ADD hkcu\Software\Microsoft\Windows\CurrentVersion\policies\system /v DisableTaklMgr /t reg_dword /d 1 ``` الأمر **REG ADD** يمكن من إضافة مفتاح إلى الرجيسرتي. - hkcu\\Software\\Microsoft\\Windows\\CurrentVersion\\policies\\system يُمثل مسار المفتاح المُضاف. - /v Disable TaskMgr : اسم المفتاح. - /t reg\_dword : نوع المفتاح. - /d 1 : فيمة المفتاح (1 تعني تعطيل و 0 تعني تفعيل) و هذا هو الكود : ```cpp #include <cstdlib> using namespace std; int main(int argc, char **argv) { system("REG ADD hkcu\\Software\\Microsoft\\Windows\\CurrentVersion" "\\policies\\system /v DisableTaklMgr /t reg_dword /d 1"); system("PAUSE"); return EXIT_SUCCESS; } ``` نفس الشيء بالنسبة للرجيستري: ```cpp #include <cstdlib> using namespace std; int main(int argc, char **argv) { system("REG ADD hkcu\\Software\\Microsoft\\Windows\\CurrentVersion" "\\policies\\system /v DisableRegistryTools /t reg_dword /d 0"); system("PAUSE"); return EXIT_SUCCESS; } ``` **خدعة:** باستخدام الأمرين **REG ADD** و **SHUTDOWN** معا يمكننا إنشاء فيروس خطير و مزعج. كالتالي: - نقوم بإنشاء ملف BATCH لإعادة تشغيل الجهاز باستخدام الأمر **SHUTDOWN** . - ننشئ ملف BATCH آخر لإضافة الملف السابق إلى الرجيستري من أجل تشغيله عند اقلاع الجهاز. - نحذف الملف السابق حتى لا نترك أثرا للفيروس. - نستخدم الأداة Iexpress لدمج الملفين في ملف واحد تنفيذي. خطر استخدام الدالة system في المعيار POSIX: لا شك أن جميع مستخدمي أنظمة لينكس يعرفون جيدا هذه الدالة, لكن على عكس عائلة exec (مثل execve()) تستخدم الدالة system الــ environment variables من أجل البحث عن الملف المُراد تنفيذه, أيضا, system تعتمد خصوصا على $PATH الذي يحتوي على لائحة المجلدات التي تحتوي الملفات التي يمكن استخدامها دون تحديد المسار الكامل للملف.**(2)** على سبيل المثال, PATH يحتوي على /bin و بالتالي إذا اردت تنفيذ الامر /bin/commande يمكنك كتابة ما يلي داخل الشل : ```bash $ commande ``` لتنفيذ أمر أو استدعاء برنامج بلغة C يُنصح دائما باستخدام أحد افراد العائلة exec لأن الدالة system تستخدم الــ environment variables لتحديد مسار الملف المُراد تنفيذه, السبب في الثغرة يعود إلى أن هذه المتغيرات يمكن للمستخدم تغييرها ! على سبيل المثال إذا كان لدينا كود يحوي الأمر التالي: ```cpp system("cat /etc/passwd"); ``` يمكننا ببساطة تغيير الــ PATH بحيث يتم تحويل استدعاءات النظام إلى المسار الجديد: ```bash export PATH=/home/snacker:$PATH ``` في هذه الحالة, سيقوم الشل بالبحث عن الملف cat داخل المجلد [![12](https://informatic-ar.com/wp-content/uploads/2013/10/122.png)](https://informatic-ar.com/wp-content/uploads/2013/10/122.png) لآن سنقوم بإنشاء برنامج ثاني يقوم باستدعاء cat الموجود داخل المجلد [![12](https://informatic-ar.com/wp-content/uploads/2013/10/122.png)](https://informatic-ar.com/wp-content/uploads/2013/10/122.png) البرنامج الجديد يحتوي على الأمر التالي : ```cpp std::cout << "La fonction system est vulnérable" << std::endl; ``` نقوم بالترجمة [![13](https://informatic-ar.com/wp-content/uploads/2013/10/132.png)](https://informatic-ar.com/wp-content/uploads/2013/10/132.png) و التنفيذ [![14](https://informatic-ar.com/wp-content/uploads/2013/10/143.png)](https://informatic-ar.com/wp-content/uploads/2013/10/143.png) مخرجات الكود ستكون "La fonction system est vulnérable" بدلا من محتوى الملف [![15](https://informatic-ar.com/wp-content/uploads/2013/10/151.png)](https://informatic-ar.com/wp-content/uploads/2013/10/151.png) و بالتالي نلاحظ جيدا أن الدالة system قامت بتنفيذ cat الموجود في في الــ PATH الجديد. عائلة exec و الدالة system, أيهما أفضل ؟ يقوم أعضاء هذه العائلة بعمل covering للـ current process بمعنى أنه يتم استبدال الـ process الذي قام بعملية الإستدعاء بآخر دون تغيير الـ PID. الوسيط الأول لكافة أفراد هذه العائلة عادة ما يكون مسار الملف المُراد تنفيبذه. **(3)** تحتوي العائلة exec على مجموعة من الدوال تختلف باختلاف طبيعة عملها : ```cpp #include <unistd.h> extern char **environ; int execl(const char *chemin, const char *arg, ...); int execlp(const char *fichier, const char *arg, ...); int execle(const char *chemin, const char *arg, ..., char *const envp[]); int execv(const char *chemin, char *const argv[]); int execvp(const char *fichier, char *const argv[]); int execve(const char *chemin, char *const argv[], char *const envp[]); ``` تستقبل دوال execl اسم البرنامج الذي سيتم تنفيذه كوسيط أول بينما يكون الوسيط الثاني : الأمر المُراد تنفيذه ثم تأتي بعد ذلك مجموعة الـ arguments التابعة لهذا الأخير و تنتهي بـ NULL. الوسيط الأول و الثاني يجب أن يكونا متطابقين بالنسبة لدوال execl. مثال : ```cpp execl("/usr/bin/ls", "/usr/bin/ls", "-l", NULL); ``` أما بالنسبة لـ execv و execvp فيستخدمان مصفوفة مؤشرات تنتهي بـ NULL و ينتهي كل مؤشر فيها بالمحرف NUL. هذه المصفوفة تُمثل الوسائط الممكنة للأمر المُراد تنفيذه. اتُفق على أن الوسيط الأول يُشير دائما إلى مسار الملف أو البرنامج الذي سيتم استدعاءه. عندما يُعيد أحد أفراد exec قيمة معينة, فهذا يعني أنه قد حدث خطأ ما. عادة ما تكون القيمة المُعادة -1 حيث تحتوي errno على الكود المعاد من طرف الدالة. بقي أن نذكر أن عائلة exec خاصة بأنظمة Unix-like, في الويندوز لا أعلم تحديدا ماالذي قد تُعطيه, لأنني أعتقد أنه لا يمكننا ملائمة عمل أفراد هذه العائلة في منصة الـ Windows نظرا إلى أن الـ managing processes تختلف بشكل كبير جداً. إذا كنتَ لا ترغب في استخدام الدالة system في الـ Windows فيمكنك استدعاء الدالة createProcess. (في الحقيقة, فإن system تستدعي createProcess في بيئة الـ Windows) أخيرا, عند استدعاء system سيتم اتباع الخطوات التالية : (للأسف, كتتبُتها بالفرنسية لأنني لم أجد ترجمة دقيقة للكلمات التي وضعتُها في المخطط) [![sh-system](https://informatic-ar.com/wp-content/uploads/2013/10/sh-system-300x207.png)](https://informatic-ar.com/wp-content/uploads/2013/10/sh-system.png) بينما يتم اتباع الخطوات الآتية عند استدعاء fork+exec : [![sh-exec](https://informatic-ar.com/wp-content/uploads/2013/10/sh-exec-300x288.png)](https://informatic-ar.com/wp-content/uploads/2013/10/sh-exec.png) مالحل ؟ ليس من السهل ايجاد بديل حقيقي للدالة system, أحد الحلول الممكنة هو استخدام execl أو execle مع العلم أن طريقة العمل تختلف بشكل كامل لأن الملف المُستدعى لن يتم تنفيذه كــ subroutine و لكن سيحل محل الــ running processes.ا**(4)** بشكل عام, يُنصح باستخدام المكتبات التي توفرها سي++ إن وُجدت و إلا فيمكن استخدام أحد أفراد العائلة exec خصوصا في الأنظمة التي تدعم POSIX و system في حالات معدودة و مُحددة. روابط مفيدة **(1)** [Comment gérer les dates et les heures en CPP ](http://cpp.developpez.com/faq/cpp/?page=bibliotheques&ref=informatic-ar.com#DIVERS%5Fdate%5Fheure "رابط خارجي")**(2)** [La faille de system ](http://gits.hydraze.org/article-14-la-faille-de-system.html?ref=informatic-ar.com "رابط خارجي")**(3)** [Man page 3 : exec ](http://manpagesfr.free.fr/man/man3/exec.3.html?ref=informatic-ar.com "رابط خارجي")**(4)** [Éviter les failles de sécurité dès le développement d'une application](http://www.linuxfocus.org/Francais/January2001/article182.shtml?ref=informatic-ar.com "رابط خارجي") أرجو أن أكون قد وُفقت في الشرح و أرجو لكم الاستفادة أيضا و لا تنسوني من صالح دعائكم. تحياتي ### برمجة التطبيقات الموزعه RMI بجافا URL: https://informatic-ar.com/distributed_objects_rmi_java/ Last updated: 2026-10-03T22:32:00.000Z التطبيقات الشبكيه عاده تكون لنوعين من البرامج: - الأول يهتم بكيفيه تبادل الملفات والبيانات بين الClient والـ Server ويتم التعامل مع هذا النوع باستخدام بروتكولات FTP,SMTP,HTTP وغيرها من البروتكولات. - النوع الثاني من التطبيقات يهتم بكيفيه تشغيل برنامج أو داله في الجهاز الأخر مثل Telnet و Remote Procedure Call أختصارا RPC . لغه جافا من اللغات الموجهه للكائنات OO ، وبالتالي سوف تطبق مفهوم RPC ولكن عن طريق الكائنات ، ومن هنا جائت تقنيات الـ Distributed Object والتي تسمح لي باستدعاء داله موجوده في كائن بعيد بدون الدخول في تفاصيل ارسال واستقبال البيانات ... والطريقه الأولى و التقليديه في كتابه برامج الشكبات Client-Server معروفه لدى الأغلب ، حيث يقوم العميل (مثلا طالب) بتعبئه Form ويرسل البيانات الى الخادم ، والذي يقوم بمعالجتها ويرجع النتيجه أو تخزينها للأستفاده منها لاحقا ... الشكل التالي بين Classical Client-Server Model : [![RMI1](https://informatic-ar.com/wp-content/uploads/2013/10/RMI1-234x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/RMI1.png) لكي يقوم المبرمج ببرمجه برنامج مثل هذا عليه أن يقوم بتحديد عده امور مهمه : - طريقه برمجه الـ Client-Server ، هل هم بالأعتماد على TCP-Socket أو UDP-Socket. - كيفيه ارسال البيانات الى الجهه الأخرى بصوره مفهمومه للجهه الأخرى Formatted Understandable Data - كيفيه عمل parse للرساله القادمه واستخلاص المعلومات منها .. كل هذه الأمور على المبرمج أن يضعها في الأعتبار قبل و أثناء كتابه البرنامج ، والا فلن يعمل بالشكل المطلوب ..فمثلا لو قام الطالب Client بالضفط على زر Delete .. يجب أن تذهب هذه المعلومه الى الطرف الأخر بطريقه ما .. ويقوم الطرف الأخر بمعرفه هذه المعلومه والقيام بالوظيفه المطلوبه ...ويرجع النتيجه بطريقه مفهومه يفهمها الكلاينت . الذي نريده هنا هو ميكانيكه تسمح لي عمل نفس الـ Client-Server بدون الحاجه الى تلك الخطوات أعلاه ، فقط أقوم بالضغط على الزر ولا أهتم بكيفيه الأرسال ، ولا كيفيه فهم المعلومه ولا توجد حاجه لparse ولا غيره ، أيضا قد يكون الخادم مكتوب باللغه أخرى مثلا سي++. المشكله هنا أن الكائن الذي يقوم بهذا العمل غير موجود لدينا في الجهاز المحلي .. الحل سوف يكون عن طريق عمل "واجهه تستطيع التعامل مع الخادم" Proxy في جهه العميل ، وعندما نضغط على ذالك الزر يقوم العميل باستدعاء الداله الموجوده في الـ Proxy .. وبعدها يقوم هذا الـ Proxy بالأتصال مع الخادم بطريقه يعرفها هو ... بنفس الأمر السيرفر قد لا يعلم عن الكلاينت شيء ، لذلك يكون فيه Proxy هو الأخر يتخاطب مع الكلاينت .. أي أن الـ Server-Proxy يقوم بالتخاطب مع الـ client-Proxy . أنظر الصوره التاليه لتبين لك : [![RMI2](https://informatic-ar.com/wp-content/uploads/2013/10/RMI2-300x202.png)](https://informatic-ar.com/wp-content/uploads/2013/10/RMI2.png) هنا طريقه التخاطب بين تلك الـ Proxies قد يعتمد على التنقيه المستخدمه ، وأشهر ثلاث تقنيات هما : RMI , CORBA , SOAP **RMI :** أختصار لـ Remote Method Invocation وتعتمد على فكره أستدعاء الكلاينت (يعمل في JVM) لداله في كائن بعيد موجود في الخادم (يعمل على JMV مختلفه) ، ويشترط أن يكون الخادم والعميل مكتوبين بجافا ، وتستخدم هذه التقنيه RMI Protocol لتطبيق التخاطب الذي يتعامل مع TCP Socket .وباستخدام RMI سوف نعطي العميل الشعور بأنه لا وجود لكائنات بعيده .. بالضبط كأنه يتعامل مع كائن محلى ، وهذه أحد ميزات الـ RMI . **CORBA :** أختصار لـ Comman Object Request Borker Architecture .. وظيفتها نفس وظفيه الـ RMI وهي أستدعاء داله في كائن بعيد ، لكن يمكن أن يكون الكائن البعيد مكتوب بأي لغه لا يهم .. وتستخدم CORBA بروتكول يسمى Internet Inter-ORB Protocol واختصارا IIOP . **SOAP :** أختصار لـ Simple Object Access Protocol وهي تقريبا نفس وظيفه CORBA ، ولكن تعتمد في عملها على ملفات XML . CORBA و SOAP تعمل على أي لغه لا يشترط جافا ، وهنا سوف نستخدم لغه خاصه لوصف الدوال الموجوده في السيرفر والتي يستطيع الكلاينت التعامل معها .. في CORBA نستخدم لغه تسمى Interface Defenition Langauge واختصارا IDL (يمكن أن نستخدم RMI مع بروتكول IIOP ولن نحتاج الى IDL) . أما في SOAP نستخدم Web Services Description Language أختصارا WSDL . طبعا هذه ليست لغات كجافا أو سي ، لكنها فقط لغه لوصف الـ Interface الذي يستطيع أستخدامه الكلاينت ... طبعا أسهل هذه التقنيات هي RMI وهي موضوع حديثنا اليوم ، أما CORBA فهي تحتاج موضوع اخر لها هي والـSOAP والتي تستخدم عند العمل مع Web-Service . ## **ما هو مفهوم RMI** بنظره سريعه في أي كود RMI ، سوف نجد أنه يبعدنا تماما من التفاصيل ، فعندما نريد أن نستدعي داله موجوده في الخادم Server ، كل ما على أن أكتب سطر واحد وسأحصل على Reference لهذا الكائن . بعدها أستدعى الداله بشكل عادي كما لو أنها كانت Local . اذا كانت هناك قيمه مرسله Parameters و قيم راجعه return Value ، ستذهب بطريقه ما الى المكان الصحيح وبدون تدخل منك .. فعلا RMI تجعل الحياه أكثر سهوله . أنظر للصوره التاليه والتي توضح هذه العمليه بشكل Abstract : [![RMI3](https://informatic-ar.com/wp-content/uploads/2013/10/RMI3-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/RMI3.png) لندخل الى العمق أكثر ولنرى كيف يقوم RMI بارسال القيم المرسله والراجعه .. أولا عندما يقوم الكلاينت باستدعاء داله موجوده في كائن بعيد سوف يقوم client باستدعاء الداله stub الموجوده في Proxy لديه .. ويمرر لها المتغيرات المطلوب ارسالها .. بعد ذلك يبدأ الـ Stub بعمل معالجه لتلك المتغيرات حتى يرسلها .. فلن يرسلها هكذا .. هذه العمليه تسمى Parameter Marshalling . في عمليه الـ Marshalling سوف ينظر أولا الى نوع المتغير أو الكائن ، فاذا كان متغير عادي Primitve Data Type فيقوم بارساله كما هو بالطبع بعد تحويله الى بايت Byte بترميز يعرف بـ Big-Endian .. أما اذا كان المتغير هو كائن reference فلا يمكن أن يرسل بهذه الطريقه ، لأنه يمكن أن يحتوي على كائنات أخرى فيه وتلك الكائنات تحتوي أيضا على كائنات وهكذا . لذلك في عمليه ارسال الكائن سوف نقوم بترميزه بـ serialization وبالتالي تذهب البايتات بطريقه معينه متسلسله الى الجهه الأخرى .وعندما تصل الى الجهه الثانيه سوف يقوم بتجميع هذه البايتات بعمليه تدعى De-Serialization . لذلك في حاله التعامل مع الكائنات (قيم مرسله أو راجعه) يجب أن يكون الكائن لديك يطبق impelements الـ serialization . الأن بعدما أنتهي الـ stub من عمليه Encoded سوف يرسل هذه البايتات الى الجهه الثانيه ، ويقوم الReceiver Object باستقبال هذه البايتات وتجميعها unmarshals the parameters وبعدها يقوم يتحديد الكائن المراد ثم استدعاء الداله المراده وفي حال كان هناك قيمه راجعه منها يقوم هذا الـ receiver object بعمل Marshals لهذه القيمه الراجعه ويعيدها الى Stub الذي يقوم بعمليه unmarshals للقيمه الراجعه ويرجعها الى الكلاينت الذي استدعى الـ Stub . الصوره التاليه توضح عمليه Parameter Marshalling : [![RMI4](https://informatic-ar.com/wp-content/uploads/2013/10/RMI4-300x212.png)](https://informatic-ar.com/wp-content/uploads/2013/10/RMI4.png) ربما تبدوا العمليه معقده وغير مفهومه ، لكن كما ذكرت لا حاجه للمبرمج بمعرفه تلك الخطوات ، لأن من ميزات RMI هي عمل أخفاء لكل هذه التفاصيل Good Tranceparency . ## **حطوات البرمجة RMI Programming Steps** هناك خطوات معينه للبرمجه في RMI سوف نذكرها هنا بشكل سريع وسوف تتضح بشكل أكبر عندما تنتاول الأمثله ان شاء الله . **أولا الخادم server :** عمل ملف Interface نضع فيه جميع الدوال التي نريد العميل أن يستدعيها . نقوم بعمل ملف Impl للأنترفيس أعلاه ونقوم بتعريف الدوال التي صرحنا عنها . نقوم بعمل ملف Server ونقوم بعمل Object من الملف Impl ونقوم بتسجيله في ملف يسمى registry باسم ما . الأن الـ server جاهز لأتصال العميل واستدعاء الدوال من الكائن الموجود به . **ثانيا الكلاينت Client :** نقوم بالأتصال بملف registry ونطلب الأسم الذي كتبه السيرفر عند تسجيله للكائن . سوف نحصل على ذلك الكائن ومنه نستدعي الداله المطلوبه .. (حتى يتضح لك سوف نحصل على Special Reference للكائن وعندما نستدعي الداله سوف يستدعي الداله الصحيحه وتنفذ هناك في السيرفر وترجع القيمه الراجعه لدينا) . طبعا مع التعامل مع الـ Exception المناسبه .. وهي في حالتنا هذه RemoteException .. وهكذا تكون أصبحت خبير في RMI Programming. ## **مثال عملي Hello RMI World** نأخذ الأن مثال على RMI Programming .. ونبدأ بالمثال الشهير Hello World . نريد أن نكتب كائن يعمل في السيرفر يحتوي على داله ترجع Hello ، ويقوم العميل باستدعاء هذه الداله عن بعد .. **نبدأ بالبرمجه في جهه الخادم :** سوف نحتاج أولا الى Interface يحتوي على جميع الدوال التي نريد العميل استدعائها وهنا فقط سوف تكون لدينا داله واحده وهي طباعه ..هذا الـ Interface يجب أن يقوم بعمل وراثه من الكلاس Remote حتى يقوم كلاس أخر (يمثل الكائن البعيد) بعمل تطبيق لهذا الأنترفيس .. اضافه الى أن الدوال التي سنكتبها في هذا الـ Interface يجب أن تقوم بعمل Throws لنوع معين من الException هو RemoteException لأنه قد تحدث مشاكل اثناء استدعاء الداله مثلا قطع الأتصال مع الخادم أو انهيار بالشبكه أو أيه مشكله أخرى .. لذلك جميع الدوال سوف تتعامل مع هذا الـ Exception . جميع الكلاسات الخاصه ببرمجه RMI موجوده في الباكج java.rmi .. لنرى الأن ملف Interface ولنطلق عليه Hello : ```java // Hello Remote Object Interface import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote { public String getHelloMessage() throws RemoteException; } ``` أنتهت الخطوه الأولى ، الأن نقوم بكتابه كلاس يطبق هذا الـInterface ، بالاضافه الى الوراثه من UnicastRemoteObject وهو الكلاس الخاص بأمور الـ Marshalling وارسال واستقبال البيانات ..وهو موجود في الباكج java.rmi.server لنرى ملف HelloImpl (جرت العاده أن تكون نهايه هذا الملف بـ Impl) : ```java // Hello Implementation import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class HelloImpl extends UnicastRemoteObject implements Hello { public HelloImpl() throws RemoteException { } public String getHelloMessage() throws RemoteException { return "Hello Distributed Computing"; } } ``` الخطوه الثالثه وهي كتابه السيرفر Server ، وهنا سوف نقوم بعمل كائن من HelloImpl وندخل هذا الكائن في الـ registry ونعطيه أي اسم ما، وبالطبع الكلاينت يجب أن يكون لديه هذا الأسم لكي يحصل على الـ reference فيما بعد .. الداله : ```java Naming.rebind(objectName, myObject); ``` هي التي تقوم باضافه سجل في ملف registry يحتوي على اسم الكائن وعنوانه Reference . طبعا هذه الخدمه Registry سو ف نقوم بتشغيلها قبل أن يعمل السيرفر حتى تتم اضافه هذا السجل فيه .. يمكنك عن طريق هذه الخدمه أن تسجل كائن باسم ما ، ويقوم الكلاينت فيما بعد بالأتصال بهذه الخدمه بالأسم المعين ليحصل على الكائن .. طبعا الأضافه في ملف Registry تتم فقط من جهه السيرفر .الكلاينت يطلب من الـ Registry كائن عن طريق الأسم .. ملف Server ، لاحظ أنه بعد عمل rebind سوف نقوم بطباعه جمله تدل أن السيرفر يعمل الأن وفي حال اتصال ... ```java // RMI Server import java.rmi.RemoteException; import java.rmi.Naming; import java.net.MalformedURLException; public class Server { final static String HOST = "localhost"; public static void main(String args[]) { try { HelloImpl myObject = new HelloImpl(); String objectName = "rmi://" + HOST + "/MyHello"; Naming.rebind(objectName, myObject); System.out.println("Binding Complete ....."); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } } ``` الأن الى هنا أنتهينا من البرمجه في جهه الخادم .. نأتي الأن لجهه العميل .. كل ما علينا في جهه الكلاينت الحصول على reference للكائن ، ومن ثم استدعاء تلك الداله ، فقط .. ويتم الحصول على الكائن من خلال الداله lookUp الموجوده في الكلاس Naming .. ```java Hello myObject = (Hello)Naming.lookup(objectName); ``` الأن بعد الحصول على reference للكائن سوف يكون نوعه Object ، لذلك نقوم بعمل cast الى النوع Hello .. ومن ثم نستدعي الداله بشكل عادي ```java // RMI Client import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.NotBoundException; import java.net.MalformedURLException; public class Client { public final static String HOST = "localhost"; public static void main(String args[]) { try { String objectName = "rmi://" + HOST + "/MyHello"; Hello myObject = (Hello)Naming.lookup(objectName); System.out.println(myObject.getHelloMessage()); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } } ``` في البدايه وللتبسيط وقبل أن نرى موضوع RMI Deployment ، ضع كل الملفات في مجلد واحد ، نريد أن نختبر البرنامج في Local Machine . الأن قم أولا بترجمه جميع الملفات: javac \*.java الأن ستخرج لك 4 ملفات .class قم بترجمه ملف HelloImpl.class باستخدام المترجم الخاص بـ rmi : rmic HelloImpl (من غير كتابه الأمتداد) .. الناتج هنا سوف يكون ملف Stub باسم HelloImpl\_Stub.class . هذا الملف يجب أن يتواجد عند الكلاينت حتى يعمل البرنامج بشكل صحيح هو والملف Hello.class (لأننا في الكلاينت سوف نقوم بعمل Cast لهذا النوع) .. لذلك عليك أن نتسخ هذين الملفات الى الكلاينت في حال كان مجلد الكلاينت في مكان أخر أو في جهاز ثاني .. حاليا انسى الكلام واستمر على أساس أنهم جميعهم في نفس المسار ، وبعد قليل نتناول هذا الموضوع بشكل أوسع . الأن (في جهه السيرفر ، لكننا حاليا نعمل وجميعهم في جهاز واحد ) قم بتشغيل ملف rmiregistry .. وذلك من خلال سطر الأوامر أكتب : rmiregistry ولن يكون هناك مؤشر أو جمله طباعه تدل على أنه يعمل .. فقط ستجد أن title Bar لسطر الأوامر أصبح يحتوي على rmiregistry وهو دليل على عمل هذه الخدمه .. الأن شغل الخادم في نافذه اوامر جديده : java server وستجد أن الجمله Binding Complete .....تم طباعتها على الشاشه ..وهكذا يكون السيرفر يعمل ، والخدمه registry تعمل ايضا .. لن تستطيع ايقافها الا بالضفط على CTRL+C . أخيرا شغل ملف الكلاينت : java Client وستجد جمله Hello Distributed Computing امامك في الشاشه (حيث تم استدعاء الداله getHelloMessage في السيرفر ، والناتج من هذه الداله هذا الString الذي تم طباعته في الكلاينت ) .. تأكد من فهم ماذا حصل ،، تم استدعاء الداله getHelloMessage الموجوده في السيرفر ، وانتفذت هناك والقيمه الراجعه رجعت للعميل ، الذي قام بطباعتها على الشاشه .. هذه هي RMI بمنتهى البساطه . ## **مثال أخر Bank Service** نأخذ مثال أخر لتأكيد فهم الموضوع ، على مثال لعميل يريد أن يدخل بياناته لدى البنك ، ويقوم بالبحث عن معلوماته من خلال الأسم .. هذه الدوال سوف تكون في السيرفر ويقوم الكلاينت باستدعاء هذه الدوال من بعد ، ويمرر لها المعاملات وترجع له الناتج .. ملف BankAccount وهو يمثل حساب العميل في البنك .. لاحظ أنه يطبق الأنترفيس Serializable.. وهكذا لجميع الكائنات في RMI يجب أن تكون مطبقه لهذا الأنترفيس والى فلن تستطيع ارسالها ```java // Person Account import java.io.Serializable; public class BankAccount implements Serializable { private String firstName; private String lastName; private double balance; public BankAccount() { setFirstName(""); setLastName(""); setBalance(0.0); } public BankAccount(String fn, String ln, double balance) { setFirstName(fn); setLastName(ln); setBalance(balance); } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public double getBalance() { return balance; } public void setFirstName(String firstName) { this.firstName = firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public void setBalance(double balance) { this.balance = balance; } } ``` ملف BankAccountInterface : ```java // Bank Account Remote Object Interface import java.rmi.Remote; import java.rmi.RemoteException; public interface BankAccountInterface extends Remote { public void insertEmployee(String firstName, String lastName, double balance) throws RemoteException; public String getEmployee(String firstName, String lastName) throws RemoteException; } ``` ملف BankAccountImpl : ```java // Bank Account Implementation import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.Vector; public class BankAccountImpl extends UnicastRemoteObject implements BankAccountInterface { private Vector<BankAccount> dataBase = new Vector<BankAccount>(); public BankAccountImpl() throws RemoteException { } public void insertEmployee(String fn, String ln, double balance) throws RemoteException { BankAccount tmp = new BankAccount(fn, ln, balance); dataBase.add(tmp); } public String getEmployee(String fn, String ln) throws RemoteException { String result = ""; for (int i = 0; i < dataBase.size(); i++) { BankAccount tmp = dataBase.elementAt(i); if (tmp.getFirstName().equals(fn) && tmp.getLastName().equals(ln)) result = tmp.getFirstName() + " " + tmp.getLastName() + " " + tmp.getBalance(); } return result; } } ``` ملف BankServer ولن يختلف عن السيرفر في المثال السابق : ```java // Bank Server import java.rmi.RemoteException; import java.rmi.Naming; import java.net.MalformedURLException; public class BankServer { private static final String HOST = "localhost"; public static void main(String args[]) { try { String myObjectName = "rmi://" + HOST + "/MyAccount"; BankAccountImpl myObject = new BankAccountImpl(); Naming.rebind(myObjectName, myObject); System.out.println("Binding Complete ...."); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } } ``` ملف BankClient وسوف نقوم باستدعاء الدوال ```java // Bank Client import java.rmi.RemoteException; import java.rmi.NotBoundException; import java.rmi.Naming; import java.net.MalformedURLException; public class BankClient { private static final String HOST = "localhost"; public static void main(String args[]) { try { String myObjectName = "rmi://" + HOST + "/MyAccount"; BankAccountInterface myObject = (BankAccountInterface)Naming.lookup(myObjectName); myObject.insertEmployee("Wajdy", "Essam", 30444); myObject.insertEmployee("Ahmed", "Ali", 2324); myObject.insertEmployee("Sami", "Essam", 3131); System.out.println(myObject.getEmployee("Wajdy", "Essam")); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } } ``` لتشغيل الملفات ، أكتب هذه الأوامر في سطر الأوامر ، وقد تحتاج الى 3 نوافذ أحدها لـ registry والأخرى للخادم والثالثه للعميل : javac \*.java rmic BankAccountImpl registry java BankServer java BankClient واستمتع بالنتائج .. في حال لاحظت المثالين السابقين ان جميع الملفات كانت في جهاز واحد ، طبعا الأمر غير مفيد بشكل كبير ، نحن نريد أن يكون كل من السيرفر والكلاينت يعمل في جهاز منفصل عن الأخر .. لذلك يجب أن نفصل ملفات السيرفر في جهاز وملفات الكلاينت في جهاز .. لكن ما هي ملفات السيرفر -في المثال السابق- : BankAccount.java BankAccountImpl.java BankAccountInterface.java BankServer.java وبعد الترجمه سوف يخرج لنا : BankAccount.class BankAccountInterface.class BankAccountImpl.class BankServer.class BankAccountImpl\_Stub.class أما ملفات الكلاينت : BankClient.java ولن نستطيع ترجمه الكلاينت (الذي يعمل في جهاز منفصل ) والسبب أنه يحتوي على عمليه كاست من Object الى الكائن BankAccountInterface وهو لا يملكه ، أيضا كما ذكرنا أن الStub هو الذي يتعامل حقيقه مع السيرفر ونحن هنا لا نملك ملف BankAccountImpl\_Stub.class .. لذلك يجب أن يتوفر للكلاينت هذين الملفين حتى يعمل ... **السؤال الأن كيف يمكن أن نتقل ملفات class هذه الى الجهاز الثاني ؟** بالطبع يمكن أن تقوم بارسالها بشكل عادي ، أي تقوم بعمل نسخ ولصق لهذه الملفات في الكلاينت ، وهكذا كل شيء سيعمل بشكل سليم 100%، لكن في حال كان الجهاز الثاني بعيد ، فلن نستطيع ذلك .. الحل هو تحميل هذه الكلاسات من مكان ما (web server ) وقت تشغيل العميل Loading Classes at Runtime .. أي أننا سنقوم بتحميل الملفات وقت تشغيل العميل ، ## **Dynamic code downloading** أحد ميزات لغه جافا هي أنك تستطيع تحميل كود Byte Code (ملفات .class) من موقع URL وتنفذه في جهازك ... مثلا عندما تقوم بفتح Applet فإن الـ JVM الموجوده في متصفح الأنترنت تقوم أولا بتحميل الByte Code من السيرفر الى جهازك ، ومن ثم تبدأ في تنفيذه .. RMI قامت بتطيق نفس الفكره ، حيث تستطيع باستخدام RMI API (وليس JVM كما في الأبليت) بتحميل الكلاسات الموجوده في الجهاز البعيد ، وهنا نحن نريد تحميل ملف Stub الذي يجب أن يكون موجود لدى الكلاينت حتى يعمل بشكل صحيح .. وتستطيع تحميل الكلاسات من الجهاز البعيد عن طريق تحديد الموقع في CodeBase .. هذه الcodebase يمكن أن نعتبرها مصدر للكود ، ويقوم العميل عندما يبدأ في العمل بالذهاب الى هذا المكان وتحميل الكود ويبدأ العمل بعدها . ويكمن التعامل مع codebase في الأبليت أو الApplication العادي . حاليا نريد تحميل كلاس Stub (واي كلاسات ثانيه في حال أحتاج العميل لذلك ، مثلا القيمه الراجعه من السيرفر كائن غير معروف لدي الكلاينت) ، هنا سنقوم بتحديد موقع الكلاسات عندما نشغل السيرفر .. وعندما يتصل الكلاينت بالسيرفر سوف يبحث لديه في classPath ، فاذا لم يجد الكلاسات فسوف يذهب الى ذلك المكان ويقوم بتحميل الكلاسات التي يحتاجها .. يقوم السيرفر بتحديد موقع الكلاسات عن طريق : **كود:** java.rmi.server.codebase=http://MyHost/MyFile/ نقوم بكتابتها وقت تشغيل السيرفر ، بالمعامل -D .. لا تنسى / في الأخر .. الصور التاليه ، تشرح طريقه العمل بشكل مبسط وجيد ... [![codebase-1](https://informatic-ar.com/wp-content/uploads/2013/10/codebase-1-300x131.gif)](https://informatic-ar.com/wp-content/uploads/2013/10/codebase-1.gif) هنا الصوره السابقه تشرح كيف يقوم الأبليت بتحميل ملفات الكلاسات التي يحتاجها ثم يقوم بتنفيذها . [![codebase-2](https://informatic-ar.com/wp-content/uploads/2013/10/codebase-2-300x162.gif)](https://informatic-ar.com/wp-content/uploads/2013/10/codebase-2.gif) هنا نوضح الصوره السابقه الكثير من الأمور ، تبدأ بتحديد الخادم موقع ملفات الكلاسات ، بعدها (الخطوه1) يقوم بتسجل اسم الكائن ، بعدها يقوم الكلاينت بأخذ الكائن الراجع بعد طلبه ، ويقوم بتحميل ملف Stub من المكان الذي حدده الخادم ويعود للكلاينت ومنه في الصوره القادمه) يقوم باستدعاء الداله البعيده .. [![codebase-3](https://informatic-ar.com/wp-content/uploads/2013/10/codebase-3-300x127.gif)](https://informatic-ar.com/wp-content/uploads/2013/10/codebase-3.gif) طبعا يمكن أن يرسل الكلاينت كائن غير معروف للسيرفر ، هنا في هذه الحاله سوف يضطر الكلاينت أن يحدد للسيرفر موقع الكلاس لهذا الكائن حتى يقوم بتحميله ، يعني أي جهه تحدد للأخرى موقع تحميل الكلاسات للكائنات التي لا تعرفها الجهه ... الصوره التاليه توضح ذلك . [![codebase-4](https://informatic-ar.com/wp-content/uploads/2013/10/codebase-4-300x155.gif)](https://informatic-ar.com/wp-content/uploads/2013/10/codebase-4.gif) هناك مسأله بسيطه وهي أن الكلاينت (في Application ) لا يستطيع تحميل الكلاسات بشكل مباشر لأنها قد تحتوي على كود خبيث -هي من أعدادات اللغه الأفتراضيه- ، لذلك يجب أن تحدد له ذلك ، وهو موضوع RMI Security . ## **RMI Security** في حال كانت جميع الكلاسات متوفره في الجهازين ، فلن توجد مشكله لأننا لن نحمل أي كود من أي جهه .. لكن في حال أحتاج الكلاينت لكلاس من السيرفر غير موجود في الكلاينت ، سوف يرسل له السيرفر هذا الكلاس ولكن سوف يبدأ هذا الكائن بالعمل مباشره بدون تدخل منك فورا بعد عمليه Deserialization .. وبالطبع مثل هذا قد يمثل خطوره لذلك لغه جافا بالوضع الأفتراضي لها لا تسمح لك بهذا (ما عدا في الأبليت) . المسؤول عن تحميل هذه الكلاسات هو SecureClassLoader ويجب أن نحدد لهذا الكائن حدود عمله restrictions موجوده في java.policy ، ويمكن أن نغير فيها عن طريق الأداه policytool .. أيضا هناك الكائن RMISecurityManager (وهو أبن لـ SecurityManager) ويمكن عن طريقه أن نتحكم بـ security policy . وهو الذي يسمح في الحقيقه للـ Applet بالعمل وتحميل الملفات، لذلك لكي نستطيع تحميل الكلاسات يجب أن نقوم بعمل كائن من RMISecurityManager . اذا يمكننا أن نتحكم بـ security policy عن طريق الأداه policytool أو عن طريق العمل مع الكائن RMISecurityManager .. لكن العمل المباشر مع هذا الكائن لن يفيد كثيرا لأنه يعتمد على الأعدادات الأفتراضيه في security policy ، لذلك يمكن أن ننشيء Security Manager خاص بنا يسمح بكل شيء وهو عن طريق وراثه الكلاس RMISecurityManager واعاده تعريف الداله checkPermission .. مثال : ```java // Zero security Manager import java.security.Permission; import java.rmi.RMISecurityManager; public class ZeroSecurityManager extends RMISecurityManager { public void checkPermission(Permission permission) { System.out.println("Check Permission for : " + permission.toString()); } } ``` الأن عندما يبدأ الكلاينت العمل يجب أن نحدد له ما يفعله عن طريق الداله setSecurityManager ونمرر له الكائن SecurityManager أو أي Subclass منه . ## **Fibonacci Computing** المثال الأخير (شامل لكل المفاهيم ان شاء الله) وهو لسيرفر لديه داله تحسب رقم الحد الفلاني في سلسله Fibonacci الشهيره .. ونريد بأن يعمل على جهازين منفصلين ، وقد تمت تجربته بين نظام windows Server2003 و Windows Xp .. الغرض من البرنامج هو أن يقوم العميل باستدعاء داله حساب Fibonacci في السيرفر ويمرر لها رقم ، وترجع هذه الداله الناتج ويطبع لدى الكلاينت .. نريد أن يكون الكلاينت مره Application ومره Applet .. **نبدأ من جهه الخادم :** سوف يكون لديه ثلاثه ملفات : Fibonacci هو الأنترفيس FibonacciImpl هو تطبيق لهذا الأنترفيس FibonacciServer هو ملف السيرفر ملف Fibonacci.java : ```java import java.rmi.RemoteException; import java.rmi.Remote; import java.math.BigInteger; public interface Fibonacci extends Remote { public BigInteger getFibonacci(int number) throws RemoteException; public BigInteger getFibonacci(BigInteger bi) throws RemoteException; } ``` ملف FibonacciImpl.java : ```java // Fibonacci Implementation import java.rmi.server.UnicastRemoteObject; import java.rmi.RemoteException; import java.math.BigInteger; public class FibonacciImpl extends UnicastRemoteObject implements Fibonacci { public FibonacciImpl() throws RemoteException { } public BigInteger getFibonacci(int number) throws RemoteException { return this.getFibonacci(new BigInteger("" + number)); } public BigInteger getFibonacci(BigInteger bInt) throws RemoteException { BigInteger zero = new BigInteger("0"); BigInteger one = new BigInteger("1"); if (bInt.equals(zero)) return one; if (bInt.equals(one)) return one; BigInteger i = one; BigInteger low = one; BigInteger high = one; while (i.compareTo(bInt) == -1) { BigInteger temp = high; high = high.add(low); low = temp; i = i.add(one); } return high; } } ``` ملف FibonacciServer.java : ```java // Fibonacci Server import java.rmi.Naming; import java.rmi.RemoteException; import java.net.MalformedURLException; public class FibonacciServer { private static final String HOST = "yourHost"; public static void main(String args[]) { try { FibonacciImpl myObject = new FibonacciImpl(); String myObjectName = "rmi://" + HOST + "/MyFib"; Naming.rebind(myObjectName, myObject); System.out.println("Server Ready to Word ......"); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } } ``` نقوم في السيرفر بترجمه هذه الملفات : javac \*.java ونقوم بتفح مترجم rmi : rmic FibonacciImpl الأن خرجت الملفات : Fibonacci.class FibonacciImpl.class FibonacciServer.class FibonacciImpl\_Stub.class نقوم الأن بنقل ملفات الـ Class التي سوف يقوم الكلاينت بتحميلها من هناك .. ننقلها في web-Server .. بعدها أحذف ملف FibonacciImpl\_Stub.class من المجلد الحالي ، فقط دع هذا الملف في الويب سيرفر ، حتى عندما نشغل السيرفر يتصل بهذا الملف .. ونشغل ملف registry : rmiregistry وأخيرا نشغل الخادم : java - Djava.rmi.server.codebase=http://MySite/file/ FibonacciServer **الأن نبدأ العمل في الكلاينت :** سوف يكون لديه ثلاثه ملفات : Fibonacci.java هو الأنترفيس ZeroSecurityManager.java FibonacciClient.java ملف الأنترفيس هو نفسه الذي في السيرفر ويجب أن يكون هنا ، حتى يتم عمل Compiler للـFibonacciClient.java . ملف ZeroSecurityManager.java عرضنا كوده في الأعلى .. FibonacciClient.java : ```java import java.rmi.RemoteException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.net.MalformedURLException; public class FibonacciClient { private static final String HOST = "ServerHost"; public static void main(String args[]) { try { if (System.getSecurityManager() == null) System.setSecurityManager(new ZeroSecurityManager()); String myObjectName = "rmi://" + HOST + "/MyFib"; Fibonacci myObject = (Fibonacci)Naming.lookup(myObjectName); System.out.println(myObject.getFibonacci(34)); System.out.println(myObject.getFibonacci(55)); System.out.println(myObject.getFibonacci(155)); } catch (RemoteException e) { e.printStackTrace(); } catch (NotBoundException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } } ``` الأن : ترجم الملفات الثلاثه : javac \*.java وشغل الكلاينت : java FibonacciClient وسترى المخرج أخييييرا على الشاشه . في حال أردنا العمل على Applet ، نكتب ملف AppletFibonacci.java ونضعه في السيرفر ، ونترجمه ، وننقل ملف الكلاس الى الويب سيرفر مع بقيه الكلاسات .. بالأضافه الى صفحه html يتصل من خلالها العميل .. AppletFibonacci.java ```java import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.rmi.*; import java.math.BigInteger; public class AppletFibonacci extends Applet { private TextArea resultArea = new TextArea("", 20, 72, TextArea.SCROLLBARS_BOTH); private TextField inputArea = new TextField(24); private Button calculate = new Button("Calculate"); private String server; public void init() { this.setLayout(new BorderLayout()); Panel north = new Panel(); north.add(new Label("Type a non-negative integer")); north.add(inputArea); north.add(calculate); this.add(resultArea, BorderLayout.CENTER); this.add(north, BorderLayout.NORTH); Calculator c = new Calculator(); inputArea.addActionListener(c); calculate.addActionListener(c); resultArea.setEditable(false); server = "rmi://" + this.getCodeBase().getHost() + "/MyFib"; } class Calculator implements ActionListener { public void actionPerformed(ActionEvent evt) { try { String input = inputArea.getText(); if (input != null) { BigInteger index = new BigInteger(input); Fibonacci f = (Fibonacci)Naming.lookup(server); BigInteger result = f.getFibonacci(index); resultArea.setText(result.toString()); } } catch (Exception ex) { resultArea.setText(ex.getMessage()); } } } } ``` HTML Page : ```markup <html> <head> <title>RMI Applet

RMI Applet

``` بعد ترجمه AppletFibonacci ضع ملف الكلاس في السيرفر هو وصفحه الـ HTML ، وسوف تتم العمليه بنجاح .. الأن ترجم في السيرفر : java FibonacciServer لا داعى لوجود codebase لأن الأبليت سوف يحمل الملفات بنفسه .. وفي الكلاينت أدخل على صفحه HTML الموجوده بالسيرفر من خلال متصفح الأنترنت [![RMI5](https://informatic-ar.com/wp-content/uploads/2013/10/RMI5-300x171.png)](https://informatic-ar.com/wp-content/uploads/2013/10/RMI5.png) الى هنا نكون أنهينا الموضوع وليس بشكل كامل ، فموضوع الـ security موضوع متداخل كثيرا وبه الكثير من الأمور الغامضه .. اضافه الى أن تحميل كود من الجهه الثانيه غير مضمون كثيرا ، فقد تحصل مشاكل مثلا قطع الأتصال أو مشكله في اعدادك للبرنامج ، لذلك اذا استطعت نقل الكلاسات يدويا فهذا أفضل بكثير ويخرجك من جميع هذه المتاهات . ## **المصادر** [Dynamic code downloading using RMI](http://java.sun.com/j2se/1.4.2/docs/guide/rmi/codebase.html?ref=informatic-ar.com) Java Network Programming, 3rd Edition Introduction to Network Programming in java Core Java 2 , Part II ### التعامل مع JList في Swing URL: https://informatic-ar.com/swing_jlist_hacking/ Last updated: 2026-10-03T22:44:56.000Z سنتناول في هذا الموضوع لمحة بسيطة عن استخدام الJList وعمل Extending لها لكي تتناسب مع حاجة المستخدم ،، والJList وكغيرها من الSwing Component مبنية على مفهوم الModel-View-Controller وبالتالي اذا كنت تريد تغيير الObject الموجود داخل الComponent سوف تتعامل مع الModel ، واذا أردت تغيير طريقة العرض بالشكل الذي تريدها سوف تتعامل مع الView ، وهذه مرونة ممتازة في تصميم الComponent . \* Inserting and Removing Values \* Rendering Values \* Custom List نبدأ بالحالة الأولى والأسهل وهي الحالة الإفتراضية للJList وسنقوم بعمل JList يتكون من العديد من النصوص String ، كما يبين ذلك في الصورة من المثال التالي: [![jlist_1](https://informatic-ar.com/wp-content/uploads/2013/10/jlist_1-300x127.png)](https://informatic-ar.com/wp-content/uploads/2013/10/jlist%5F1.png) سنقوم بعمل الJList مكونة من العديد من النصوص : ```java String mySite[] = {"arabteam2000.com", "sudancs.com", "stackoverflow.com", "google.com"}; JList list = new JList(mySite); ``` وبالشكل الإفتراضي الJList ليست Scrollable لذلك يجب أن تضيف الlist الى JScrollPane وتضيف الأخير للPanel : ```java JScrollPane pane = new JScrollPane(list); panel.add(pane); ``` هذه الlist أيضا بالوضع الإفتراضي تتكون من 8 خانات يمكنك تغييرها باستخدام الدالة setVisibleRowCount : ```java list.setVisibleRowCount(4); ``` الإتجاه Orientation في هذه الlist في الوضع الإفتراضي هو الvertical ويتم تحديد ذلك من خلال الدالة setLayoutOrientation ، ومن القيم لهذه الدالة: JList.VERTICAL (الإفتراضي وكل الitems ستكون عموديا أسفل بعضها ) JList.VERTICAL\_WRAP وهنا تكون الItems أيضا عموديا ولكن يتم الإنتقال لعمود أخر اذا كان عدد الitems أكبر من المسموح به في العمود الواحد) JList.HORIZONTAL\_WRAP نفس السابق ولكنه أفقيا .. طريقة التحديد Selection mode في الوضع الإفتراضي هي Multiple وتسمح لك باختيار أكثر من item سواء بالضغط على الitems مع الزر CTRL لتحديد عدة items متفرقة أو SHIFT لتحديد عدة items متجاورة . وتستطيع تغيير الselection mode باستخدام الدالة setSelectionMode كما يلي : ```java list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // select one item at a time list.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION); // select one item or one range of items ``` بالنسبة للتعامل مع الأحداث في الJList فسوف يحدث الevent بمجرد تحديد أي من الitem في الlist ، وهنا يجب أن يكون لديك listener يقوم بتطبيق implements الListSelectionListener ويعرف الدالة valueChanged ```java public void valueChanged(ListSelectionEvent evt) ``` في الحقيقة عند الضغط على الitem سوف يحدث حدثين واحد عن الضغط بالفأرة mouse down والأخر عند رفع الفأرة mouse up ، لذلك في الغالب سوف نتعامل مع حدث واحد ، ويمكن معرفة نوع الحدث الحالى عن طريق الدالة : ```java event.isAdjusting() ``` في حال أرجعت الدالة true هذا يعني أنه الأن تم الضغط بالفأرة mouse down ، والا فإنه mouse up .. عادة سنتعامل مع الMouse up ولك الخيار فيما تريد. بعد أن تم تنبيهك بالحدث ، يجب أن تعرف ما هي الitems التي تم اختيارها ، ويمكنك ذلك من خلال الدالة getSelectedValues وهي ترجع مصفوفه من الكائنات Array of Objects ، يمكنك عمل cast to string لأي خانه منها ، كما يلي : ```java Object[] values = list.getSelectedValues(); for (Object value : values) do something with(String) value; ``` في حال كانت الlist لا تسمح بالإختيار المتعدد ، فيمكنك الحصول على الitem الذي تم الضغط عليه من خلال: ```java String value = (String)list.getSelectedValue(); ``` هذا هو المثال : ```java import java.awt.EventQueue; import java.awt.BorderLayout; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JList; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionEvent; public class SimpleJList { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { SimpleListFrame app = new SimpleListFrame(); app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); app.setVisible(true); } }); } } class SimpleListFrame extends JFrame { public SimpleListFrame() { // set application title setTitle("Simple JList Test"); // set Application size setSize(300, 150); // set application location setLocationByPlatform(true); // fill JList list = new JList(mySite); list.setVisibleRowCount(5); list.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { Object[] value = list.getSelectedValues(); StringBuilder text = new StringBuilder(prefix); for (Object obj : value) { String word = (String)obj; text.append(word); text.append(" "); } lbl.setText(text.toString()); } }); lbl = new JLabel("no Selected site"); JScrollPane pane = new JScrollPane(list); JPanel panel = new JPanel(); panel.add(pane); add(panel, BorderLayout.NORTH); add(lbl, BorderLayout.SOUTH); } private String mySite[] = {"arabteam2000.com", "sudancs.com", "stackoverflow.com", "google.com"}; private String prefix = " Im Visit : "; private JList list; private JLabel lbl; } ``` **List Internals :** كما ذكرنا في بداية الموضوع أنه الJList مصممه بالMVC ، وهو يفصل الواجهه (التي تعرض Rendered بشكل ما ) عن المحتوى (Underlying Objects) . وهنا سنجد أن الكلاس JList يمثل الواجهه View (أي هو المسؤول عن العرض) ، أما المحتوي فيحصل عليها من خلال كائن يطبق implements الواجهه interface التاليه : ```java public interface ListModel { int getSize(); Object getElementAt(int i); void addListDataListener(ListDataListener l); void removeListDataListener(ListDataListener l); } ``` من خلال هذه الواجهه يستطيع الJList الحصول على عدد الitems والحصول على الitems ، أيضا يضع listener على أي تغيير (اضافة وحذف) في المحتوي وبالتالي يستطيع اعادة الرسم مرة أخرى بعد عمل التحديث المطلوب . وبما أن JList فقط يحصل على الitems ولا يهمه كيف يخزن ، فيمكن أن تقوم بعمل دالة getElementAt لكنها تقوم بتوليد نصوص في وقت التشغيل. هناك interface أخر مفيد وهو AbstractListModel وهو مشابه لListModel ولكنه يعرف الaddListDataListener و removeListDataListener وبالتالي سوف يسهل علينا كثيرا ( فقط سنقوم بتعريف getSize و getElementAt ) .. : ```java class MyNewModel extends AbstractListModel { public int getSize() { /* anythings */ ; } public Object getElementAt(int n){// Anythings ...} ... } ``` **Inserting and Removing Values** لعمل list قابلة للتعديلات (الإضافة والحذف ) فهذا يتطلب اضافة بسيطة وذلك لأن الJList والListModel لا يحتويان على أي دوال للإضافة والحذف ، وهنا سوف نستخدم الكلاس DefaultListModel ونقوم بادخال العناصر فيها باستخدام addElement .. وأخيرا ندخل الكائن من الDefaultListModel في الJList : ```java DefaultListModel model = new DefaultListModel(); model.addElement("site1"); model.addElement("site 2"); // other adding ...... JList list = new JList(model); ``` الأن يمكنك اجراء التعديلات من خلال الmodel وهو سيتولي اخبار الJList بعملية التغيير وحينها سيقوم الJList باعادة الرسم : ```java model.removeElement("site 1"); model.addElement("site 3"); ``` **Rendering Values** الى هنا فإن أي list قمنا بعمله يتكون من Strings فقط ، وفي الحقيقة يمكن انشاء list من أي Objects كان ، ولكن لكي يتم عرضه بشكل صحيح ، يجب أن نضع طريقة لعرض المحتوي List Cell Rendering في الlist وذلك عن طريق أي كائن يطبق الواجهه التالية : ```java interface ListCellRenderer { Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus); } ``` هذه الدالة getListCellRendererComponent يتم استدعائها لعرض كل items وهي ترجع الitem لكي يتم وضعه في الlist . وأحد الطرق لذلك هو وراثه أي من الComponent كما يلي : ```java class MyCellRenderer extends JComponent implements ListCellRenderer { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { // stash away information that is needed for painting and size measurement return this; } public void paintComponent(Graphics g) { // paint code goes here } public Dimension getPreferredSize() { // size measurement code goes here } // instance fields } ``` المثال التالي سوف نقوم بعمل list مكونه من ImageIcon كما في الشكل التالي : [![jlist_2](https://informatic-ar.com/wp-content/uploads/2013/10/jlist_2.png)](https://informatic-ar.com/wp-content/uploads/2013/10/jlist%5F2.png) والRendering يتم عن طريق الوراثه من الJLabel وبالتالي لن نحتاج الى الpaintComponent والgetPreferredSize فهو يقوم بها بالطريقة التي نريد . ```java import java.awt.Color; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.Component; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.ImageIcon; import javax.swing.Icon; import javax.swing.SwingConstants; import javax.swing.JList; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionEvent; import javax.swing.ListCellRenderer; import javax.swing.JScrollPane; public class ListTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { FrameList app = new FrameList(); app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); app.setVisible(true); } }); } } class FrameList extends JFrame { public FrameList() { setTitle("List Test"); setSize(300, 300); Icon[] icon = {new ImageIcon(("gpg.png"), "Encryption"), new ImageIcon(("folder_outbox.png"), "Send Message"), new ImageIcon(("image.png"), "Hide in Photo")}; list = new JList(icon); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.setCellRenderer(new MyCellRenderer()); list.setVisibleRowCount(3); JScrollPane pane = new JScrollPane(list); JPanel panel = new JPanel(); panel.add(pane); add(panel, BorderLayout.WEST); list.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent event) { if (event.getValueIsAdjusting()) return; int s = (int)list.getSelectedIndex(); System.out.println(s); } }); } private JList list; } class MyCellRenderer extends JLabel implements ListCellRenderer { public MyCellRenderer() { setOpaque(true); } public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { ImageIcon icon = (ImageIcon)value; setText(icon.getDescription()); setIcon(icon); setVerticalTextPosition(SwingConstants.BOTTOM); setHorizontalTextPosition(SwingConstants.CENTER); if (isSelected) { setBackground(Color.GRAY); setForeground(Color.WHITE); } else { setBackground(Color.WHITE); setForeground(Color.BLACK); } return this; } } ``` **Custom** **JLists** لو فرضنا أن لديك list من 1000 خانة ، فعملية البحث عن العنصر المطلوب أمر مرهق وفي هذه الحالة يعتبر التصميم bad design ، لكن ماذا لو قمنا بوضع text field يكتب فيه المستخدم الحرف الأول من الخانه وسيتم تعديل الlist وفقط تظهر الخانات التي تبدأ بهذا الحرف الأول .. كما توضح ذلك الصورة التالية عند الضغط على الحرف S سيظهر كل الأسماء التي تبدأ بS . [![jlist_3](https://informatic-ar.com/wp-content/uploads/2013/10/jlist_3.png)](https://informatic-ar.com/wp-content/uploads/2013/10/jlist%5F3.png) في هذه الحالة سوف يكون لدينا Model ولكن طريقتين للعرض ، الأولى عرض جميع الخانات (تكون النصوص موجودة في مصفوفه )والثانية عرض جميع الخانات التي تبدأ بالحرف المعين (وأيضا سيتم تخزينها في مصفوفه أخرى ) ،، سنقوم الأن بتصميم الكلاس FilteredList وسيكون بداخلها كلاسين الأول FilterModel للعرض والثاني FilterField للكتابة . نبدأ الأن بالوراثة من الJList ونضيف الأشياء المطلوبة : ```java class FilteredList extends JList { public FilteredList() { super(); setModel(new FilteredModel()); filteredField = new FilteredField(20); } public void setMode(ListModel model) { if (!(model instanceof FilteredModel)) throw(new IllegalArgumentException()); super.setModel(model); } public void addItem(Object o) { ((FilteredModel)getModel()).addElement(o); } public JTextField getFilteredField() { return filteredField; } private FilteredField filteredField; } ``` هذه الlist الجديدة ، وفي دالة البناء وضعنا الModel بأنه هو FilteredModel وهو كلاس داخلي يحتفظ بالنصوص جميعها والنصوص التي تبدأ بالحرف المعين وبالطبع هو يرث AbstractListModel كما سنشاهد بعد قليل ، ويحتوي الlist الجديد على دالة setMode تتأكد من النوع أولا .. وهناك دالة لإدخال العناصر في الmodel وهناك دالة لأرجاع الtextfield حيث أننا نريد أن نضعه على الframe . أما الكلاس FilteredModel فهو يرث AbstractListModel ويعيد تعريف الدالة getElementAt و getSize ، ويحتوي كما ذكرنا على مصفوفتين الأولى لجميع النصوص وهي التي سوف تضاف للmodel ، والأخرى للنصوص التي تحتوي على الحرف المعين .. أما الكلاس FilterField فهو يرث الJTextField ووظيفته باستدعاء الدالة refilter متى تغير أي شيء فيه . الكود هنا : ```java import java.awt.*; import javax.swing.*; import javax.swing.event.*; import java.util.*; public class CustomeList { public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { CustomeListFrame app = new CustomeListFrame(); app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); app.setVisible(true); } }); } } class CustomeListFrame extends JFrame { public CustomeListFrame() { setTitle("Filter List"); setSize(300, 300); fList = new FilteredList(); for (int i = 0; i < names.length; i++) fList.addItem(names[i]); JScrollPane pane = new JScrollPane(fList, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); add(pane, BorderLayout.CENTER); add(fList.getFilteredField(), BorderLayout.NORTH); } private String[] names = {"Ahmed", "Abdu", "Ali", "Sami", "Salem", "Sameer", "Salman", "Khalid", "Bassem"}; private FilteredList fList; } class FilteredList extends JList { public FilteredList() { super(); setModel(new FilteredModel()); filteredField = new FilteredField(20); } public void setMode(ListModel model) { if (!(model instanceof FilteredModel)) throw(new IllegalArgumentException()); super.setModel(model); } public void addItem(Object o) { ((FilteredModel)getModel()).addElement(o); } public JTextField getFilteredField() { return filteredField; } private class FilteredModel extends AbstractListModel { public FilteredModel() { super(); items = new ArrayList(); fItems = new ArrayList(); } public Object getElementAt(int index) { if (index < fItems.size()) return fItems.get(index); else return null; } public int getSize() { return fItems.size(); } public void addElement(Object o) { items.add((String)o); refilter(); } private void refilter() { fItems.clear(); String term = getFilteredField().getText(); for (int i = 0; i < items.size(); i++) if (items.get(i).toString().indexOf(term, 0) != -1) fItems.add(items.get(i)); fireContentsChanged(this, 0, getSize()); } private ArrayList items; private ArrayList fItems; } private class FilteredField extends JTextField implements DocumentListener { public FilteredField(int width) { super(width); getDocument().addDocumentListener(this); } public void changedUpdate(DocumentEvent e) { ((FilteredModel)getModel()).refilter(); } public void insertUpdate(DocumentEvent e) { ((FilteredModel)getModel()).refilter(); } public void removeUpdate(DocumentEvent e) { ((FilteredModel)getModel()).refilter(); } } private FilteredField filteredField; } ``` أمل أن يكون الموضوع المفيد ،، ويمكن تخصيص الlist بالشكل الذي تريد بعد الأن .. وسبب كتابتي للموضوع هو أني أستخدمت list بها أيقونات في مشروع بسيط ، وأردت مشاركة الفكرة ، واجهه المشروع Frame تقسم الى قسمين Panel على اليمين وPanel على اليسار (باستخدام JSplitterPane ) و تكون هناك JList بها أيقونات على الجهه اليسري من الJSplitterPane، وكل أيقونة تمثل حدث معين مثلا Connect أو Send أو Encryption .. وعند الضغط على احد الأيقونات فسوف يظهر البانل الخاص به على الجهه اليمني من الJSplitterPane. سوف يظهر أي بانل في الجهه اليمني وبنفس الحجم ، والسبب هو استخدام الCardLayout في تقسيم الجهه اليمني من الJSplitterPane . **References for more :** Swing Hacks , By Chris Adamson, Joshua Marinacci Core Java™ Volume II–Advanced Features Happy Swing Hacking :) . ### خوارزميات مطابقة النصوص Pattern Matching Algorithm URL: https://informatic-ar.com/string_matching/ Last updated: 2026-10-03T22:32:02.000Z خوارزميات مطابقة النصوص Pattern Matching أو البحث في النصوص من المواضيع المهمه في مجال الخوارزميات ، حيث الهدف هو البحث عن نص ما سوف نطلق عليه Pattern داخل مجموعه كبيره من النصوص Text ـ ويمكن أن يكون البحث عن النمط بالضبط exact او عن أي نمط قريب للنمط المراد البحث عنه ، وبما أن خوارزميات البحث المتسلسل Linear Search والبحث الثنائي Binary Search تستخدم في البحث عن "مفتاح واحد" داخل مجموعه كبيره من النصوص (أو الأرقام) فإنها لا تصلح استخدامها في حالتنا هذه ، فنحن نريد البحث عن نمط معين (مجموعه من المفاتيح أو الحروف ) داخل النص الكبير وليس مفتاح واحد فقط وأحيانا نريد البحث عن أي نمط أخر مشابه. ## أنواع خوارزميات مطابقة النصوص خوارزميات المطابقة مهمه للغايه لأي مبرمج، حيث أن تطبيقات موضوع بحث النصوص كبيره ومتنوعه، مثلا أغلب برامج محررات النصوص توجد فيها خاصيه بحث وأستبدال Find and Replace هل تسأئلت يوما كيف تجري هذه العمليه وما هي الخوارزميه المستخدمه ؟ -أغلب البرامج تستخدم خوارزميه Boyer-Moore algorithm - حيث تقوم هذه الخوارزمية بالبحث عن جميع الكلمات في الملف لكي تستبدل النص الجديد بجميع النصوص المشابه للنمط المراد البحث عنه. هذه الخوارزمية Boyer-Moore وغيرها من الخوارزميات التي تبحث عن النص المشابه تسمى Exact String Searching وهناك حوالى 20 خوارزميه أو أكثر. نوع أخر من الخوارزميات المطابقة وهي البحث عن طريق أول مجموعه من الأحرف فقط ، وأقرب مثال يوضح فائدته هو أن يكون لدينا قاعده بيانات ضخمه لجميع هواتف المواطنين ، ونريد البحث عن رقم أي مواطن بإسم "محمد أحمد" بالطبع استخدام خوارزميه تعمل بمبدأ البحث اسم بأسم في هذه الحاله غير مجدي بتاتا خصوصا في حاله كان القاعده كبيره جدا (عشرات ال gigabyte) ولذلك سوف نوع من أنوع الأشجار B-Tree ونرتب فيها البيانات (لكي نحمل الإسماء من الملف الى الذاكرة) وبعدها نجري عمليه البحث باستخدام خوارزمية ال Prefix Searching، مثال أخر على هذه الخوارزميات وهو في البحث عن نمط ناقص، لنفرض أنك شاهدت حادث ما في الطريق وأستطعت أن ترى أول رقمين في لوحه السياره ولم تستطع معرفه الثالث الأن باستخدام خوارزميات المطابقه (Prefix Searching) تستطيع بكل سهوله وسرعه ايجاد جميع الأرقام التي تبدأ بهذه الرقمين ، مثال أخر على تطبيق هذا النوع من البحث prefix searching وهو في محرك البحث Google حيث لديه ميزه جميله وهو أنك بمجرد كتابه الكلمه الأولى في المحرك فيقوم بعرض جميع الكلمات التي تبدأ بهذا الأسم ،وأيضا خوارزميات prefix searching تستخدم في شريط عنوان المتصفح Firefox. نوع أخر من الخوارزميات تستخدمه برامج الأنتي فايروس حيث تقوم بالبحث عن التوقيع في قاعده بيانات تحتوي على الألاف التواقيع في مده صغيره جدا قد لا تتجاوز ثانيه واحده وتحتاج الى خوارزميات للبحث عن أكثر من نمط في المره الواحده Multiple pattern searching مثل خوارزمية Aho-Corasick حيث في هذا النوع سوف تستخدم الأشجار Tree كهيكل للبيانات أو الأصح شجرة ال ternary Search Tree والتي تم الحديث عنها هنا، حيث البحث في الأشجار سريع جدا مقارنه مع غيره، او حتى تستخدم بنيه Hash Table (سبق الحديث عنها هنا) مثل طريقة Veld man. الأمثله على التطبيقات في مطابقه النصوص لا تنتهي ويمكن أن تدمج هذه الخوارزميات وخوارزميات أخرى لتكون لدينا مشاريع رائعه للغايه وعمليه أيضا ، فمثلا يمكن عمل برنامج لكشف الغش في الأختبارات، حيث يقوم البرنامج باستقبال اجابات طالبين ومن ثم يقوم بتظليل جميع الكلمات المتطابقه وفي النهايه يعرض تقرير يوضح عدد الكلمات المتطابقه وفي حال زاد العدد عن عدد معين ، فنستنتج أن الطالبين غاشين . مثال أخر وهو المحرر الأملائي في برامج محررات النصوص ، حيث أنك بمجرد كتابه الكلمه فيقوم الجزء المسؤول عن التأكد من الأخطاء (عادة هو Thread أخر يعمل بمجرد بدء البرنامج ) بالبحث في القاعده التي تحتوي على جميع الكلمات في اللغه العربيه فاذا وجد أنها موجوده فهذا يعني أن الكلمه صحيحه ، والا فيقوم بعرض جميع الكلمات القريبه ويكون هذا باستخدام خوارزميات لايجاد النصوص الأقرب للنمط وذلك بالأعتماد على طريقه نطقها Sound وأشهر خوارزميات هذا النوع هو Soundix Searching. ## **الخوارزمية** **Naive Searching Algorithm** **تعريفات**: عندما يتم ذكر مصطلح النمط أو Pattern فهذا يعني النص الذي نريد البحث عنه، وعندما يتم ذكر النص الكبير أو Text فنحن نقصد النص الذي يحتوي على جميع الحروف . نبدأ الأن في أحد أقدم وأبسط الخوارزميات وفكرتها تكون عن طريق مقارنه حرف من ال pattern مع حرف مع ال Text، فاذا تطابق الحرفين ، فنقوم بالذهاب الى الحرف التالى في كل من النمط pattern وال text. اما في حال لم يتطابقا فنقوم بتحريك مؤشر الحرف في ال text الى الحرف التالي ونقوم بارجاع مؤشر الحرف في pattern الى البدايه ، وسوف نبدأ عمليه المقارنه مره أخرى . وسوف نستمر هكذا الى أن نجد التطابق في كل حروف ال pattern، أو أن نصل لنهايه ال text. وسوف نشير الى طول ال pattern بالحرف M أما طول ال text سوف نشير له بالحرف N. وسوف نتوقف في البحث عندما نصل ل N-M لأننا عندما نصل لتلك الخانه وحتى اذا كان الحرف التالي متطابق فسوف نتوق ف لأن ال Text سوف يكون أصغر من ال pattern. الصوره التاليه توضيح عمليه البحث عن النمط abaa ، حيث تمت مقارنه الحرف الأول في النمط مع الحرف الأول من الtext وكانت النتيجه صحيحه ، وسيتم الانتقال الى الحرف التالى في كل من text وpattern . وهنا عندما تتم المقارنه فالنتيجه غير صحيحه وبالتالى نحرك مؤشر ال text للأمام ، ونرجع ال pattern للبدايه ، ونبدأ البحث مره أخر. لاحظ عدد مرات الshift هي 3 مرات حيث توصلنا للموقع الحرف المطابق في ال Index ثلاثه . [![الشكل يبين خوارزمية ال Naïve Search](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching_1-300x93.png)](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching%5F1.png) الشكل يبين خوارزمية ال Naïve Search والصوره التاليه توضح الخوارزميه بشكل بسيط ، ويمكن أن تطبقها باللغه التي تريد: [![الشكل يبين ال Pseudo code للخوارزمية](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching_2-300x117.png)](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching%5F2.png) الشكل يبين ال Pseudo code للخوارزمية هناك من طبق الخوارزميه بأكثر من وجه ولكنها في النهايه هي نفس النتيجه ، الكود التالى يوضح لنا تطبيق الخوارزميه بعده طرق ممكنه (قد يصعب تتبع مثل هذه الحلقات لذلك لا أفضل من الورقه والقلم في حال لم تستطيع فهم الحلقه بشكل جيد). **كود سي++ لتطبيق خوارزمية Naïve Search** ```cpp // implement Naive String Search (also known as Brute-Force) Using Several Method // By : Wajdy Essam #include using std::string; // C implementation // return the first index when match string int BruteForceSearch1(char *pattern, char *text) { int i, j, m = strlen(pattern), n = strlen(text); for (i = 0, j = 0; j < m && i < n; i++, j++) { while (pattern[j] != text[i]) { i -= j - 1; j = 0; } if (j == m) return i - m; else return i; } } // C implementation // printing index for all matching string in text void BruteForceSearch2(char *pattern, char *text) { int i, j, m = strlen(pattern), n = strlen(text); for (j = 0; j <= n - m; j++) { for (i = 0; i < m && pattern[i] == text[i + j]; i++) ; // note ; here if (i >= m) std::cout << j << " "; } } // C++ Implementation // return first index when mathc string , else return -1 class NaiveSearch { public: NaiveSearch() { mText = ""; } NaiveSearch(const string &text) : mText(text) { } bool matchAt(const string &pattern, int position) const { for (int i = 0; i < pattern.length(); i++) { if (pattern[i] != mText[i + position]) return false; } return true; } int match(const string &pattern) const { for (int i = 0; i + pattern.length() < mText.length(); i++) if (matchAt(pattern, i)) return i; return -1; } private: string mText; }; int main(int argc, char *argv[]) { // test first method std::cout << "index is : " << BruteForceSearch1("ss", "wajdy essam is assembly programmer") << std::endl; // test second method std::cout << "index is : "; BruteForceSearch2("ss", "wajdy essam is assembly programmer"); std::cout << std::endl; // test third method NaiveSearch ns("wajdy essam is assembly programmer"); std::cout << "index is : " << ns.match("ss"); return (0); } ``` **كود جافا لتطبيق خوارزمية Naïve Search** ```java // Java Implementation for Naive Search Algorithm public class Demo { public static void main(String args[]) { // test first method System.out.println("Index is : " + BruteForceSearch1("ss", "wajdy essam is assembly programmer")); // test second method System.out.print("index is : "); BruteForceSearch2("ss", "wajdy essam is assembly programmer"); System.out.println(); // test third method NaiveSearch ns = new NaiveSearch("wajdy essam is assembly programmer"); System.out.println("index is : " + ns.match("ss")); } // return the first index when match string static int BruteForceSearch1(String pattern, String text) { int i, j, m = pattern.length(), n = text.length(); for (i = 0, j = 0; j < m && i < n; i++, j++) { while (pattern.charAt(j) != text.charAt(i)) { i -= j - 1; j = 0; } if (j == m) return i - m; else return i; } return -1; } // printing index for all matching string in text static void BruteForceSearch2(String pattern, String text) { int i, j, m = pattern.length(), n = text.length(); for (j = 0; j <= n - m; j++) { for (i = 0; i < m && pattern.charAt(i) == text.charAt(i + j); i++) ; // note ; here if (i >= m) System.out.print(j + " "); } } } // return first index when mathc string , else return -1 class NaiveSearch { public NaiveSearch() { mText = ""; } public NaiveSearch(String text) { mText = text; } public boolean matchAt(String pattern, int position) { for (int i = 0; i < pattern.length(); i++) { if (pattern.charAt(i) != mText.charAt(i + position)) return false; } return true; } public int match(String pattern) { for (int i = 0; i + pattern.length() < mText.length(); i++) if (matchAt(pattern, i)) return i; return -1; } private String mText; } ``` هذه الخوارميه غير جيده وذلك لأنها تقوم بمقارنه كل حرف مع الحرف الأخر ، وفي حال لم يتطابقا سوف تعيد عمليه المقارنه لجميع حروف الpattern مع الحرف التالي من النص ، فاذا كان طول النص عدد N و والpattern عدد M فسوف يكون ال Big-O لهذه العمليه هو N\*M ## **الخوارزمية** **Knuth - Morris - Pratt String Matching** في عام 1977 نشر الثلاثي Knuth (صاحب كتاب Art of programming الشهير) و Morris و Pratt مقاله Fast Pattern Matching in Strings وتحدثوا فيها عن ايجاد خوارزميه أسرع من الطريقه التي تعتمد على Brute-Force وسميت هذه الخوارزميه بأسم هؤلاء الأشخاص وأختصارا KMP . للننظر قليلا الى ال Brute-Force وسنجد أنها عندما تجد حرف في pattern لا يطابق الحرف في text فإنها تقوم بالذهاب الى الحرف التالي من الtext وتعيد المقارنه من أول الPattern . خوارزميه KMP جائت لتحسين تلك الخوارزميه حيث تم التخلص من عمليات المقارنه المتكرره، طريقه عملها كالتالي: أولا تقارن الحرف الأول في الpattern مع الحرف الأول من ال text في حاله كانا مختلفين فتقوم بالذهاب الى الموقع التالى من text ( كما في Brute-Force بالضبط) ولكن الأختلاف يكون في حال قمنا بمقارنه ثلاثه حروف من الtext مع ال pattern وكانت النتيجه صحيحه ولكن الحرف الرابع يختلف ، هنا في هذه الحاله لن نقوم كما في الbrute-force باعاده مقارنه جميع الحروف مع الحرف التالى من text ، لكن بما أننا نعلم بأن هذه الحروف قمنا بمطابقتها سابقا فنقوم بالذهاب الى الموقع التالى من text الذي يشابه هذه الحروف التي طابقناها . وبالتالى ستكون هناك عمليات أقل وبالتالى الخوارزميه أسرع بكثير من السابقه. في البدايه يجب أن نقوم بحساب Prefix للpattern وسوف نحتاجه عندما نقوم بمقارنه الpattern مع text ، حيث هذه prefix array تحتوي على عدد الأحرف المشابه من أول الpattern للحرف الحالى من الpattern ، الصوره التاليه توضح كيف يمكن حسابه : [![الشكل يبين كيف يمكن حساب ال Prefix Array](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching_3.png)](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching%5F3.png) الشكل يبين كيف يمكن حساب ال Prefix Array لاحظ في الصوره الموقع 3 (الحرف r) يحتوي على 1 والسبب أن الحرف r موجود في الموقع 0 . 1 تشير الى عدد الأحرف الprefix . أيضا الموقع التالى 4 (الحرف e) يحتوي على 2 والسبب أن الحرفين في الموقعين 0و1 مشابهين للحرفين في الموقع 3و4\. يعني الprefix تعني اذا كانت هناك عدد n من الحروف من أول الpattern مشابه لعدد n من الحروف في الحرف الحالى من ال pattern اذا نقوم بكتابه ذلك العدد n في الprefix array . مثال أخر : [![الشكل يبين كيف يمكن حساب ال Prefix Array](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching_4-300x76.png)](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching%5F4.png) الشكل يبين كيف يمكن حساب ال Prefix Array كما ذكرنا هذه الprefix arrayسوف نستخدمها في خوازرميه البحث عندما لا يتطابق الحرف من pattern مع الحرف من الtext وبالتالي يتم الرجوع الى الخانه المكتوبه في الPrefix array وبالتالى سوف نتجنب عمليه المقارنه للأحرف التي قمنا بها . الشكل التالي يوضح كيفية البحث عن ال pattern في ال text: [![الشكل يبين كيفية البحث عن النمط في النص الأصلي](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching_5-300x254.png)](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching%5F5.png) الشكل يبين كيفية البحث عن النمط في النص الأصلي نبدأ شرح طريقه عمل الخوارزميه (البحث) ، أول نقارن الحرف الأول من الpattern مع الحرف الأول من ال text فنجد أنهما متطابقان ، نقوم بالذهاب الى الحرف التالى في كل من ال pattern والtext . نختبر وسنجد أن الحرفين غير ذلك ،، اذاً في هذه الحاله سوف يكون الرقم الموجود في الخانه الأولى في الprefix array ونبدأ منه (وهو في الحاله 0) ، اذاً سبندأ نختبر مره أخرى من الحرف r . سنختبره مع u وسنجد أنهم غير متساوين ، مره أخرى نختبره مع الحرف التالى n ونجد أنهما غير متساوين ثم نختبره مره أخرى مع r وسنجد أنهما متساوين ، نختبر الحرف التالي في كل من pattern والtext وسنجد أن الحروف e و t و r و e متطابقين ، نذهب للحرف التالى وهو a في ال pattern و n في الtext ، نقوم بالأختبار وسنجد أنهم غير متساوين ؟ اذاً نقوم بالذهاب الى الرقم الموجود في الخانه match-1 (المتغير match يشير الى Index الحرف في الpattern) في prefixarray وهو 2 ، ونقوم بالأختبار مره أخرى الحرف t من الpattern مع الحرف n من الtext ؟ سنجد أنهم غير متطابقين ، سنذهب الأن الى الخانه match-1 في ال prefixArray وهو هنا 0 أي الحرف r ، وسنقوم باختباره مع الحرف n في text ؟ والنتيجة غير متطابقين ، وسوف نقوم بالذهاب الى الحرف التالى وهو c وسنجد أنه غير كذلك ، نذهب للحرف التالى والتالى الى أن نصل للحرف r . وسنختبر وسنجد التطابق للحرف r وما بعده . **الكود التالي تطبيق لخوارزمية KMP بلغه سي++** ```cpp // C++ Implementation , Wajdy Essam // Knuth - Morris - Pratt Algorithm #include using namespace std; class KMP { public: KMP() : mText(""), mPattern("") { } KMP(const string &txt) : mText(txt), mPattern("") { } KMP(const string &txt, const string &pattern) : mText(txt), mPattern(pattern) { } int search() { return search(mText, mPattern); } int search(const string &pattern) { return search(mText, pattern); } int search(const string &text, const string &pattern) { int prefixArray[pattern.length()]; initializeToZero(prefixArray, pattern.length()); preProcessing(pattern, prefixArray); int i = 0, match = 0; while (i < text.length()) { if (text[i] == pattern[match]) { match++; if (match == pattern.length()) return i + 1 - pattern.length(); else i++; } else if (match > 0) match = prefixArray[match - 1]; else i++; } return -1; } private: void initializeToZero(int *next, int count) { for (int i = 0; i < count; i++) next[i] = 0; } void preProcessing(const string &pattern, int *next) { int i = 1, match = 0; while (i < pattern.length()) { if (pattern[i] == pattern[match]) { match++; next[i] = match; i++; } else if (match > 0) match = next[match - 1]; else i++; } } private: string mText; string mPattern; }; int main(int argc, char *argv[]) { KMP kmp("aaabaaaabaaaaab"); cout << "Find (aaaaa) At index : " << kmp.search("aaaaa") << endl; return (0); } ``` **وكود جافا لتطبيق خوارزمية KMP:** ```java // Java Implementation // Knuth - Morris - Pratt Algorithm class JavaKMP { public static void main(String args[]) { JavaKMP kmp = new JavaKMP("aaabaaaabaaaaab"); System.out.println("Find (aaaaa) At Index : " + kmp.search("aaaaa")); } public JavaKMP() { mText = ""; mPattern = ""; } public JavaKMP(String txt) { mText = txt; mPattern = ""; } public JavaKMP(String txt, String pattern) { mText = txt; mPattern = pattern; } public int search() { return search(mText, mPattern); } public int search(String pattern) { return search(mText, pattern); } public int search(String text, String pattern) { int prefixArray[] = new int[pattern.length()]; initializeToZero(prefixArray, pattern.length()); preProcessing(pattern, prefixArray); int i = 0, match = 0; while (i < text.length()) { if (text.charAt(i) == pattern.charAt(match)) { match++; if (match == pattern.length()) return i + 1 - pattern.length(); else i++; } else if (match > 0) match = prefixArray[match - 1]; else i++; } return -1; } private void initializeToZero(int next[], int count) { for (int i = 0; i < count; i++) next[i] = 0; } private void preProcessing(String pattern, int next[]) { int i = 1, match = 0; while (i < pattern.length()) { if (pattern.charAt(i) == pattern.charAt(match)) { match++; next[i] = match; i++; } else if (match > 0) match = next[match - 1]; else i++; } } private String mText; private String mPattern; } ``` ## **الخوارزمية** **Boyer-Moore Algorithm** نتحدث هذه المره عن أحد أسرع الخوارزميات المستخدمه في عمليه البحث عن النصوص وهي Boyer-Moore وسميت بذلك بالطبع نسبه لمخترعي هذه الخوارزميه حيث قدموها في عام 1977 كبديل للطريقه البحث البدائيه أو ما يسمى ب Naive Searching. المقارنه في هذه الخوارزميه تعتمد على البدء من اليمين الى اليسار وليس كما هو الحال مع الخوارزميات العاديه ،، بالاضافه الى انها تقلل كثيرا من عمليات المقارنه خصوصا في حال لم يكن الحرف في text موجود في الـ pattern حيث تقفر بمقدار معين نقوم بحسابه كما سيتبين الأن ، هذا المقدار سوف يكون في جدول الازاحه skip ( أو بالأسم الصحيح Bad-Character Shift) بمعنى أن هناك عمليه تحليل للـ pattern قبل البدء في البحث ، كما هو الحال مع KMP. جدول الإزاحه يجب أن يكون حجمه مساوي لحجم ال character set التي نريد أن يتكون منها النص text والنمط pattern، وبما أننا حاليا سوف نبحث عن الحروف الأنجليزيه سوف يكون حجم الجدول بعدد حروف ال ASCII وهي 256 . وقبل أن نبدأ في البحث في النص ، يجب أن نقوم بتحليل النمط ونقوم بتعبئه الحرف المقابل في الجدول للحرف المقابل للنمط بمقدار ظهور أخر index للنمط . المثال التالي يوضح لنا كل شيء ، لنفرض لدينا الcharacter set مكونه من الحروف A,B,C,D,E والنمط هو DECADE ، سوف يكون شكل الجدول كالتالي : [![الشكل يبين جدول الإزاحة](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching_6-300x122.png)](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching%5F6.png) الشكل يبين جدول الإزاحة تم حساب هذا الجدول كالتالي ، نبدأ من اليسار لليمين ونقوم بوضع ال Index لكل حرف في الpattern في الجدول .أولاً D موقعه هو 0، وسنضع 0 في الخانه المناسبه للحرف D في الجدول(الخانه الثالثه -الترقيم من 0-). ثانياً الحرف E موقعه هو 1 وسنضع 1 في الخانه المناسبه للحروف E في الجدول .وسنستمر كذلك. لاحظ أن الحرف D سيتكرر مره ثانيه وسنقوم بكتابه الIndex الجديد (4) في الخانه القديمه ، وبنفس الأمر للحرف E. وهذا هو لب عمليه التحليل في البدايه ، فقط نحن نريد موقع أخر ظهور للحرف ، وهو ما سنحتاجه في عمليه البحث في حال أختلف الحرف من النص مع النمط .لاحظ أيضا بقيه الحروف في الcharacter set والتي لا توجد لها قيم سنضع لها -1\. الكود التالي يبين كيف يمكن حساب هذا الجدول: هناك طريقه أخرى لعمل هذا الجدول حيث يتم تهيئه المصفوفه أولا بطول النمط ، سوف يتم وضح الحرف M-i-1 بدلا من i في الحلقه الثانيه ، وسوف تختلف عمليه البحث بشكل صغير أيضا ، لكن التطبيق أعلاه أسهل في الفهم والاستعياب من الثاني. طريقه البحث في خوارزميه BM تكون كما توضح الصوره التاليه : [![الشكل يبين طريقة البحث في خوارزمية BM](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching_7.png)](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching%5F7.png) الشكل يبين طريقة البحث في خوارزمية BM أولاً البحث كما ذكرنا في هذه الخوارزميه سوف يكون من اليمين لليسار بمعنى أول حرف سوف نبدأ به المقارنه هو الحرف i من text والحرف g من pattern . في حال لم يتساوى الحرفان والحرف i موجود في النمط سوف نقوم بتحريك النمط حرفين للأمام وهكذا يتطابق i مع i . وهكذا تطابقت الكلمتين . نكمل لكي نوضح الحاله الثانيه في حال عدم التطابق وعدم وجود الحرف في النمط ، السطر التالي يقارن g من النمط مع " " مسافه من text ، وبما أنهم لا يتطابقان والحرف " " لا يوجد في النمط سوف نقوم بتحريك النمط بعدد حروف النمط وهي 4 في حالتنا هذه (وهذا تحسين كبير جدا مقارنه مع البحث بالطريقه naive ) . يمكن النظر للصوره القادمه وهي تبين كيف يتم البحث بشكل أوضح وأفضل : [![الشكل يبين طريقة البحث في خوارزمية BM](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching_8-300x96.png)](https://informatic-ar.com/wp-content/uploads/2013/10/stringmatching%5F8.png) الشكل يبين طريقة البحث في خوارزمية BM **كود تطبيق الخوارزميه Boyer-Moore بسي++** ```cpp // Boyer Moore String Searching #include using namespace std; class BoyerMoore { public: BoyerMoore(const string &text) : mText(text) { } BoyerMoore() : mText("") { } int match(const string &pattern) { int skipTable[256]; setSkipTable(pattern, skipTable); int s = 0; while (s <= mText.length() - pattern.length()) { int i = pattern.length() - 1; char c = 0; while (i >= 0 && pattern[i] == (c = mText[s + i])) { --i; } if (i < 0) { return s; } s += max(i - skipTable[c], 1); } return -1; } private: void setSkipTable(const string &pattern, int *skipTable) { for (int i = 0; i < 256; i++) skipTable[i] = -1; for (int i = 0; i < pattern.length(); i++) skipTable[pattern[i]] = i; } private: string mText; }; ``` **كود تطبيق الخوارزمية Boyer-Moore بجافا:** ```java // Java Implementation for BM public class JavaBM { public static void main(String argc[]) { JavaBM bm = new JavaBM("String Seraching in Boyer Moore Algorithms"); System.out.println("Find Index At : " + bm.match("oor")); } private String mText; public JavaBM(String txt) { mText = txt; } public JavaBM() { mText = ""; } public int match(String pattern) { int skipTable[] = new int[256]; setSkipTable(pattern, skipTable); int s = 0; while (s <= mText.length() - pattern.length()) { int i = pattern.length() - 1; char c = 0; while (i >= 0 && pattern.charAt(i) == (c = mText.charAt(s + i))) { --i; } if (i < 0) { return s; } s += Math.max(i - skipTable[c], 1); } return -1; } private void setSkipTable(String pattern, int skipTable[]) { for (int i = 0; i < 256; i++) skipTable[i] = -1; for (int i = 0; i < pattern.length(); i++) skipTable[pattern.charAt(i)] = i; } } ``` **ملاحظه** :الخوارزميه الأصليه BM تحتوي بالاضافه على جدول Bad character heuristic على جدول أخر Good suffix heuristic وهو يحتوي أيضا على عدد مرات الإزاحه ولكن هذه المره للحروف المتطابقه ، على العموم الكثير من المقالات تحدثت حول أمكانيه ازاله هذا الجزء من الخوارزميه وتبقى بنفس الكفائه تقريبا .. وهذا ما قمنا به في درسنا اليوم وهو simplified version for BM . ## **خاتمة** نصل لنهايه المقالة ، أرجوا أن تكون مفيدة للباحث في هذا المجال، ويمكن ان تحل التمرين التالي لتعزيز المفاهيم في هذا الموضوع. **فكرته**: نريد عمل تطبيق بسيط نستفيد منه من هذه الخوارزميه ، مثلا نقوم بعمل محرر نصوص صغير جدا ، يحتوي فقط على مربع نص text area ويوجد زر اسمه find & replace نقوم فيه بكتابه الاسم المراد البحث عنه والكلمه المراد تغييرها ،، ومن ثم نضغط بدء العمليه ،، أغلب محررات تستخدم BM لأنها الأفضل وهو ما سنطبقه أيضا .. يمكن التطبيق بأي لغه برمجه .. ### مفهوم البرمجة المتوازية URL: https://informatic-ar.com/parallel_programming_dot_net/ Last updated: 2026-10-03T22:54:37.000Z ###### المقالة - [البرمجة المتزامنة](#synchronous) - [مميزات البرمجة المتزامنة](#synchronous%5Ffeatures) - [البرمجة المتوازية](#parallel%5Fprogramming) - [مميزات البرمجة المتوازية](#parallel%5Ffeatures) #### البرمجة المتزامنة هي عملية كتابة وتنفيذ للكود بشكل متزامن بمعني ان يتم تنفيذ الكود step by step خطوة بخطوة، وهي الطريقة التقليدية في تنفيذ الاوامر واعطائها لل Processor احادي النواه كي يتم معالجتها، ويتم تنفيذ هذه التعليمات والاوامر بطريقة متزامنة\\بتسلسل بمعني ان المعالج ينفذ المهمة الاولي ثم الثانيه ثم الثالثه وهكذا.... ، في هذه الصورة توضح كيف يتعامل المعالج احادي النواه مع التعليمات ![تنفيذ متسلسل: المهمة الأولى ثم الثانية ثم الثالثة على نواة واحدة](https://informatic-ar.com/assets/images/sequential-execution.svg) رسم توضيحي بديل للصورة الأصلية غير المتاحة. يقوم بتنفيذها واحدة تلو الاخري. مثال علي كود يتم تنفيذه بشكل متزامن: ```csharp class Program { static void Main(string[] args) { Console.WriteLine("Application Started "); Thread.Sleep(2000); foreach (int number in (new List { 1, 2, 3, 4, 5, 6, 7, 8, 9 })) Console.WriteLine(number); Thread.Sleep(2000); Console.WriteLine("Application Finished "); } } ``` ناتج هذا الكود ```csharp Application Started 1 2 3 4 5 6 7 8 9 Application Finished Press any key to continue... ``` في هذا البرنامج البسيط يتم طباعة كلمة "Application Started" علي الشاشة، وبعدها يتوقف لمدة ثانيتين، وبعدها يتم عمل حلقة تكرارية متزامنة تقوم بطباعة الارقام من 1 الي 9 بشكل متزامن اي 1 ثم 2 ثم 3 ثم 4 وهكذا,وبعدها يتوقف ثانيتين، وبعدها يتم طباعة كلمة "Application Finished" ، هذا مايسمي بمفهوم البرمجة المتزامنة اي تنفيذ البرنامج خطوة خطوة، لايمكن لامر ان يسبق الاخر. #### مميزات البرمجة المتزامنة للبرمجة المتزامنة مميزات منها سهوله كتابة الكود وسهوله عمل debugging له واختباره ، وسهوله اقتناص الاخطاء وهي الطريقة التقليدية لكتابة الكود. #### البرمجة المتوازية البرمجة المتوازية ظهرت بعد ظهور [المعالجات المتعددة](https://ar.wikipedia.org/wiki/%D9%85%D8%B9%D8%A7%D9%84%D8%AC%5F%D9%85%D8%AA%D8%B9%D8%AF%D8%AF%5F%D8%A7%D9%84%D9%84%D8%A8?ref=informatic-ar.com) النواه ([Multi-Core Processors](https://en.wikipedia.org/wiki/Multi-core%5Fprocessor?ref=informatic-ar.com)) ليتم استخدام موارد المعالج بالكامل للحصول علي اعلي اداء لحل المشكلة ([Problem Solving](http://en.wikipedia.org/wiki/Problem%5Fsolving?ref=informatic-ar.com)) . يتم تنفيذ الاوامر علي طريقة البرمجة المتوازية كالموضح في الصورة [![Parallel Processing](https://informatic-ar.com/wp-content/uploads/migrated/external-15a23f6db421d080014792fecc9fbe79ad663b9f9d544665503e2fc1ad38ddc7.gif)](http://xdevlog.files.wordpress.com/2013/07/rrf1j.gif?ref=informatic-ar.com) من الموضح في الصورة انه يتم تنفيذ التعليمات/الاوامر بصورة متوازية بمعني ان الوقت الذي تنفذ فيه المهمة الاولي هو الوقت التي تنفذ فيه المهمة الثانية والثالثه والرابعة الخ... . ولا شك ان هذه الطريقة تعطي اداء اعلي من طريقة البرمجة المتزامنة/التقليدية. مثال علي كود C# ينفذ تعليمات بشكل متوازي : ```csharp class Program { static void Main(string[] args) { Console.WriteLine("Application Started "); Thread.Sleep(2000); Parallel.ForEach((new List { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }), (number) => { Console.WriteLine(number); }); Thread.Sleep(2000); Console.WriteLine("Application Finished "); } } ``` ```csharp  Application Started 1 7 8 9 10 11 12 5 6 2 3 4 Application Finished Press any key to continue... ``` في هذا البرنامج البسيط يتم طباعة كلمة “Application Started” علي الشاشة، وبعدها يتوقف لمدة ثانيتين، وبعدها يتم عمل حلقة تكرارية متوازية متوازية من 1 الي 12، بمعني ان الوقت الذي تنفذ فيه الحلقة الاولي نفس الوقت الذي تنفذ فيه الثانية والثالثه والرابعة والخامسة الخ.وبعدها يتوقف ثانيتين، وبعدها يتم طباعة كلمة “Application Finished” ، هذا مايسمي بمفهوم البرمجة المتوازية؛ اي يتم تنفيذ مجموعة من التعليمات بشكل متوازي وفي نفس ذات الوقت، ولا يتم انتظار الاولي حتي تنتهي ليتم تنفيذ الثانيه ، بل بالعكس كل الحلقات تعمل في ان واحد. #### مميزات البرمجة المتوازية من مميزاتها اعطاء اداء عالي من خلال استخدام كل الموارد المتوفرة. حل مشاكل كبيرة ومعقدة. اغلب محركات البحث وانظمة قواعد البيانات وانظمة التعامل مع المعلومات الضخمة تقلل من استخدام الطرق التقليدية/المتزامنة لمعالجة البيانات وحل المشكلات، بل تستخدم طرق اخري مثل البرمجة المتوازية وتستخدم مثل طرق تعدد المهام Multi-Tasking و Asynchronous Programming. وفي مواضيع اخري سأكتب عن تلك الطرق ان شاءالله . ### بناء الأجهزة الالكترونية باستخدام .NET Framework URL: https://informatic-ar.com/dot_net_embedded/ Last updated: 2026-10-03T22:44:53.000Z [![388786_357331917685049_764680304_n](https://informatic-ar.com/wp-content/uploads/migrated/external-993f1a21cc0eea008fa0d27f25df2aee8396645345e74da2d54a6f17b93bec8a.jpeg)](http://xdevlog.files.wordpress.com/2013/07/388786%5F357331917685049%5F764680304%5Fn.jpg?ref=informatic-ar.com) في الغالب انت لم تسمع من قبل عنها والسبب يرجع الي انها حديثة العهد ! اذاً لنتعرف عليها علي شكل سؤال وجواب (س و ج) س : ماهي .NET Micro Framework ؟ ج : هي نسخة مصغره من اطار عمل الــ .NET مفتوحة المصدر تعمل ع اي جهاز بدون الحاجة الي نظام تشغيل ,يمكنها العمل علي قطع اليكترونية صغيره مثل Netduino و GHI Electronics والمزيد من الشركات قامت بصُنع قطع جاهزة يمكنك نقل تطبيقاتك اليها مباشرتاً بدون بذل مجهود في نقل المنصة الي القطع الاليكترونية . س : من هو مؤسس/مطور الــ .NET Micro Framework ؟ ج : Microsoft . س : كيف تعمل ال .NET Micro Framework ؟ ج : بداية من عملية ترجمة تطبيقك ( Compiling ) يوجد عمليتين العملية الاولي : يتم تحويل الكود الخاص بك سواء سي# او VB.NET الي لغة وسيطة تسمي MSIL وهذه العملية تتم علي جهاز المطور/المبرمج العملية الثانية : هذه العملية تتم في وقت تشغيل التطبيق حيث تتولي المنصة ترجمة اللغة الوسيطة الي لغة الاّلة وقت تشغيل التطبيق علي القطعة الاليكترونية وتسمي هذه العملية ب (Jitting) اختصار ل Just in Time Compiling منصة ال .NET Micro Framework مع انها صغيره ولكن يمكنها القيام بهذه العملية بدون الحاجة الي نظام تشغيل. س : هل يوجد قطع اليكترونية تحمل منصة .NET Micro Framework متوفرة في السوق المصري ؟ ج : نعم متوفرة الوحة ال Netduino تفضل هذه نسختين بسعر لا بأس به يمكنك طلبها Online وتصلك الي منزلك [http://bit.ly/PEZ66l](http://bit.ly/PEZ66l?ref=informatic-ar.com) س : اكثر ما قلت عنه كلام اين المصادر أريد ان انطلق ؟ ج : تفضل انطلق بالدخول ع هذه المواقع وقرائه المقالات الشفيره المصدرية [http://netmf.codeplex.com/](http://www.facebook.com/l.php?u=http%3A%2F%2Fnetmf.codeplex.com%2F&h=CAQHJDdaPAQFpagpu4pcUDOsCPCNTTkzK1Aft3gwNxMW%5FHg&s=1) لاننا تحدثنا في الاعلي انها مفتوحة المصدر [http://en.wikipedia.org/wiki/.NET\_Micro\_Framework](http://en.wikipedia.org/wiki/.NET%5FMicro%5FFramework?ref=informatic-ar.com) [http://www.netmf.com/](http://www.facebook.com/l.php?u=http%3A%2F%2Fwww.netmf.com%2F&h=fAQFQVBoNAQEYOntCikU%5FANQZmlsUSg3ZC0IaIf0D1%5FUyqw&s=1) [http://www.hanselman.com/blog/TheNETMicroFrameworkHardwareForSoftwarePeople.aspx](http://www.hanselman.com/blog/TheNETMicroFrameworkHardwareForSoftwarePeopl?ref=informatic-ar.com) [http://www.codeproject.com/Articles/144365/A-Leap-In-Embedded-Programming-NET-Micro-Framework](http://www.facebook.com/l.php?u=http%3A%2F%2Fwww.codeproject.com%2FArticles%2F144365%2FA-Leap-In-Embedded-Programming-NET-Micro-Framework&h=qAQFhhvmtAQEzOImfFCwDPbxtICi%5FLlKL8juXz5rNwCy0LQ&s=1) ### Caller Info Attributes in C# 5.0 URL: https://informatic-ar.com/caller-info-attributes-in-c-5-0/ Last updated: 2026-10-03T22:32:04.000Z في بعض الاحيان عندما تكتب كود تحتاج لمعرفة معلومات عن ال Method التي منها تم استدعاء كودك ! قبل C# 5 كنا نستخدم الكلاس System.Diagnostics.StackFrame , علي سبيل المثال ```csharp using System; using System.Diagnostics; namespace CS_05 { class Program { static void Main(string[] args) { Print("Hello World"); Console.ReadKey(); } static void Print(string message) { var sf = new StackFrame(1, true); // Back up 1 level from the current method var methodName = sf.GetMethod().Name; var sourceFile = sf.GetFileName(); var lineNumber = sf.GetFileLineNumber(); Console.WriteLine("[{2} -- {0}] - {1} ({3}:{4})", methodName, message, DateTime.Now, sourceFile, lineNumber); } } } ``` النائج سيكون ع هذا النحو : ```csharp [2 / 22 / 2013 11:11:15 PM-- Main] - Hello World(f:\VisualStudioProjects\CS_05\CS_ 05\Program.cs: 9) ``` في الاصدار الجديد للغة C# تم توفير Attributes جديدة لل Methods ويمكن استخدامها كالتالي : ```csharp using System; using System.Diagnostics; using System.Runtime.CompilerServices; namespace CS_05 { class Program { static void Main(string[] args) { Print("Hello World"); Console.ReadKey(); } private static void Print(string message, [CallerMemberName] string methodName = null, [CallerFilePath] string sourceFile = null, [CallerLineNumber] int lineNumber = 0) { Console.WriteLine("[{2} -- {0}] - {1} ({3}:{4})", methodName, message, DateTime.Now, sourceFile, lineNumber); } } } ``` والناتج واحد :) هذه الطرق كلها مفيدة في حالة اذا كنت تريد ان تعرف من اي Method يتم استدعاء Method اخري؛ وبالتالي ستسهل عليك اقتناص الاخطاء في برنامجك واصلاحها. ### خوارزمية البحث الثنائي Binary search URL: https://informatic-ar.com/binary_search/ Last updated: 2026-10-03T22:44:55.000Z السلام عليكم و رحمة الله و بركاته [![Couverture](https://informatic-ar.com/wp-content/uploads/2013/09/Couverture1-231x300.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Couverture1.png) تحدثنا في الحلقة الأولى عن [خوارزمية البحث الخطي](https://informatic-ar.com/linear%5Fsearch/) و في الحلقة الثانية عن [خوارزمية ترتيب الفقاعات](https://informatic-ar.com/bubble%5Fsort/) و سنتحدث في هذه المقالة عن خوارزمية البجث الثنائي. [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **في هذا الدرس سنتطرق إلى النقاط التالية :** - الخوارزمية (الهدف, الفكرة, النتيجة, الإيجابيات و السلبيات) - الخوارزمية بلغة السي++ - أمثلة على الخوارزمية - التعقيد الزمني. - ملأ مصفوفة عشوائيا و اختبار سرعة الخوارزمية. - اختبر قدراتك ! [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **1\. الخوارزمية (الهدف, الفكرة, النتيجة, الإيجابيات و السلبيات)** **الهدف** : البحث عن قيمة المفتاح key داخل المصفوفة X. **الفكرة** : تعتمد هذه الخوارزمية على البحث الثنائي (Binary search) في المصفوفة X حيث يبدأ البحث من العنصر الذي يقع في وسط المصفوفة, و في كل مرة نقارنه مع المفتاح Key, إذا كانت القيمتان متساويتان, فهذا يدل على أنه تم إيجاد قيمة المفتاح في المصفوفة, أما إذا كانت القيمتان مختلفتان ستقوم الخوارزمية بإجراء فحص جديد, إذا كانت قيمة المفتاح أصغر من قيمة العنصر الأوسط سيتم البحث في الجزء الأيسر من المصفوفة و في الحالة المعاكسة سيتم البحث في الجزء الأيمن من المصفوفة, و هكذا .. حتى نحصل على مصفوفة تتكون من خانة واحدة قيمتها مساوية للمفتاح أو مختلفة عنه. **النتيجة** : إذا كانت X تحتوي على Key فسنحصل على رقم الخانة التي يوجد بها الأخير و إلا فالقيمة المـُعادة ستكون -1. **الإيجابيات**: الـ Binary search تُنصف (تقلص للنصف) عدد عناصر المصفوفة في كل تكرار, لذا تستغرق عملية البحث وقت قليل جدا. تنتمي هذه الخوارزمية إلى عائلة **فرق تسد.** **السلبيات** : خوارزمية البحث الثنائي أكثر تعقيدا من خوارزمية البحث الخطي التقليدية, كما أن الأولى تشترط الترتيب عند البحث. [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **2\. الخوارزمية بلغة السي++** يمكننا كتابة الخوارزمية باستخدام الـ While loop أو الـ Recursive Function. **2.1 - الطريقة الأولى:** [![1](https://informatic-ar.com/wp-content/uploads/2013/10/17-300x228.png)](https://informatic-ar.com/wp-content/uploads/2013/10/17.png) الدالة binarySearch تستقبل 3 وسائط, المصفوفة المـُراد البحث داخلها و عدد عناصرها و قيمة المفتاح. يبدأ ترقيم عناصر المصفوفة بالقيمة low و ينتهي عند high, ويمثل المتغير mid رقم الخانة الوسطى من المصفوفة. في كل مرة نقارن قيمة المفتاح بقيمة أوسط عناصر المصفوفة, إذا كانتا متساويتين نُعيد رقم الخانة و إذا كانتا مختلفتين نتقدم خطوة إلى الأمام أو نرجع خطوة إلى الوراء حسب وضعية المفتاح. نكرر الخطوات السابقة ما دام low أقل أو يساوي high (بمعنى آخر, المصفوفة تحتوي على خانة أو أكثر). إذا تم الخروج من الحلقة while دون إعادة قيمة فهذا يعني أن المفتاح غير موجود, في هذه الحالة ستعيد الدالة -1. في الدالة الرئيسية قمنا بالإعلان عن مصفوفة تحوي ألف عنصر ثم ملأناها بالقيم الواقعة في المجال \[500,500-\] لاحظ أن عناصر المصفوفة يجب أن تكون مرتبة. بعد ذلك قمنا بالبحث عن القيمة -73 داخل المصفوفة و قد تم إيجادها في الخانة رقم 427 وهذا شيء طبيعي لأن الصفر يتواجد في الخانة رقم 500 و بالتالي القيمة -73 ستتواجد في الخانة رقم 73-500. يمكننا استدعاء الدالة binarySearch على جزء من المصفوفة, في هذه الحالة نضع low و high كوسائط للدالة و ليس كمتغيرات محلية. **2.2 - الطريقة الثانية:** [![2](https://informatic-ar.com/wp-content/uploads/2013/10/21-300x227.png)](https://informatic-ar.com/wp-content/uploads/2013/10/21.png) تستقبل الدالة binarySearch أربع وسائط, هم على التوالي : اسم المصفوفة و المفتاح و من أين يبدأ البحث ؟ و أين ينتهي ؟ المتغير mid يحتوي على رقم الخانة الوسطى من المصفوفة. إذا كانت قيمة المفتاح أكبر (أصغر) من mid سيتم تطبيق الدالة على الجانب الأيسر (الأيمن) من المصفوفة. إذا كانت قيمة high أقل تماما من low فهذا يعني أن المصفوفة أصبحت فارغة. [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **3\. أمثلة على الخوارزمية** نبدأ مع مثال بسيط يُوضح فكرة البحث الثنائي. **3.1 – المثال الأول** أحمد: ما رأيك بلعبة الكاهن ؟ عمرو: الكاهن ؟؟ أحمد: نعم, أختارُ عددا عشوائيا يقع في المجال \[0,100\] و عليك معرفة العدد ! عمرو: هذا مستحيل !! أحمد: لا تستعجل .. في كل مرة تختار فيها عددا, سأخبرك ما إذا كان أكبر أو أصغر من العدد الذي تبحث عنه. عمرو: لا بأس, سأحاول .. سأبدأ بالعدد الذي يقع في منتصف المجال, هل عددك أكبر من 50 ؟ أحمد: نعم ! عمرو : سأختار الآن أوسط أعداد المجال الجديد, هل العدد أكبر من 75 ؟ أحمد: لا ! عمرو: إذا العدد يقع بين 51 و 75, هل هو أكبر من 63 ؟ أحمد: نعم ! عمرو: جيد, أصبح المجال ضيقا الآن, هل العدد أصغر من 69 ؟ أحمد : نعم ! عمرو: أكبر من 66 ؟ أحمد: لا ! عمرو: أصغر من 65 ؟ أحمد: لا ! عمرو: إذا فالعدد المُختار هو 66 ؟ أحمد: نعم, أحسنت ! **ملاحظة:** - لو وضع عمرو في الحسبان أن العدد الذي يبحث عنه يمكن أن يتواجد في منتصف المجال, لاستغنى عن آخر سؤالين ! - للفائدة, سبق و أن كتبتُ موضوعا في الفريق العربي للبرمجة يحتوي برنامجا صغيرا يُحاكي لعبة الكاهن, يمكنك تجربته من [هنا](http://www.arabteam2000-forum.com/index.php?showtopic=231557&ref=informatic-ar.com). **3.2 – المثال الثاني** نريد البحث عن العدد 8 في مصفوفة مُكونة من 10 خانات, قيمها كالآتي: [![3](https://informatic-ar.com/wp-content/uploads/2013/10/31.png)](https://informatic-ar.com/wp-content/uploads/2013/10/31.png) الخانات الملونة باللون الأخضر تُمثل الجزء الذي سيتم البحث فيه من المصفوفة و الخانة ذات اللون الأزرق تمثل أوسط العناصر. **3.3 – المثال الثالث** ابحث عن القيمة 33 في المصفوفة التالية : [![4](https://informatic-ar.com/wp-content/uploads/2013/10/41-300x84.png)](https://informatic-ar.com/wp-content/uploads/2013/10/41.png) **الحل:** **1.** نحدد منتصف المصفوفة: [![5](https://informatic-ar.com/wp-content/uploads/2013/10/51-300x54.png)](https://informatic-ar.com/wp-content/uploads/2013/10/51.png) **2.** تقسيم المصفوفة إلى قسمين وفقا لموقع mid: [![6](https://informatic-ar.com/wp-content/uploads/2013/10/61-300x81.png)](https://informatic-ar.com/wp-content/uploads/2013/10/61.png) **3.** بما أن 33 أصغر من 53, فعملية البحث ستنفذ على النصف الأول: [![7](https://informatic-ar.com/wp-content/uploads/2013/10/71-300x82.png)](https://informatic-ar.com/wp-content/uploads/2013/10/71.png) **4.** نعيد عملة التقسيم على النصف الأول و تصبح قيمة mid تساوي: [![8](https://informatic-ar.com/wp-content/uploads/2013/10/81.png)](https://informatic-ar.com/wp-content/uploads/2013/10/81.png) [![9](https://informatic-ar.com/wp-content/uploads/2013/10/91-300x80.png)](https://informatic-ar.com/wp-content/uploads/2013/10/91.png) **5.** بما أن 33 أكبر من 25, سيتم استبعاد النصف الأول و تستمر عملية البحث في النصف الثاني: [![10](https://informatic-ar.com/wp-content/uploads/2013/10/101-300x81.png)](https://informatic-ar.com/wp-content/uploads/2013/10/101.png) **6.** من جديد, نعيد عملية التقسيم حيث تصبح قيمة mid تساوي 5: [![11](https://informatic-ar.com/wp-content/uploads/2013/10/111-300x83.png)](https://informatic-ar.com/wp-content/uploads/2013/10/111.png) **7.** و أخيرا نجد أن القيمة 33 موجودة في الموقع low و الذي يساوي 4. [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **4\. التعقيد الزمني** خوارزمية البحث الثنائي سريعة جدا و فعالة أيضا, خصوصا عند التعامل مع المصفوفات الكبيرة, لأنها تُلغي في كل مرة نصف المصفوفة و تبحث في النصف الآخر (طبعا, دون أن ننسى أن المصفوفة يجب أن تكون مُرتبة و هذه ضريبة السرعة ^\_^ ) لو أخذنا على سبيل المثال مصفوفة تتكون من 1024 خانة, في أسوأ الحالات ستقوم الخوارزمية بـــ 10 مقارنات لمعرفة ما إذا كان المفتاح موجود أم لا ! (لاحظ أن [![12](https://informatic-ar.com/wp-content/uploads/2013/10/121.png)](https://informatic-ar.com/wp-content/uploads/2013/10/121.png) ). و لو زدنا عدد خانات المصفوفة ليصبح 1048576 , ستقوم الخوارزمية بـــ 20 مقارنة في أسوأ الحالات ! في **أفضل الحالات**, يكون التعقيد الزمني T(n) = 1 و في **أسوأ الحالات** يكون المفتاح في بداية أو نهاية المصفوفة أو غير موجود في كل مرة يتم إلغاء نصف المصفوفة و العمل على النصف الآخر, لذا سيكون طول المصفوفة يساوي [![13](https://informatic-ar.com/wp-content/uploads/2013/10/131.png)](https://informatic-ar.com/wp-content/uploads/2013/10/131.png) في الخطوة رقم k. نتوقف عندما تصبح المصفوفة عبارة عن خانة واحدة: [![14](https://informatic-ar.com/wp-content/uploads/2013/10/141-300x39.png)](https://informatic-ar.com/wp-content/uploads/2013/10/141.png) إذا, في هذه الحالة سيكون التعقيد يساوي [![14](https://informatic-ar.com/wp-content/uploads/2013/10/142.png)](https://informatic-ar.com/wp-content/uploads/2013/10/142.png) **الحالة المتوسطة**: عندما تكون هناك احتمالية وجود المفتاح في أي جزء من المصفوفة, و عليه سيكون التعقيد: [![16](https://informatic-ar.com/wp-content/uploads/2013/10/161-300x40.png)](https://informatic-ar.com/wp-content/uploads/2013/10/161.png) [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **5\. ملأ مصفوفة عشوائيا و اختبار السرعة** في هذه الفقرة, سنملأ عشوائيا مصفوفة تحتوي على 1000 خانة ثم نرتب المصفوفة لنبحث في داخلها عن قيمة معينة, سنحتاج إلى الدوال التالية لتنفيذ ما سبق: [![17](https://informatic-ar.com/wp-content/uploads/2013/10/171-300x266.png)](https://informatic-ar.com/wp-content/uploads/2013/10/171.png) لا شيء جديد, فقط قمتُ بتجميع الدوال التي تعرفنا عليها سابقا ! محتوى الدالة الرئيسية سيكون هكذا: [![18](https://informatic-ar.com/wp-content/uploads/2013/10/18-300x181.png)](https://informatic-ar.com/wp-content/uploads/2013/10/18.png) قمنا بملأ المصفوفة عشوائيا ثم رتبنا القيم تصاعديا, بعد ذلك قمنا بتخزين قيمة إحدى خانات المصفوفة في المتغير Random بشكل عشوائي ثم بحثنا عن مكان تلك القيمة داخل المصفوفة X. لاحظ سرعة الخوارزمية مُقارنة مع خوارزمية البحث الخطي التي تعرفنا عليها في الدرس الأول. [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **6\. اختبر قدراتك !** هذه المرة احببت وضع تمرين محلول يكون بمثابة مراجعة للحلقات الثلاثة السابقة. لتكن M مصفوفة ثنائية البعد, عدد صفوفها L وعدد أعمدتها C. المصفوفة مُرتبة تصاعديا حسب الأعمدة و حسب الصفوف أيضا. - اكتب دالة تبحث داخل M عن قيمة معينة (تستقبلها كوسيط) ثم تُعيد true إذا كانت القيمة موجودة و false في الحالة المعاكسة. - أعط التعقيد الزمني للدالة السابقة بدلالة L و C ؟ **الحل :** لإيجاد مكان المفتاح في المصفوفة, نبحث عن رقم السطر الذي يحوي قيمة المفتاح ثم نُطبق عليه خوارزمية البحث الثنائي, مع الأخذ في الاعتبار أن المصفوفة مُرتبة حسب الأعمدة و الصفوف أيضا: ```cpp #include using namespace std; #define numberOfColumns 4 bool searchTwoDimensional(int array[][numberOfColumns], int rows, int columns, int value) { int i = 0; while (i <= rows - 1 && array[i++][0] <= value) ; if (i == 0) return false; else { i--; int low = 0, high = columns - 1, mid = (low + high) / 2; while (array[i][mid] != value && low <= high) { array[i][mid] < value ? low = mid + 1 : high = mid - 1; mid = (high + low) / 2; } if (high < low) return false; else return true; } } int main() { int newArray[3][numberOfColumns] = {{1, 3, 5, 7}, {10, 15, 27, 28}, {30, 39, 52, 100}}; cout << searchTwoDimensional(newArray, 3, numberOfColumns, 52); return 0; } ``` بعد الخروج من الحلقة الأولى, لدينا ثلاث حالات: - i=0 و هذا يعني أنه لم يتم الدخول إلى الحلقة ! لأن قيمة أول عنصر في المصفوفة أكبر تماما من قيمة المفتاح. و بما أن المصفوفة مرتبة حسب الأعمدة و الصفوف, فيمكننا القول بأن المفتاح غير موجود في المصفوفة. في هذه الحالة ستعيد الدالة false. - i أكبر أو يساوي 2 و أقل أو يساوي rows, في هذه الحالة سيمثل i رقم أول سطر يبدأ بقيمة أكبر تماما من قيمة المفتاح, لذا فإن المفتاح سيتواجد في السطر رقم i-1. - i = rows + 1, هذه الحالة تعني أن جميع الأسطر تبدأ بقيمة أكبر تماما من قيمة المفتاح , قد يتواجد المفتاح في السطر الأخير من المصفوفة. بعد الخروج من الحلقة الأولى (في حالة i يختلف عن الصفر), سيتنقل التنفيذ إلى الجزء المخصص للبحث عن قيمة المفتاح باستخدام خوارزمية البحث الثنائي. (راجع [الحلقة السابقة](https://informatic-ar.com/bubble%5Fsort/) إن لم تفهم هذا الجزء) في الدالة الرئيسية, قمنا بالإعلان عن مصفوفة ثنائية البعد, تحتوي على 3 أسطر و أربعة أعمدة. ثم قمنا بالبحث عن القيمة 52 التي تتواجد في آخر أسطر المصفوفة. بالنسبة للتعقيد الزمني : في أسوأ الحالات, ستمر الحلقة الأولى على جميع أسطر المصفوفة و بالتالي سيكون تعقيدها الزمني من النوع [![1](https://informatic-ar.com/wp-content/uploads/2013/10/1.gif)](https://informatic-ar.com/wp-content/uploads/2013/10/1.gif) الحلقة الثانية لها تعقيد لوغاريتمي [![3](https://informatic-ar.com/wp-content/uploads/2013/10/3.gif)](https://informatic-ar.com/wp-content/uploads/2013/10/3.gif) إذا, التعقيد الكلي سيكون [![2](https://informatic-ar.com/wp-content/uploads/2013/10/2.gif)](https://informatic-ar.com/wp-content/uploads/2013/10/2.gif) لا يمكننا تبسبط هذه العبارة لأننا لا نعرف العلاقة ما بين rows و columns. تحياتي. ### خوارزمية ترتيب الفقاعات Bubble Sort URL: https://informatic-ar.com/bubble_sort/ Last updated: 2026-10-04T04:35:45.000Z السلام عليكم و رحمة الله و بركاته [![Couverture](https://informatic-ar.com/wp-content/uploads/2013/09/Couverture1-231x300.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Couverture1.png) تحدثنا في [الجزء الأول](https://informatic-ar.com/linear%5Fsearch/) من هذه السلسلة عن خوارزمية البحث الخطي و كان من المفترض أن نتحدث في هذه المقالة عن خوارزمية الترتيب الثنائي و لكن أحببت أن أبدأ بإحدى خوارزميات الترتيب نظرا لأن الـــ Binary search algorithm يعتمد على فكرة الترتيب. في هذه المقالة (أو لنقل بقية حلقات السلسلة) ستجدني أتحدث أحيانا مع شخص افتراضي اسمه عمرو, ألجأ إلى الحديث مع هذا الشخص عندما أصل إلى فقرة تحتاج إلى توضيح أكثر. [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **في هذا الدرس سنتطرق إلى النقاط التالية :** - الخوارزمية (الهدف, الفكرة, النتيجة, الإيجابيات و السلبيات) - قبل أن نبدأ .. إليك 4 طرق لعمل Swap - كتابة دالة تقوم بعملية التبديل - الخوارزمية بلغة السي++ - التعقيد الزمني. - ملأ مصفوفة عشوائيا و اختبار سرعة الخوارزمية. [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **1\. الخوارزمية (الهدف, الفكرة, النتيجة, الإيجابيات و السلبيات) :** **الهدف** : ترتيب عناصر المصفوفة X تصاعديا أو تنازليا. (في بقية الدرس سنعتبر أن الترتيب تصاعدي و n هو عدد عناصر المصفوفة X) **الفكرة** : تقارن هذه الخوارزمية بين قيم الخانات المتجاورة, تبدأ بالمقارنة بين أول خانتين من المصفوفة, إذا كان محتوى الخانة الأولى أكبر من محتوى الخانة الثانية سيتم تبادل محتوى الخانتين و هكذا مع بقية الخانات. عند انتهاء الدورة الأولى ستكون الخوارزمية قد أنجزت n-1 مقارنة و بهذا ستتم إزاحة أكبر عناصر المصفوفة إلى الخانة الأخيرة. بقي الآن ترتيب الــ n-1 عنصر. و هكذا الأمر مع بقية الدورات ... **النتيجة** : عملية الترتيب تـخـتلف عن عملية البحث في هذه النقطة, فالترتيب لا يقبل وجود عدة احتمالات في النتيجة, فعند تطبيق الخوارزمية سنحصل على مصفوفة مرتبة تصاعديا و بالتالي لا توجد احتمالات للمناقشة. **الإيجابيات** : الخوارزمية سهلة التصور و بسيطة المفهوم. **السلبيات** : بطيئة شيئا ما وغير عملية, خصوصا عند معالجة المصفوفات الضخمة. [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **2\. قبل أن نبدأ .. إليك 4 طرق لعمل Swap :** من خلال دراستك لخوارزميات الترتيب المختلفة ستجد أن مفهوم Swap أو تبادل مُحتوى متغيرين يتكرر باستمرار .. فأغلب خوارزميات الترتيب (إن لم تكن كلها !) تعتمد في مرحلة ما على تبديل محتوى خانتين لوضعهما في الترتيب الصحيح, لذا سنوضح كيفية تبديل محتوى متغيرين قبل أن نبدأ بكتابة الخوارزمية بلغة السي++. سنتناول فيما يلي 4 طرق لعمل Swap, لكل منها إيجابيات كما لها سلبيات أيضا. **الطريقة الأولى** (و هي الأكثر شهرة) تكمن في استخدام وسيط مساعد يُساعدنا على تبديل محتوى المتغيرين, فنضع قيمة المتغير الأول في الوسيط ثم نضع قيمة المتغير الثاني في المتغير الأول ثم نضع قيمة الوسيط في المتغير الثاني, و بهذا نكون قد بادلنا بين قيمة المتغير الأول و الثاني. لنفرض أن x=5 و y=-1 : ```pseudocode t = x //t = 5 x = y //x = -1 y = t //y = 5 ``` في النهاية تكون x=-1 و y=5 . و بالتالي تم تبديل قيمتي x و y. يمكنك تـخيل الفكرة كما يلي .. لدينا كأسين x و y, الكأس x يحتوي على العصير و الكأس y يحتوي على الماء, لو أردنا أن نُبادل محتوى الكأسين, (أي نجعل الماء في الكأس x و العصير في الكأس y) سنقوم باستخدام كأس ثالث فارغ (وهو الوسيط المساعد t) و تكون عملية التبادل كما يلي : - نجعل العصير في الكأس الفارغ. - نجعل الماء في الكأس x. - نجعل العصير في الكأس y. الآن أصبح الكأس x يحتوي على الماء و الكأس y يحتوي على العصير. هذه الطريقة بسيطة و تقليدية أيضا, إلا أنها تأخذ مساحة أكثر من الذاكرة بالإعلان عن الوسيط الثالث. يمكننا عمل Swap بدون حاجة إلى وسيط مساعد عن طريق إجراء بعض العمليات الحسابية البسيطة على المتغيرين. **الطريقة الثانية** (تصلح للمتغيرات العددية فقط) : ```pseudocode x=x+y; y=x-y; x=x-y; ``` **الطريقة الثالثة** (تصلح للمتغيرات العددية فقط و يجب أن تختلف قيمة y عن الصفر) : ```pseudocode x=x*y; y=x/y; x=x/y; ``` **ملاحظة** : الطريقتان السابقتان يمكنهما أن يتسببا في حدوث Overflow إذا كانت قيمة x+y أو x\*y أكبر من القيمة العظمى لنوع المتغيرين x,y. توجد أيضا **طريقة رابعة** لا تحتاج إلى وسيط مساعد و لا يمكن أيضا أن تكون سببا في حدوث **Overflow**. تعتمد هذه الطريقة على أحد الــ Bitwise (مؤثرات الــ Bit) وهو المؤثر XOR (اختصار لـــ Exclusive OR) و الذي يُرمز له بــ **^** , هذا المؤثر يعطينا True إذا و فقط إذا كان المدخلين مختلفين, أما إذا كانا متشابهين فالنتيجة ستكون False و يعمل هذا المؤثر كالتالي : [![1](https://informatic-ar.com/wp-content/uploads/2013/10/1.png)](https://informatic-ar.com/wp-content/uploads/2013/10/1.png) و كمثال على ذلك : [![2](https://informatic-ar.com/wp-content/uploads/2013/10/2.png)](https://informatic-ar.com/wp-content/uploads/2013/10/2.png) سنقوم بتجربة هذه الطريقة على المتغيرين x و y حيث x = 15 و y=7, لذلك سنقوم بالخطوات الثلاثة التالية : ```pseudocode x = x^y // x = 15 XOR 7 = 8 y = x^y//y = 8 XOR 15 = 7 x = x^y//x = 7 XOR 8 = 15 ``` و بهذا تم تبديل محتوى المتغيرين x و y. يمكننا الآن كتابة دالة تبادل بين مُحتوى مُتغيرين باستخدام إحدى الطرق الأربعة الموضحة أعلاه, مع الأخذ في الاعتبار سلبيات كل طريقة. إذا أدرت التوسع أكثر في هذه الجزئية, فيمكنك البحث عن أفضل طريقة لتبديل محتوى متغيرين بحيث تحقق النقاط الآتية في آن واحد : - لا تحتاج إلى وسيط مساعد. (المستوى : **بسيط**) - لا تسبب Overflow. (المستوى : **صعيب**) - لا تضع أي شرط على قيم أو نوع المتغيرين. ( المستوى : **أكثر صعوبة**, إن لم يكن مستحيلا !) [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **3\. كتابة دالة تقوم بعملية التبديل :** بعد أن تطرقنا إلى الطرق الأربعة التي يمكننا من خلالها تبديل محتوى متغيرين. سنقوم الآن بكتابة دالة تُبادل بين محتوى خانتين من المصفوفة X باستخدام الطريقة الأولى (لأنها الأكثر شعبية .. رغم احتوائها على انتهاك صارخ لحقوق الــ Overflow !) : [![3](https://informatic-ar.com/wp-content/uploads/2013/10/3-300x116.png)](https://informatic-ar.com/wp-content/uploads/2013/10/3.png) عمرو :لم أفهم استخدام المؤشرات ! أحمد : لا بأس !, سترتاح قليلا عندما تعلم أن الكتابة السابقة مُطابقة تماما للكتابة الآتية : [![4](https://informatic-ar.com/wp-content/uploads/2013/10/4-300x116.png)](https://informatic-ar.com/wp-content/uploads/2013/10/4.png) عمرو : كيف !؟ أحمد : الدالة السابقة تستقبل 3 وسائط, الأول عبارة عن اسم المصفوفة و الوسيطين, الثاني و الثالث عبارة عن أرقام الخانات الـمُراد تبديلها. عمرو : لكن .. لماذا تُعيد الدالة void ؟ أحمد : ببساطة .. لأن هدف الدالة يكمن في تبديل محتوى الخانتين, فقط ! لا أكثر و لا أقل, لذا ليست هناك قيمة مُعادة. أغلب الدوال التي تستقبل "بالمرجع" تعيد void لأن التغيير سيحصل في المتغير نفسه. لذا لسنا بحاجة إلى إعادة قيمة المتغير الجديدة. عمرو : طيب, لكنني لم أفهم الطريقة التي تستخدم المؤشرات ! أحمد : هل فهمتَ الطريقة الثانية ؟ عمرو : نعم أحمد : إذا أنت بالتأكيد قد فهمت الطريقة الأولى !, دعني أسألك ! عمرو : تفضل أحمد : ألم نقل أن الدالة Swap تستقبل مصفوفة ؟ عمرو : بلى ! أحمد : و هل المصفوفة عبارة عن كتلة واحدة ؟ عمرو : عفوا, لم أفهم السؤال ! أحمد : ...كنت أقصد, هل تُـخَزَّن المصفوفة في خانة واحدة من الذاكرة ؟ كما يحدث مع الأنواع البدائية لللغة كــ int مثلا ؟ عمرو : لا! طبعا, فالمصفوفة عبارة عن مجموعة من الخانات المتجاورة فيما بينها و ليست خانة واحدة. أحمد : جميل جدا, إذا .. كيف تستقبل الدالة هذه الخانات في آن واحد و تميزها عن غيرها ؟ عمرو: بصراحة .. لا أدري ! أحمد : الأمر بسيط جدا .. كما قلت أنت بنفسك قبل قليل " المصفوفة عبارة عن مجموعة من الخانات المتجاورة فيما بينها " و لكي يكون الكلام أكثر دقة يمكننا القول بأن المصفوفة عبارة عن مجموعة من العناصر المتجانسة(من نفس النوع) مُسجلة تحت اسم واحد ,حيث يمكن تمييز كل عنصر بترتيبه (دليله) في المصفوفة. عمرو : ...متجانسة ؟! أحمد : نعم, متجانسة ! و إلا فسنحصل على structure أو class ...عموما, دعنا في الموضوع !, قل لي .. كيف تُفرق بين مصفوفتين ؟ عمرو : بالإسم طبعا .. لأنه لا يمكن وجود أكثر من مصفوفة بنفس الإسم ! أحمد : طيب, لكن اسم المصفوفة ما هو إلا مؤشر يشير إلى أول عنصر فيها. عمرو : أمممممم .. يبدو أن مفتاح الفكرة يكمن في العنصر الأول ! أحمد : بالضبط, فانطلاقا من العنصر الأول يمكننا الوصول إلى بقية عناصر المصفوفة عن طريق إزاحة المؤشر إلى الأمام, لأننا قلنا سابقا أن خانات المصفوفة مُتجاورة ! عمرو: و ماذا لو حصلنا على مصفوفة خاناتها غير مُتجاورة ؟ أحمد: مُستحيل .. المصفوفة بالتعريف هي مجموعة من الخانات المُتجاورة فيـــ... عمرو: نعم .. نعم !, تذكرت ! ...طيب, هل لك أن تُلخص لي فكرة الكود السابق ؟ فأفكاري حوله لم تنضج بعد ! أحمد: الدالة Swap تستقبل مؤشر من نوع int بالإضافة إلى رقمان يدلان على مكان وجود القيم التي نريد إبدالها فيما بينها. واضح ؟ عمرو: مفهوم ! أحمد: في جسم الدالة قمنا باستخدام وسيط مساعد من شأنه مُساعدتنا في عملية Swap. الكتابة \*(X + i) يمكننا قراءتها "قم بإزاحة المؤشر i إلى الخانة الموالية" و بالتالي سيشير المؤشر إلى الخانة رقم i+1 لأن الترقيم يبدأ من صفر. بنفس الطريقة قمنا بإزاحة المؤشر للحصول على محتوى الخانة رقم j.و بهذا تمت عملية التبادل. عمرو: اتضحت الفكرة. طيب, هل من تطبيق لما سبق ذكره ؟ أحمد: على مهلك, جواب سؤالك سيكون في الفقرة الموالية : ) [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **4\. الخوارزمية بلغة السي++ :** كما ذكرنا سابقا فإن هذه الخوارزمية تقوم في الدورة الأولى بإزاحة أكبر عناصر المصفوفة إلى الخانة الأخيرة ثم تقوم في الدورة الثانية بإزاحة العدد الذي يلي أكبر العناصر إلى الخانة القبل الأخيرة و هكذا .. آخر دورة سيتم فيها وضع أصغر العناصر في الخانة الأولى. كل دورة ينتج عنها بعض التبديلات بين خانات المصفوفة مما يُساعد على تحسين ترتيب المصفوفة. [![5](https://informatic-ar.com/wp-content/uploads/2013/10/5-300x81.png)](https://informatic-ar.com/wp-content/uploads/2013/10/5.png) تستقبل الدالة bubbleSort وسيطين: اسم المصفوفة و عدد عناصرها. تبدأ الحلقة الأولى ذات العداد i من أول خانة في المصفوفة و حتى الخانة القبل الأخيرة. عمرو : هل أفهم من كلامك أن الترتيب لن يشمل الخانة الأخيرة ؟ أحمد : كلا, لكن إذا وصل عداد الحلقة الأولى إلى القيمة size-1 سيؤدي هذا إلى حصول Underflow !, ستفهم أكثر بعد قليل ... هناك حلقة ثانية, تبدأ في كل مرة من الخانة الأولى و حتى الخانة رقم size - i – 2. عمرو : .. size - i – 2 !!؟, من أين أتيت بهذا العدد ؟؟ أحمد : الحلقة الثانية تعمل على ترتيب المصفوفة و في أسوأ الأحوال سيتم وضع عنصر واحد في الترتيب الصحيح. منطقيا .. إذا عادت الخوارزمية من جديد للترتيب فيجب أن تستثني العناصر التي تم ترتيبها. من أجل i=0 سيتم وضع أكبر الأعداد في الخانة الأخيرة (حصلنا على خانة واحدة مرتبة) و من أجل i=1 سيتم وضع العدد الذي يلي أكبر الأعداد في الخانة القبل أخيرة (حصلنا على خانتين مرتبتين). إذا في كل مرة سنحصل على size-i-1 من العناصر الغير مرتبة. عمرو : و بعد ؟ أحمد: حتى لا نُعيد اختراع العجلة .. لكي يتم ترتيب العناصر الغير مرتبة فقط يجب أن تبدأ الحلقة الثانية من الخانة الأولى (رقم صفر) و تتوقف عند الخانة رقم size-i-1.. و لكي لا يحدث Overflow يجب أن يكون j+1 = size-i-1 مما يعني أن j=size-i-2 و هي القيمة التي تتوقف عندها الحلقة الثانية. أحمد :نعود الآن إلى قضية الــ Underflow, عدد عناصر المصفوفة يساوي size, إذا رقم الخانة الأخيرة هو size-1 و رقم الخانة القبل أخيرة هو size-2, لاحظ أنه إذا كانت i=size-2 ستصبح j=0 وهنا ستتم مقارنة قيمة الخانة الأولى مع الثانية و إبدال محتوى الخانتين إذا كان الترتيب معكوسا و هذه هي آخر خطوات الخوارزمية. أما إذا كانت i=size-1 فستصبح j=-1 و هذا خطأ منطقي لأن ترقيم المصفوفات يبدأ من صفر. نأتي الآن إلى كتابة كود متكامل يحتوي على الدالة bubbleSort : [![6](https://informatic-ar.com/wp-content/uploads/2013/10/6-243x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/6.png) المتغير LENGTH يمثل طول المصفوفة x. قمنا بطباعة عناصر المصفوفة قبل و بعد استدعاء دالة الترتيب. لاحظ الفرق. **مثال للتوضيح :** لنفترض أننا نريد ترتيب الجدول التالي : [![7](https://informatic-ar.com/wp-content/uploads/2013/10/7-300x15.png)](https://informatic-ar.com/wp-content/uploads/2013/10/7.png) عند تطبيق الخوارزمية على الجدول سنحصل على النتائج التالية : إذا كانت i=0 : [![8](https://informatic-ar.com/wp-content/uploads/2013/10/8-300x70.png)](https://informatic-ar.com/wp-content/uploads/2013/10/8.png) في كل مرة يتم تبديل محتوى الخانتين ذوات اللون الأزرق الفاتح, الخانة ذات اللون الأخضر تدل على أن ترتيب الخانتين في الوضع الصحيح لذا لم تتم مبادلتهما. لاحظ أنه من خلال الدورة الأولى تمت إزاحة أكبر عناصر الجدول إلى الخانة الأخيرة (في هذا المثال تمت إزاحة العدد 11 إلى الخانة الأخيرة). i=1 : [![9](https://informatic-ar.com/wp-content/uploads/2013/10/9-300x69.png)](https://informatic-ar.com/wp-content/uploads/2013/10/9.png) إذا كانت i = 1 فإن القيمة العظمى لــ j ستكون 3 و بالتالي لن تتم المقارنة بين محتوى الخانة الأخيرة و الخانة الموجودة قبلها. لاحظ أنه سيتم ترتيب عناصر الجدول انطلاقا من نهايته. i = 2 : [![10](https://informatic-ar.com/wp-content/uploads/2013/10/10-300x69.png)](https://informatic-ar.com/wp-content/uploads/2013/10/10.png) إذا كانت i=2 فإن Jmax\=2 و هذا يعني أن الخانات رقم 3,4,5 مُرتبة بشكل صحيح. i = 3 : [![11](https://informatic-ar.com/wp-content/uploads/2013/10/11-300x69.png)](https://informatic-ar.com/wp-content/uploads/2013/10/11.png) لاحظ معي أنه تم ترتيب عناصر الجدول مع أن الحلقة لم تنتهي بعد فما زالت دورتان **i=4, i=5**. ما رأيك لو أضفنا شرطا بسيطا يتعلق بالخروج من الدالة إذا وصلت الخوارزمية إلى ترتيب الجدول قبل انتهاء الحلقة ؟ **إضافة بسيطة :** [![12](https://informatic-ar.com/wp-content/uploads/2013/10/12-300x168.png)](https://informatic-ar.com/wp-content/uploads/2013/10/12.png) الفكرة ببساطة تكمن في استخدام متغير منطقي وتغيير قيمته عندما يتم تبادل محتوى خانتين. عندما نجد أنه لم تتغير قيمة المتغير منطقي في إحدى الدورات فهذا يعني أن خانات الجدول أصحبت مُرتبة بشكل صحيح. مُحتوى الدالة الرئيسية سيكون هذا : [![13](https://informatic-ar.com/wp-content/uploads/2013/10/13.png)](https://informatic-ar.com/wp-content/uploads/2013/10/13.png) [![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **5\. التعقيد الزمني :** تُعد خوارزمية ترتيب الفقاعات من أبطئ خوارزميات الترتيب حيث يعود السبب إلى كثرة عمليات **المقارنة** و **التبديل**. من أجل ترتيب جدول يحتوي على n عنصر سنحتاج إلى n-1 مقارنة (الأول و الثاني, الثاني و الثالث, ... , ما قبل الأخير والأخير) ثم نكرر نفس العملية من جديد على جدول يحتوي على n-1 عنصر و هكذا دواليك. إذا المرور رقم i سيحوي n-i مقارنة و بالتالي سيكون التعقيد الزمني للخوارزمية من حيث عدد المقارنات يُساوي : [![14](https://informatic-ar.com/wp-content/uploads/2013/10/14-300x36.png)](https://informatic-ar.com/wp-content/uploads/2013/10/14.png) بالنسبة لعدد التبديلات : في أسوأ الحالات سنحتاج إلى n-i في المرور رقم i. في هذه الحالة نقول أن الجدول مُرتب بشكل مقلوب. إذا نـخلص إلى أنه في المرور رقم i **سنحتاج بالضبط إلى n-i مقارنة و n-i تبديل في أسوأ الحالات**. في أسوأ الحالات سيكون تعقيد الخوارزمية يساوي : [![o_n1](https://informatic-ar.com/wp-content/uploads/2013/10/o_n1.png)](https://informatic-ar.com/wp-content/uploads/2013/10/o%5Fn1.png) و في المتوسط سيكون عدد التبديلات يُساوي : [![o_n2](https://informatic-ar.com/wp-content/uploads/2013/10/o_n2.png)](https://informatic-ar.com/wp-content/uploads/2013/10/o%5Fn2.png) أما في أفضل الحالات فسيكون التعقيد خطيا : [![o_n3](https://informatic-ar.com/wp-content/uploads/2013/10/o_n3.png)](https://informatic-ar.com/wp-content/uploads/2013/10/o%5Fn3.png) .[![0](https://informatic-ar.com/wp-content/uploads/2013/10/0-300x60.png)](https://informatic-ar.com/wp-content/uploads/2013/10/0.png) **6\. ملأ مصفوفة عشوائيا و اختبار سرعة الخوارزمية :** سبق و أن تعرضنا في الحلقة السابقة إلى ملأ مصفوفة عشوائيا, الجديد هنا هو استخدام عداد و زيادته كل ما تمت عملية تبديل, لا أكثر : [![15](https://informatic-ar.com/wp-content/uploads/2013/10/15-300x162.png)](https://informatic-ar.com/wp-content/uploads/2013/10/15.png) عند استدعاء الدالة bubbleSort سيتم إظهار عدد المرات التي تمت فيها عملية التبديل بالإضافة طبعا إلى رقم المرور الذي تمت فيه عملية الترتيب. ملأ المصفوفة x عشوائيا و استدعاء الدالة bubbleSort داخل main سيكون هكذا : [![16](https://informatic-ar.com/wp-content/uploads/2013/10/16.png)](https://informatic-ar.com/wp-content/uploads/2013/10/16.png) إلى هنا أصل بكم إلى نهاية الدرس, أرجو أن تكونوا قد استفدتم. لا تنسوني من صالح الدعاء. تحياتي. ### أساسيات البرمجه في نظام الويندوز URL: https://informatic-ar.com/win32_programming_part1/ Last updated: 2026-10-04T04:35:46.000Z بســم الله الـرحمــن الرحيــم فهم البنية التحتية لأنظمه الويندوز مطلب ضروري لأي مبرمج يريد بناء برامج تعمل بكفائه عالية على نظام ويندوز ، وفي هذه السلسله من الدروس سوف نتناول أساسيات البرمجه تحت نظام الويندوز والتي تعطيك الأساس الصحيح لانشاء واستخدام الProcess/Thread وMemory Management والتعامل مع ال DLL وال SEH . سوف تبدأ المقالة بشرح كيف يعمل نظام ويندوز ومن ثم نقوم بعرض مراحل تطور النظام وميزاته الأساسية، وكيف تتعامل مع الأخطاء بداخل برنامجك في دوال الويندوز Win32 Function باستخدام مفهوم ال Error-Code ، وسوف سيتم استعراض دوال الويندوز للتعامل مع الASCII وال Unicode حتى تكتب برامج Unicode بالسي++ وأمنة بدون مشاكل Buffer overflow ، وسوف ستتعرف اخيراً على ال Kernel Objects وكيف يمكن أن تتشاركها في أكثر من Process أو Thread ، وسيتم طرح مثال على الاستفاده منها من خلال منع البرنامج لأن يعمل أكثر من مرة في الويندوز باستخدام ال Mutex. هذه المقالة تعتبر حجر الأساس لمن يريد البدء في مجال برمجة الويندوز باستخدام سي++ لكي يعمل برامج Low Level مثلاً برامج الحماية أو الفايرول أو برامج التحكم في النظام وغيرها من ال System Software. **1.1\. نظرة على أنظمة الويندوز:** أنظمه الويندوز تقسم الى قسمين أساسين الأول وهو Windows NT (ويشمل Windows Server 2003,2000,XP) والثاني وهو Window 9x (ويشمل Windows ME,98,95,16 bit versions). صمم الNT من البداية ليكون 32 بت ويدعم الذاكرة الظاهرية Virtual Memory ويدعم تعدد المسارات Multithreading وتعددية المعالجات Multiprocessors مما جعله مناسب لأغلب البرامج والأجهزه الحديثة . كل من هذين النظامين متوافقين مع Win32 API لكتابة برامج تعمل على النظامين ومنذ 2001 توقف مايكروسوفت عن دعم 9x وأصبحت تتعامل مع ال NT-Based System . وأحد أفضل نسخ الNT التي أنتجتها مايكروسوفت هي Windows XP والتي سيكون عليها محور الحديث في هذا الفصل والقادم ولكن بما أن جميع الأنظمه مايكروسوفت الحديثه هي نسخ NT فالمفاهيم التي سندرسها الأن تنطبق عليها . تميزت أنظمه Windows NT بأنها نسخ 32 بت 32-bit Architecture وحالياً وبدءً من أخر أصدار لويندوز 7 فلن تكون هناك نسخ 32 بت بعد ذلك، وكل النسخ سوف تكون 64 بت. حاليا تتوفر نسخ NT لل64 بت . ميزة أخرى وهي دعم الVirtual Memory وسوف نتحدث عنها فيما بعد . أيضا يتميز النظام بأنه يعمل على أكثر من منصة بعكس الإصدارات الأولى لويندوز حيث يمكن عمل اعادة ترجمته في المنصة لكي يعمل عليها. والتعامل مع الهاردوير يتم عبر طبقة Hardware abstract Layer والتي تعزل النظام من الهارديور وتسمح بنقل النظام لأي هارديور جديد. أيضا يتميز النظام بأنه يدعم أسلوب الPreemptive في جدولة العمليات بالإضافة الى دعم الMultithreading .وبالرغم من أن أنظمه ويندوز القديمه كانت تدعم تعدد المسارات الا أنها كانت تستخدم أسلوب non-preemptive في بعض المكونات المكتوبة من نوع 16 بت مثل GDI و USER . ويتميز كيرنل NT بدعم أكثر من معالج وهذا يعني أنه مناسب لبيئات العمل التي تتطلب أداء عالي مثل Data-Center Server أو التطبيقات التي تستهلك المعالج. وبعكس أنظمة ويندوز القديمة فNT بنى من الأساس لكي يكون أكثر أماناً Security فكل كائنا في النظام له محدد وصول Access Control List والذي تحدد المستخدم الذي يتعامل مع هذا الObject وحتى نظام الملفات NTFS يدعم ACL لكل ملف ويدعم التشفير لكل ملف أو القرص بالكامل. اضافة الى أن نظام NT متوافق Compatible مع التطبيقات القديمه ويستطيع تشغيل بعض تطبيقات ويندوز 16 بت أو تطبيقات الدوز وذلك من خلال تشغيها في بيئة تخيلية تكون معزولة عن النظام حتى لا تؤثر فيه. أخيرا فإن نظام NT مصمم من الأساس لكي يكون متعدد المنصات ولكي يعمل على أكثر من معمارية مثل IA-32,DE,ALPHA لكن النسخ الأخيره تدعم فقط IA-32 ولكنها تدعم المنصات المتوافقه مع انتل مثل AMD64. **1.2\. التعامل مع الأخطاء Error Handling:** قبل التعرف على خصائص نظام الويندوز يجب فهم كيف تتعامل الدوال التي نقوم باستدعائها Win32 Function مع الأخطاء. فعندما يتم استدعاء دالة يتم التحقق من المعاملات المرسلة واذا كانت صحيحه القيم فيتم تنفيذ الدالة والا في حال لم تكن صحيحه أو لم تعمل لأي سبب من الأسباب فإن الدالة سوف ترجع قيمه تدل على الخطأ Error-Code . والجدول التالي يوضح القيم الراجعه في أغلب دوال الويندوز: [![الشكل ‏1 يبين القيم الراجعه من أغلب دوال الويندوز](https://informatic-ar.com/wp-content/uploads/2013/10/win32image1-300x150.png "الشكل ‏1 يبين القيم الراجعه من أغلب دوال الويندوز")](https://informatic-ar.com/wp-content/uploads/2013/10/win32image1.png) الشكل ‏1 يبين القيم الراجعه من أغلب دوال الويندوز وعندما يحصل خطأ في الدالة تقوم الدالة باستخدام Thread Local Storage بوضع رقم الخطأ في الThread الذي قام باستدعاء الدالة وحصل بها الخطأ وبهذه الألية سوف يتم السماح لأكثر من Thread بالعمل دون أن يأثر رقم الخطأ الموجود في أحد الThread عن بقية الأرقام الموجوده في الThreads الأخرى في العملية. عند حدوث الخطأ فأن الدالة ترجع رقم الخطأ Error Code ويمكن معرفة هذا الرقم من خلال الدالة GetLastError والتي لها التصريح التالي: [![الشكل ‏2 يبين تصريح الدالة GetLastError](https://informatic-ar.com/wp-content/uploads/2013/10/win32image2-300x31.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image2.png) الشكل ‏2 يبين تصريح الدالة GetLastError وترجع الدالة أخر خطأ حصل في الThread ، وبعد الحصول على رقم هذا الخطأ فمن المفيد تحويل هذا الرقم الى رسالة نصية توضح سبب الخطأ بشكل أكبر ، وتوجد جميع رسائل الخطأ والثوابت التي ترجعها الدالة GetLastError في ملف الرأس winerror.h والشكل 3 يبين مقطع صغير من ملف الرأس والذي يتكون من حوالى 3000 سطر في المترجم GCC بينما في الPSDK لمايكرسوفت فيبلغ حوالى 40000 سطر! والسبب في ذلك أن ملف الرأس في تطبيق مايكروسوفت يحتوي على الكثير من التعليقات بالإضافة الID للرسالة وماكرو مشابه للID . [![الشكل ‏3 يبين التعاريف في الملف winerror.h](https://informatic-ar.com/wp-content/uploads/2013/10/win32image3-300x263.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image3.png) الشكل ‏3 يبين التعاريف في الملف winerror.h وتوجد أداه تأتي مع الVisual Studio تسمى Error Lookup تستطيع من خلالها تحويل رقم الخطأ الى النص المقابل لرقم الخطأ والموجود في الشكل السابق باسم MessageText وهذا البرنامج يستخدم الدالة FormatMessage لتحويل رقم الخطأ الى الرسالة MessageText وللدالة FormatMessage التصريح التالي: [![الشكل ‏4 يبين تصريح الدالة FormatMessageW](https://informatic-ar.com/wp-content/uploads/2013/10/win32image4-300x214.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image4.png) الشكل ‏4 يبين تصريح الدالة FormatMessageW هذه الدالة من الدوال المفيده لاظهار النصوص للمستخدم حيث أنها تتعامل مع أكثر من لغه وتستقبل الدالة معرف اللغه Language ID كمعامل للدالة وترجع النص المناسب، وفيما يلي مثال لأداه command line تستخدم هذه الدالة وتعمل مثل عمل برنامج Error Lookup. [![الشكل 5 يبين مثال على برنامج لمعرفه الخطأ Error Lookup](https://informatic-ar.com/wp-content/uploads/2013/10/win32image5-300x202.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image5.png) الشكل 5 يبين مثال على برنامج لمعرفه الخطأ Error Lookup ويتم ارسال رقم الخطأ من خلال الargument للبرنامج كما في الشكل التالي: [![الشكل 6 يبين مخرج البرنامج السابق](https://informatic-ar.com/wp-content/uploads/2013/10/win32image6-300x91.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image6.png) الشكل 6 يبين مخرج البرنامج السابق **1.3\. Working with Characters and Strings:** بما أن نظام الويندوز يعد من أكثر الأنظمه انتشاراً حول العالم ويدعم أغلب اللغات المعروفة فيجب على المبرمجين كتابة برامج تدعم تلك اللغات لكي ينتشر التطبيق بشكل كبير في International Market. في السابق كان المبرمجين يكتبوا التطبيق باللغه الإنجليزية ثم يتم طرح دعم اللغات المختلفه مع الإصدارات التالية للبرنامج. وبالإستفادة من دعم النظام لأغلب اللغات فيمكن من البدايه كتابة البرنامج وجعله يدعم تلك اللغات المختلفة. ومن المواضيع المهمه للغايه عند التعامل مع العمليات على النصوص هو تجنب الثغرات Buffer Overrun والتي تكون عادة عند التعامل مع النصوص. وأولت مايكروسوفت إهتماماً لهذا الأمر وقدمت مجموعه جديده من الدوال الأمنه والتي عند استخدامها ستحمي التطبيق من هذه الثغرات كما سيتبين ذلك لاحقاً. المشكلة الحقيقية في موضوع الLocalization هو التعامل مع أبجديات مختلفة Character Sets ففي لغه السي نجد أن حجم المتغير من النوع char يبلغ واحد بايت (8 بت) وهو كافي لتمثيل جميع الحروف اللاتينية ولكن بعض اللغات مثل اليابانية تحتوي على الكثير من الأحرف في الأبجدية وهنا لن يكفى بايت واحد لمثيل كل هذه الحروف. ولحل هذه المشكلة تم تطوير أبجدية Double-Byte Character Sets )DBCS) وهي تمثل الحرف ببايت وحد أو 2 بايت. ففي اللغه اليابانية Kanji اذا كان الحرف الأول يقع في المدى من 0x81 الى 0x9F أو من المدى 0xE0 الى 0xFC فيجب النظر للبايت التالي لمعرفه الحرف (حيث يكون مكون من بايتين). والتعامل مع هذه النظام DBCS كان غير مريح للمبرمجين حيث بعض الأحرف تكون بحجم واحد بايت والأخرى بحجم 2 بايت ومن هنا كانت ولادة النظام اليونيكود Unicode (في عام 1988 بواسطة Apple و Xerox وبعدها انضمت بقية الشركات الكبرى في دعم هذا المقياس وتطويره مثل Oracle, IBM, Microsoft وغيرهم). ونظام الويندوز يدعم اليونيكود سواء في داول الويندوز Win32 API أو في دوال مكتبة السي القياسية C Run-Time Library. ويستخدم النظام الترميز UTF-16 (وهو اختصار لUnicode Transformation Format) وهذا النظام UTF-16 يرمز كل حرف من خلال بايتين (16 بت) وهو كافي لمتثيل أغلب لغات العالم وبالتالي يسهل التعامل مع النصوص وحساب طولها عند العمل بهذا الترميز. هناك ترميزات مختلفة من الUnicode منها الUTF-8 وهو يرمز الحرف الواحد بواحد أو 2 أو 3 أو 4 بايت! فالحروف التي هي أصغر من 0x0080 ترمز بواحد بايت (مثل أحرف اللغه الإنجليزي) والأحرف من 0x0080 الى 0x07FF ترمز ب2 بايت (مثل اللغات الأوروبية ولغات الشرق الأوسط Middle East) والأحرف من 0x0080 وما فوق ترمز ب3 بايت (مثل لغات الEast Asian) . وبعض الرموز ترمز من خلال 4 بايت. وبشكل عام نظام الUTF-8 استخدم كثيرا في السابق ولكنه أقل كفائه منUTF-16 في حال تم التعامل مع أحرف أكبر من 0x0080. ترميز أخر وهو الUTF-32 وهو يرمز الحرف الواحد ب4 بايت وهو بالطبع يتعامل مع أي لغه ولكنه غير كفئ لأنه يستهلك الذاكرة (فلكل حرف 4 بايت) وبالتالي لا يستخدم هذه الترميز خصوصا في عمليات الحفظ Saving أو الإرسال عبر الشبكة بل يستخدم فقط من داخل البرنامج. تقسم الينوكود القيم الى مناطق وكل منها تتعامل مع أبجدية مختلفة ، الجدول التالي يبين بعضا من المناطق والأبجديات المستخدمه فيها: [![الشكل ‏7 يبين الأبجديات المختلفة في ترميز UTF-16](https://informatic-ar.com/wp-content/uploads/2013/10/win32image7-300x111.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image7.png) الشكل ‏7 يبين الأبجديات المختلفة في ترميز UTF-16 **ANSI and Unicode Character and String Data Type 1.3.1 :** كل من تعامل مع لغة السي يعلم أن حجم المتغير من نوع الحرفي char هو 1 بايت وفي حال كتب الحروف داخل علامتي تنصيص Literal String فيقوم المترجم بتحويل كل حرف الى 1 بايت. [![win32image_8](https://informatic-ar.com/wp-content/uploads/2013/10/win32image_8-300x16.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image%5F8.png) ولإستخدام الUTF-16 يتم استخدام النوع wchar\_t ومترجمات مايكروسفت القديمه لا تدعم هذا النوع فعند استخدامه يتم كتابة المفتاح \\Zc:wchar\_t وهذا المفتاح يكون موجود تلقائياً في خيارات الترجمة عند عمل مشروع جديد في Visual Studio. ويعرف النوع wchar\_t على أنه unsigned short كما يوضحه الشكل التالي: [![الشكل 8 يوضح تعريف المتغير wchar_t](https://informatic-ar.com/wp-content/uploads/2013/10/win32image8-300x68.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image8.png) الشكل 8 يوضح تعريف المتغير wchar\_t الكود التالي يبين استخدام الينوكود في النصوص string والأحرف character باستخدم wchar\_t : [![الشكل 9 يبين مثال للتعامل مع الANSI and Unicode Characters](https://informatic-ar.com/wp-content/uploads/2013/10/win32image9-300x224.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image9.png) الشكل 9 يبين مثال للتعامل مع الANSI and Unicode Characters استخدام الحرف الكبير L قبل أي Literal String يخبر المترجم بأن هذا النص يجب ترجمته على أنه Unicode وعندما يضع المترجم النص في قسم البيانات Data Section فهو يرمز كل حرف باستخدام UTF-16. وقامت مايكروسوفت بتعريف أنواع متغيرات بأسماء مختلفه عن أنواع المتغيرات الموجودة في لغه السي لكي تفصل التعامل مع Win32 API قليلا عن الأنواع في لغه السي ، فمثلا في ملف الرأس WinNT.h نجد التعاريف التالية: [![الشكل 10 يبين تعاريف الأنواع الأساسية في ملف WinNt.h](https://informatic-ar.com/wp-content/uploads/2013/10/win32image10-300x126.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image10.png) الشكل 10 يبين تعاريف الأنواع الأساسية في ملف WinNt.h ونجد أيضا للWide Char هذه الأنواع : [![الشكل ‏11 يبين تعاريف الأنواع الأساسية والتي تتعامل بالUnicode](https://informatic-ar.com/wp-content/uploads/2013/10/win32image11-300x172.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image11.png) الشكل ‏11 يبين تعاريف الأنواع الأساسية والتي تتعامل بالUnicode بالنسبة \_\_nullterminated فهي header annotation تصف كيف سيستخدم النوع سواء في المعاملات أو في القيم الراجعه وهي غير مهمه في أغلب الأحوال. عند البرمجه بWin32 API يفضل استخدام هذه الأنواع بقدر الإمكان للتوافق أولا مع توثيق الMSDN وهذا يسهل قرائه الكود وصيانته. ومن الأفضل دائما كتابة كود عام بحيث يمكن أن يتترجم الى ANSI أو الى Unicode بدون أي تغيير. فلو نظرنا الى الملف WinNT.h مره أخرى سنجد أنه اذا كان يوجد تعريف للUnicode فسيتم تحويل Expand أي اسم TCHAR الى WCHAR (والذي هو في النهايه wchar\_t) . واذا لم يكن هناك تعريف سيكون النوع TCHAR هو char عادي. [![الشكل ‏12 يبين تحديد النوع على حسب وجود تعريف للUnicode](https://informatic-ar.com/wp-content/uploads/2013/10/win32image12-246x300.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image12.png) الشكل ‏12 يبين تحديد النوع على حسب وجود تعريف للUnicode وبالتالي عند كتابة الكود التالي سنجد أن السطر الأول قد يكون حجمه 1 بايت أو 2 بايت وذلك بالإعتماد على هل الثابت UNICODE معرف في الكود أم لأ. ونفس الأمر بالنسبة للسطر التالي. [![win32image_13](https://informatic-ar.com/wp-content/uploads/2013/10/win32image_13-300x54.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image%5F13.png) **1.3.2\. ANSI and Unicode Functions in Widows** منذ صدور Windows NT وكل النسخ التي تليه بنيت بدعم كامل للينوكود، وكل الدوال سواء كانت لإنشاء النوافذ أو عرض ومعالجه النصوص تتطلب نصوص بنظام يونيكود Unicode string. وفي حال قمت باستدعاء أحد تلك الدوال بنص ANSI (1 بايت لكل حرف) فسوف تقوم الدالة أولا بتحويل النص الى Unicode وبعدها تكمل العمل وعند ارجاع القيمه الراجعه وكنت تستقبلها في نص ANSI فسوف تقوم الدالة بعملية تحويل النص من اليونيكود الى الANSI وترجعه الى الدالة المستدعية. وكل من هذه التحويلات لا يعلم عنها المستخدم ولكن لها استهلاك overhead سواء في الوقت المستغرق لعملية التحويل أو الذاكرة التي تتطلبها عملية التحويل. وأغلب دوال الويندوز والتي تتطلب string كمعامل يكون لها نسختين الأولى للتعامل مع الANSI String وتنتهي هذه الدوال بA والنسخ الثانية للتعامل مع الUnicode وتنتهي هذه الدوال بW (نسبة لWide Character) . مثال الدالة CreateWindowEx هي ليست دالة في الحقيقة بل ماكروا سيتم عمل تحويل expand له الى النسخه ANSI (CreateWindowExA) في حال كان لا يوجد تعريف للUNICODE أما اذا كان هناك تعريف للUNICODE سيتم تحويل الماكروا (قبل البدء بالترجمه بواسطة الPreprocessor ) الى النسخه CreateWindowExW . الكود التالي من ملف الرأس WinUser.h وهو يحتوي على تصاريح الدالتين CreateWindow(Ex) ويبين كيفية الexpand في حال كان هناك معرف لليونيكود: [![الشكل ‏13 يبين اختلاف الدالة على حسب تعريف الماكروا UNICODE](https://informatic-ar.com/wp-content/uploads/2013/10/win32image13-300x83.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image13.png) الشكل ‏13 يبين اختلاف الدالة على حسب تعريف الماكروا UNICODE في نظام Windows Vista نجد دوال الANSI والتي تتنهي بA هي مجرد طبقة للتحويل حيث تقوم بتحويل النص من ANSI الى Unicode ثم تقوم باستدعاء النسخه Unicode من الدالة وترسل لها النص وعندما تعود الدالة الUnicode الى النسخه ANSI تقوم الأخيره بتحويل النص الANSI وترجعه للتطبيق. وبهذه الطريقة سوف تجعل التطبيق أكثر استهلاكا للذاكرة وأكثر بطئا في العمل. لذلك استخدام الUnicode يجعل التطبيق أكثر كفائه. في حال كنت تطور ملف dll يمكنك ان تزود ملف الDll بدالتين للتصدير الأول تدعم ANSI والأخرى Unicode وتقوم الأولى بعملية التحويل لليونيكود واستدعاء نسخه الUnicode كما في نظام vista بالضبط. بعض الدوال في Win32 API مثل OpenFile و WinExec ما زالت تعمل كنوع من التوافق مع التطبيقات القديمه Backward Compability وهي تستقبل ANSI String فقط. وهذه الدوال يجب تجنبها واستخدام CreateProcess بدلا من WinExec واستخدام CreateFile بدلا من OpenFile. وعلى أيه حال هذه الدوال القديمه تستدعي تلك الدوال الجديدة من الداخل ولكن المشكلة في أنها لا تستقبل Unicode. وحاليا بدأت مايكروسوفت بعمل دوال تستقبل Unicode فقط مثلا الدالة ReadDirectoryChangeW. عند ترجمه ملف الresource (والذي يحتوي على قالب الDialog والصور والنصوص في واجهه التطبيق) باستخدام مترجم الresource فسوف يقوم المترجم بتحويل النصوص الى Unicode مباشره. **1.3.3\. ANSI and Unicode Functions in C Run-Time Library:** كما في دوال نظام الويندوز Win32 API نجد أن مكتبة لغه السي القياسية تحتوي على دوال تتعامل مع الANSI ودوال تتعامل مع الUnicode ولكن تفرق مكتبة السي عن دوال الويندوز في أن نسخ الANSI لا تستدعي نسخ الUnicode بل تقوم بالعمل بمفردها وبنفس الأمر دوال الUnicode تقوم بكل العمل ولا تستدعي نسخه الANSI . مثلا الدالة strlen وهي نسخه ANSI و ترجع طول النص ANSI string أما الدالة wcslen في نسخه Unicode وترجع طول الUnicode string وكل من هذه الدالتين مصرحات في ملف الرأس String.h . ولكتابة برنامج يترجم عام يترجم الى ANSI أو الى Unicode يجب أستخدام الماكروا \_tcslen وهو موجود في ملف الرأس TChar.h والذي يتم عمل expand له الى wcslen في حال كان هناك تعريف لل \_UNCODE والا سيتم تحويله الى strlen . **1.3.4\. Secure String Functions in C Run-Time Library:** الدوال التي تتعامل مع النصوص يجب أن تكتب بعنايه وخاصه تلك الدوال التي تقوم بنقل أو نسخ النصوص من مكان لأخر ، ففي حال كان المكان الذي سننقل اليه النص destination Buffer ليس كبيرا كفاية لإستقبال النص سوف تحدث مشكلة بالذاكرة Memory Corruption. المثال التالي يوضح ذلك : [![win32image_14](https://informatic-ar.com/wp-content/uploads/2013/10/win32image_14-300x47.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image%5F14.png) المشكلة في دالة \_tcscpy بنوعيها (strcpy و wcscpy) وأيضا أغلب دوال التعامل مع النصوص في أنها لا تستقبل معامل يحدد لنا طول الDestination Buffer وبالتالي فإن الدالة لن تعلم بأن هناك مشكلة ولن تظهر أي رسالة تدل على خطأ وهكذا لن يعلم المستخدم بأن هناك مشكلة في الذاكرة. ومن الأفضل أن لا تعمل الدالة على أن تعمل وتحدث مشكلة قد تؤدي لحدوث مشاكل أخرى فيما بعد. استغلت الMalware هذا النوع من الأخطاء بشكل كبير في الماضي ، لذلك قدمت مايكروسوفت مجموعه جديدة من الدوال الأمنه تقوم بنفس وظائف تلك الدوال ولكن مع اضافة التحقق من حجم الBuffer وهل هو مساوي للSource String . وهذه الدوال مصرحه عنها في ملف الرأس StrSafe.h . وقامت مايكروسوفت بتعديل مكتبة MFC و ATL وجعلها تتعامل مع هذه الدوال الأمنه وبالتالى اعادة بناء Rebuilding تطبيق MFC قديم بأحد الإصدارات الجديدة كافي للتخلص من مشاكل التعامل مع الدوال غير الأمنه. وعند تضمين ملف الرأس StrSafe.h في البرنامج (والذي يحتوي على تضمين ملف String.h ) وأستخدام دوال السي غير الأمنه مثل \_tcscpy فسوف تخرج أخطاء عند الترجمة . كما يوضح ذلك المثال التالي : [![الشكل 14 يبين مثال لإستخدام الدوال غير الأمنه](https://informatic-ar.com/wp-content/uploads/2013/10/win32image14-300x193.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image14.png) الشكل 14 يبين مثال لإستخدام الدوال غير الأمنه اذا قمنا بالنظر الى ملف StrSafe.h سنجد أن هناك أسطر توضح انه اذا تم استخدام أي من تلك الدوال القديمه سيخرج لنا الخطأ المناسب والذي يحدد لنا الدالة الأمنه (من ملف StrSafe.h) المقابلة لها : [![الشكل ‏15 يبين الدالة الأمنه المقابلة للدالة غير الأمنه في الملف StrSafe.h](https://informatic-ar.com/wp-content/uploads/2013/10/win32image15-300x28.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image15.png) الشكل ‏15 يبين الدالة الأمنه المقابلة للدالة غير الأمنه في الملف StrSafe.h وفي مترجمات مايكروسوفت الجديده وحتى ولم نصرح عن StrSafe.h سنجد بأن هناك تحذير ينبهنا الى عدم استخدام تلك الدوال ، ويفضل التعامل مع تلك التحذيرات بجدية واستبدال الدوال غير الأمنه بنظيراتها الأمنه. [![الشكل 16 يبين التحذير الذي يخرج عند استدعاء الدوال غير الأمنه](https://informatic-ar.com/wp-content/uploads/2013/10/win32image16-300x25.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image16.png) الشكل 16 يبين التحذير الذي يخرج عند استدعاء الدوال غير الأمنه ولكل دالة في دوال مكتبة السي مثل \_tcscpy و \_tcscat ما يقابلها في الدوال الجديدة وله نفس الإسم ولكن ينتهي بعلامه \_s وكل من هذه الدوال الجديدة اضافت معامل يوضح عدد الحروف في الBuffer والتي تستقبل النص. التصريح التالي للدالة الأمنه \_tcscpy\_s ونلاحظ أن هناك معامل واحد زياده عن الدالة الأصليه \_tcscpy . التصريح للنسخه ANSI : [![الشكل ‏ 17 يبين تصريح الدالة strcpy_s (النسخه ANSI ) .](https://informatic-ar.com/wp-content/uploads/2013/10/win32image17-300x74.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image17.png) الشكل ‏ 17 يبين تصريح الدالة strcpy\_s (النسخه ANSI ) . وهذا التصريح للنسخه Unicode : [![الشكل ‏18 يبين تصريح الدالة strcpy_s (النسخه UNICODE ) .](https://informatic-ar.com/wp-content/uploads/2013/10/win32image18-300x74.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image18.png) الشكل ‏18 يبين تصريح الدالة strcpy\_s (النسخه UNICODE ) . لاحظ أن هذه الدوال تستقبل عدد الأحرف وليس عدد البايتات (والتي تخرجها الدالة sizeof ) ويتم استخراج عدد الأحرف بقسم حجم المصفوفه على حجم العنصر الأول منها أو باستخدام الماكروا \_countof الموجود في stdlib.h (لا يوجد في GCC) . [![الشكل ‏ 19 يبين تعريف الماكروا _countof في مترجم مايكروسوفت](https://informatic-ar.com/wp-content/uploads/2013/10/win32image19-300x98.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image19.png) الشكل ‏ 19 يبين تعريف الماكروا \_countof في مترجم مايكروسوفت كل الدوال الأمنه والتي تنتهي بعلامه (\_s) تتحقق من جميع المعاملات قبل أن تبدأ بالعمل ، حيث تقوم بالتأكد من المؤشر (أول معامل) صحيح ولا يؤشر لNULL ويتأكد من أن المعامل الثاني (عدد الأحرف) هو عدد صحيح integer ، وتتأكد من المعامل الثالث بأنها صحيحه وأن الBuffer حجمه كبير كفاية لنقل المعامل الثالث اليه. وفي حال حدثت مشكلة أثناء أحدى هذه الفحوصات فتضع الدالة خطأ errno وترجع في كلا الأحوال errno\_t وهي قيمه تدل على النجاح (S\_OK) أو الفشل (أي قيمه أخرى مثل EINVAL ومعناها أن المؤشر يؤشر لNULL ، بقية الأخطاء توجد في ملف الرأس errno.h ). وفي الحقيقة هذه الدالة عند الخطأ لا ترجع قيمه وإنما بدلا من ذلك (عند العمل في طور الdebug) تقوم باخراج رسالة للمستخدم عن المشكلة ويتم انهاء التطبيق . أما في طور النسخه النهائية Release فيتم انهاء التطبيق مباشره بلا أي رسالة . المثال التالي ينقل نص أكبر من الBuffer باستخدام الدالة الأمنه وبما أن التطبيق يعمل في Debug mode فسوف تخرج الرسالة التاليه وهيUnfriendly بكل تأكيد. [![الشكل ‏ 20 يبين اكتشاف خطأ BOF عند استخدام الداول الأمنه](https://informatic-ar.com/wp-content/uploads/2013/10/win32image20-300x131.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image20.png) الشكل ‏ 20 يبين اكتشاف خطأ BOF عند استخدام الداول الأمنه وعند تنفيذ المثال التالي: [![الشكل ‏21 يبين مثال على استخدام الدوال الأمنه](https://informatic-ar.com/wp-content/uploads/2013/10/win32image21-300x131.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image21.png) الشكل ‏21 يبين مثال على استخدام الدوال الأمنه سوف يحصل نفس الخطأ السابق ويتم انهاء التطبيق ، وسوف تكون قيمه szBuffer بعد الدالة هي NULL والسبب أن هذه الدوال الأمنه والتي تنتهي ب \_s لا تقوم بعمل تخزين النتيجة المناسبة وترك الباقي بل تضع NULL في حال الخطأ. وأضافت مايكروسوفت للC Run-Time Library العديد من الدوال الجديدة والتي تعطي تحكم أكثر عن التعامل مع النصوص مثلا التحكم بالحروف التي ستملاء الBuffer عند حدوث الoverrun وكيف يطبق الTruncation . من هذه الدوال StringCchCopy)Ex) و StringCchCat)Ex) و StringCchPrintf)Ex) وغيرها الكثير من الدوال وهي مصرحه في ملف الرأس StrSafe.h . الشكل التالي يبين تعريف الدالة StringCchCopy(نسخه الANSI): [![الشكل ‏22 يبين تعريف الدالة StringCchCopyA](https://informatic-ar.com/wp-content/uploads/2013/10/win32image22-300x188.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image22.png) الشكل ‏22 يبين تعريف الدالة StringCchCopyA لاحظ الأحرف Cch في هذه الدوال والتي هي اختصار ل Count of Character ويتم الحصول عليها باستخدام \_countof .أيضا هناك دوال لها Cb مثل StringCbCopy و StringCbCat وغيرها والتي تعني Count of Byte ويتم الحصول عليها بواسطة sizeof. كل من هذه الدوال ترجع HRESULT والجدول التالي يبين القيم الراجعه: [![الشكل ‏23 يبين القيم الراجعه من الدوال StringC**](https://informatic-ar.com/wp-content/uploads/2013/10/win32image23-300x61.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image23.png) الشكل ‏23 يبين القيم الراجعه من الدوال StringC\*\* وبعكس الدوال الأمنه والتي تنتهي ب \_s فهذه الدوال تقوم بقطع القيم الزائده Truncation عندما يكون الBuffer صغيرا وتنقل ما تستطيع نقله بالإضافة الى علامه ال\\0 وتستطيع معرفه هذا الوضع عندما تكون القيمه الراجعه هي INFUICEINT\_BUFFER . وفي حال طبقنا المثال السابق (نقل الأرقام 0123456789 الى Buffer) باستخدام الدالة StringCchCopy فسوف تكون محتوى الBuffer هو 012345678 . [![الشكل 24 يبين استخدام الدالة StringCchCopy](https://informatic-ar.com/wp-content/uploads/2013/10/win32image24-300x192.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image24.png) الشكل 24 يبين استخدام الدالة StringCchCopy لاحظ أن خاصية الTruncation قد تكون مطلوبه في أحدى الأحيان وأحيانا غير ذلك لذلك على المبرمج استخدام هذه الدوال عندما يريد هذه الخاصية. بعض الدوال تنتهي ب (Ex) ولها المعاملات والقيم الراجعه التالية: [![الشكل ‏ 25 بين المعاملات المرسلة والقيم الراجعه للدوال الأمنه](https://informatic-ar.com/wp-content/uploads/2013/10/win32image25-300x250.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image25.png) الشكل ‏ 25 بين المعاملات المرسلة والقيم الراجعه للدوال الأمنه **1.3.5\. Windows String Functions:** أيضا قدم نظام ويندوز العديد من الدوال لمعالجة النصوص ولكن البعض منها لا يفضل استخدامه لأنها لا تحتوي على تحقق من حجم الBuffer وتعتبر ملغية Deprecated خاصه دوال الكيرنل lstrcpy و lstrcat . أيضا قدم ويندوز العديد من دوال معالجة النصوص المفيده في ملف الشل ShlwApi.h . بالنسبة لمقارنه النصوص فتوجد الدالتين CompareString(Ex) و CompareStringOrdinal ، وتوجد الدالتين في ملف WinNls.h وتصريح الدالة الأولى CompareString كما يلي (نسخه الANSI ) : [![الشكل 26 يبين تصريح الدالة CompareStringA](https://informatic-ar.com/wp-content/uploads/2013/10/win32image26-300x175.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image26.png) الشكل 26 يبين تصريح الدالة CompareStringA لاحظ أن هذه الدالة تستقبل معامل يحدد معرف اللغه ويمكن الحصول عليه باستخدام الدالة GetThreadLocale وترجع هذ الدالة قيمه LCID.والمعامل الثاني يحدد الFlags المستخدم والجدول التالي يوضح هذه الFlags [![الشكل ‏27 يبين الFlag للدالة CompareStringA](https://informatic-ar.com/wp-content/uploads/2013/10/win32image27-300x106.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image27.png) الشكل ‏27 يبين الFlag للدالة CompareStringA أما بقية المعاملات فهي لتحديد النصين وعدد الأحرف لهذين النصين ، واذا تم ارسال قيمه سالبة لعدد الأحرف (سواء ccCount1 أو cchCount2) ستقوم الدالة بحساب طول النص بنفسها. أما الدالة الثانية وهي لعمل مقارنه ولكن بدون النظر للLocale كما في الدالة الأولى والتي تستقبله كمعامل. وتصريح الدالة CompareStringOrdinal كالتالي: [![الشكل 28 يبين تصريح الدالة CompareStringOrdinal](https://informatic-ar.com/wp-content/uploads/2013/10/win32image28-300x198.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image28.png) الشكل 28 يبين تصريح الدالة CompareStringOrdinal وتقوم بعمل مقارنه بدون النظر للLocale وبالتالي فهي أسرع في العمل ويفضل استخدامها عند مقارنه النصوص العادي(مثل المسارات Path و قيم الRegistry وغيرها) . وترجع هذين الدالتين قيمه مختلفه عن القيم الناتجة من دوال المقارنه في لغه السي حيث ترجع هذه الدوال القيمه 0 في حال الفشل والقيمه 1 ( CSTR\_LESS\_THAN) في حال كان النص الأول lpString1 أقل من الثاني، وترجع 3 (CSTR\_GREATER\_THAN) في حال كان النص الأول lpString1 أكبر من الثاني ،وترجع 2 (CSTR\_EQUAL) في حال التساوي. ولجعل ناتج هذه الدوال مشابه لدوال المقارنه في لغه السي يمكن (في حال نجاح الدالة) أن نطرح من القيمه الناتجه 2 وبالتالي يكون الناتج 0 في حال التساوي و1 في حال النص الأول أكبر و-1 في حال كان النص الأول أصغر. **1.3.6\. لماذا يجب أستخدام اليونيكود Why You Should Use Unicode:** يفضل عند تطوير التطبيقات استخدام ال Unicode لأنه يجعل عملية الLocalization سهله وبالتالي يكون هناك ملف exe أو dll واحد يدعم كل اللغات. بالإضافة الى أن استخدام الUnicode يجعل التطبيق أكثر كفائه ويستهلك ذاكرة أقل. وأيضا يكون متوافق مع مخرجات مترجم الResource بالإضافة الى العمل على .NET و COM والتي تدعمان الUnicode بالكامل. ولذلك يفضل تحويل التطبيقات وجعلها جاهزه للUnicode وحتى لو لم تكن هناك نية لاستخدام الUnicode في الوقت الحالي ، ويمكن أتباع ما يلي: • استخدام أنواع البيانات العامه Generic Data Type مثل TCHAR و PTSTR. • استخدام الماكروا \_T أو TEXT لأي نصوص Literal String في البرنامج . • الدوال التي تستقبل عدد الأحرف نرسل لها \_countof أما التي تستقبل عدد البايتات نرسل لها sizeof . وعند إراده حجز مساحة وكان لدينا عدد الأحرف فيجب حجز مساحة تساوي عدد الأحرف مضروبا في عدد بايتات الحرف الواحد ، كما يوضح الشكل التالي: [![الشكل ‏ 29 يحدد كيف يمكن حجز مساحة بطول النص بطريقة صحيحه](https://informatic-ar.com/wp-content/uploads/2013/10/win32image29-300x37.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image29.png) الشكل ‏ 29 يحدد كيف يمكن حجز مساحة بطول النص بطريقة صحيحه • تجاهل استخدام عائلة printf بالكامل وخصوصا التي تستخدم %s و %S للتحويل من الANSI الى الUnicode والعكس ، ويتم استخدام الدوال MultiByteToWideChar و WideCharToMultiByte . • دائما يتم تحديد المعرفين UNICODE و \_UNICODE (الأثنين معا أو بدونهم). • عند معالجة النصوص يجب استخدام الدوال الأمنه والتي تنتهي ب \_s واذا كان المبرمج يريد قطع النتيجة فيستخدم دوال StringCch\* . • لا تستخدم دوال الكيرنل lstrcpy , lstrcat . **1.4\. كائنات النظام Kernel Objects:** كائنات الكيرنل هي عباره عن بنية بيانات في الذاكرة ينشئها الكيرنل وهو الوحيد الذي يستطيع التعامل معها بشكل مباشر, وتتكون هذه البنية من عده حقول تحتوي على معلومات حول الكائن ، وبعض الحقول تكون موجوده في كل كائنات الكيرنل المختلفه (مثل عداد الإستخدام Usage Count و الSecurity Descriptor) . والأغلب في الحقول تكون متعلقة بالكائن نفسه مثلا الكائن من نوع Process Object يكون له معرف ID وله Exit Code وله أولويه Priority وأما الكائن من نوع File Object يكون له إزاحه Byte Offset وله طريقة للفتح Open Mode وللتشارك Share Mode. وهكذا .. هناك العديد من كائنات الكيرنل Kernel Objects منها File و Event و Pip و Process و Thread و Semaphore و FileMapping وغيرها الكثير . ولأن هذه الكائنات هي عباره عن بني بيانات ينشئها ويتعامل معها الكيرنل فيستحيل على التطبيقات العادية الوصول لتلك البني في الذاكرة وتعديل محتوياتها بشكل مباشر. واذا أرادت التطبيقات التعامل مع هذه البنى فهي تستخدم دوال معينه تستطيع التعامل مع تلك البني باستخدام مقبض Handle للKernel Object. فعند انشاء أي Kernel Object فسوف تعود الدالة بمقبض Handle لذلك الكائن ويمكن أن يستخدم هذا المقبض أي Thread داخل العملية . هذه المقابض (طولها 32 بت في أنظمه 32 ) هي متعلقة بالعملية Process-Relative بمعنى في حال أرسلت هذا المقبض لThread في عملية أخرى (باستخدام أي من طرق التواصل بين التطبيقات IPC) فإن ذلك الThread لن يصل لذلك المقبض بل سيصل ربما لمقبض أخر تماما أو سوف تفشل العملية ، وسوف نوضح هذه العملية بشيء من التفصيل بعد التعرف على جدول المقابض في كل عملية Process Handle Table. **1.4.1\. Usage Count:** كائنات الكيرنل يمتلكها الكيرنل وليست العملية بمعنى اذا قامت العملية باستدعاء دالة لإنشاء Kernel Object وأنتهت هذه العملية فقد يمكن أن لا يقوم النظام بحذف هذا الكائن من الذاكرة Destroy في حال كانت هناك عملية أخرى تستخدم هذا الكائن. وبالتالي لن يحذف destroy الكيرنل هذا الكائن حتى تقوم العملية التالية بالتوقف عن استخدام هذه الكائن. باختصار شديد وهي النقطة المهمه هو أن الKernel Object يمكن أن يتواجد حتى بعد انتهاء العملية التي قامت بانشائه. ويعلم الكيرنل عدد العمليات التي تستخدم هذا الكائن من خلال الحقل Usage Count وهو يوجد في أي Kernel Object . فعندما يقوم الكيرنل بانشاء Object يتم وضع الUsage Count لها الكائن بالقيمه 1 وعندما تقوم عملية اخرى باستخدام هذا الكائن يزداد الUsage Count بواحد وهكذا. وعند تنتهي العملية يتم طرح واحد من الUsage Count من جميع الكائنات التي انشئأتها العملية. وأي كائن له Usage Count مساوي لصفر يقوم الكيرنل مباشره بتحريره من الذاكرة. وهكذا لن يبقى أي كائن في الذاكرة لا تستخدمه أي عملية. **1.4.2\. Security:** يتم حمايه كائنات الكيرنل من خلال الـ security descriptor وهو يصف من الذي يملك الكائن (العملية التي أنشأته) وما هي المجموعه groups والمستخدمين Users المخولين للوصول لهذا الكائن واستخدامه وما الذين لا يحق لهم الوصول. وكل الدوال التي تنشئ Kernel Object لها معامل وهو SECURITY\_ATTRIBUTE مثلا الدالة التالية لانشاء ملف CreateFile سنجد أن هناك معامل للحماية وهكذا مع جميع الدوال التي تنشئ كائنات الكيرنل: [![الشكل ‏30 يبين تصريح الدالة CreateFileA](https://informatic-ar.com/wp-content/uploads/2013/10/win32image30-300x142.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image30.png) الشكل ‏30 يبين تصريح الدالة CreateFileA في أغلب التطبيقات نرسل NULL لهذا المعامل وبالتالي يتم انشاء الكائن بالحماية الموجوده في العملية الحالية. واذا أردنا عمل حماية يجب عمل كائن من البنية SECURITY\_ATTRIBUTES وتهيئتها ثم ارسال العنوان كمعامل لأي داله من دوال انشاء الKernel Object. ويتكون SECURITY\_ATTRIBUTES من الحقول التالية: [![الشكل ‏31 يبين تعريف الSECURITY_ATTRIBUTES](https://informatic-ar.com/wp-content/uploads/2013/10/win32image31-300x57.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image31.png) الشكل ‏31 يبين تعريف الSECURITY\_ATTRIBUTES هناك حقل واحد من هذه الحقول متعلق بالحماية وهو lpSecurityDescriptor واذا كنا نريد تحديد الوصول للكائن فيجب انشاء SECURITY\_ATTRIBUTES واعطاء قيمة lpSecurityDescriptor . بالنسبة للحقل الأخير bInheritHandle فهو غير متعلق بالحماية وسنتحدث عنه بعد قليل عند الحديث عن مشاركة المقابض بين العمليات. وعند الوصول لأي كائن في الكيرنل Kernel Object (وصول وليس انشاء) فيجب تحديد العملية التي نريد أن نجريها على هذا الكائن عن الوصول اليه. فمثلا اذا كنا نريد الوصول لكائن من نوع FileMapping للقرائه سوف نستدعي دالة الوصول Open : [![الشكل 32 يبين استدعاء دالة تنشئ Kernel Object](https://informatic-ar.com/wp-content/uploads/2013/10/win32image32-300x49.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image32.png) الشكل 32 يبين استدعاء دالة تنشئ Kernel Object المعامل الأول لهذه الدالة يحدد نوع العملية (القرائه في هذه الحالة) التي نريد أن نجريها على الكائن ، وتقوم الدالة OpenFileMapping أولا بفحص الحماية قبل أن تعيد المقبض للكائن الموجود. فاذا كان المستخدم الذي استدعى هذه الدالة ضمن مجموعه المستخدمين المخولين للوصول لهذا الكائن فسوف تعيد الدالة المقبض والا سوف تعيد NULL وعند معرفه الخطأ باستخدام GetLastError سيخرج (5) وهو ERROR\_ACCESS\_DENIED . على العموم أغلب التطبيقات لا تستخدم الحماية Security كثيرا. يمكن للتطبيق التعامل مع أنواع كائنات أخرى بخلاف كائنات الكيرنل مثل User Object أو GDI Object والتي تستطيع انشاء والتعامل مع النوافذ والفأره والخطوط وغيرها. لمعرفة هل الكائن الحالي هو Kernel Object أم لا يكون ذلك عن طريق النظر الى الدالة التي قامت بانشائه فاذا وجدت معامل SECURITY\_ATTRIBUTES سوف يكون الكائن هو كيرنل والا سوف يكون خلاف ذلك. **1.4.3\. Process Kernel Object Handle Table:** عندما يتم انشاء عملية يتم انشاء Handle Table لهذه العملية (جدول للمقابض) ويستخدم هذا الجدول لحفظ معلومات كائنات الكيرنل (كائنات الكيرنل فقط Kernel Object وليس بقية الكائنات مثل كائنات الرسم والمستخدم User and GDI Objects). طريقة بناء هذه الجدول وكيفية ادارته غير موثقة Undocumented وبالتالي المعلومات التي ستوضح أدناه هي ليست معلومات تفصيليه حول طريقة العمل ولكنها تعطي فكره عن المفهوم بشكل عام حيث هذا المفهوم ضروري لمبرمج نظام الويندوز. الشكل التالي يبين كيف يبدوا شكل جدول المقابض Handle Table وكما يتضح فهو مصفوفه من بنى البيانات Array of Data Structure وكل خانه فيها تتكون من مؤشر للكائن و Access Mask و Flags . [![الشكل ‏33 يبين تركيب جدول المقابض في العملية](https://informatic-ar.com/wp-content/uploads/2013/10/win32image33-300x46.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image33.png) الشكل ‏33 يبين تركيب جدول المقابض في العملية وعند انشاء العملية يكون هذا الجدول فارغاً ويتم التعامل معه عندما يقوم الThread داخل العملية بانشاء أي Kernel Object (عن طريق استدعاء أي دالة من دوال انشاء Kernel Object ) حيث يقوم الكيرنل بحجز منطقة في الذاكرة للكائن ويقوم بتهيئتها. ثم بعد ذلك يقوم الكيرنل بالبحث في جدول المقابض عن مكان فارغ وبما أن الجدول حاليا فارغا (كما في الشكل السابق) يقوم الكيرنل بايجاد الموقع الأول 1 ويقوم باستخدامه . وهكذا تكون قيمه مؤشر الكائن (العمود الثاني) هي عنوان الذاكرة التي يوجد بها الكائن ، وستكون الAccess Mask هي Full Access وسيتم وضع الFlag ببعض القيم سنوضحها بعد قليل. من الدوال التي تنشئ Kernel Object : CreateThread , CreateProcess CreateFile, CreateSemaphore وغيرها الكثير . وكل من هذه الدوال تنشئ Kernel Object وترجع Handle متعلق بالعملية Process-Relative . وهذا المقبض يمكن ان يستخدمه أي Thread داخل العملية. قيمه هذا المقبض يجب أن تقسم على 4 (أو اجراء عمليتين ازاحه لليمين Two Right Bit-Shift) . للحصول على رقم الموقع index في جدول المقابض للعملية. وبالتالي عند عمل debug للتطبيق وفحص قيمه هذا المقبض Handle سنجد أنها قيم صغيره 4 و 8 وهكذا . وعندما يتم استدعاء دالة تستقبل Kernel Object كمعامل سوف يتم ارسال القيمه العاده من الدوال (المقبض) انشاء كائن الكيرنل Create\* ومن داخل الدالة سوف تقوم بالنظر الى جدول المقابض للحصول على عنوان الكائن في الذاكرة حتى تقوم بمعالجته. وفي حال تم ارسال مقبض خاطئ سوف تعود الدالة بخطأ وتكون قيمه GetLastError هي ERROR\_INVALID\_HANDLE . أما في حال تم استدعاء دالة لانشاء Kernel Object ولم تعمل الدالة بشكل صحيح سوف ترجع 0 (NULL) ولذلك يكون دائما قيمه أول مقبض تبدأ من 4\. وهناك بعض الدوال ترجع -1 (INVALID\_HANDLE\_VALUE) عند الخطأ مثل الدالة CreateFile لذلك يجب النظر الى التوثيق Documentation والتأكد من القيمه الراجعه. [![الشكل 34 يبين استخدام خاطي للقيم الراجعه.](https://informatic-ar.com/wp-content/uploads/2013/10/win32image34-300x135.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image34.png) الشكل 34 يبين استخدام خاطي للقيم الراجعه. **1.4.4\. Closing Kernel Object:** بغض النظر عن كيفية انشاء الكائن سوف يتم انهاء التعامل معه من خلال الدالة CloseHandle وتقوم الدالة بفحص هل المقبض المرسل صحيح أم لا واذا كان كذلك يتم الذهاب لموقع الكائن في الذاكرة من خلال العنوان الموجود في جدول المقابض ويقوم بطرح واحد من الUsage Count للكائن واذا وصلت قيمه الUsage Count للكائن صفر سيقوم الكائن بحذف الكائن وتحريره من الذاكرة . أما اذا تم ارسال مقبض خاطئ للدالة فسوف يحدث شيء من اثنين ، الأول أن الدالة سوف ترجع خطأ FALSE ويكون الخطا عند استدعاء GetLastError هو ERROR\_INVALID\_HANDLE ، أما الإحتمال الثاني هو أن يتم عمل throws للexception (0xC0000008) اذا كانت العملية تعمل في طور التنقيح. وقبل أن تعود الدالة CloseHandle تقوم بحذف المدخل لهذا الكائن من جدول المقابض للعملية. وبالتالى يكون المقبض غير صحيح ويجب عدم استخدامه . (سوف يتم حذف المدخل سواء تم حذف الكائن من الذاكرة أو اذا لم يتم حذفه –في حال وجود عملية اخرى تستخدم هذا الكائن-). ومن الأفضل بعد استدعاء الدالة CloseHandle هو وضع القيمه NULL في المقبض ، لأنه اذا تم استخدامه بعد ذلك سوف يقوم النظام باصدار خطأ لأن المقبض غير صحيح حيث لا وجود لمدخل لهذا المقبض في جدول المقابض. أو الأسوأ وهو أن يتم استخدام هذا المدخل بواسطه Kernel Object جديد وبالتالي يتم الوصول لمقبض أخر وقد يكون من نوع مختلف وبالتالي حدوث مشاكل غير متوقعه في البرنامج. وفي حال لم يتم عمل CloseHandle سوف يحدث Leak (تسرب للذاكرة) اثناء عمل العملية ولكن عندما تنتهي يتم انهاء وتحرير كل المصادر حيث يقوم النظام بالبحث في جدول المقابض للعملية واذا وجد مدخل (كائن لم يتم انهاء التعامل معه) يقوم النظام بطرح واحد من الUsage Count وحذف المدخل من الجدول. واذا وصل الUsage Count لهذه المقبض الى صفر يقوم النظام بحذف الكائن من الذاكرة. بالتالي يمكن أن يحدث Leak للKernel Object أثناء عمل العملية ولكن عند انتهاءها يضمن النظام تحرير كل شيء.والحديث ينطبق على بقية أنواع الكائنات الأخرى مثل GDI and User Object. وهناك طريقة لكشف وجود Kernel Object Leak وعن عن طريق مراقبة الHandle للعملية فاذا كان الرقم يتصاعد فهذا يعني أن هناك Leak وأن هناك العملية لم تنهي العمل من الكائنات بعد الإنتهاء منها. [![الشكل ‏ 35 يبين وجود Leak في برنامج حيث يتصاعد عدد الHandle بشكل كبير](https://informatic-ar.com/wp-content/uploads/2013/10/win32image35-300x165.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image35.png) الشكل ‏ 35 يبين وجود Leak في برنامج حيث يتصاعد عدد الHandle بشكل كبير **1.4.5\. تشارك كائنات الكيرنل بين العمليات:** تحتاج المسارات Threads في العمليات عادة للتشارك في كائنات الكيرنل والتي أنشأتها عمليات أخرى وقد يكون ذلك لأسباب مختلفة منها عند اراده تشارك البيانات في عمليتين مختلفتين في النظام (يتم ذلك باستخدام FileMapping) ، أو اراده تشارك البيانات بين عمليتين مختلفتين في نظامين مختلفين يرتبطان بشكبة (ويتم ذلك من خلال MailSlots أو named pipes)، أو عند اراده استخدام الsynchronization لجعل عملية تنبة notify العملية الأخرى عند حدوث حدث معين (يتم من خلال Mutex أو Semaphore). وبما أن أي مقبض الكائن متعلق بالعملية Process-Relative فإن العملية ليست سهله ، وكما ذكرنا سابقا أن مايكروسوفت جعلت المقبض متعلق بالعملية وذلك لجعل النظام أكثر متانه Robustness حتى لا تجعل أي عملية تستخدم هذا الكائن بكل سهوله ، والسبب الثاني هو الحماية security حيث أن أي Kernel Object له حماية يحددها من ينشئ هذا الكائن ، وأي عملية تريد أن تستخدم هذا الكائن يجب أن يكون لها الصلاحية لذلك . سوف نتناول الأن 3 طرق لكي تجعل العمليات تتشارك في الKernel Object وهي : 1. Object Handle Inheritance 2. Naming Objects 3. Duplicating Object Handle **A. الطريقة الأول لتشارك كائنات الكيرنل Object Handle Inheritance:** يتم استخدام هذه الطريقة عندما تكون للعمليات علاقه parent-Child بمعنى أن العملية الأولى تقوم بانشاء العملية الثانية Spawn Process وهنا يمكن السماح للعملية الإبن بالوصول الى كائنات العملية الأب. ولعمل ذلك يجب اتباع الخطوات التالية. أولا عندما تقوم العملية الأب بانشاء Kernel Object تحدد هذه العملية بأن المقبض قابل للوراثه Inheritable Handle وذلك من خلال عمل نسخه من التركيب SECURITY\_ATTRIBUTES واسناد القيمه TRUE للعضو bInheritHandle . كما يبين المقطع التالي. [![الشكل 36 يبين جعل مقبض الكائن قابل للوراثه](https://informatic-ar.com/wp-content/uploads/2013/10/win32image36-300x56.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image36.png) الشكل 36 يبين جعل مقبض الكائن قابل للوراثه نعود الى جدول المقابض والى العمود Flags والذي يحدد هل المقبض قابل للوراثه (1) كما في الشكل السابق ، أو أنه غير قابل للوراثه (0) وذلك في حال انشاء الKernel Object وتمرير القيمه NULL للمعامل SECURITY\_ATTRIBUTES . لنفرض أن جدول المقابض كان بهذا الشكل: [![الشكل ‏ 37 يبين جدول المقابض وهناك مقبضين أحدهما قابل للوراثه (3) والأخر غير ذلك (1)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image37-300x46.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image37.png) الشكل ‏ 37 يبين جدول المقابض وهناك مقبضين أحدهما قابل للوراثه (3) والأخر غير ذلك (1) الخطوه الثانيه عند استخدام هذه الطريقة (Object Handle Inheritance) هي انشاء العملية الإبن Spawn Child Process ويتم ذلك من خلال الدالة CreateProcess. [![الشكل 38 يبين تصريح الدالة CreateProcess](https://informatic-ar.com/wp-content/uploads/2013/10/win32image38-300x162.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image38.png) الشكل 38 يبين تصريح الدالة CreateProcess هذه الدالة تنشئ عملية وليست مهمه حاليا في سياق الحديث ، ما يهمنا الأن هو المعامل bInheritHandle وعاده يتم ارسال القيمه FALSE وهكذا يتم اخبار النظام بإن العملية الإبن Child Process لن ترث أي مقبض قابل للوراثة في العملية الأب. وعند ارسال القيمه TRUE فإن العملية الأبن سوف ترث أي مقبض قابل للوراثه في العملية الأب. وحين ارسال القيمه TRUE سيقوم النظام بعد انشاء مساحة العنواين للعملية الإبن وقبل أن تبدأ بالعمل بنسخ مداخل (نقصد بها السطر بالكامل ) جميع المقابض القابلة للوراثه في جدول المقابض في العملية الأب الى جدول المقابض في العملية الإبن ويضعها في نفس المواقع بالضبط وهذه معلومة مهمه. وبالإضافة الى نسخ مداخل المقابض في الجدول يقوم الكيرنل بزيادة الUsage Count للكائن لأن هناك عملية جديده تستخدم هذا الكائن ولكي يتم حذف هذا الكائن من الذاكرة يجب أن تقوم العمليتين باستدعاء CloseHandle أو أن يتم انهاء العمليتين (وكما ذكرنا سابقا ان عند انتهاء العملية سيقوم النظام بتحرير كل المصادر المستخدمه بواسطة العملية). ولا يشترط أن تنهي أي عملية استخدام الكائن قبل العملية الثانيه ويمكن لأي عملية سواء الأب أو الإبن أن تنهي التعامل مع الكائن وهذا لن يؤثر على تعامل العملية الأخرى مع الكائن. الشكل التالي نجد جدول المقابض للعملية الإبن قبل أن تبدأ بالعمل وسوف نجد أنه تم نسخ مدخل المقبض القابل للوراثه في العملية الأب (وهو في الموقع 3) وتم نسخه الى نفس الموقع في العملية الإبن (3) كما نلاحظ أن عنوان الكائن في الذاكرة و الAccess Mask والFlags هي نفسها كما في العملية الأب. [![الشكل 39 يبين جدول المقابض للعمليه الإبن](https://informatic-ar.com/wp-content/uploads/2013/10/win32image39-300x47.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image39.png) الشكل 39 يبين جدول المقابض للعمليه الإبن وهذا يعني أنه في حال قامت العملية الإبن بانشاء عملية جديدة spawn process ومررت القيمه TRUE للمعامل bInheritHandle في الدالة CreateProcess فسوف يتم انشاء عملية ترث المقبض بنفس العنوان والAccess Mask والFlags. (لاحظ أن وراثه المقابض تكون في لحظه انشاء العملية الإبن وسوف ترث المقابض القابله للوراثه الحالية، وفي حال بعد ذلك قامت العملية الأب بانشاء Kernel Object ومقبضه قابل للوراثه فإن العملية الإبن لن ترث ذلك المقبض) . **B. الطريقة الثانية لتشارك كائنات الكيرنل Named Object :** الطريقة الثانيه لتشارك كائنات الكيرنل وهي عن طريق تسمية الكائنات Naming Objects، فأغلب كائنات الكيرنل يمكن تسميتها وقت انشائها ، على سبيل المثال كل الدوال التالية تستقبل معامل يحدد إسم كائن الكيرنلCreateMutex , CreateEvent , CreateSemaphore CreateFileMapping ,CreateJobObject . [![الشكل 40 يبين أحد الدوال التي تنشئ Kernel Object](https://informatic-ar.com/wp-content/uploads/2013/10/win32image40-300x108.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image40.png) الشكل 40 يبين أحد الدوال التي تنشئ Kernel Object كل هذه الدوال تستقبل معامل pszName وهو يحدد لنا اسم الكائن ، وعندما نرسل NULL لهذا المعامل فيتم انشاء كائن من غير اسم Anonymous Kernel Object وفي تلك الحالة اذا أردت مشاركة الكائن للعمليات الأخرى فيجب استخدام طريقة وراثه المقابض التي وضحناها قبل قليل أو أستخدام الدالة DuplicateHandle كما سيتبين عند الحديث حول الطريقة الثالثه للتشارك كائنات الكيرنل بين العمليات . أما في حال ارسال اسم للكائن عند انشائه (ويجب أن يكون طوله بحدود MAX\_PATH) وهنا سيتم وضع الكائن مع بقية كائنات الكيرنل المنشئة مسبقا في مساحة أسماء واحده Single Namespace وفي حال كان هناك كائن كيرنل له نفس الإسم (وحتى اذا لم يكن من نفس النوع فلن يتم انشاء الكائن الجديد) . المثال التالي يوضح أنه لم يتم انشاء Semaphore وسترجع الدالة NULL لأن هناك كائن كيرنل له نفس الإسم AnyName وسوف تكون قيمه الناتج من الدالة GetLastError هي القيمه 6 (ERROR\_INVALID\_HANDLE) ! [![الشكل 41 يبين انشاء كائنان في الكيرنل بنفس الإسم](https://informatic-ar.com/wp-content/uploads/2013/10/win32image41-300x36.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image41.png) الشكل 41 يبين انشاء كائنان في الكيرنل بنفس الإسم تعرفنا في الأعلى حول انشاء كائن في الكيرنل له اسم Naming Kernel Object ، ولكي يتم مشاركة الكائنات بين العمليات ستقوم العملية الأولى A بالإستدعاء للدالة وستقوم بانشاء كائن في الكيرنل وتعطي له الإسم MyMutex : ```cpp HANDLE hMutexProcessA = CreateMutex(NULL, FALSE, TEXT("MyMutex")); ``` وعندما تبدأ العملية الثانية بالعمل (لا يشترط أن تكون أبن للعملية A وهذا ما يميز طريقة الNaming Object على طريقة Handle Inheritance) سوف يستدعي الدالة التالية : ```cpp HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, TEXT("MyMutex")); ``` قامت العملية الثانية بتسمية الكائن بنفس الإسم وسوف يقوم النظام في البداية بالتحقق من وجود كائن بنفس الإسم ولأنه يوجد هذا الكائن سوف يقوم النظام بالتحقق من نوع هذا الكائن وبما أنها من نفس النوع سوف يقوم النظام بالتحقق من الحماية Security وهل العملية الثانية لها صلاحيات الوصول وفي حال كان كذلك يقوم النظام بتهيئة مدخل جديد في جدول المقابض للعملية B ويكون المؤشر للكائن الموجود . أما اذا لم يكن الكائن من نفس النوع أو العملية ليست لها صلاحيات للوصول سوف تفشل الدالة وترجع NULL . عادة الدوال التي تنشئ كائنات في الكيرنل (مثلا CreateMutex ) ترجع مقبض له كامل الصلاحيات Full Access وفي حال أردنا تحديد الصلاحيات لهذا المقبض فيتم استدعاء الدالة الإضافية والتي تنتهي بEx (مثل CreateMutexEx) . نعود للمثال السابق ولنفرض أن الدالة عملت بنجاح وهنا بالطبع لن يتم انشاء كائن جديد ولكن كل ما في الأمر أن العملية B تكون حصلت على مقبض للكائن (المقبض كالعاده متعلق بالعملية Process-Relative ) وقد يكون قيمه هذا المقبض مختلفة ولكنه في النهايه يؤشر للكائن في الذاكرة . ولأنه تم الحصول على مقبض للكائن سوف يتم زياده الUsage Count للكائن بواحد ولن يتم حذف الكائن من الذاكرة الا في حال قامت العمليتان A و B باغلاق المقبض CloseHandle . طريقة أخرى لتشارك الكائنات من خلال الأسماء وهو باستدعاء الدوال Open\* وليس من خلال الدوال Create\* ، ومن هذه الدوال : OpenMutex, OpenEvent OpenSemaphore , OpenJobObject . ولهذه الدوال نفس المعاملات للدوال التي تقوم بانشاء الكائن . ولكن لن تستطيع ارسال القيمه NULL لإسم الكائن عند استدعاء دوال الOpen\* ، ويجب أن يتم ارسال اسم للكائن وستقوم هذه الدوال بالبحث في مساحة الأسماء Namespace عن كائن له هذا الإسم واذا لم تم ايجاده سوف ترجع الدالة NULL وترجع دالة الخطأ GetLastError القيمه 2 وهي (ERROR\_FILE\_NOT\_FOUND ) . واذا تم ايجاد كائن له نفس الإسم ولكنه من نوع مختلف سيتم ارجاع NULL وسوف ترجع دالة الخطأ GetLastError القيمه 2 وهي ERROR\_INVALID\_HANDLE. أما في حال كان من نفس النوع وسوف يفحص النظام الصلاحية (من خلال المعامل dwDesiredAccess) وهل يسمح لها وفي تلك الحالة سوف يتم الحصول على المقبض ويدخل في جدول المقابض وتزداد Usage Count للكائن بواحد. وفي حال تم ارسال القيمه TRUE للمعامل bInheritHandle في دوال Open\* سوف يكون المقبض قابل للوراثه. اذا الفرق الرئيسي بين دوال الCreate\* والOpen\* هي أن دوال الCreate\* تنشئ الكائن اذا لم يكن موجود بينما تفشل دوال Open\* في تلك الحالة. ولم تضع مايكروسوفت طريقة أو أي نصائح لتسمية تلك الكائن وقد تحصل أحيانا مشكلة في حال قام شخص بكتابة برنامج وأنشئ كائنات بالإسم X وقام شخص أخر في برنامج أخر وأنشئ كائنات بنفس الإسم ، والحل الأفضل هو انشاء GUID واستخدام الإسم لهذا الGUID في اسم الكائنات. وهناك حل أخر عن طريق وضع الكائن في مساحة أخرى بخلاف المساحة العامة وسوف نتطرق لها بعد قليل. الكائنات التي تسمى Named Object عاده تستخدم لكي تمنع تشغيل أكثر من نسخه واحده من التطبيق ولعمل ذلك فقط يتم استدعاء أي دالة من دوال Create\* في الدالة الرئيسية وقم بتسمية الكائن بأي اسم ، وعندما ترجع الدالة يتم النظر لGetLastError فاذا كانت قيمته هي ERROR\_ALREADY\_EXISTS فهذا يعني أن هناك نسخه من التطبيق تعمل وبالتالي نقوم باغلاق النسخه الحالية ، المثال التالي يبين ذلك . [![الشكل ‏42 يبين كود للتحقق من وجود نسخه أخرى تعمل من التطبيق](https://informatic-ar.com/wp-content/uploads/2013/10/win32image42-300x179.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image42.png) الشكل ‏42 يبين كود للتحقق من وجود نسخه أخرى تعمل من التطبيق **C. الطريقة الثالثة لتشارك كائنات الكيرنل DuplicateHandle:** الطريقة الثالثة لتشارك الكائنات بين العمليات وهو باستخدام الدالة DuplicateHandle ولها التصريح التالي. [![الشكل 43 يبين تصريح الدالة DuplicateHandle](https://informatic-ar.com/wp-content/uploads/2013/10/win32image43-300x165.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image43.png) الشكل 43 يبين تصريح الدالة DuplicateHandle وتقوم هذه الدالة بنسخ المدخل للمقبض من جدول المقابض في العملية الأولى الى جدول المقابض في العملية الثانية . والمعامل الأول والثالث في الدالة هما مقبضان يجب أن يكونا متعلقين بالعملية Process-Relative التي سوف تستدعي الدالة DuplicateHandle ويجب أن يكونا من نوع Process والا فلن تعمل الدالة . المعامل الثاني وهو مقبض Handle من أي نوع من كائنات الكيرنل وهو غير متعلق بالعملية التي سوف تستدعي الدالة DuplicateHandle ويجب أن يكون متعلق بالعملية التي مقبضها هو المعامل الأول للدالة DuplicateHandle ، أما المعامل الرابع فهو مؤشر للمقبض والذي سيم نسخ قيمه المقبض في العملية hTargetProcessHandle اليه .أما أخر ثلاثه معاملات فهي لتحديد قيمه الAccess Mask والFlag وأخيرا المعامل dwOptions فهو يمكن أن بالقيمة 0 أو أحد الFlags التالية DUPLICATE\_SAME\_ACCESS و DUPLICATE\_CLOSE\_SOURCE .وعند تحديد الFlag الأول سوف يتم نسخ الAccess Mask كما هو من المقبض في العملية الأولى الى العملية الثانية وسوف يتم حينها تجاهل المعامل الخامس في الدالة DuplicateHandle ، أما الFlag الثاني فيعني أن العملية سوف تغلق المقبض بعد النسخ وبالتالى لن يتأثر الUsage Count عند استخدام هذا الFlag (لأنه سيطرح منه واحد وسوف يزيد بواحد). نأخذ مثال وليكن لدينا العملية S وهي العملية الأولى source process والتي تملك بعضا من كائنات الكيرنل وليكن لدينا العملية T وهي العملية الثانية والتي تريد الوصول لذلك الكائن Target Process وليكن لدينا العملية C وهي العملية التي ستقوم باستدعاء الدالة DuplicateHandle لكي تقوم بالنسخ. الشكل التالي يبين جدول المقابض للعملية C ويحتوي على مقبض للعملية الأولى S ومقبض للعملية الثانية T . [![الشكل 44 يبين Process C Handle Table](https://informatic-ar.com/wp-content/uploads/2013/10/win32image44-300x36.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image44.png) الشكل 44 يبين Process C Handle Table والشكل التالي يبين جدول المقابض للعملية S ويحتوي على مقبض واحد لكائن في الكيرنل : [![الشكل 45 يبين Process S Handle Table](https://informatic-ar.com/wp-content/uploads/2013/10/win32image45-300x35.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image45.png) الشكل 45 يبين Process S Handle Table الشكل التالي بين جدول المقابض للعملية T قبل أن تستدعي العملية C الدالة DuplicateHandle ويحتوي جدول المقابض للعملية T على مقبض لكائن في الكيرنل. [![الشكل ‏46 يبين Process T Handle Table](https://informatic-ar.com/wp-content/uploads/2013/10/win32image46-300x36.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image46.png) الشكل ‏46 يبين Process T Handle Table بعد ذلك عندما تقوم العملية C باستدعاء الدالة DuplicateHandle بالكود التالي سوف يكون شكل جدول المقابض للعملية T كما يوضحه الشكل التالي. ```cpp DuplicateHandle(1, 2, 2, &hObj, 0, TRUE, DUPLICATE_SAME_ACCESS); ``` [![الشكل 47 يبين Process T Handle Table](https://informatic-ar.com/wp-content/uploads/2013/10/win32image47-300x36.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image47.png) الشكل 47 يبين Process T Handle Table وكما يوضح الشكل السابق فإن مدخل المقبض الثاني في العملية S تم نسخه كمدخل أول في جدول المقابض في العملية T ، وقامت الدالة DuplicateHandle بوضع القيمه 1 في المتغير hObj وهو موقع Index المدخل في العملية T . ولأنه تم استخدام الFlag (DUPLICATE\_SAME\_ACCESS) فسوف يكون الAccess Mask لهذا المقبض في العملية T مشابه للAccess Mask في مدخل المقبض في العملية S وهكذا لن يتم النظر الى المعامل dwDesiredAccess. وأخيراً سنجد أن الFlag المختص بالوراثه (العمود الأخير في الشكل السابق) للمدخل الجديد أصبح 1 والسبب أننا أرسلنا القيمه True كمعامل للدالة DuplicateHandle وقمنا بتحديد أن ذلك المقبض قابل للوراثه . لكن بعد استدعاء الدالة DuplicateHandle لن تعرف العملية أن الكائن أصبح متاح للإستخدام ولذلك يجب أن تقوم العملية C باخبار Notify العملية T بعد استدعاء الدالة DuplicateHandle وذلك باستخدام أي طريقة من طرق التواصل بين العمليات Interprocess Communication (IPC) . ما قمنا بعمله الأن باستخدام الدالة DuplicateHandle من خلال 3 عمليات حتى نقوم بعملية تشارك الكائن ولكن في الغالب لا تستخدم هذه الطريقة بسبب أن العملية الثالثه C قد لا تعرف رقم المقبض للكائن في العملية الأولى S . لكن الدالة DuplicateHandle دالة مرنه ويمكن أن نستخدمها فقط بين عمليتين كما سنوضح ذلك الأن ، ولنفرض أن لدينا العملية S تمتلك كائن في الكيرنل وتريد أن تسمح للعملية T الوصول لهذا الكائن وسوف يتم استدعاء الدالة DuplicateHandle بالطريقة التي يوضحها الشكل 6-48. استدعاء الدالة GetCurrentProcess سوف يرجع مقبض للعملية الحالية ، وبعد استدعاء الدالة DuplicateHandle سوف يكون المتغير hObjInProcessT هو مقبض متعلق بالعملية T ويؤشر لنفس الكائن الذي يؤشر اليه hObjInProcessS . ويجب على العملية S عدم اغلاق المقبض المتعلق بالعملية T كما يوضح الكود التالي ذلك. ```cpp // Process S should never attempt to close the duplicated handle. CloseHandle(hObjInProcessT); ``` [![الشكل ‏ 48 يبين طريقة تمرير المقبض للعملية T بعد استخدام الدالة DuplicateHandle](https://informatic-ar.com/wp-content/uploads/2013/10/win32image48-300x163.png)](https://informatic-ar.com/wp-content/uploads/2013/10/win32image48.png) الشكل ‏ 48 يبين طريقة تمرير المقبض للعملية T بعد استخدام الدالة DuplicateHandle الى هنا نصل لنهاية الجزء الأول .. المره القادمة سنبدأ بالغوص في Memory Management أو الProcess/Thread **المرجع الأساسي**: Windows via C/C++ by Jeffrey Richter and Christophe Nasarre Windows System Programming by Johnson M. Hart ### ما هي رائحة الكود Code Smell؟ URL: https://informatic-ar.com/code-smell/ Last updated: 2016-05-25T18:47:16.000Z ال Code Smell هي علامات وإشارات داخل الكود تدل على أن هناك خلل في التصميم. بحد ذاتها لا تعتبر خطأ برمجي Software Bugs ، وربما نجد أكواد مليئة بهذه الروائح لكنها تعمل على ما يرام دون أي مشاكل. أحد الأمثلة الشهيرة على هذه الراوئح هي وجود دالة تحوي مئات الأسطر Long Method وربما تستقبل العديد من الوسائط Too many parameters ، أو نجد كائن يقوم بعمل كل شيء God Object. أيضا كمثال آخر- دائما ما يقع فيه المطورين المبتدئين- هو وجود أجزاء مكررة في الكود Code Duplication. لو نظرنا الى هذه العلامات أو الروائح Smells في الكود نجد أنها من الناحية التقنية ربما تكون صحيحة ، لكن تكمن المشكلة في حالة تم تغيير متطلبات المشروع وكانت هناك حوجة الى القيام بالتعديل على مثل هذه الأكواد أو إضافة خصائص وميزات جديدة. ما يمكن أن نتوقعه في مثل هذه الحالات ، هو أن سرعة تنفيذ هذه المتطلبات الجديدة ستكون بطيئة جدا وربما لن يتمكن المطور من تطبيقها أو في بعض الحالات سينتج عن تطبيقها العديد من الأخطاء البرمجية. مهندس البرمجيات [Kent Beck](http://en.wikipedia.org/wiki/Kent%5FBeck?ref=informatic-ar.com) مخترع منهجية التطوير [XP](http://en.wikipedia.org/wiki/Extreme%5FProgramming?ref=informatic-ar.com) هو أول من بدأ باستخدام هذا المصطلح للدلالة على أن هناك خلل ربما يبدوا بسيطا في أول الأمر لكن سرعان ما سيتحول إلى كارثة حقيقة في المستقبل. ظهر المصطلح وانتشر بين مهندسي البرمجيات بعدما شارك Kent Beck في كتابة الكتاب [Refactoring: Improving the Design of Existing Code](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672?ref=informatic-ar.com) والذي يعتبر من أفضل الكتب في المجال لمهندسي البرمجيات ومطوري التطبيقات. وللتخلص من هذه الروائح سنحتاج الى وضع تعريف لعملية إعادة هيكلة وتنظيم الكود **Code refactoring** وفق القواعد والأسس التي تم اقتراحها في الكتاب أعلاه وفي عدة كتب أخرى. وسنترك تلك المعلومة في درس قادمة ان شاء الله. نختم الحديث بأن هناك العديد من هذه ال Smells التي يصعب سردها جميعا ، لذلك حتى تنضج هذه المعلومة نرحب بإضافة أي Smell واجهتك خلال كتابتك للتطبيقات أو عند قرائتك لأكواد زملائك. ### خوارزمية QuickHull URL: https://informatic-ar.com/quickhull/ Last updated: 2026-10-03T22:32:07.000Z سنتحدث اليوم عن مشكلة معروفة في مجال ال [Computational Geometry](https://informatic-ar.com/http://en.wikipedia.org/wiki/Computational%5Fgeometry) وهي تحديد أصغر مضلع يحوي مجموعة من النقاط! وقبل أن ندخل في حل المشكلة ، دعونا نتعرف على بعض المصطلحات . **Points** وهي نقاط في المستوى Plane ، مثلا النقاط *p(2,2) ,q(3,2),...etc* في المستوى *R x R* ويمكن أن تكون في المستوى الثالث . **Polygon** مضلع ، وقد يكون مثلث،مربع،دائرة،....الخ. **Convex** وهو مضلع يحقق الخاصية التالية : لكل نقطتين فيه *p,q* فان الخط الواصل بينها يقع داخل هذا الشكل . [![convex_polygon](https://informatic-ar.com/wp-content/uploads/2013/09/convex_polygon.png)](https://informatic-ar.com/wp-content/uploads/2013/09/convex%5Fpolygon.png) **Concave** هو المضلع الذي لا يحقق الخاصية أعلاه. [![concave_polygon](https://informatic-ar.com/wp-content/uploads/2013/09/concave_polygon.png)](https://informatic-ar.com/wp-content/uploads/2013/09/concave%5Fpolygon.png) **Convex Hull** هو أصغر مضلع Convex Polygon يحوي مجموعة من النقاط ، وهذا هو ما نريد ان نقوم بانشائه . [![convexhull](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull.png)](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull.png) المضلع -باللون البرتقالي- هو ال convex hull . **خوارزمية QuickHull لايجاد الى convex-Hull:** يوجد العديد من الخوارزميات لايجاد هذا المضلع ، وسنذكر هنا خوارزمية QuickHull وهي خوارزمية تعمل بمبدأ [Divide and Conquer](https://informatic-ar.com/http://en.wikipedia.org/wiki/Divide%5Fand%5Fconquer%5Falgorithm) حيث يتم تقسيم المشكلة الى اجزاء صغيرة -وهكذا يتم التقسيم الى ان نصل الى أصغر جزء ممكن- ثم تحل المشكلة وتجمع الحلول مع بعضها . وتطبيقها سيكون بالاستدعاء الذاتي ، وهو يعطي كود جميل ومقروء، لذلك يفضل الكثير هذه الخوارزمية على الرغم من انه توجد بعض الخورازميات الاخرى ذات أداء أفضل لكن بكود أكثر تعقيدا ! وسأشرح الخوارزمية مع مثال نظري لكي تتضح أكثر. نفرض ان لدينا مجموعة نقاط على المستوى : [![convexhull1](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull1.png)](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull1.png) الخطوة الاولى : ايجاد أصغر نقطة وأكبر نقطة على المحور الافقي - محور X [![convexhull2](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull2.png)](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull2.png) ومن ثم نوصل هاتين النقطتين معا . هاتين النقطتين ستكون داخل المضلع النهائي Proved هذا الخط سيقسم النقاط الى مجموعتين - فوق الخط وتحت الخط - لكن رياضيا نقول - يسار الخط ويمين الخط- [![convexhull3](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull3.png)](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull3.png) ونقول أن النقطة p تقع يسار الخط الواصل بين النقطتين u,v اذا كان حاصل الضرب الاتجاهي Cross Product للخط (المتجه) up و uv أكبر من الصفر ، اما اذا كان أقل من الصفر فهذا يعني أن النقطة p تقع يمين الخط (المتجه) uv. ملاحظة : هناك عدة طرق اخرى لمعرفة مكان النقطة ، وسأستخدم هنا قانون الضرب الاتجاهي. وكما هو معروف أن حاصل الضرب الاتجاهي لمتجهين هو متجه اخر عمودي على المتجهين ، هذا المتجه وليكن uw لديه اتجاه معين (موجب او سالب) ، ويحدد اتجاه هذا المتجه بناءا على موضع النقطة المراد معرفة مكانها ، فاذا كان المتجه up (الذي طرفه الاخير النقطة p ) يصنع زاوية بعكس عقارب الساعة مع المتجه uv فان حاصل الضرب الاتجاهي سيكون موجب ، اما اذا كان المتجه up يصنع زاوية في اتجاه عقارب الساعة clockwise فان حاص الضرب الاتجاهي مع uv سيكون سالب . \- سأحدث هذه الفقرة لاحقا لكي اضيف بعض الصور التوضيحية وكذلك رموز LaTeX للمعادلات -. انظر في هذا ال Applet للمزيد : http://physics.syr.edu/courses/java-suite/crosspro.html نعود الان الى بقية خطوات الخوارزمية ، وقد وصلنا الى أن هناك مجموعتين من النقاط ، وسنشرح على المجموعة الاولى ، والثانية ستكون بنفس الفكرة. الخطوة الثانية : ايجاد أبعد نقطة من الخط uv . حيث أن أبعد نقطة من الخط ستكون داخل المضلع النهائي. [![convexhull4](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull4.png)](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull4.png) ولايجاد أبعد نقطة من خط ، توجد العديد من الطرق الرياضية ، ساستخدم المعادلة التالية : \- سأحدث هذه الفقرة لاحقا لكي أضيف رموز LaTeX للمعادلات -. وكما تلاحظ من الشكل السابق ، أبعد نقطة قد شكلت لنا مثلث. الخطوة الثالثة : استبعاد النقاط الموجودة داخل المثلث . لانها بالطبع ليست نقاط عظمى Extreme Points [![convexhull5](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull5.png)](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull5.png) الخطوة الرابعة : كرر الخطوات 2 و 3 ! [![convexhull6](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull6.png)](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull6.png) لان الخوارزمية تعمل بالطريقة التكرارية recursively فكل المطلوب الان هو أن نمسك الضلع (اليمين واليسار) في المثلث كل على حدة ، ثم نقوم بايجاد ابعد نقطة عن الخط وبعدها سيظهر لنا مثلث ، وسنتجاهل النقاط بداخله ، .... وهكذا . **متى سنتوقف ؟** بالتأكيد عندما لا نجد أي نقطة فوق الخط uv مثلا. الخطوة الخامسة : كرر الخطوات على الجزء الاخر (الذي يقع يمين الخط) ، وهكذا تكون قد انتهت الخوارزمية [![convexhull7](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull7.png)](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull7.png) **تنفيذ الخوارزمية** Implementation of QuickHull in C++/Qt ```c #ifndef QUICKHULL_H #define QUICKHULL_H #include class QLine; class QuickHull { public: QuickHull(const QPolygon &polygon); static QPoint minPoint(const QPolygon &polygon); static QPoint maxPoint(const QPolygon &polygon); const QPolygon &convexHull(); private: void findHull(QPoint p1, QPoint p2, QPolygon set); static bool isLeft(const QLine &segment, const QPoint &point); static int distance(const QPoint &p1, const QPoint &p2, const QPoint &p3); static QPoint maxPointDistance(const QPoint &A, const QPoint &B, const QPolygon &set); QPolygon m_points; QPolygon m_convexhull; }; #endif // QUICKHULL_H ``` الفئة QPolygon هي عبارة عن مصفوفة من النقاط . دالة ايجاد أصغر نقطة : ```c /* Find the minimum point in the horizontal axis */ QPoint QuickHull::minPoint(const QPolygon &polygon) { QPoint min = polygon.at(0); foreach (QPoint p, polygon) { if (min.x() < p.x()) min = p; } return min; } ``` ايجاد أكبر نقطة : ```c /* Find the maximum point in the horizontal axis */ QPoint QuickHull::maxPoint(const QPolygon &polygon) { QPoint max = polygon.at(0); foreach (QPoint p, polygon) { if (max.x() > p.x()) max = p; } return max; } ``` معرفة مكان النقطة بالنسبة لخط ما ، يمين الخط ام يساره؟ ```c /* Check if point P is left to the line segment AB or not. ** This is done by take the Cross product AB x AP. ** if the result is positive (angle are counter-clockwise) it return true, else return false. */ bool QuickHull::isLeft(const QLine &line, const QPoint &p) { int cp = (line.x2() - line.x1()) * (p.y() - line.y1()) - (line.y2() - line.y1()) * (p.x() - line.x1()); return (cp > 0) ? true : false; } ``` حساب أبعد نقطة من خط ما ، لاحظ ان القانون ليس دقيقا فهو يتجاهل الكثير من الاشياء التي لن تأثر على المسافة ```c /* Compute the (Pseudo)distance between the line segment (p1,p2) and the point p3. */ int QuickHull::distance(const QPoint &p1, const QPoint &p2, const QPoint &p3) { int ux = p2.x() - p1.x(); int uy = p2.y() - p1.y(); int s = ux * (p1.y() - p3.y()) - uy * (p1.x() - p3.x()); if (s < 0) s = -s; return s; } ``` **بداية الخوارزمية** ```c /* Start of QuickHull Algorithm */ const QPolygon &QuickHull::convexHull() { if (m_points.size() < 3) return m_points; // Find the min/max point in the convex polygon QPoint A = minPoint(m_points); QPoint B = maxPoint(m_points); QLine segment(A, B); // Add min/max point to the m_convexhull m_convexhull << A << B; // Remove min/max from the set of all points m_points m_points.remove(m_points.indexOf(A)); m_points.remove(m_points.indexOf(B)); // Find the left and right points lies with the line segment (A,B). // This line divide the hull into upper and lower hull. QPolygon upperHull; QPolygon lowerHull; foreach (QPoint p, m_points) { if (isLeft(segment, p)) upperHull << p; else lowerHull << p; } findHull(A, B, upperHull); findHull(B, A, lowerHull); return m_convexhull; } ``` **الدالة التكرارية - تقسيم المشكلة وجمع الحلول هنا-** ```c void QuickHull::findHull(QPoint A, QPoint B, QPolygon set) { // Place to put the new extreme point int index = m_convexhull.indexOf(B); if (set.size() == 0) return; if (set.size() == 1) { QPoint p = set.at(0); set.remove(set.indexOf(p)); m_convexhull.insert(index, p); return; } // Find point that maximize the area of the triangle. QPoint C = maxPointDistance(A, B, set); // Add this point to the convex hull(proved) set.remove(set.indexOf(C)); m_convexhull.insert(index, C); // Add points left of AC to leftAC set and add points left of CB to leftCB // This will automatically discard all points inside this triangle ACB. QPolygon leftAC; QPolygon leftCB; foreach (QPoint q, set) { if (isLeft(QLine(A, C), q)) leftAC << q; } foreach (QPoint r, set) { if (isLeft(QLine(C, B), r)) leftCB << r; } findHull(A, C, leftAC); findHull(C, B, leftCB); } ``` وفي الملف المرفق ستجد مثال بسيط Quick & Dirty، وكل ما عليك ان تحدد مجموعة من النقاط ثم تضغط على زر start وسيتم تحديد ال convex hull. [![convexhullapp](https://informatic-ar.com/wp-content/uploads/2013/09/convexhullapp-300x195.png)](https://informatic-ar.com/wp-content/uploads/2013/09/convexhullapp.png) تطبيقات على الخوارزمية : Computer Visualization, Ray Tracing, Video Games, Replacement of Bounding Boxes Path Finding - Embedded AI of Mars mission Rovers Geographical Information Systems (GIS) - Computing Accessibility Maps Visual Pattern Matching - Detecting Car License Plates Verification Methods - Bounding of Number Decision Diagrams Geometry - Diameter Computation http://www.tcs.fudan.edu.cn/rudolf/Courses/Algorithms/Alg\_ss\_07w/Webprojects/Chen\_hull/applications.htm **روابط :** http://en.wikipedia.org/wiki/Convex\_hull http://westhoffswelt.de/blog/0040\_quickhull\_introduction\_and\_php\_implementation.html http://www.cse.yorku.ca/\~aaw/Hang/quick\_hull/Algorithm.html http://www.ahristov.com/tutorial/geometry-games/convex-hull.html **مرفق:** [convexhull.tar](https://informatic-ar.com/wp-content/uploads/2013/09/convexhull.tar.gz) ### خوارزمية البحث الخطي Linear search URL: https://informatic-ar.com/linear_search/ Last updated: 2026-10-03T22:32:08.000Z السلام عليكم و رحمة الله و بركاته هذه المقالة تابعة لسلسلة خوارزميات البحث و الترتيب و التي ستأتيكم بقية حلقاتها تباعا و كلي أمل بأن يستفيد الجميع. [![Couverture](https://informatic-ar.com/wp-content/uploads/2013/09/Couverture1-231x300.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Couverture1.png) **مقدمة :** تُعتبر خوارزمية البحث ركنا أساسيا من أركان علم الخوارزميات و تتخذ هذه الخوارزمية عدة أشكال, من أبسطها البحث عن عدد في مصفوفة مُحددة الحجم و يزداد الأمر تعقيدا عند الانتقال إلى البحث عن كلمة داخل نص, تماما كما ترى في محررات النصوص العادية والتي تحتوي على خاصية Find & Replace حيث أن أغلب المحررات الحالية تستخدم خوارزميه بحث تسمى [Boyer-Moore Searching](http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%5Fstring%5Fsearch%5Falgorithm?ref=informatic-ar.com "رابط خارجي") التي تُعد تقريبا من أسرع الخوارزميات في مجال البحث. هناك أيضا بحث من نوع آخر, فماذا لو كانت لدينا مجموعة حروف و نريد إيجاد جميع الكلمات التي تبدأ بهذه الحروف ؟؟ عادة ما يُسمى هذا النوع من الخوارزميات بــ prefix searching و لهذا النوع تطبيقات كثيرة خصوصا في محركات البحث و القواميس و المتصفحات التي تستخدم هذه الخوارزمية عند كتابتك لموقع يبدأ بحرف كنت قد زرته سابقا. لا تقتصر خوارزمية البحث على ما ذكرناه آنفا, فهناك نوع آخر من الخوارزميات يُستخدم لإيجاد نص قريب من النص الذي كنت تبحث عنه, حيث تقوم الخوارزمية بالبحث عن 4 أو 5 كلمات قريبة من الكلمات الخاطئة, تماما كما يفعل Google عند الترجمة أو Office Word عند كتابة نص يحتوي على أخطاء إملائية و تعتمد أغلب هذه الخوارزميات على الوزن الصوتي للحرف و من أشهرها [Soundex Searching](http://en.wikipedia.org/wiki/Soundex?ref=informatic-ar.com "رابط خارجي"). ليس هذا فقط، فمضادات الفيروسات تستخدم خوارزميات بحث سريعة للبحث عن وجود توقيع مطابق لأحد بيانات الملف المراد فحصه، وبما أن قواعد بيانات التواقيع تكون ضخمة للغاية فالبحث عن كل توقيع سيكون بطيئا جدا وهناك الأفضل، حيث توجد خوارزميات بإمكانها البحث عن عده تواقيع في نفس اللحظة وتستخدم بنية شجرية للقيام بهذا الأمر، هناك أيضا خوارزميات أخرى تعتمد على مفاهيم مختلفة مثل [Hash Table](http://www.arabteam2000-forum.com/index.php?showtopic=191706&ref=informatic-ar.com) وعدة أمور أخرى، و يُسمى هذا النوع من الخوارزميات بـ Multiple pattern searching و هو من أصعب الخوارزميات، وهناك العديد من مضادات الفيروسات التي تستخدم مثل هذه الخوارزميات مثل [ClamAV](http://fr.wikipedia.org/wiki/ClamAV?ref=informatic-ar.com "رابط خارجي"). في هذا الدرس سنتكلم عن أبسط هذه الخوارزميات وهي خوارزمية البحث الخطي أو Linear search algorithm. بعدها سنتكلم عن خوارزميات الترتيب و بعدها سننتقل إلى خوارزمية البحث الثنائي Binary search algorithm إن شاء الله. في هذه المقالة سنتطرق إلى النقاط التالية : 1\. الخوارزمية (الهدف, الفكرة, النتيجة, الإيجابيات و السلبيات) 2\. الخوارزمية بلغة السي++. 3\. التعقيد الزمني. 4\. ملأ مصفوفة عشوائيا و اختبار سرعة الخوارزمية. 5\. كم تحتاج هذه الخوارزمية من الوقت لتنفيذ مهمتها ؟ 6\. اختبر قدراتك ! **1\. الخوارزمية (الهدف, الفكرة, النتيجة, الإيجابيات و السلبيات)** الهدف : البحث عن قيمة المفتاح key داخل المصفوفة X. الفكرة : تعتمد هذه الخوارزمية على البحث التسلسلي (Sequential Search) في المصفوفة X حيث يبدأ البحث من أول عنصر إلى أن تنتهي المصفوفة, و في كل مرة نقارن محتوى الخانة الحالية X\[i\]l مع المفتاح Key, فإذا كانت القيمتان متساويتان (X\[i\] = Key) ستتم إعادة قيمة المتغير i الذي يُمثل مكان وجود المفتاح Key في المصفوفة X, أما إذا كانت القيمتان مختلفتان فسننتقل إلى الخانة الموالية X\[i+1\]l للبحث من جديد و هكذا. النتيجة : إذا كان Key موجود في X فسنحصل على رقم الخانة التي يوجد بها المفتاح و إلا فالقيمة المـُعادة ستكون -1. الإيجابيات : الخوارزمية بسيطة و تقليدية أيضا كما لا يُشترط الترتيب عند البحث. السلبيات : بطيئة و غير عملية, خصوصا عند معالجة المصفوفات الضخمة. **2\. الخوارزمية بلغة السي++** [![1](https://informatic-ar.com/wp-content/uploads/2013/09/1-300x68.png)](https://informatic-ar.com/wp-content/uploads/2013/09/1.png) الدالة linearSearch تستقبل 3 وسائط, الأول هو اسم المصفوفة المراد البحث داخلها و الوسيط الثاني هو طول المصفوفة أما الوسيط الثالث فهو المفتاح الذي نبحث عنه. بكل بساطة قمنا باستخدام حلقة for للمرور على جميع عناصر المصفوفة (لاحظ أن البحث خطي) واضعين بذلك شرط الحلقة كما يلي : إذا كانت قيمة الخانة الحالية مُساوية لقيمة المفتاح فقم بإعادة رقم الخانة. (مفهوم إعادة القيمة يرتبط بالخروج من الدالة) طيب ماذا عن الحالة العكسية ؟ أقصد إذا كان المفتاح غير موجود في المصفوفة ؟ هنا تأتي فائدة إعادة -1\. يُمكنك إبدال -1 بأي عدد سالب تماما, المهم أن لا يكون موجبا أو معدوما حتى لا يقع التباس بين القيمة التي تدل على عدم وجود المفتاح و رقم الخانة التي يوجد بها الأخير. نأتي الآن إلى الدالة الرئيسية main : [![2](https://informatic-ar.com/wp-content/uploads/2013/09/2-300x114.png)](https://informatic-ar.com/wp-content/uploads/2013/09/2.png) في البداية قمنا بالإعلان عن مصفوفة باسم xInt عدد عناصرها 5 ثم قمنا أيضا بالإعلان عن متغير باسم key حيث طلبنا من المستخدم إدخال قيمة للمتغير. لننتقل إلى الجزء الأهم في الــ main و هو تطبيق الدالة linearSearch على المصفوفة xInt و المتغير key : تم الإعلان عن متغير جديد باسم found الذي يحوي القيمة المـُعادة من طرف الدالة. بعد ذلك قمنا بفحص قيمة found فإذا كانت قيمته مساوية لــ -1 فهذا يعني أن المفتاح غير موجود في المصفوفة و إلا فالمفتاح موجود في الخانة رقم found+1 لأن الترقيم يبدأ من صفر فالخانة رقم 0 هي الخانة الأولى و هكذا. **تحذير** : الوسيط الثاني للدالة يُمثل الطول الفعلي للمصفوفة لذا لا تحاول أن تُرسل إلى الدالة عدد أكبر من طول المصفوفة لأن هذا الفعل يؤدي إلى حدوث الــ buffer overflow الذي ينتج عنه انتهاك لتسيير الذاكرة و قد يتوقف برنامجك. يمكنك إرسال عدد أصغر من طول المصفوفة إذا كنت ترغب في أن يقتصر البحث على جزء من المصفوفة. ملاحظة : إذا وُجد المفتاح أكثر من مرة في المصفوفة X فإن الدالة ستعيد رقم أول خانة يظهر فيها المفتاح.عليك الآن بتغيير الدالة لتعيد رقم آخر خانة يُوجد بها المفتاح. **إضافة بسيطة :** ماذا لو أردنا معرفة عدد المرات التي ظهر فيها المفتاح داخل المصفوفة ؟ لنفترض أن المصفوفة كبيرة جدا و سيتم البحث داخل مصفوفة جزئية من المصفوفة الرئيسية. إذا كل ما سنقوم به هو إضافة متغير جديد إلى جسم الدالة (أو لنقل عداد) و زيادة هذا العداد كلما وجدنا خانة مساوية للمفتاح هكذا : [![3](https://informatic-ar.com/wp-content/uploads/2013/09/3-300x71.png)](https://informatic-ar.com/wp-content/uploads/2013/09/3.png) في هذه الحالة ستستقبل الدالة أربع وسائط هي : اسم المصفوفة الجزئية و من أين تبدأ و أين تنتهي ؟ إضافة إلى المفتاح. كالعادة نبدأ بالمرور على جميع عناصر المصفوفة و كل ما وجدنا خانة مُساوية للمفتاح أضفنا 1 إلى قيمة العداد. عند نهاية الحلقة, إذا كانت قيمة العداد أكبر أو تساوي واحد فإن المفتاح يوجد على الأقل مرة واحدة داخل المصفوفة و هنا ستتم إعادة قيمة المتغير found و في حالة العكس ستتم إعادة -1. [![4](https://informatic-ar.com/wp-content/uploads/2013/09/4-300x120.png)](https://informatic-ar.com/wp-content/uploads/2013/09/4.png) في الدالة الرئيسية قمنا بالإعلان عن مصفوفة تحتوي على 18 عنصر بينما قمنا بالبحث عن القيمة 17 داخل مصفوفة جزئية تتكون من 6 عناصر فقط (المصفوفة الجزئية تبدأ من الخانة السادسة و حتى الثانية عشر). برأيك ما مُخرجات البرنامج ؟ **3.التعقيد الزمني :** كما ذكرنا آنفا فإن هذه الخوارزمية تعتمد على البحث الخطي أو المتسلسل حيث يبدأ البحث من أول خانة منتقلا إلى الخانة المجاورة إن لم يجد المفتاح و هكذا حتى الوصول إلى الخانة الأخيرة. مما يعني أن الزمن المستغرق لتنفيذ هذه الخوارزمية يزيد كلما زاد حجم المصفوفة. إذا خوارزمية البحث الخطي لها تعقيد زمني[![cmplx1](https://informatic-ar.com/wp-content/uploads/2013/09/cmplx1.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/cmplx1.gif) في أفضل الحالات يمكن أن نجد المفتاح في أول خانة[![cmplx2](https://informatic-ar.com/wp-content/uploads/2013/09/cmplx2.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/cmplx2.gif) و في المتوسط [![cmplx3](https://informatic-ar.com/wp-content/uploads/2013/09/cmplx3.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/cmplx3.gif) و في أسوء الحالات يمكن أن نجد المفتاح في الخانة الأخيرة كما يمكن أن يكون هذا الأخير غير موجود. كمثال للتوضيح لنفترض الجدول الآتي : [![tab1](https://informatic-ar.com/wp-content/uploads/2013/09/tab1-300x20.png)](https://informatic-ar.com/wp-content/uploads/2013/09/tab1.png)إذا قمنا بتطبيق خوارزمية البحث الخطي على الجدول فسنمر بالخطوات التالية : [![tab2](https://informatic-ar.com/wp-content/uploads/2013/09/tab2-300x180.png)](https://informatic-ar.com/wp-content/uploads/2013/09/tab2.png) يوجد المفتاح في الخانة ذات **اللون الأبيض و الخلفية السوداء** و في كل مرة تتم مقارنة الخانة ذات **اللون الأحمر و الخلفية البيضاء** مع المفتاح. لاحظ أنه من خلال البحث عن المفتاح تم تشكيل **قطر** لمثلث قاعدته عبارة عن المصفوفة التي تبدأ بأول عنصر و تنتهي بالمفتاح. **4\. ملأ مصفوفة عشوائيا و اختبار سرعة الخوارزمية** قمت بإدراج هذه الفقرة لكي ألفت انتباهك أخي القارئ إلى مفهوم التعقيد الزمني من الناحية العملية. لذا سنقوم بالإعلان عن مصفوفة (كبيرة الحجم نسبيا) تحتوي على مائة ألف خانة ثم نقوم بملأ هذه المصفوفة بشكل عشوائي و من ثم نستدعي دالة البحث للبحث عن المفتاح. بكل بساطة, توجد حالتان, إما أن يكون المفتاح غير موجود داخل المصفوفة أو العكس. الحالة الأولى ثُمثل أسوأ حالات الخوارزمية أما الحالة الثانية فقد تمثل أفضل الحالات أو الحالة المتوسطة. ما يهمنا في هذه الفقرة هو عدد الخانات التي مرت بها الدالة قبل الوصول إلى المفتاح أو لنقل عدد المقارنات اللازمة للحصول على الخانة المساوية لقيمة المفتاح. إذا لم يتم العثور على المفتاح فهذا يعني أن عدد المقارنات يساوي عدد عناصر المصفوفة أما إذا تم العثور على المفتاح فهذا يعني أن عدد المقارنات يساوي رقم الخانة التي يوجد بها المفتاح + 1 لأن ترقيم عناصر المصفوفة يبدأ من صفر. [![5](https://informatic-ar.com/wp-content/uploads/2013/09/5-300x158.png)](https://informatic-ar.com/wp-content/uploads/2013/09/5.png) **ملاحظة :** - **تكرار** الأعداد العشوائية المـــُولدة يعتمد على عدة عوامل منها **سعة المجال** الذي تم توليد الأعداد العشوائية بداخله. - سبق و أن كتبتُ موضوع يتحدث عن توليد الأعداد العشوائية بشيء من التفصيل في منتديات الفريق العربي للبرمجة, يمكنك مراجعة الموضوع من هنا : [توليد الأرقام العشوائية ! (نظرة تحليلية مفصلة)](http://www.arabteam2000-forum.com/index.php?showtopic=217773&ref=informatic-ar.com) **5\. كم تحتاج هذه الخوارزمية من الوقت لتنفيذ مهمتها ؟** قد يدور في ذهنك السؤال التالي : هل نستطيع رصد الوقت الذي تستغرقه هذه الخوارزمية ؟ و الإجابة هي نعم فهناك دوال API تمكننا من الحصول على فترات توقيت أقل من 1 ميلي ثانية، لكن ولسوء الحظ فإن هذه الدوال تعتمد بشكل كامل على النظام و خصوصا المعالج المركزي. يجب أن يدعم النظام ما يسمى بــ عداد الأداء عالي الدقة (High Resolution Performance Counter) حيث تعتمد دقة هذا العداد على المعالج المركزي. توجد دالتان من دوال API تستخدمان لهذا الغرض : الدالة الأولى هي QueryPerformanceFrequency حيث تعطينا تردد العداد أو لنقل عدد الدورات في الثانية الواحدة و تعيد صفراً إذا كان النظام لا يدعم العداد العالي الدقة. أما الدالة الثانية فهي QueryPerformanceCounter التي تعيد القيمة الحالية للعداد. بهذا يمكننا تقييم المدة التي يأخذها تنفيذ جزء معين من الكود و ذلك باستدعاء الدالة QueryPerformanceCounter مباشرة قبل الجزء الذي نريد تقييم مدته الزمنية ثم استدعائها مرة أخرى مباشرة بعده، ثم نحسب الفرق و نقسمه على التردد فنحصل على الوقت الذي استغرقه تنفيذ ذلك الجزء من الكود. نفرض مثلاً أن التردد كان 50000 دورة في الثانية، و أن القيمة التي حصلنا عليها قبل الكود هي 1500 و القيمة بعد الكود هي 3500, الفرق هو 2000، و بالقسمة على 50000 نحصل على 0.04 ثانية. [![6](https://informatic-ar.com/wp-content/uploads/2013/09/6-300x46.png)](https://informatic-ar.com/wp-content/uploads/2013/09/6.png) يمكنك أيضا استخدام timeGetTime أو GetTickCount إذا أردت معرفة التوقيت بدقة أقل. يمكنك بهذه الإجراءات قياس الفروق الزمنية بدقة ميللي ثانية واحدة على الأكثر، وهذه الدقة قد تكون كافية لقياس عمل الخوارزمية التي نتحدث عنها. بهذه الطريقة ستريح نفسك من الصداع الذي تسببه لك المؤقتات الأخرى مثل QPC و RDTSC. المؤقت QPC يستخدم ما يحلو له في الجهاز ليقدم أكبر دقة ممكنة. قد يعتمد في بعض الأجهزة على تعليمات مباشرة للـــ BIOS ، وفي بعض الحالات قد يقرأ القيم من أماكن أخرى غريبة. لذا ليست له وحدة ثابتة تستطيع استخدامها، فعندما تنادي الدالة QueryPerformanceCounter ستستقبل قيمة .. الله أعلم ماذا تعني !! قد تكون الدورة هي معدل نبض ساعة المعالج Processor Clock وقد تعني أي شيء آخر. لذلك أنت بحاجة إلى شيء ما يساعدك على تحويل هذه القيمة إلى وحدة زمنية كالثواني, وهنا تأتي الإجراء فائدة الدالة QueryPerformenceFrequency حيث تعيد عدد "الوحدات" في الثانية الواحدة. (انظر [Timing ومشاكله !](http://www.agdn-online.com/communities.aspx?view=posts&threadid=391&ref=informatic-ar.com "رابط خارجي")) مثلاً : أعطتنا QueryPerformanceFrequency القيمة 15000000 (15 مليون وحدة في الثانية)، وسنسميها التردد. وقمنا باستدعاء QueryPerformanceCounter مرتين على فترتين متباعدتين وطرحنا الفرق لنحصل على 45000000. الآن يمكننا أن نقسم هذه القيمة على التردد لنحصل على 3 ثواني. وهذا هو مبدأ عمل المؤقت QPC الذي تصل دقته إلى النانوثانية، ويستخدم في تزمين الصوت مع الصورة في مشغلات الأفلام كما يُستخدم في قياس الزمن المستغرق لتنفيذ الإجراءات السريعة في ++C. إلا أن هناك بعض الأمور التي تمنع QPC من أن يكون مؤقتاً مثاليا, تتخلص هذه الأمور في شيئين : تعدد المعالجات، والمعالجات التي تغير سرعتها بشكل مستمر. في حالة تعدد المعالجات، لو اعتمد QPC على عداد التعليمات في المعالج (وهي الحالة الأكثر شيوعاً) فإنك قد تحصل على نتائج غريبة بين النداءات المختلفة للإجراء، وذلك بحسب أي المعالجات قام بتنفيذ الطلب. فمثلاً المعالج الأول أنجز مئة مليون تعليمة حتى الآن، بينما الثاني أنجز 90 مليون تعليمة فقط، ولذلك فقد تحصل أحياناً على نتائج مضحكة ! كأن يعود الزمن إلى الوراء ( يكون الفرق سالبا ) أو يقفز قفزات كبيرة مفاجئة للأمام. في الحالة الثانية فإن بعض المعالجات تغير سرعتها بشكل مستمر على حسب الطاقة المتوفرة، وهذه المعالجات تتواجد بشكل رئيسي في الأجهزة المحمولة laptops حيث يخفف المعالج من سرعته عندما يدخل في نظام توفير طاقة البطارية.. وهنا فإن قيمة التردد التي حسبناها مسبقاً تصبح خاطئة لأن التردد قد تغير ويجب قراءته مرة أخرى ...! **6.اختبر قدراتك :** لدينا مؤسسة صغيرة تحتوي على مجموعة من العمال, كل عامل مُعرف برقم ID واسم و مرتب الشهر, عند تشغيل البرنامج سيُدخل المستخدم اسم العامل و كلمة المرور ثم يقوم البرنامج بالبحث داخل بيانات العمال, فإذا كان اسم العامل موجود و كلمة المرور صحيحة فسيتم إظهار رسالة ترحيبية بالإضافة إلى المرتب الشهري للعامل و إلا فسيُظهر البرنامج رسالة تفيد بأن هذا العامل غير مُسجل. إلى هنا أصل بكم إلى نهاية الدرس, أرجو أن تكونوا قد استفدتم. لا تنسوني من صالح الدعاء. تحياتي. ### تعرَّف على أشهر مكتبات الــ GUI في سي++ URL: https://informatic-ar.com/gui_library_cpp/ Last updated: 2026-10-03T22:32:09.000Z السلام عليكم و رحمة الله و بركاته لا يزال السؤال الذي يدور في أذهان الكثير من عشاق ++C : مالسبيل إلى برمجة الواجهات الرسومية !؟ في هذه المقالة سأحاول أن أجيب على هذا السؤال من خلال التطرق لأشهر مكتبات الــ GUI في السي++. **الفهرس** 1. **هل توجد مكتبات GUI لـــ ++C ؟** 2. **Microsoft Visual C++ and MFC** مالفرق بين ++C و ++VC ؟ الــ ++VC هو الوحيد في هذا المجال ؟ أشهر البرامج التي كُتبت بــ ++VC الإيجابيات و السلبيات ما هي MFC ؟ إيجابيات و سلبيات الـــ MFC 3. **C++ Builder and VLC** نبذة تاريخية ما هي VCL ؟ البرامج التي كُتبت في بيئة C++ Builder 4. **wxWidgets** ما هي wxWidgets ؟ نبذة تاريخية الترخيص السلبيات المميزات برامج تستخدم wxWidgets 5. **Framework QT** ما هي QT ؟ نبذة تاريخية Modules إصدارات QT مميزات QT أشهر البرامح المكتوبة بــ QT 6. **الخاتمة** **هل توجد مكتبات GUI لـــ ++C ؟** نعم, توجد عدة مكتبات C/CPP لبناء الواجهات الرسومية, بعضها متعدد المنصات و بعضها الآخر يعمل على نظام تشغيل واحد فقط, المكتبات الأشهر هي : Microsoft Foundation Classes - MFC Visual Component Library - VCL wxWidgets Qt **Microsoft Visual C++ and MFC** [![VC++](https://informatic-ar.com/wp-content/uploads/2013/09/VC++.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/VC++.jpg) **مالفرق بين ++C و ++VC ؟** ++C هي إحدى اللغات البرمجية العالية المستوى, صُممت لتدعم بشكل مباشر أنماط برمجية متعددة : Procedural programming, Object-oriented programming, Generic programming السي++ هي اللغة الأفضل لتصميم التطبيقات ذات الواجهات المعقدة، وللتعامل مع البنية الصلبة للحاسوب وذلك لسرعتها في التنفيذ والتي ورثتها عن أمها الحنون : السي. بالإضافة إلى ما سبق, يعتبر البعض أن مشكلة السي++ هي أنها تعطي للمبرمج الخيار حتى لو كان اختياره غير صحيح (خصوصا في قضايا إدارة الذاكرة : حجز/تحرير/تسرب, ..) إلا أن أصحاب اللغة ينظرون إلى هذه الأمور باعتبارها دليل على حرية المبرمج و عدم التضييق عليه, و يقولون أنها إحدى مميزات السي++ التي تنفرد بها عن بقية اللغات .. بالسبة لــ Microsoft Visual CPP فهو عبارة عن IDE أي بيئة تطوير متكاملة (Integrated Development Environment) و سُميت كذلك لأنها تساعد المبرمج في كل من التحرير, الترجمة و الربط, ففي السابق كانت الترجمة و الربط يتمان على شكل أوامر ! أما في الـ IDE فأصبحت عملية الربط و الترجمة تتم عن طريق الضغط على زر واحد من لوحة المفاتيح و بالتالي بيئة التطوير هي من يقوم بتنفيذ هذه الأوامر بدلا منك. إذا, MSVC ليس لغة برمجة .. **الــ ++VC هو الوحيد في هذا المجال ؟** لا, ليس الوحيد و لكنه عملاق الساحة !, يقول الخبير Scott Meyers في سلسلة مقالاته البرمجية الشهيرة The most important ever : إن مترجم مايكروسوفت منذ عام 92 هو أحد أسباب تطور لغة ++C ولو جمعنا كافة مبرمجي ++C في الكرة الأرضية ثم سألناهم عن المترجمات التي يستخدمونها في إنجاز أعمالهم فستكون الإجابة لدى الغالبية الكبرى هي مترجم مايكروسوفت, لذلك يشتبه على الكثير من المبتدئين الفرق بين لغة ++C و بين ++VC كبيئة تطوير. في إصداراته حتى عام 98 لم يكن VC يلتزم بالمعايير لأنه لم يكن هناك معايير أصلاً, المعايير ظهرت في عام 98 قبيل إصدار VC 6 بقليل, و المعايير تحتاج إلى وقت ليس بالقصير لتبنيها من قبل مصنعي المترجمات وقد حلت معظم مشاكل التوافق مع المعايير منذ عام 2003. **أشهر البرامج التي كُتبت بــ ++VC** - حزمة الأوفيس (Microsoft Office) بجميع إصداراتها. - النسخة التي تعمل على الويندوز من برنامج MathType (كُتبت بلغة C). - معظم برامج Windows الناجحة كُتبت بــ VC++. **السلبيات و الإيجابيات** سلبياته : يعمل على الـ Windows فقط !, كبير الحجم و بطئي شيئا ما !!, غير مجاني أيضا. إيجابياته : يوفر العديد من الأدوات التي تُسهل عمل المبرمج (مثل برمجة الشبكات, الاتصال بقواعد البيانات, ...) كما يحتوي على Debugger قوي جدا ! **ما هي MFC ؟** مكتبة MFC هى اختصار لـــ The Microsoft Foundation Class وهى مكتبة تتكون من مجموعة من أنواع البيانات (data types) والدوال (functions) والفئات (classes) والثوابت (constants), تُستخدم عادة في إنشاء التطبيقات التي تعمل على عائلة نظام التشغيل ويندوز . **إيجابيات و سلبيات الـــ MFC** الإيجابيات : الـ MFC قريبة جدا من نظام التشغيل, قوية, سريعة, خفيفة, تستهلك ذاكرة أقل. السلبيات: - مشاكل في دعم و تسيير اليونيكود (management unicode). - التوثيق ضخم لكن غير منظم (bloated, but not structured). - استخدام الــ event loop (messages) بدلا من Callback /Listener. - وجود طبقة كائنية التوجه (object-oriented wrapper) تسمح بالوصول إلى API Windows المكتوبة بــ C. **C++ Builder and VLC** [![C++ Builder](https://informatic-ar.com/wp-content/uploads/2013/09/C++-Builder-300x158.png)](https://informatic-ar.com/wp-content/uploads/2013/09/C++-Builder.png) **ما هو C++ Builder ؟** C++ Builder عبارة عن RAD قامت بتصميمه شركة Borland حيث يعتمد على نفس المفاهيم, الواجهة و المكتبة الـمُستخدمة مع Delphi. يسمح CB بإنشاء تطبيقات Win32 سريعة كما يُوفر واجهة رسومية أنيقة و سهلة أيضا وهو مُتوافق مع ISO CPP. **نبذة تاريخية** بدأ C++ Builder حربه الشرسة مع الــ VC في تسعينيات القرن الماضي, حيث أطلقت شركة Borland أول نسخة من CB في عام 1997\. بالرغم من أن تلك النسخة لم تُشكل خطرا يُذكر على MSVC إلا أن ذلك لم يقف عائقا دون إصدار نسخ جديدة من الـــ Builder حيث توالت النسخ تباعا .. وصولا إلى C++ Builder 6. عندما أعلنت ميكو عن Microsoft Visual Studio قامت شركة بورلاند أيضا بحركة مماثلة حيث أعلنت عن المنافس الجديد Borland Developer Studio, في عام 2007 تولى فرع Code Gear (التابع لشركة بورلاند) برمجة و تطوير BDS. شهد عام 2008 بيع فرع كود جير إلى شركة Embarcadero قبل أن يتم إصدار نسخة CB9 التي تعتبر من أقوى نسخ C++ Builder و أكثرها أمنا, سرعة و ثباتا. الجدول التالي يحتوي على أرقام نسخ CB منذ بدايته و حتى الآن : [![Historique](https://informatic-ar.com/wp-content/uploads/2013/09/Historique-142x300.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Historique.png) **ما هي VCL ؟** الـــ VCL اختصار لـــ Visual Components Library وتعني مكتبة المكونات المرئية, عبارة عن إطار كائني التوجه مكتوب بلغة الـــ Object Pascal, من ضمن المكتبات الرسومية الموجهة إلى سي++, نجد أن VCL هي الوحيدة الغير مكتوبة بــ C++), تتميز VCL بسهولة VB و قوة VC. الشيء الذي جعلها تخطو خطوات إلى الأمام لتأخذ مكانا متقدما في لائحة البكتبات الرسومية. ابتداء من 2009, أصبحت VCL تدعم اليونيكود بشكل ممتاز حيث يمكن التعامل مع ملفات ذات أسماء non-ANSI لذا تجد أنه تمت إضافة فئات جديدة من أجل حل مشكلة ترميز Unicode. المبرمجون الذين انتقلوا من MFC إلى VCL يُدركون جيدا أن إنشاء الواجهات في C++ Builder سهل جدا لأنه يستخدم VCL وهي أسهل بكثير من MFC. المكتبة المكافئة لــ VCL في Linux هي LCL و التي تعرف اختصارا بــ Lazarus Component Library, هذه الأخيرة متعددة المنصات وهي متوافقة 100% مع VCL. **البرامج التي كُتبت في بيئة C++ Builder** كُتب بها البرنامج الشهير WinRAR و المنقح المعروفIDA Pro وOllyDbg أيضا, بالإضافة إلى برنامج Magic ISO الذي يُستخدم للتعامل مع ملفات ISO لنسخ أقراص CDROM وحرقها دون أن ننسى العملاق Panda Titanium Antivirus و اللائحة تطول: - ImgBurn - Audiograbber - GoldWave - Clone CD - Norton PartitionMagic - Easy CD-DA Extractor - AVS Video Tools - ... **wxWidgets** [![wxWidgets](https://informatic-ar.com/wp-content/uploads/2013/09/wxWidgets-300x63.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/wxWidgets.jpg) **ما هي wxWidgets ؟** wxWidgets كانت تُسمى wxWindows و لكن تم تغيير الاسم بعد أن تقدمت Microsoft بشكوى ضد الإسم القديم, wxWidgets عبارة عن مكتبة رسومية حرة و متعددة المنصات, تتكون من مجموعة أدوات لإنشاء واجهات رسومية يتغير شكلها تبعا لنظام التشغيل و هذا ما يُميزها عن بقية المكتبات المنافسة. تملك wxWidgets دعم صناعي عريض, حيث تضم قائمة المستخدمين AOL و AMD و CALTECH و Lockheed Martin و NASA و مؤسسة تطبيقات المصادر المفتوحة و Xerox و آخرون كُثر. المكتبة الأصلية مكتوبة بــ C++ لكن هذا لم يمنع من وجودة مجموعة Bindings للغات برمجية أخرى, مثل : Python - wxPython Perl - wxPerl BASIC - wxBasic Lua - wxLua OCaml - wxCaml JavaScript - wxJavaScript Java - wxJava ou wx4j Ruby - wxRuby Eiffel - wxEiffel Haskell - wxHaskell C#/.NET - wx.NET Euphoria - wxEuphoria D – wxD بعض هذه المكتبات أكثر تطورا من غيرها, لكن تبقى المكتبات الأكثر شعبية هي wxPython, wxPerl and wxBasic. **نبذة تاريخية** بدأت مكتبة wxWidgets و التي كانت تسمى wxWindows في 1992 في معهد تطبيقات الذكاء الاصطناعي بجامعة إدنبرغ بواسطة جوليان سمارت عندما كان يعمل على أداة رسم بياني أطلق عليها Hardy ، لم يرغب في الاختيار بين نشرها على منصات صن أو الحواسيب الشخصية ، فلذا قرر أن يختار أطار عمل متعدد المنصات . كانت الأدوات التجارية متعددة المنصات قليلة جدا ، فضلا على أن المعهد لا يملك الميزانية الكافية لها على أية حال ، لذا كان الخيار الوحيد هو بناء أداة متعددة المنصات، و في سبتمبر 1992 سمح الجامعة لجوليان أن يرفع الإصدار الأول من مكتبته على موقع ال FTP للجامعة. مكتبة wxWidgets (حرف w من Windows و x من X خادم النوافذ في اليونكس ) بدأت بدعم Xview و MFC 1.0 . بعدما أصبح واضحا أن XView قد بدأ عصره بالذهاب، كتبت نسخة wxWidgets لمنصة Motif . بدأ مستخدمو ++ Borland C بطلب نسخة لا تعتمد على MFC ، لذا نسخة الويندوز أعيدت كتابتها لتستخدم دوال API الويندوز الأصلية. \[المصدر : وادي التقنية\] مع الوقت، بدأ بالظهور مجتمع صغير لـ wxWidgets ولكن متحمس، و أعدت القوائم البريدية. المشاركات و الإصلاحات بدأت بالإرسال إلى المشروع الجديد، كانت أكبر المشاركات هي نسخة Mac و Xt. بدأت مكتبة wxWidgets بشكل تدريجي باستقطاب المزيد والمزيد من المستخدمين من شتى أنحاء العالم: التجاري ، و الحكومي و مستخدمين الشركات – معظمهم راضين – الذين وجدوا أن wxWidgets توفر منتج أفضل و دعم أفضل من المنتجات التجارية التي نظروا إليها أو استخدموها. خلال 1995 ، أصدر ماركوس هولزم نسخته من wxWidgets إلى منصة Xt وهي مجموعة أدوات X . هذا عنى أن البرمجيات يمكن أن تكتب و تعمل على أنظمة X من دون الحاجة إلى تثبيت Motif التي هي منتج تجاري. في 1996 ، ركد مشروع wxWidgets قليلا. غادر جوليان معهد تطبيقات الذكاء الاصطناعي ليتفرغ إلى أعمال الاستشارة الحرة، وكانت هناك القليل من الإصدارات. على ما يبدو أن تقدم جافا غير المتوقف ألقى بعض الشك على مميزات مكتبات ++C . بعد عدة مشاركات ، أصبح كود wxWidgets بالأحرى متعب وبحاجة إلى إعادة تفكير جدية. في بداية عام 1997 ، بدأ التفكير جديا بإعادة هندسة wxWidgets مع دوال API مرنة بما فيه الكفاية لواجهات المستخدم الحالية والمستقبلية، مع المزيد من الأدوات و استخدام أفضل لـ ++C. بعد تداول الحوارات بين جوليان سمارت و ماركوس هولزم ( مؤلف نسخة Xt) كان هناك القرار الجدير بالاهتمام. جوليان سيكتب نسخة الويندوز و ماركوس سيجمع نسخة Motif و Xt ، وسيتشارك كلاهما في الأجزاء العامة. أفكار دوال wxWidgets 2 بدأت بالتشكل و بدأ العمل على كتابتها. خلال 1997 بدأت في الظهور فكرة إنتاج بيئة سطح مكتب قياسي للينكس (GNOME) . هذه البيئة تعتمد على +GTK التي بنيت فوق X11، و بالرغم ذلك فإن التطبيقات المبنية على +GTK ستصبح قياسية لعالم اللينكس. كانت هناك مشكلة واحدة رئيسية، +GTK مبنية على لغة السي، ويوجد لها تغليف بسيط لـ ++C ( وغير قابل للنقل). في أغسطس 1997 ، قدم والفرام جلوجر اقتراح بأن wxWidgets2 يجب أن تنقل إلى +GTK. كان لروبرت روبلينج تحفظات أولية على الفكرة، ولكن في العموم هو داعم للفكرة. أصبح المحرك الرئيسي لـ wxGTK ، ونسخة اﻷلفا أصبحت متوفرة في بداية 1998\. في مارس 1998 دمجت نسخة الويندوز و +GTK و وضعت في مستودع CVS وأصبحت متوفرة لجميع المساهمين لـ wxWidgets. في تلك الأثناء، أصبح فاديم زيتلين مطور نشطا في wxWidgets، وأعد معظم نشاط المطورين. في بداية 1998 ، انقطع ماركوس هولزم عن wxWidgets2 و لكن جوليان سمارت بدأ نسخة wxMotif جديدة. فكرة نسخة Xt أبعدت، بعد أن توفر احتمالين للبرمجة اليونكس مجانية مع wxWidgets: أولا مكتبة wxGTK و wxMotif مع نسخة Motif المجانية Lesstif. في سبتمبر 1998 ، بدأ ستيفن سومور نسخة جديدة من wxMac 2 مبنية في جزء منها على عمل جريج وايتهد المبدئي. ظهرت wxWidgets 2.1.11 مصحوبة بصفوف wxHTML الرائعة من عمل فاكلاف سلافيك ، مع تحسينات لاحقة تشمل القدرة على الطباعة ومعاينة ، و تطبيق helpview و صف متحكم المساعدة لنظام المساعدة HTML متعدد المنصات. أعطى wxHTML التطبيقات وصول سهل إلى عرض النص المنسق و قابلية التقارير. شهد منتصف 2000 إطلاق wxDesigner ، محرر الحوارات و أداة التطوير السريع من عمل روبرت روبلينج التي امتلكت انتشار واسعا. أيضا شهدت عام 2000 بداية wxUniversal وهي عبارة عن مجموعة من الأدوات للمنصات التي لا تمتلكها، لأن بعض المنصات تمتلك أدوات لا تمتلكها المنصات الأخرى. كتب wxUniversal بشكل أساسي فادم زيتلين و موله شركة تطبيقات SciTech ، وهي أيضا التي قامت بتمويل فاكلاف سلافيك لكتابة نسخة إلى منصتهم MGL . في يوليو 2000 انضم جوليان سمارت إلى إدارة ردهات كامبردج في بريطانيا، وغادرها في فبراير 2002 ليتابع منتجاته عندما قررت ردهات إيقاف مشروع eCos الهادف إلى إنشاء نظام تشغيلي مضمن. من أواخر 2001 إلى منتصف 2002 ، قام جوليان سمارت و روبرت روبلينج بإضافة نسخة wxX11 باستخدام أدوات wxUniversal ، وهذه النسخة تتطلب فقط نظام اللينكس و خادم X11، وهي مناسبة لأي بيئة لينكس تستخدم مواصفات بسيطة من المساحة. في أكتوبر 2002 نجح روبرت روبلينج ببناء نسخة wxX11 لنسخ اللينكس المألوفة التي تعمل على iPAQ. روبرت نجح أيضا في امتلاك wxGTK تعمل بـ +GTK مع دعم اليونيكود ، فاتحا بذلك إلى wxWidgets أسواقا جديدة. في يوليو 2003 بدأت wxWidgets بالعمل على Windows CE ، و أثبت روبرت روبلينج أن تطبيقات wxGTK تعمل على GPE منصة اللينكس المضمنة. نسخة OS/2 بواسطة دايفد ويبستار و ستيفان نيس تطور بشكل بطيء ولكن ثابت خلال سنوات الماضية. في فبراير 2004 ، أعلن جوليان سمارت أن اسم المشروع سيغير اسمه إلى wxWidgets بدلا من wxWindows ، كنتيجة لضغط شركة مايكروسوفت عليه لاحترام العلامة التجارية " Windows “ المسجلة لدى مايكروسوفت في فرع المملكة المتحدة. ويخطط لإصدار wxWidgets 3 مع بداية 2008 حيث تركز أكثر شيء على دعم اليونيكود بشكل واسع مع تطوير الدوال و تسهيل استخدامها. **ترخيص wxWidgets** توزع مكتبة wxWidgets تحت ترخيص مخصص ، مشابه إلى GNU Lesser General Public License - الذي يسمح باستغلالها لكتابة برامج حرة وتجارية معا - مع استثناء أن وضع العمل المشتق منها في صغيته الثنائية يمكن أن ينشر تحت شروط المستخدم الخاصة. هذا الترخيص هو ترخيص برامج الحرة ، ويجعل wxWidgets برنامجا حرا. **سلبيات wxWidgets** لكل مكتبة سلبيات و مكتبة wxWidgets لا تخلو منها ، وهذه أهمها: - ضعف في المكونات الرسومية التجارية لصنع شبكات رسومية جميلة و لصنع الرسوم البيانية، ولكن يمكن إلقاء نظرة على wxCode . - لا يوجد دعم لثيمات ، ما لم تستخدم wxUniversal أو wxSkin. - منصة wxX11 لا تقارن بالمكتبات الآخرى و غير مستقرة. يجب عليك استخدام wxGTK بدلا منها، وتبني تطبيقاتك على GTK بدلا عن X11 مباشرة. منصة wxX11 غالبا تستهدف إلى الأجهزة المضمنة والتي لا تملك GTK. - تحاول wxWidgets أن تدعم طاقم المميزات مكلفة ، وكنتيجة بعض المكونات قليلة الاستخدام ليست مستقرة أو لا يعتمد عليها بالمقارنة بالمكونات العامة. ومثلما مع كل مكتبة أدوات مفتوحة المصدر ، الاختبارات المعمقة أفضل حل هنا. - بالنسبة للغة العربية فإنها لا تدعم تغيير اتجاه البرنامج من اليمين إلى اليسار بشكل جيد ، بالرغم أن المكتبات الأصلية تدعم هذه الخاصية. - تطورها بطيء نوعا ما وهذا ما يعيقها عن الإنتشار الكامل. **مميزات wxWidgets** - مكتبة wxWidgets لا فقط على ++C ، ولكنها أيضا تملك جسورا إلى بايثون و بيرل و جافا و إيفيل و سي شارب (الدوت نت ) و البيسك و روبي و lua و حتى جافا سكربت. مما يسهل عملية البرمجة بها لمن لا يعرفون لغة ++C - تعتبر أحد المكتبات أدوات GUI المكتملة، وتملك العديد من الصفوف الخدمية. - تمتلك توثيقا جيدا . - مجانية للاستخدام الشخصي و التجاري معا. - كلما أمكن تستخدم مكتبة wxWidgets منصة التطوير الأصلية. وهذا يعني أن البرنامج إذا بني على الويندوز سيأخذ مظهر و سمة برامج الويندوز و عندما يبنى على منصة اللينكس سيأخذ مظهر وسمة برامج اللينكس. **برامج تستخدم wxWidgets** - Poedit: برنامج لترجمة ملفات PO التي تستعمل في برامج اللينكس بكثرة. - Xara Xtreme : برنامج لرسوميات المتجهة وهو نسخة خاصة باللينكس. - AOL Communicator : برنامج عميل الانترنت المشهور - Audacity : برنامج تحرير الصوتيات و المقاطع الصوتية - Kirix Strata : برنامج قواعد بيانات دينامكي - Chandler : برنامج الجدولة والمواعيد مشابه جدا لبرنامج أوتلوك. - Code::Blocks : بيئة التطوير المعروفة. - مشغل الملتيمديا المشهور VLC قبل أن ينتقل إلى QT في عام 2008. **Framework QT** [![QT](https://informatic-ar.com/wp-content/uploads/2013/09/QT-300x104.png)](https://informatic-ar.com/wp-content/uploads/2013/09/QT.png) **ما هي QT ؟** QT عبارة عن بيئة رسومية جميلة و التعامل معها سهل جدا, تمت كتابتها بــ C++ من طرف Qt Development Frameworks. تتميز هذه المكتبة بالسرعة و تعدد المنصات. لكي تعمل على بيئة QT يجب أن تكون ملما بأساسيات الـــ OOP و هذا طبيعي جدا لأن QT عبارة عن Object-Oriented Framework. Qt متوفرة تحت نوعين من الرخص: رخصة خاصة (مدفوعة الثمن) للبرامج المغلقة ورخصة الـ LGPL من أجل معظم أنظمة التشغيل (Windows, Unix with X11, Linux MacOS,...) صُممت QT أساسا لـ C++ لكن لم يمنعها هذا من التوسع إلى لغات أخرى مثل Python, PHP, Ruby, C, Perl, C#, Java. **نبذة تاريخية** منصة العمل Qt أصبحت متوفرة للعامة في مايو 1995\. لقد طورت مبدئيا من قبل هافارد نورد (المدير التنفيذي لشركة Trolltech) و إيريك شامبنج ( رئيس شركة Trolltech ) . هافارد و إيريك اجتمعا في معهد النرويجي للتكنولوجيا في تروندهايم ، حيث تخرج كلاهما بشهادة الماجستير في علوم الحاسوب. \[المصدر : وادي التقنية\] يعود اهتمام هافارد في تطوير واجهة المستخدم الرسومية GUI باستخدام ++C لعام 1988 عندما كلف بواسطة شركة سويدية بتطوير منصة عمل GUI متوافقة مع ++C. بعد سنتين وفي صيف 1990 عمل هافارد وإيريك معا في تطبيق قاعدة بيانات ++C لصور الأشعة فوق السمعية. النظام تطلب أن يكون قادرا أن يعمل بواجهة رسومية للمستخدم ويعمل على منصات اليونكس والماكنتوش و الويندوز. في أحد أيام ذلك الصيف ذهب هافارد وإيريك للخارج للاستمتاع بشروق الشمس و لما كانا جالسين مقعد الحديقة ، قال هافارد : " نحن بحاجة إلى نظام عرض كائني المنحى." نتيجة النقاش خلصت إلى قاعدة عقلانية لمنصة عمل رسومية متعددة الأنظمة وكائنية المنحى يجب أن يبدؤوا حالا في بناءها. في عام 1991 ، بدأ هافارد بكتابة الصفوف التي في النهاية أصبحت Qt ،بالتعاون مع إيريك في التصميم. في السنة اللاحقة، أتي إيريك بفكرة " signals and slots" بالعربي " الإشارات والمستقبلات" ، بسيطة و لكن نموذج برمجة واجهة رسومية GUI قوية ، والتي تبنتها عدة أدوات برمجية أخرى. أخذ هافارد الفكرة و أنتج تطبيق مكتوب. في عام 1993 هافارد وإيريك طوروا أول نواة رسومية و استطاعوا إعداد مكوناتهم الخاصة. مع نهاية السنة ، اقترح هافارد بأن يدخلوا في العمل معا لبناء " أفضل منصة عمل رسومية مكتوبة باستخدام ++C في العالم". بدأ عام 1994 بشكل عاثر مع مبرمجين صغار يريدون دخول سوق راسخ ، من دون زبائن ومنتج غير مكتمل و بدون أموال. لحسن الحظ ، كانتا زوجاتهما تعملان ، ولذا استطاعن أن يدعمن أزواجهن للسنتين التي توقعهما إيريك وهافارد بأنهما بحاجة لهما لتطوير المنتج و البدء باكتساب عائدات. حرف " Q" اختير كبادئة للصف بسبب أن الحرف يظهر جميلا في خط إيماكس هافارد. وحرف "t" أضيف لاختصار " toolkit" اشتقاقا من Xt والتي تعني X Toolkit . أعلن عن الشركة في 4 مارس من عام 1994، في الأصل كـ Quasar Technologies ثم كـ Troll Tech و اليوم كـ Trolltech. في أبريل 1995 ، يعود الفضل إلى أحد أساتذة هافارد الجامعيين إلى حدوث تواصل بالشركة النرويجية Metis التي أعطتهم عقد لتطوير برمجيات معتمدة على Qt. في ذلك الوقت ، استأجرت ترولتك أرنت جولبراندسن الذي خلال ست سنواته في ترولتك أبدع و أعد نظام توثيق مبتكر ، بالإضافة إلى المشاركة في كود Qt. في 20 مايو 1995 رفعت Qt 0.90 إلى سيرفر sunsite.unc.edu. بعد ستة أيام ، أعلن عن اﻹصدارة على comp.os.linux.announce. كانت هذه أول إصدارة عام من Qt. كيوتي يمكن أن تستخدم لتطوير كلا النظامين الويندوز واليونكس و تقدم نفس الدوال API في كلا المنصتين. Qt توفرت تحت ترخيصين من اليوم الأول: الترخيص التجاري والذي يستخدم للتطوير التجاري و نسخة البرنامج المجانية تحت ترخيص FreeQt للتطوير البرمجيات مفتوحة المصدر. العقد مع شركة Metis ترك ترولتك عائمة ، خلال عشرة أشهر طوال لا أحد اشترى الترخيص التجاري لـ Qt. في مارس 1996 ، أصبحت وكالة الفضاء الأوربية الزبون الثاني لدى Qt بشراء عشرة تراخيص تجارية. مع رباطة الجأش ، استأجر إيريك وهافارد مطور آخر. أطلقت اﻹصدارة Qt 0.97 بنهاية مايو وفي 24 سبتمبر 1996 ظهرت Qt 1.0 إلى النور. مع نهاية السنة ، وصلت Qt إلى اﻹصدارة 1.1 و ثمانية زبائن جدد ، كل من بلاد مختلفة اشتروا 18 ترخيصا تجاريا فيما بينهم. وفي هذه السنة ظهر تأسيس مشروع KDE بقيادة ماتثياس إيتراك. أصدرت Qt 1.2 في أبريل 1997\. كان قرار ماتثياس إيتراك باستخدام Qt لبناء KDE ساعد Qt لأن تصبح معيار واقعي لتطوير الواجهات الرسومية باستخدام ++C في اللينكس. اﻹصدارة 1.3 ظهرت في سبتمبر 1997. في عام 1998 انفجر خلاف بعدما أصبح واضحا أن KDE ستكون المسيطرة على أسطح المكتب في اللينكس ﻷن رخصة FreeQt لم تكن تتوافق مع فلسفة المصادر الحرة مع أنها كانت مفتوحة المصدر ، وبما أن KDE مبنية على Qt ، فإن الكثير من الناس في حركة المصادر الحرة قلقوا بأن الجزء اﻷساسي من نظامهم التشغيلي يمكن أن يكون مملوكة وليست حرة. وهذا أعطى دافعا ﻷمرين : مشروع مكتبة Harmony toolkit والتي هدفت إلى إنتاج نسخة مطابقة لمكتبة Qt تحت ترخيص البرمجيات الحرة، و الأمر الثاني و اﻷكثر أهمية مشروع سطح مكتب GNOME والذي هدف أساسا ﻹنتزاع مكانة KDE. سطح مكتب جنوم استخدم مكتبة أدوات GTK+ والتي في اﻷصل كتبت لبرنامج GIMP و تستخدم بشكل أساسي لغة السي للبرمجة. في هذه اللحظة ارتكبت شركة ترولتك خطأ بعدم تعديل رخصة FreeQT لتكون متوافقة مع رخصة غنو العمومية ، وهي غلطة تمنى رؤساء ترولتك لو لم يقعوا فيها، ﻷنهم ضيعوا فرصة السيطرة على سطح المكتب في اللينكس و ظهر جنوم الذي دعمته شركة ردهات في اﻷساس ثم انتشر بشكل واسع حتى أصبح سطح المكتب المفضل لجمهور واسع من مستخدمي اللينكس وخاصة بعد صدور توزيعة أوبنتو الشهيرة. انضم ماتثياس إلى ترولتك في عام 1998، وآخر إصدارة رئيسية من Qt 1 ، الاصدارة 1.40 صنعت في سبتمبر من ذلك العام. أطلقت Qt 2.0 في يونيو 1999\. Qt 2 امتلكت ترخيص مفتوح المصدر جديد ، ترخيص Q العام QPL ، والذي يتوافق مع تعريف المصادر المفتوحة، بعد إصدار QPL اختفى مشروع مكتبة Harmony toolkit ﻷنه لم يكن له داعي. في أغسطس 1999 ، فازت Qt بجائزة عالم اللينكس لأفضل مكتبة أو أداءة. و في ذلك الوقت ، أسست Trolltech Pty Ltd فرع أستراليا. أطلقت ترولتك نواة Qtopia ( ثم أطلق عليها Qt المضمنة ) في عام 2000\. لقد صممت للعمل في أجهزة اللينكس المضمنة ولتقدم نظام النوافذ الخاص بها كبديل خفيف عن X11 . كلا المنتجان Qt/X11 و نواة Qtopia قدّما تحت استخدام رخصة غنو العمومية ( GPL) بالإضافة مع الترخيص التجاري. مع نهاية 2000 ، أسست ترولتك فرعها في الولايات المتحدة الأمريكية و أطلقت أول إصدارة من Qtopia منصة التطبيق إلى الهواتف النقالة و المساعدات الشخصية PDA. فازت نواة Qtopia بجائزة عالم اللينكس كـ"أفضل حل لينكس مضمن" في عام 2001 و 2002 و حقق هاتف Qtopia نفس النتيجة في 2004. في عام 2001 أطلقت Qt 3.0\. توفرت Qt لمنصة الويندوز و الماكنتوش Mac OS X و اليونكس واللينكس ( سطح المكتب و المضمن ). قدمت Qt 3 24 صفا جديدا و تجاوز كودها 500000 سطر. كانتQt 3 خطوة كبيرة للأمام من Qt2 ، احتوت بشكل كبير تحسين التوطين و دعم اليونيكود، وأداة جديدة بالكامل لعرض النصوص وتحريرها، و صف للتعابير الاعتيادية المماثلة لبيرل. فازت Qt3 بجائزة " جولت الإنتاجية" لتطوير البرمجيات في عام 2002. في صيف 2005 أطلقت Qt 4.0\. بحوالي 500 صف و أكثر من 9000 دالة وظيفية ، Qt4 أكبر و أغنى من أي إصدارة سابقة، وقد قسمت إلى عدة مكتبات لذا المطورين يحتاجون ربط برامجهم بالأجزاء التي يحتاجونها من Qt فقط. Qt4 متقدمة جدا على الإصدارات السابقة بتحسينات اشتملت على مجموعة جديدة فاعلة وسهلة الاستخدام من حاويات القوالب، و أدوات عرض ووحدات متقدمة ، و منصة رسم 2D سريعة ومرنة ، و صفوف تحرير وعرض النصوص اليونيكود قوية، وبدون ذكر الالآف من التحسينات الصغيرة في كل نطاق صفوف Qt. كانت اﻹصدارة Qt 4 أول إصدارة من Qt تتوفر بترخيصين التجاري ومفتوح المصدر لكل المنصات التي تدعمها. أيضا في 2005 ترولتك فتحت مكتب ممثل لها في بكين لتقديم خدمات البيع و التدريب و الدعم الفني لـ Qtopia للزبائن في الصين و تلك المنطقة. في هذه السنة وصلت أرباح الشركة ولأول مرة 6.7 مليون دولار. وبعدها في السنوات التالية أصبحت نسبة النمو في إيرادات الشركة 40 % وبشكل متوالي في سنوات 2006 و 2007 . تملك ترولتك اﻻن أكثر 5000 زبون و تملك علاقات تجارية مع أكثر من 2000 شركة عالمية من بينها IBM و Adobe و Skype و Google و Shell وغيرها من الشركات. في عام 2007 وصل عدد موظفي شركة ترولتك إلى 250 موظف في جميع أفرع الشركة في النرويج و ألمانيا و أمريكيا و الصين و أستراليا. منذ انطلاق ترولتك، نمت شهرت Qt بدون انقطاع واستمرت في النمو لهذا اليوم. هذا النجاح انعكاس لكل من جودة Qt ومدى الاستمتاع أثناء استخدامها. في العقد الأخير ، نمت Qt من منتج يستخدم بواسطة فئة محددة تعرفه إلى منتج يستخدم بشكل يومي بواسطة الآلاف من الزبائن و عشرات اﻵﻵف من مطوري البرامج الحرة في كل أنحاء العالم. في عام 2010, أطلقت شركة نوكيا الإصدارة 4.7 من منصة التطوير Qt متعددة المنصات لتصميم التطبيقات. هذه الإصدارة قدمت تقنية جديدة وهي Qt UI Creation Kit وهي تتيح إنشاء واجهات رسومية دينامكية بشكل أسهل وأكثر احترافية عن الطرق التقليدية. هذه التقنية تتكون من ثلاثة أشياء: \- لغة QML وهي لغة مبنية مشابهه لجافا سكربت تستغل قدرات مكونات Qt لتمكين المطورين والمصممين للتعاون معا لإنشاء تجربة للمستخدم في التحريك والانسيابية. \- مكتبة QtDeclarative وهي تقدم كمحرك داخلي يقوم بترجمة QML إلى QGraphicsScene. \- تحسين Qt Creator لدعم وتحرير ملفات QML. في التاسع من أغسطس 2012, وقعت شركة Digia اتفاقية مع شركة Nokia لنقل ملكية Qt مقابل 4,9 مليون دولار. في نفس الوقت, أعلنت Digia أنها تريد توسيع نطاق دعم QT ليشمل Windows 8 و Android و iOS. **Modules** تنقسم مكتبات Qt إلى عدة موديلات (Modules ), مثل : - QtGui : و هو موديل خاص بالتعامل مع الواجهة, تستطيع من خلاله إضافة نوافذ, أزرار, رسائل, صور و ما إلى ذلك. - QtOpenGL : و هو موديل خاص برسومات الثلاثية الأبعاد, يُستعمل في الألعاب, المحاكات و غيرها. - QtSql : و هو موديل خاص بالتعامل مع قواعد بيانات SQL و تسييرها. - QtNetwork : موديل خاص ببرمجة الشبكات و التعامل مع الإنترنت كتحميل الملفات, و رفعها توجد موديلات أخرى للتعامل مع XML و غيرها الكثير .. **إصدارات QT** توجد أربعة أنواع من إصدارات QT : - Qt Console نسخة التطوير الغير رسومي non-GUI. - GUI Framework وهي موجهة لبناء واجهات التطبيقات الرسومية والشبكات وقواعد البيانات، يطلق عليها أيضا اسم Desktop Light. - Full Framework وهي نسخة تجارية كاملة تحتوي على كل شيء. - إصدار مفتوح المصدر بالكامل (Qt Open Source Edition) . كما توجد نسخ تجارية توقف تطويرها ولها دعم تجاري فقط مثل Qt Jambi الموجهة للجافا. **مميزات QT** - توفر أكثر من 500 فئة و أكثر من 9000 دالة تغطى كافة متطلبات السوق البرمجي. - التوثيق الممتاز, لكافة الدوال والفئات, وسهل الاستخدام والبحث معا. - الدعم الفني الرائع الذي يركز على الجودة المنتج. - تعمل على جميع المنصات من دون الحاجة إلى تعديل الكود لكل منصة. - كلا الترخيصين يوفر سورس مكتبة Qt بالكامل مما يسمح لك بالتعديل فيها لتتناسب احتياجاتك البرمجية المختلفة. - وفرة البرامج مفتوحة المصدر المكتوبة بها مما يوفر لك مادة برمجية هائلة للتعلم والاستفادة منها. **أشهر البرامح المكتوبة بــ QT** هناك الكثير من التطبيقات التي بنيت بواسطة مكتبة Qt ، و هذه بعض أشهر الأمثلة : - Adobe Photoshop Album برنامج لإدارة وترتيب الصور - Doxygen منتج توثيق API - Google Earth برنامج ثلاثي الأبعاد للخرائط العالم - KDE سطح المكتب المشهور في عالم اللينكس - KDELibs المكتبة التي بنيت عليها الآلاف من تطبيقات KDE مثل Amarok و K3b و KDevelop و KOffice - Last.fm Player عميل سطح المكتب لإذاعة الإنترنت و موقع مجتمع الموسيقى - Motorola A760 يستخدم Qtالمضمنة في واجهته الرسومية - Opera متصفح الإنترنت - Psi عميل الرسائل اللحظية لـ XMPP - Scribus برنامج للنشر المكتبي - Skype برنامج المحادثة الشهير, يُستخدم للتحدث عبر الويب P2P VOIP - TOra أداة للتحكم بقواعد البيانات وخاصة أوراكل. - VirtualBox برنامج لمحاكة اﻷجهزة يستخدم في العمليات التخيلية - Xconfig أداة إعداد نواة اللينكس. **الخاتمة** إلى هنا أصل بك أخي القارئ إلى نهاية هذه الجولة السريعة, كنت أود وضع روابط فرنسية تحتوي على دورات و دروس مفصلة في كل مكتبة لكن تراجعتُ عن الأمر نظرا لأن أغلب أعضاء المنتدى يدرسون البرمجة بالإنجليزية فقط. نظرا لطول المقالة, اكتفيتُ بأربع مكتبات رسومية. أعتذر للآنسة GTK التي لم نتطرق لها نظرا لضيق الوقت. تحياتي. ### خوارزمية Diffie-Hellman (رؤية من الداخل) URL: https://informatic-ar.com/diffie-hellman-algorithm/ Last updated: 2026-10-03T22:32:10.000Z السلام عليكم و رحمة الله و بركاته في هذه المقالة, سأضع بين أيديكم شرحا لخوارزمية **Diffie-Hellman** و كلي أمل بأن يستفيد الجميع. [![Couverture](https://informatic-ar.com/wp-content/uploads/2013/09/Couverture-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Couverture.png) قمتُ بإدخال بعض الروابط المهمة في بعض الكلمات للذين يريدون الاستزادة, الكتابة المُظللة, ذات اللون الأزرق تدل على وجود رابط, مثل هذه الكتابة [انفورماتيك.](https://informatic-ar.com/) **فهرس المقالة :** 1. **مُقدمة** - ما هو التشفير ؟ - التشفير في العصر القديم - التشفير الحديث 2. **نظرة تحليلية على الخوارزمية** - فكرة الخوارزمية - البروتوكول - مثال تطبيقي - التعقيد الزمني 3. الخاتمة 4. المراجع **1\. مُقدمة** - 1.1 ما هو التشفير ؟ - 1.2 التشفير في العصر القديم - 1.3 التشفير الحديث **1.1 - ما هو التشفير ؟** هو عملية يتم فيها إخفاء المعلومات عن طريق مفتاح سري وخوارزمية, حيث يمكن للشخص الذي يعرف المفتاح و خوارزمية التشفير, فك الشفرة ( أي استعادة المعلومات الأصلية), يمكن أيضاً أن يقوم شخص آخر لا يعرف المفتاح و لا الخوارزمية بفك الشفرة !! و تُسمى العملية هنا "عملية غير مخولة". في عام 1900 قبل الميلاد لم تكن هناك سوى مصطلحات هيروغليفية, استخدم الإنسان التشفير منذ حوالي ألفي عام قبل الميلاد لحماية رسائله السرية, وبلغ هذا الاستخدام ذروته في فترات الحروب, خوفا من وقوع الرسائل الحساسة في أيدي الأعداء, فالحروب دائما كانت الملهم الأكبر لظهور خوارزميات التشفير.(1) **1.2 - التشفير في العصر القديم** يُعد [علم التشفير](http://en.wikipedia.org/wiki/Cryptography?ref=informatic-ar.com "رابط خارجي") من أقدم العلوم الموجودة في يومنا هذا حيث تمتد أصوله إلى زمن [الفراعنة](http://en.wikipedia.org/wiki/Pharaoh?ref=informatic-ar.com "رابط خارجي") و [القياصرة](http://en.wikipedia.org/wiki/Caesar%5F%28title%29?ref=informatic-ar.com "رابط خارجي") أيضا, فقد كان الفراعنة أول من قام بعملية التشفير للتراسل بين قطاعات الجيش, دون أن ننسى أن أفضل طريقة استُخدمت في القدم هي طريقة يوليوس قيصر ([Julius Caesar](http://en.wikipedia.org/wiki/Julius%5FCaesar?ref=informatic-ar.com "رابط خارجي")) وهو أحد قياصرة الروم, كما استخدم الصينيون القدامى طُرقا عديدة في علم التشفير والتعمية لنقل الرسائل السرية أثناء الحروب, فقد كانوا يستخدمون التشفير من أجل إخفاء الشكل الحقيقي للرسائل حتى لو سقطت في يد العدو فإنه يصعب عليه فهمها. كما يُعتبر علماء المسلمين و العرب أول من اكتشف طرق استخراج المعمَّى(2), من أشهرهم العلامة [يعقوب بن إسحاق الكندي](http://ar.wikipedia.org/wiki/%D9%8A%D8%B9%D9%82%D9%88%D8%A8%5F%D8%A8%D9%86%5F%D8%A5%D8%B3%D8%AD%D8%A7%D9%82%5F%D8%A7%D9%84%D9%83%D9%86%D8%AF%D9%8A?ref=informatic-ar.com "رابط خارجي") و [ابن وَحشِيَّة النبطي](http://www.diwanalarab.com/spip.php?article7624&ref=informatic-ar.com "رابط خارجي") الذي كشف اللثام عن رموز الهيروغليفية قبل أن يكتشفها العالم الفرنسي [Jean-François Champollion](http://en.wikipedia.org/wiki/Jean-Fran%C3%A7ois%5FChampollion?ref=informatic-ar.com "رابط خارجي") بعشرة قرون !! (3), و كذلك اشتهر [ابن دريهم](http://www.alukah.net/Literature%5FLanguage/0/2661/?ref=informatic-ar.com "رابط خارجي") الذي كان لا يشق له غبار في فك التشفير فكانت تُعطى له الرسالة معماة فما إن يراها حتى يحولها في الحين إلى العربية ويقرئها .. وله قصيدة طويلة يشرح فيها مختلف الطرق في تعمية النصوص وكان يحسن قراءة الهيرغليفية.(4) يعتبر البعض أن طريقة الألغاز كانت من أوائل الطرق الـُمستخدمة قديما في التشفير, فكانوا يأخذون جملة, مثل (ادفع لي أجرا ) ويدخلون كل حرف في بداية كلمة جديدة فتصبح (إذا دخل فاروق عليه لباس يبدو أكثر جمالا راتبه أكثر) وللحصول على الجملة المطلوبة نأخذ الحروف التي تبدأ بها كلمات الجملة الجديدة فنحصل على (ادفع لي أجرا). لكن هذه الطريقة صعبة جداً خاصة إذا كانت حجم المعلومات المـُراد إرسالها كبيراً حيث تكمن صعوبتها في إيجاد جمل تحمل المعلومات المطلوبة ولها مدلول واضح لا يثير الشك، لذا فإن هذا النوع نادراً ما يُستخدم في الوقت الحالي. **1.3 - التشفير الحديث** في العصر الحديث, تُعد [آلة Enigma](http://en.wikipedia.org/wiki/Enigma%5Fmachine?ref=informatic-ar.com "رابط خارجي") التي استخدمها الجيش الألماني في الحرب العالمية الثانية, أبرز مثال على استخدام التعمية لتحقيق تفوق على العدو في مجال الاتصالات، وكانت الأبحاث التي جرت بشكل منفصل في كل من المؤسستين العسكريتين الأمريكية والبريطانية في سبعينيات القرن العشرين فتحا جديدا فيما صار يعرف الآن بتقنيات التعمية القوية المعتمدة على الحوسبة، وارتبطت التعمية بعلوم الجبر ونظرية الأعداد ونظرية التعقيد ونظرية المعلوماتية. في نهاية السبعينات من القرن المنصرم, و مع الاستخدام المـُكثف لأجهزة الكمبيوتر, دخل علم التشفير مرحلة جديدة حيث أصبح البعض يُسميه "التشفير الحديث" (Modern cryptography) و مع التطور السريع الذي يشهده مجال الحماية و الأمن, أصبحت الحاجة ملحة لطرق تشفير قوية, لأن زيادة سرعة الكمبيوتر تعني قصر الوقت الذي يحتاجه الأخير لكسر أو كشف مفتاح تشفير معين. يرجع الفضل في إظهار مفهوم التشفير الغير متناظر إلى الرائدين [Whitfield Diffie](http://fr.wikipedia.org/wiki/Whitfield%5FDiffie?ref=informatic-ar.com "رابط خارجي") و [Martin Hellman](http://fr.wikipedia.org/wiki/Martin%5FHellman?ref=informatic-ar.com "رابط خارجي"), حيث قدَّما هذا المفهوم لأول مرة في [المؤتمر الوطني للحاسوب](http://www.bricklin.com/history/saincc.htm?ref=informatic-ar.com "رابط خارجي") في عام 1976 (5) قبل أن يتم نشره بعد بضعة أشهر (6) في "التوجهات الجديدة في علم التشفير" (New Directions in Cryptography). [![Diffie-Hellman](https://informatic-ar.com/wp-content/uploads/2013/09/Diffie-Hellman-300x145.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Diffie-Hellman.png) يظل المخترع الأب مُختفيا خلف الكواليس - كما يحدث دائما في تاريخ التشفير - إذْ يعتبر البعض (7) أن الباحث الأمريكي [Ralph Merkle](http://fr.wikipedia.org/wiki/Ralph%5FMerkle?ref=informatic-ar.com "رابط خارجي") هو أول من اكتشف فكرة "تشفير المفتاح العام" ([Cryptography Asymmetric](http://en.wikipedia.org/wiki/Public-key%5Fcryptography?ref=informatic-ar.com "رابط خارجي")) بشكل مستقل, رغم أن كتاباته (8) عن الموضوع لم تُنشر إلا مؤخرا. [![Ralph Merkle](https://informatic-ar.com/wp-content/uploads/2013/09/Ralph-Merkle.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Ralph-Merkle.png) لم يستطع كل من W. Diffie و M. Hellman تقديم مثال حي على نظام المفتاح العام في البحث الذي قدماه سنة 1976\. كان يجب عليهم أن ينتظروا سنة 1978 للحصول على مثال واقعي(9) مُقدم من طرف الثلاثي المميز : [Adi Shamir](http://fr.wikipedia.org/wiki/Adi%5FShamir?ref=informatic-ar.com "رابط خارجي"), [Ronald Rivest](http://fr.wikipedia.org/wiki/Ronald%5FRivest?ref=informatic-ar.com "رابط خارجي") and [Leonard Adleman](http://fr.wikipedia.org/wiki/Leonard%5FAdleman?ref=informatic-ar.com "رابط خارجي") [![Ronald Rivest, Adi Shamir and Leonard Adleman](https://informatic-ar.com/wp-content/uploads/2013/09/Ronald-Rivest-Adi-Shamir-and-Leonard-Adleman-300x100.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Ronald-Rivest-Adi-Shamir-and-Leonard-Adleman.png) **2\. نظرة تحليلية على الخوارزمية** - 2.1 - فكرة الخوارزمية - 2.2 - البروتوكول - 2.3 – مثال تطبيقي - 2.4 - التعقيد الزمني **2.1 - فكرة الخوارزمية** تُعتبر [خوارزمية](http://en.wikipedia.org/wiki/Algorithm?ref=informatic-ar.com "رابط خارجي") [D-H](http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman%5Fkey%5Fexchange?ref=informatic-ar.com "رابط خارجي") الأول من نوعها في موضوع تبديل [المفاتيح](http://fr.vikidia.org/wiki/Clef%5Fpublique?ref=informatic-ar.com "رابط خارجي"), حيث تسمح لشخصين (عادة ما يُطلق عليهما Alice و Bob) بتبادل بيانات حساسة دون أن يفهمها الطرف الثالث (المتصنت) حتى و لو حصل على نسخة منها. تعتمد الخوارزمية في عملها على إنشاء مفتاح سري مشترك يمكن استخدامه فيما بعد لتشفير المحادثات باستخدام خوارزمية تشفير مفتاح متماثل Symmetric-key. [![Alice and Bob](https://informatic-ar.com/wp-content/uploads/2013/09/Alice-and-Bob-300x156.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Alice-and-Bob.png) **2.2 – البروتوكول** - ليكن n و B العددان الصحيحان اللذان اختارهما كل من Alice و Bob علناً, n و B يجب أن يكونا أوليين فيما بينهما. - سريا, تختار Alice بدورها عددا صحيحا بشكل عشوائي, نُسميه a, ثم تحسب العدد [![image1](https://informatic-ar.com/wp-content/uploads/2013/09/image1.png)](https://informatic-ar.com/wp-content/uploads/2013/09/image1.png) ثم تُرسل - علناً - العدد الجديد إلى Bob. - يقوم Bob بنفس الحركة السابقة: يختار بشكل سري عددا عشوائيا g ثم يحسب [![image2](https://informatic-ar.com/wp-content/uploads/2013/09/image2.png)](https://informatic-ar.com/wp-content/uploads/2013/09/image2.png) ثم يُرسل الناتج إلى Alice. - من الآن فصاعدا, كل طرف يملك نتيجة حساب الآخر, Alice ما عليها سوى حساب [![image3](https://informatic-ar.com/wp-content/uploads/2013/09/image3.png)](https://informatic-ar.com/wp-content/uploads/2013/09/image3.png) و Bob ما عليه سوى حساب [![image4](https://informatic-ar.com/wp-content/uploads/2013/09/image4.png)](https://informatic-ar.com/wp-content/uploads/2013/09/image4.png) و انتهى الأمر ! في الحقيقة, فإن العددان السابقان متساويان : [![post-219439-073764400 1344288997](https://informatic-ar.com/wp-content/uploads/2013/09/post-219439-073764400-1344288997-300x33.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-219439-073764400-1344288997.png) الآن, أصبح Alice و Bob يمتلكان العدد [![image5](https://informatic-ar.com/wp-content/uploads/2013/09/image5.png)](https://informatic-ar.com/wp-content/uploads/2013/09/image5.png) و الذي لا يعرفه أحد سواهما, يمكنهما استخدام هذا العدد كمفتاح, و يمكنها أن يتبادلا البيانات الحساسة أمام الجميع ...! لكن, **كيف يمكن هذا ؟؟** لا يُمكن للطرف الثالث (المتصنت) أن يعرف العدد a أو g (هذا العددان ضروريان لإيجاد العدد [![image6](https://informatic-ar.com/wp-content/uploads/2013/09/image6.png)](https://informatic-ar.com/wp-content/uploads/2013/09/image6.png) ), العددان السابقان لا يدخلان ضمن المعلومات المتبادلة علنا, المعلومات التي يمكن للمتصنت الحصول عليها هي n, B, A and G و لتحديد a انطلاقا من A يجب تخطي عقبة [Discrete logarithm](http://en.wikipedia.org/wiki/Discrete%5Flogarithm?ref=informatic-ar.com "رابط خارجي") التي من المستحيل "عمليا" كسرها حتى يومنا هذا. **كيف تتأكد Alice من وصول الرسالة إلى Bob و عدم تحريفها ؟** عندما تُرسل Alice رسالة سوف يتم تشفيرها بالمفتاح الخاص بها أو المفتاح العام التابع لــ Bob، بحيث تتحول هذه الرسالة إلى رموز لا يمكن فهمها ويتم إرفاق معها توقيع المـُرسل. عند إذن يقوم المستقبل بإرسال نسخه من التوقيع الالكتروني إلى الجهة المختصة بإصدار الشهادة، لتتأكد من صحة التوقيع ومن ثم تقوم أجهزة الكمبيوتر التابعة للجهة المختصة بالتحقق من صحة التوقيع وتُعاد النتيجة للمستقبل مرة أخرى، ليتأكد من صحة وسلامة الرسالة، فيقوم المستقبل بقراءة الرسالة وذلك باستخدام مفتاحه الخاص إذا كان التشفير قد تم على أساس رقمه العام أو بواسطة الرقم العام للمرسل إذا تم التشفير بواسطة الرقم الخاص للمرسل، ومن ثم يجيب على المرسل باستخدام نفس الطريقة وهكذا تتكرر العملية، و يُستخدم أيضا مع التوقيع الالكتروني عملية الهاش التي توفر تكلفة أقل من تشفير الرسالة بحيث تقوم بإنشاء قيمة رقمية معينة تكون أصغر من الرسالة بحيث تضمن عدم تغييرها, عندما يستقبل المستخدم الرسالة و الهاش يقوم بعملية الهاش مرة أخرى على هذه الأخيرة ومن ثم يقارن ما بين الهاش الأصلي و الهاش المـُستقبل, إذا كانت القيم متساوية فهذا يدل على سلامة البيانات من التحريف والتزوير وإذا اختلفت القيم دل ذلك على تزوير الرسالة. (10) **2.3 – مثال تطبيقي** **الجزء الأخضر** يُمثل البيانات العامة التي يتم تبادلها أمام الجميع بينما يُمثل **الجزء الأحمر** البيانات الخاصة بكل طرف: [![Example](https://informatic-ar.com/wp-content/uploads/2013/09/Example-300x95.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Example.png) يُمكن لــ Alice و Bob أن يستخدما العدد **107** كمفتاح لتشفير الرسائل المـُتبادلة بينهما (11), عمليا نستخدم أعداد ضخمة جدا و لكن الهدف هنا هو توضيح الفكرة فقط. **2.4 - التعقيد الزمني** عند تطبيق الخوارزمية سيتم حساب أربع exponentiations mod p, باستخدام خوارزمية [الأس المعياري](http://en.wikipedia.org/wiki/Modular%5Fexponentiation?ref=informatic-ar.com "رابط خارجي") [السريع](http://en.wikipedia.org/wiki/Exponentiation%5Fby%5Fsquaring?ref=informatic-ar.com "رابط خارجي") فإن وقت التنفيذ يتغير بتغير حجم العدد p الذي عادة ما يكون عددا أوليا ضخما. (12) إذا أردنا تبادل مفتاح حجمه L سيكون عدد الـ binary operations يساوي [![image7](https://informatic-ar.com/wp-content/uploads/2013/09/image7.png)](https://informatic-ar.com/wp-content/uploads/2013/09/image7.png) **3\. الخاتمة** إلى هنا أصل بك أخي القارئ إلى نهاية هذه الجولة السريعة, بالطبع الموضوع شيق و يحتاج إلى الكثير من الشرح, ما زالت هناك العديد من النقاط التي كنت أود التحدث عنها (لكن أخاف أن يزيد الحجم عن حجم المقالة القياسي) مثل كتابة [الخوارزمية بلغة الــ Java](http://code.google.com/p/joid/source/browse/trunk/src/org/verisign/joid/DiffieHellman.java?r=96&ref=informatic-ar.com "رابط خارجي") أو [السي++](http://www.example-code.com/vcpp/dh%5Fkey%5Fexchange.asp?ref=informatic-ar.com "رابط خارجي") و إدراج [خوارزمية DSS](http://en.wikipedia.org/wiki/Digital%5FSignature%5FStandard?ref=informatic-ar.com "رابط خارجي") في D-H من أجل إضافة التواقيع الرقمية ([Digital signature](http://en.wikipedia.org/wiki/Digital%5Fsignature?ref=informatic-ar.com "رابط خارجي")) إلى البيانات المـُتبادلة و الكثير الكثير من النقاط المهمة التي ربما أكتب عنها لاحقا. **4\. المراجع** (1) : [ما هو التشفير ؟](http://www.ejabah.info/wiki/%D9%85%D8%A7%5F%D9%87%D9%88%5F%D8%A7%D9%84%D8%AA%D8%B4%D9%81%D9%8A%D8%B1%28Cryptographic%29%5F%D8%9F?ref=informatic-ar.com "رابط خارجي") \- موسوعة الأسئلة والإجابات الحرة (2) : THE CODEBREAKERS, David Kahn, page 93 ; Kahn on Codes, David Kahn, page 41 (3) : [كتاب علم التعمية واستخراج المعمَّى عند العرب](http://www.moslimonline.com/?page=detials&id=254&ref=informatic-ar.com "رابط خارجي") (4) : [ابن الدريهم وجهوده في علم التعمية (التشفير)](http://www.alukah.net/Literature%5FLanguage/0/2661/?ref=informatic-ar.com "رابط خارجي"), الدكتور محمد حسان الطيان (5) : W. Diffie and M.E. Hellman, Multiuser cryptographic technics, Proceedings of AFIPS National Computer Conference, 109-112, 1976 (6) : W. Diffie and M.E. Hellman, New directions in cryptography, IEEE transactions on information theory, 22(1976), 644-654 (7) : A.J. Menezes, P.C Van Oorschot, S.A. Vanstone, Handbook of applied cryptography, CRC Press, 1997, p47 (8) : R.C. Merkle, Secure communications over insecure channels, Communications of the ACM, 21(1978),294-299 (9) : Ronald Rivest, Adi Shamir, Leonard Adleman, A Method for Obtaining Digital Signatures and Public-key Cryptosystems, Communications of the ACM, 21(1978), 120-126 (10) : التوقيع الالكتروني .. خطوة إلى الأمام, جريدة الخليج الإماراتية - الملحق الاقتصادي (11) : [Exemple d'algorithme asymétrique : Diffie-Hellman](http://sebsauvage.net/comprendre/encryptage/crypto%5Fdh.html?ref=informatic-ar.com "رابط خارجي") (12) : [La Naissance de la Cryptographie Asymétrique](http://www-polsys.lip6.fr/%7Erenault/CryptoL3/cours5.pdf?ref=informatic-ar.com "رابط خارجي"), Guénaël Renault, SALSA - LIP6/UPMC - 14 mars 2012 تحياتي. ### هل تهتم لحماية التطبيق قبل أو بعد الانتهاء من البرمجة ؟ URL: https://informatic-ar.com/designing-in-security/ Last updated: 2026-10-03T22:32:10.000Z كنت في مناقشة مع أحد المطورين حول تطوير برنامج مختص بال Authentication يعمل على الاندرويد، وبما أن البرنامج يصنف تحت برامج الحماية Security Applications فقد أفترضت أن صديقنا سوف يراعي كل ال Standards المستخدمة في تطوير الأنظمة الأمنة (استخدام مثلاً RSA بمفاتيح كبيرة مثلاً 2048 على الأقل، استخدام الشهادات الرقمية Certificates الخ..) ولكن صدمت عندما وجدت ان التشفير في برنامجه هو عباره عن عملية حساب لل Hash فقط (تحقق من البيانات اي بعباره اخرى فحص للIntegrity للبيانات) وهل تغيرت أم لأ. لننسى نقطة ان Data Integrity هي ليست الا فائده أولى من فوائد التوقيع الرقمي Digital Signature، ولننسى نقطة أن أغلب المواقع العربية ما زالت تسمي الـ Hash بالتشفير Encryption ، ولنركز حول أخر فقرة في النقاش وهي بعدما عرف ان هذا لن يكون برنامج جيد لأنه بلا اي حماية وقابل للاختراقات، بأنه سوف يقوم فيما بعد باستخدام خوارزمية تشفير بمفتاح واحد Symmetric Key (مثل AES) ويقوم بعمل مفتاح واحد وينشره بين مستخدمين البرنامج، لأنه برنامج محدد للأقارب مثلاً وبالتالي الجميع يعرف ويثق الآخر. **لذلك السؤال المطروح: هل تقوم بعمل الحمايه في تطبيقات الحماية قبل أو بعد الانتهاء من المشروع (هل تتبع Designing in Security أو لا تتبعها).** ال Designing-In Security يعني أنك ستدخل مسائل التحقق والتشفير والbackup وكل جوانب الحماية في كل مرحلة من مراحل التطوير سواء كانت في مرحلة متطلبات المشروع Requirement أو كانت في مرحلة وضع هيكله المشروع، ولا يجب بالبدء البرمجة وبعد ذلك تفكر باضافة طبقة الحمايه لاحقا. **لماذا؟ لن أقول لك لماذا ، ولكن دع التاريخ يسرد نفسه ويريك الحقائق...** ## **نظام ويندوز 98** [![windows98](https://informatic-ar.com/wp-content/uploads/2013/09/windows98.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/windows98.jpg) بسبب تم دعم نافذه السؤال عن اسم المستخدم والباسورد password dialog بعد صدور نسخ الويندوز الأولى، فمن خلال ال Safe Mode (عند اقلاع الجهاز بعد الضغط على F8) يمكن تجاوز هذه النافذه والدخول مباشرة للنظام. هذا بسبب أن هذه الحماية الأولى لم تكن في التصاميم الأولية للنظام Initial Design والا فكان المفترض ان يتم السؤال عن تلك المعلومات حتى اثناء الدخول في ال Safe Mode. هذا مثال واحد على ان اضافة الSecurity كLayer جديدة في النظام قد لا تنفع. ## **الإنترنت** **Internet** [![internet](https://informatic-ar.com/wp-content/uploads/2013/09/internet.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/internet.jpg) أحد الأمثلة الأخرى التي تبين لنا مدى صعوبة ادخال ال Security بعد اطلاق الأنظمة هو الأنترنت، فعندما صممت هذه الشبكة كانت كل الأجهزة التي تتصل ببعضها موثوقه لأنها كل كانت تتبع لجهات عسكرية أو جامعات. وبعد ذلك في منتصف التسعينيات عندما توسعت الشبكة ودخلت كثير من أجهزة الشركات والأفراد الشبكة بدئت بعض الجهات باستخدام ال Firewalls للحماية. ال Firewall يقوم بايقاف كل البيانات التي لا تريدها من اي مصدر تقوم بتحديده، لكن المشكلة التي حدثت هو أنك يمكن أن تتلاعب في الباكت نفسه IP Packet وتقوم بتغيير عنوان المرسل في الباكت الخارج منك الى عنوان B وترسله الى الجهاز A والذي يثق فيه وسيتم دخول الباكت بواسطة الFirewall والسبب أنه يسمح لB بالتعامل معه لأنه موثوق. [![spoofing](https://informatic-ar.com/wp-content/uploads/2013/09/spoofing-300x197.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/spoofing.jpg) هذه كانت من احدى مشاكل التصاميم الأولية لشبكة الانترنت وبالتحديد TCP/IP ويمكنك قرائه الورقة العملية التي تصف المشكلة بالتفصيل: [https://www.cs.columbia.edu/\~smb/papers/ipext.pdf](https://www.cs.columbia.edu/~smb/papers/ipext.pdf?ref=informatic-ar.com) أو قرائه هذه المقالة من سميانتيك: [IP Spoofing: An Introduction](http://www.symantec.com/connect/articles/ip-spoofing-introduction?ref=informatic-ar.com) ولو تم تصميم البرتوكول منذ الأول على اساس عدم الوثوقية والحماية لكن الأمر مختلف الآن. ## **خلاصة** عندما تبدأ في تطوير برامجك (خاصه المختصة بالحمايه والتشفير) ففكر في جانب الحماية منذ الأول بشكل صحيح. وراعي بين ال Security وسهولة Convenience استخدام المنتج، فمثلاً لو قمت باستخدام طرق حماية قصوى ولكن المنتج اصبح صعب الاستخدام فلن يستخدمه احد، على سبيل المثال عندما تسجل برقم سري في خدمة على الانترنت فبعض المواقع الجيدة تمنعك من اختيار كلمات مرور سيئة وسهل تخمينها وهذا لكي يعطيك حمايه افضل، وفي نفس الوقت لا تولد لك كلمات صعبه حتى لا تنساها أو تكتبها على ورقة وبالتالي تزيد المخاطرة ، ولكن تجعلك تكتبه بنفسك مع استخدام Policy في الكتابه مثلاً احتوائه على رقم أو رمز والخ، وكلما وازنت في الحماية Security مع السهوله Convenience كلما كان افضل، وحتى لو قلت السهوله بدرجة واحدة وحافظت على الحماية فهذا سوف يقلل من احتمالات اختراق أو حدوث خلل في منتجك الأمني وفي نفس الوقت لن تزعج مستخدمك كثيراً. أتمنى من الصديق قرائه هذه المقالة ### هل تستطيع طباعه السورس الكود كمخرج للبرنامج ؟ URL: https://informatic-ar.com/quine_self_reproducing_program/ Last updated: 2026-10-03T22:32:11.000Z أكثر برامج يعشقها برمجي لغه السي ، هي تلك البرامج التي لا يفهمها أي أحد ، حتى هم أنفسهم ، وعاده تطلق عليها برامج "مشوشه" Obfuscated وهناك بعض المسابقات تقام ، لأكثر برنامج "مشوش" ، [وقد تكلمنا عن برامج الـ Obfuscated في هذا الموضوع](https://informatic-ar.com/obfuscated%5Fcode%5Fcpp/) . [برامج الـ Quines](http://en.wikipedia.org/wiki/Quine%5F%28computing%29?ref=informatic-ar.com) (في بعض الأحيان تسمى Self-Reproducing program ) تعتبر أحد أنواع البرامج المشوشه هذه ، ولها ثقلها في المسابقات الغريبه تلك ، الأسم quine أتي من أسم مخترعها الفيلسوف [Willard Van Orman Quine](http://en.wikipedia.org/wiki/Willard%5FVan%5FOrman%5FQuine?ref=informatic-ar.com) وفكرتها في أن مخرج البرنامج هو نفسه السورس كود ،الصورة التالية تبين مخرج برنامج جافا وستجد أن المخرج هو نفسه الكود [![QuineProgram](https://informatic-ar.com/wp-content/uploads/2013/09/QuineProgram-300x289.png)](https://informatic-ar.com/wp-content/uploads/2013/09/QuineProgram.png) لنلق نظره أكثر ، قم بنسخ هذا الكود وقم بتشغيله ، ويفضل تشغيله في Visual C++ لأن هناك التنفيذ لا ينتهي وتبقى الشاشه السوداء موجوده ، أو قم بوضعه في البيئه المفضله لديك ، ولا تنسى وضع system("pause") أو أي جمله أدخال getchar في نهايه البرنامج ، وقم بوضع جمله الأدخال أيضا داخل المصفوفه الحرفيه التي يحتوي على كود البرنامج ، وستلاحظ أنه أطول متغير في البرنامج . أو ريح نفسك وشغل البرنامج من نافذه الاوامر Command Prompt . نعود الى برنامجنا ، لا تسألني عن مخرجاته ```c /* * Quine Program * Coded By : wajdy essam * Date : 08-09-2007 * This is Very Simple Form of Quine Program * I make it's easy for Beginners */ #include void charAnalysis (const char ch[] ); int main () { int i; char programCode[] = "/*\n" " * Quine Program\n " "* Coded By : Romansy\n " "* Feed Back : SudanGeek@HotMail.com\n " "* Date : 08-09-2007\n " "* This is Very Simple Form Of Quine Program\n " "* I make it's easy for Beginners\n " "*/\n\n" "#include \n\n" "void charAnalysis (const char ch[] ); \n\n" "int main () \n" "{\n" " int i;\n" " char programCode[] = \n ###; \n\n\n" " // Now Analysis Prgram char\n\n" " int i= 0;\n" " for (; programCode[i]; i++)\n " " { \n" " if ( programCode[i] == '#' && programCode[i+1] == '#'" " && programCode[i+2] == '#') \n" " {\n" " charAnalysis(programCode); \n" " i += 2; // skip second and third # \n" " }\n\n" " else \n" " printf(\"%c\",programCode[i]); \n" " } \n\n" " return 0;\n" "}\n" "void charAnalysis ( const char ch[] )\n" "{\n int i;\n printf(\" \\\" \");\n\n for (i=0; ch[i]; i++)\n" " {\n\n if ( ch[i] =='\\\\' )\n printf(\"\\\\\\\\\");\n\n" " else if ( ch[i] == '\"' )\n printf(\"\\\\\\\"\");\n\n" " else\n printf(\"%c\",ch[i]);\n\n if ( i % 49 == 47)\n" " printf(\"\\\"\\n \\\"\");\n }\n\n printf(\"\\\"\");\n\n}\n\n"; // Now Analysis Program char for ( i=0; programCode[i]; i++) { if ( programCode[i] == '#' && programCode[i+1] == '#' && programCode[i+2] == '#') { charAnalysis(programCode); i += 2; // skip second and third # } else printf("%c",programCode[i]); } return 0; } void charAnalysis ( const char ch[] ) { int i; printf(" \" "); for (i=0; ch[i]; i++) { if ( ch[i] =='\\' ) printf("\\\\"); else if ( ch[i] == '"' ) printf("\\\""); else if ( ch[i] == '\n' ) printf("\\n"); else printf("%c",ch[i]); if ( i % 49 == 47) printf("\"\n \""); } printf("\""); } ``` وهذا البرنامج أيضا من الـ Wikipeadia : ```c /* A simple quine (self-printing program), in standard C. */ /* Note: in designing this quine, we have tried to make the code clear * and readable, not concise and obscure as many quines are, so that * the general principle can be made clear at the expense of length. * In a nutshell: use the same data structure (called "progdata" * below) to output the program code (which it represents) and its own * textual representation. */ #include void quote(const char *s) /* This function takes a character string s and prints the * textual representation of s as it might appear formatted * in C code. */ { int i; printf(" \""); for (i = 0; s[i]; ++i) { /* Certain characters are quoted. */ if (s[i] == '\\') printf("\\\\"); else if (s[i] == '"') printf("\\\""); else if (s[i] == '\n') printf("\\n"); /* Others are just printed as such. */ else printf("%c", s[i]); /* Also insert occasional line breaks. */ if (i % 48 == 47) printf("\"\n \""); } printf("\""); } /* What follows is a string representation of the program code, * from beginning to end (formatted as per the quote() function * above), except that the string _itself_ is coded as two * consecutive '@' characters. */ const char progdata[] = "/* A simple quine (self-printing program), in st" "andard C. */\n\n/* Note: in designing this quine, " "we have tried to make the code clear\n * and read" "able, not concise and obscure as many quines are" ", so that\n * the general principle can be made c" "lear at the expense of length.\n * In a nutshell:" " use the same data structure (called \"progdata\"\n" " * below) to output the program code (which it r" "epresents) and its own\n * textual representation" ". */\n\n#include \n\nvoid quote(const char " "*s)\n /* This function takes a character stri" "ng s and prints the\n * textual representati" "on of s as it might appear formatted\n * in " "C code. */\n{\n int i;\n\n printf(\" \\\"\");\n " " for (i=0; s[i]; ++i) {\n /* Certain cha" "racters are quoted. */\n if (s[i] == '\\\\')" "\n printf(\"\\\\\\\\\");\n else if (s[" "i] == '\"')\n printf(\"\\\\\\\"\");\n e" "lse if (s[i] == '\\n')\n printf(\"\\\\n\");" "\n /* Others are just printed as such. */\n" " else\n printf(\"%c\", s[i]);\n " " /* Also insert occasional line breaks. */\n " " if (i % 48 == 47)\n printf(\"\\\"\\" "n \\\"\");\n }\n printf(\"\\\"\");\n}\n\n/* What fo" "llows is a string representation of the program " "code,\n * from beginning to end (formatted as per" " the quote() function\n * above), except that the" " string _itself_ is coded as two\n * consecutive " "'@' characters. */\nconst char progdata[] =\n@@;\n\n" "int main(void)\n /* The program itself... */\n" "{\n int i;\n\n /* Print the program code, cha" "racter by character. */\n for (i=0; progdata[i" "]; ++i) {\n if (progdata[i] == '@' && prog" "data[i+1] == '@')\n /* We encounter tw" "o '@' signs, so we must print the quoted\n " " * form of the program code. */\n {\n " " quote(progdata); /* Quote all. */\n" " i++; /* Skip second '" "@'. */\n } else\n printf(\"%c\", p" "rogdata[i]); /* Print character. */\n }\n r" "eturn 0;\n}\n"; int main(void) /* The program itself... */ { int i; /* Print the program code, character by character. */ for (i = 0; progdata[i]; ++i) { if (progdata[i] == '@' && progdata[i + 1] == '@') /* We encounter two '@' signs, so we must print the quoted * form of the program code. */ { quote(progdata); /* Quote all. */ i++; /* Skip second '@'. */ } else printf("%c", progdata[i]); /* Print character. */ } return 0; } ``` لن أقوم بتشريح الكود ، فالمخرج واضح ، وعمليه شرح هذه الأكواد ، سوف يدخلنا في متاهات ، أفضل تجربه الكود بنفسك . ينبغي معرفه أن مثل هذه البرامج تحتاج الى خطوتين فقط لعملها ، أول خطوه هي كتابه كود البرنامج كاملا بكل ما فيه تعليقات داخل مصفوفه من الحروف . الخطوه الثانيه هي كود لطباعه محتويات هذه المصفوفه الحرفيه (اي طباعه الكود الذي كتبناه في المصفوفه) ، وطباعتها على انها حروف أي كما هي (يأخذ هذه المصفوفه ويعالجها بطريقه ما ، سوف يطبعها) . بشكل مبسط ننشيء مصفوفه s ونكتب فيها كود البرنامج كاملا ، من البدايه الى النهايه ، لكن هناك نقطتان مهمتان ، اولأ مثلا لدى جمله ```c printf("hello"); ``` عندما أضعها داخل مصفوفه من الحروف ، يجب أن أضع علامه \\ على كل من " بداخل جمله الطباعه ، لأنني اذا لم أضعها سيعتبرها المترجم نهايه المصوفه . نفس الكلام بالنسبه الى الاشاره \\ عندما أريد أن اضعها ، أكتب \\\\ . النطقه الثانيه ، عندما أبدأ في كتابه الكود داخل المصفوفه ، وأصل الى نفس المتغير ، أضع علامه ما مثلا حرفين ## .هذه العلامه ضروريه لاننا هي بمثابه الجزء الذي أريد طباعته بالاضافه الى الكود . الان في سأطبع المصفوفه الحرفيه كما هي ، ولانها تحتوي على الكود ، سينطبع الكود . وفي خلال طباعه الكود ، سأطبع المصفوفه الحرفيه على أنها حروف ، أي أقوم باستبدال مثلا \\n ب \\\\n حتى تطبع كما هي وبدون تنفيذها . متى أعرف أننا وصلنا ألى المصفوفه الحرفيه ونحن نطبع حروف في الأصل ، هل تذكر العلامتان ## وهن يشيران الى بدايه المصفوفه . ربما لن تفهم أي شيء الى الأن -وهذا محتمل- لكن هذه هي برامج الـ Obfuscated ، أفضل طريقه هي كتابه البرنامج من الصفر بنفسك ، وستلاحظ مدى بساطه الفكره وسهولتها ، وصعوبه شرحها . برنامج أخر ، قم بتشريحه بنفسك (مع العلم 34 تشير الى " ) : ```c main() { char *s = "int main() { char* s = %c%s%c; printf(s, 34, s, 34); }"; printf(s, 34, s, 34); } ``` وهذه العشرات من البرامج في المرفق لسي و سي++ ، [قم بتحميلها من هنا للتسليه](https://informatic-ar.com/files/Quines%5FProgram.rar) . ### الكود الأكثر صعوبه Obfuscated Code URL: https://informatic-ar.com/obfuscated_code_cpp/ Last updated: 2026-10-03T22:32:11.000Z مقالنا اليوم يتكلم حول طرق سيئه "للغايه" لكتابه الكود بحيث لن يفهم هذا الكود الا القليل ، وهناك بعض الأسباب لعمل مثل هذه البرامج منها مثلا التسليه Fun ، التحدي ، حمايه كود البرنامج ، الفوز بمسابقه ما (هناك مسابقه عالميه تقام كل سنه حول هذا النوع من البرامج International Obfuscated C Code Contest ) . [![Obfuscated Code](https://informatic-ar.com/wp-content/uploads/2013/09/Obfuscated-Code-279x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Obfuscated-Code.jpg) أغلب هذه البرامج تكون مكتوبه بسي ، أو بيرل ، ونادرا ما نجد سي++ ، وذلك لأن سي تحتوي بعضا من الأمور قد تم تلافيها في سي++ ، منها مثلا استدعاء الداله main من داخل البرنامج . أحب أن أذكر أن الطرق الموجوده هنا ، لا ينصح باستخدامها أبدا ، فهي طرق سيئه جدا ، وكما ذكرت فقط الغرض منها هو التشويش ... ولنأخذ مثال "فاتح للشهيه" قبل أن نبدأ بالطرق ، قم بقرائه المثال ، وفي حال فهمت المثال كاملا وعرفت المخرجات ، فسوف أجزم أنك أحد هؤلاء المجانين . ```c #include main(t, _, a) char *a; { return !0 < t ? t < 3 ? main(-79, -13, a + main(-87, 1 - _, main(-86, 0, a + 1) + a)) : 1, t < _ ? main(t + 1, _, a) : 3, main(-94, -27 + t, a) && t == 2 ? _ < 13 ? main(2, _ + 1, "%s %d %d\n") : 9 : 16 : t < 0 ? t < -72 ? main( _, t, "@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l,+,/n{n+,/+#n+,/#\ ;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l \ q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# \ ){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw' \ iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c \ ;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# \ }'+}##(!!/") : t < -50 ? _ == *a ? putchar(31 [a]) : main(-65, _, a + 1) : main((*a == '/') + t, _, a + 1) : 0 < t ? main(2, 2, "%s") : *a == '/' || main(0, main(-61, *a, "!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m .vpbks,fxntdCeghiry"), a + 1); } ``` حاول أن تتخيل ما هي المخرجات قبل أن تقوم بتشغيله ، تخيل فقط !! **كيف تكتب برنامج مشوش ؟** هنا علينا أن نرجع الى بدايه تعلمنا للغه ونتذكر ما هي أصعب الأشياء التي واجهتنا في التعلم ، أذكر أنني جلست كم يوم حتى فهمت ما هو المؤشر Pointer ، مممم أيضا الأستدعاء الذاتي مربك أيضا ، أيضا المصفوفات وخاصه عندما نستخدم Pointer Arithmetic ، معامل الفاصله Comma مربك ايضا لان تنفيذه غريب بعض الشيء ، جميع المواضيع السابقه التي كنت تعتبرها صعبه ، هنا ستكون هي سلاحك الأول أيضا المتغيرات ذات الشكل المتشابه مثلا الحرف O و الحرف o ( الأول كبير والثاني صغير ) و الرقم 0 ، أيضا الحرف i والحرف l والحرف L الرقم 1 ، هذه المتغيرات تسبب لي التشاؤم من البرنامج ، خاصه عندما كنت أقرأ برنامج أحد الطلاب ، وهذه أحد سطور أحد الطلاب : ```c char clear, ch, c, bag[100], bag1[10][10], bag2[100]; int n = 0, m, x, w = 0, aa = 0, z, l = 0, nn[50], mm[50], qw, az, i, j; // other code for (int fg = 1; fg <= az; fg += 2) { if (nn[fg - 1] == nn[fg]) { bag2[fg - 1] = bag1[nn[fg - 1]][(mm[fg - 1] + 1) % 5]; bag2[fg] = bag1[nn[fg - 1]][(mm[fg] + 1) % 5]; } else if (mm[fg - 1] == mm[fg]) { bag2[fg - 1] = bag1[(nn[fg - 1] + 1) % 5][mm[fg - 1]]; bag2[fg] = bag1[(nn[fg] + 1) % 5][mm[fg]]; } else { bag2[fg - 1] = bag1[nn[fg - 1]][mm[fg]]; bag2[fg] = bag1[nn[fg]][mm[fg - 1]]; } } ``` يبدوا لي أن هذا الطالب يكتب برنامج Obfuscated بالفطره ، وأعتقد أنه سيفوز بمثل هذه المسابقات بكل سهوله :) . نبدأ الأن بقطع من الكود ، ونحاول أن نغيرها من كود عادي الى مشوش ، وبعدها نأخذ مثال Hello World ومن ثم نحوله الى Hello Devil . **التلاعب بأسماء المتغيرات :** نأخذ مثال لبرنامج يطبع الحروف من A الى Z ، ونقوم ببعض التغيرات عليه في اسامي المتغيرات : ```cpp #include int main() { for (int i = 'A'; i <= 'Z'; i++) printf("%c", i); return 0; } ``` الان قمنا بوضع جمله الطباعه في داله ، ومررنا لها الحرف الأول والأخير ، ومن ثم طبعنا الحروف : ```c #include void _(int O, int _O) { for (; O <= _O; O++) printf("%c", O); } int main() { // you can change A with 65 in decimal OR with 0x41 in hex // olse you can change Z to 90 in decimal or 0x5A in hex _(0x41, 90); return 0; } ``` لاحظ استخدام الحروف المشابه O و \_O ، أيضا ارسال الحرف A بقيمته في النظام Hex ، ويمكن أستخدام أي نظام أخر . (طبعا عليك بازاله التعليق من البرنامج ، وهو أصلا موضوع لك أنت ، حيث البرنامج المفترض يشوش القارئ وليس يشرح للقارئ). **التلاعب بالمؤشرات والمصفوفات :** مراجعه سريعه للأمر ، تجدها في هذه السطور : ```c printf("\n\n%d\n", arr[1]); // print 20 printf("%d\n", *(arr + 1)); // print 20 printf("%d\n", *(1 + arr)); // print 20 printf("Address = %x\n", &arr[1]); // print Address for element 1 printf("Address = %x\n", arr + 1); // print Address for element 1 ``` الأن كيف يمكن أن نتلاعب هنا ، كلنا نعرف أذا أردنا أن نرسل مصفوفه لداله نقوم بارسال عنوانها ، حسنا ، ممكن أن نضع هذا العنوان في متغير int أولا ، ونقوم بارسال هذا العدد (العنوان) ، الان من داخل المصفوفه نقوم بتحويل هذا العدد int الى مؤشر عن طرق الCast . أنظر الى المثال القادم ، وهو لطباعه الأعداد من 1 الى 10 ، والذي يليه هو بعد التلاعب فيه قليلا : ```c #include void functionOne(int *array, int size) { for (int i = 0; i < size; i++) printf("%d ", array[i]); // or *array++ or *(array+i) } int main() { int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; functionOne(array, 10); return 0; } ``` الان النسخه المعدله ، ولاحظ أننا في الداله main قمنا بأخذ عنوان المصفوفه ، وارساله الى الداله functionOne ، وهناك قمنا بعمل cast لهذا الرقم وحولناه الى مؤشر ، بعدها باستخدام pointer arithmetic قمنا بالوصول الى عناصر المصفوفه . ```c #include void functionOne(int address, int size) { int i = 0; for (; i < size; i++) printf("%d ", *((int *)address + i)); } int main() { int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int address = (int)array; // (int) &array[0] functionOne(address, 10); return 0; } ``` **المعامل الشرطي الثلاثي Conditional Operator ومعامل الفاصله Comma Operator :** معامل الفاصله يعمل من اليسار الى اليمين ، ومن ثم يسند قيمه المتغير الموجود في الجهه اليمني الى المتغير اذا كان هناك. نأخذ مثال : ```c int a = 0; int b = 3; a = (b += 3, b - 1); ``` الان عند تنفيذ السطر الثالث ، نقوم بزياده b بمقدار 3 وتصبح 6 ، بعدها نطرح من b واحد وتصبح 5 ، وأخيرا نسند b الى a (اي تصبح قيمه a هي 5 ). المعامل الشرطي هو بديل لجمله If : ```c if () { = ; } else { = ; } ``` وباستخدام Conditional Operator : ```c = () ? () : (); ``` في حال كان test\_condition صحيح سوف ينفذ value\_if\_true والا value\_if\_false . هذا المعامل له دور كبير جدا في هذه النوعيه من البرامج ، حيث يتم استبدال أي جمله شرطيه بهذا المعامل . وفي حاله كانت هناك nested if ، يمكن استخدام المعامل الشرطي مع معامل الفاصله لكي يؤدي الغرض : ```c = () ? (, ..., ) : (, ..., ); ``` مثال : ```c int a = 1; int b = 2; int c = 3; if (a < 0) { b += 3; a = b - 1; } else { c += b; a = c + 2; } ``` الان نحوله الى conditional operator و comma operator : ```c int a = 1; int b, c; a = (b = 2, c = 3, a < 0) ? (b += 3, b - 1) : (c += b, c + 2); ``` وهنا أولا سوف يسند 2 الى b ، بعدها 3 الى c ، بعدها يقارن هل a أصغر من 0 ، في حال كان ذلك ، يزيد على b ثلاثه ، ومن ثم يطرح واحد ، ويرجع القيمه ويسندها الى a . اما اذا كان a أكبر من 0 ، سوف يزيد على c مقدار b وهي 2 ، بعدها يزيد على c 2 ويسند الناتج الى a . **الأستدعاء الذاتي :** الجميع يعلم هذا الموضوع ، وهي داله تنادي نفسها في حال تحقق شرط ما ، وهنا أغلب الأحيان نرسل قيمه وهي التي تدل على نهائيه الأستدعاء ، أو نقوم بعمل متغير static لكن لا يجب أ ن تستخدم هذه الطريقه في حاله استدعيت الداله أكثر من مره . نأخذ مثال لطباعه الأعداد من 1 - 10 ، وبعدها نحوله الى النداء الذاتي : ```c #include "stdio.h" int main() { int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int i; for (i = 0; i < 10; ++i) { printf("%d ", array[i]); } return 0; } ``` وها هو البرنامج بعد التعديل : ```c #include "stdio.h" void recursiveFunction(int array, int i, int stop) { printf("%d ", *((int *)array + i)); i++; if (i < stop) { recursiveFunction(array, i, stop); } } int main() { int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; recursiveFunction((int)array, 0, 10); return 0; } ``` الأن نأخذ مثال شامل لـ Hello World ، المثال هو : ```c #include "stdio.h" int main() { printf("Hello World!"); return 0; } ``` وبعدها سندخل هذا المثال غرفه العمليات ، للقيام بعمليه تجميل ، ليخرج بالشكل : ```c #include "stdio.h" _(__, _0, O_, ___) { (O_ < _0) ? printf("%c", (O_ == 2) || (O_ == 3) || (O_ == 9) ? ___++, O_++, 108 : *((int *)__ + O_++ - ___)), _(__, _0, O_, ___) : 0; } main() { int array[] = {72, 'e', 111, 040, 0127, 0x6F, 'r', 0x64, 041}; _(array, 12, 0, 0); } ``` جميل أليس كذلك ! نبدا الأن . **الخطوه الأولى :** نعمل داله للطباعه ، نرسل اليها المصفوفه ، القيم التي في المصفوفه كتبناها بالنظام العشري . ```c void myFunction(int array[], int arraySize) { int i; for (i = 0; i < arraySize; ++i) { printf("%c", array[i]); } } int main() { int array[] = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33}; myFunction(array, 12); return 0; } ``` **الخطوه الثانيه :** استخدمنا فكره ارسال متغير يحمل العنوان (التي شرحناها في الأعلى) وبعدها نقوم بعمل cast للمتغير ونحوله الى مؤشر : ```c #include "stdio.h" void myFunction(int integer, int arraySize) { int i; for (i = 0; i < arraySize; ++i) { printf("%c", *((int *)integer + i)); } } int main() { int array[] = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33}, integer = (int)array; myFunction(integer, 12); return 0; } ``` **الخطوه الثالثه :** غيرنا الداله وقمنا بعمل استدعاء ذاتي ، وغيرنا if الى Conditional Operator : ```c void myFunction(int integer, int arraySize, int i) { (i < arraySize) ? printf("%c", *((int *)integer + i++)), myFunction(integer, arraySize, i) : 0; } int main() { int array[] = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33}; myFunction((int)array, 12, 0); return 0; } ``` **الخطوه الرابعه :** نتخلص من بعض الحروف المكرره في المصفوفه (وهو الحرف L) يتكرر ثلاث مرات 108 ، في الموقع 2 و 3 و 9 : ```c #include "stdio.h" void myFunction(int integer, int arraySize, int i, int k) { (i < arraySize) ? (i == 2) || (i == 3) || (i == 9) ? k++, i++, printf("%c", 108) : printf("%c", *((int *)integer + i++ - k)), myFunction(integer, arraySize, i, k) : 0; } int main() { int array[] = {72, 101, 111, 32, 87, 111, 114, 100, 33}; myFunction((int)array, 12, 0, 0); return 0; } ``` **الخطوه الخامسه :** نغير من ترتيب معامل الشرط في الداله : ```c void myFunction(int integer, int arraySize, int i, int k) { (i < arraySize) ? printf("%c", (i == 2) || (i == 3) || (i == 9) ? k++, i++, 108 : *((int *)integer + i++ - k)), myFunction(integer, arraySize, i, k) : 0; } int main() { int array[] = {72, 101, 111, 32, 87, 111, 114, 100, 33}; myFunction((int)array, 12, 0, 0); return 0; } ``` **الخطوه السادسه :** نغير من أسماء المتغيرات في الداله ، ودائما تغير اسماء المتغيرات يأتي في أخر مرحله حتى لا تضيع أنت وتشوش على نفسك اثناء كتابة البرنامج ، أيضا تغيير القيم في المصفوفه وجعلها بعده أنظمه Hex و octal وdecimal : ```c #include "stdio.h" void myFunction(int __, int _0, int O_, int ___) { (O_ < _0) ? printf("%c", (O_ == 2) || (O_ == 3) || (O_ == 9) ? ___++, O_++, 108 : *((int *)__ + O_++ - ___)), myFunction(__, _0, O_, ___) : 0; } int main() { int array[] = {72, 'e', 111, 040, 0127, 0x6F, 'r', 0x64, 041}; myFunction((int)array, 12, 0, 0); return 0; } ``` **الخطوه السابعه :** تغير اسم الداله ، حذف القيمه الراجعه ، وسوف يعتبرها المترجم هي int ، أيضا حذف نوع المتغير المرسل في الداله وأيضا سيتعبرها int : ```c _(__, _0, O_, ___) { (O_ < _0) ? printf("%c", (O_ == 2) || (O_ == 3) || (O_ == 9) ? ___++, O_++, 108 : *((int *)__ + O_++ - ___)), _(__, _0, O_, ___) : 0; } main() { int array[] = {72, 'e', 111, 040, 0127, 0x6F, 'r', 0x64, 041}; _(array, 12, 0, 0); } ``` **الخطوه الأخير :** نقوم بحذف المسافات بين الجمل ، وضغطها : ```c #include "stdio.h" _(__, _0, O_, ___) { (O_ < _0) ? printf("%c", (O_ == 2) || (O_ == 3) || (O_ == 9) ? ___++, O_++, 108 : *((int *)__ + O_++ - ___)), _(__, _0, O_, ___) : 0; } main() { int array[] = {72, 'e', 111, 040, 0127, 0x6F, 'r', 0x64, 041}; _(array, 12, 0, 0); } ``` واوووو ...... من كان يصدق أن هذا البرنامج Hello World ! **نقطه أخيره** البرنامج السابق هو أسهل برنامج مشوش ، نعم !! هناك بعض البرامج معقده جدا وخاصه عندما يتم استخدام جمل الهاش # أو Preprocessor Directive هناك بعض البرامج ، يمكن أن تقوم بكشفها بسرعه مثل : ```c _(__, ___, ____) { ___ / __ <= 1 ? _(__, ___ + 1, ____) : !(___ % __) ? _(__, ___ + 1, 0) : ___ % __ == ___ / __ && !____ ? (printf("%d\t", ___ / __), _(__, ___ + 1, 0)) : ___ % __ > 1 && ___ % __ < ___ / __ ? _(__, 1 + ___, ____ + !(___ / __ % (___ % __))) : ___ < __ * __ ? _(__, ___ + 1, ____) : 0; } main() { _(100, 0, 0); } ``` وهو يطبع الأعداد الأوليه من 1 الى 100 ، وهنا أولا أستعن بخاصيه Find & Replace الموجوده في الDE أو في أي محرر نصوص ، وغير كل : \_ بمثلا Function ، وبعدها سوف تصبح النتيجه : ```c Function(__, ___, ____) { ___ / __ <= 1 ? Function(__, ___ + 1, ____) : !(___ % __) ? Function(__, ___ + 1, 0) : ___ % __ == ___ / __ && !____ ? (printf("%d\t", ___ / __), Function(__, ___ + 1, 0)) : ___ % __ > 1 && ___ % __ < ___ / __ ? Function(__, 1 + ___, ____ + !(___ / __ % (___ % __))) : ___ < __ * __ ? Function(__, ___ + 1, ____) : 0; } main() { Function(100, 0, 0); } ``` الان غير كل : \_\_ بمثلا varOne وقم بعمل replace All : ```c Function(varOne, ___, ____) { ___ / varOne <= 1 ? Function(varOne, ___ + 1, ____) : !(___ % varOne) ? Function(varOne, ___ + 1, 0) : ___ % varOne == ___ / varOne && !____ ? (printf("%d\t", ___ / varOne), Function(varOne, ___ + 1, 0)) : ___ % varOne > 1 && ___ % varOne < ___ / varOne ? Function(varOne, 1 + ___, ____ + !(___ / varOne % (___ % varOne))) : ___ < varOne * varOne ? Function(varOne, ___ + 1, ____) : 0; } main() { Function(100, 0, 0); } ``` واستمر هكذا الى أن تنتهي من جميع أسماء المتغيرات العجيبه ، وبعدها قم بتنظيم البرنامج ، وهنا ستجد نفسك أمام الكود مباشره ! أخيرا ، لما لا يطرح كل منا كوده الـ Obfuscated ونشاهد من هو صاحب الكود الأكثر تشويشا ؟ ### اجعل المترجم يساعدك في أكتشاف الأخطاء باستخدام const URL: https://informatic-ar.com/using_const_cpp/ Last updated: 2026-10-03T22:32:12.000Z في المقال السابق ( [لماذا عليك أن تقلل من الأعتماد على Preprocessor ؟](https://informatic-ar.com/dont%5Fuse%5Fpreprocessor/)) تحدثنا عن ضروره التقليل من استخدام الـ Preprocessor وخاصه عند تعريف الثوابت ، وذكرنا البديل الأفضل وهو أستخدام const .. مقالنا الليله يدعوك الى استخدام الqualifier المسمى const في أي مكان واي زمان لأنك هكذا ستكون في الـ Safe Side وستتعامل بشكل أكثر صرامه مع عملاء طبقاتك Your Client وسوف يساعدك المترجم في هذه المهمه . الثوابت يمكن ان تكون في أي جزء في برنامجك ، داخل Block Scope مثلا الداله main ، أو Global Scope ، أو مثلا في داله سواء في القيم المرسله لها Argument أو حتى القيمه الراجعه منها return value .. يمكن أن يكون الثابت داخل الكلاس أو حتى داخل الـ Namespace .. أخيرا يمكن أن يكون مع المؤشرات ، وهنا يوجد له ثلاث استخدامات : مؤشر الى ثابت ، مؤشر ثابت الى متغير ، مؤشر ثابت الى ثابت ... نقوم بعمل Refresh بسيطه للأساسيات: ```cpp char arr[] = "wajdy Essam"; char *p = arr; // This is pointer to char char *const cp = arr; // This is const Pointer to char const char *p = arr; // This is Pointer to const char char const *p = arr; // This is Pointer to const char const char *const p = arr; // This is const Pointer to const char ``` لكي لا تقع في متاهه التفكير في الأمثله أعلاه ، دائما أنظر الى علامه \* ، اذا كان بعدها على اليمين const فهذا يعني أن المؤشر هو ثابت (لا يؤشر الى شيء أخر) ، أما اذا قبلها (في اليسار) const فهذا يعني أن ما يؤشر اليه pointed to هذا المؤشر ثابت لا تتغير قيمته .. أما اذا ظهرت في الجهتين فمعناه أن الأثنين ثابتين .. بالنسبه الى مؤشر الى ثابت يمكن أن تكون كلمه const بعد أسم المؤشر (قبل علامه \* ) ، مثل : ```cpp const char *p = arr; // This is Pointer to const char char const *p = arr; // This is Pointer to const char ``` لذلك دائما أنظر الى علامه \* وهي أوضح وأسهل طريقه لكي تعرف الثبوتيه :) .. استخدام الثوابت يعني أن قيمتها لن تتغير أثناء تنفيذ برنامجك ... والمترجم يضمن لك ذلك أثناء ترجمه برنامجك .. حيث أن أي تغيير في أي بت في ذلك الثابت سوف يصرخ المترجم في وجهك ويخرج لك رساله بأنك تحاول أن تغير في ثابت .. ومن هنا كان تعريف الثوابت في سي++ يعرف بـ Bitwise Constness أي أنه لا يمكن تغيير قيمه أي بت في المتغير . من أشهر أستخدامات الثوابت هي في القيم المرسله والراجعه في الداله .. بالنسبه للقيم المرسله فمعناه أنه الداله لن تغير من قيمه هذا (المتغير أو الكائن ) .. أما بالنسبه الى القيمه الراجعه فمعناه أن القيمه الفلانيه الراجعه من الداله لا يمكن أن تتغير (أي لن تكون في الجهه اليمنى في جمله الأسناد) ، وطبعا استخدام const في القيمه الراجعه أمر مهم ويضمن لك سلامه أستخدام الـ Client للطبقه الخاصه بك ولن توقعهم في مشاكل ما .. لنذكر مثال بسيط يبين الفكره : ```cpp int a, b, c; // here put any value in this varaible a + b = c; ``` السطر الأخير خاطئ ، والسبب أن الـ Left value دائما يجب أن تكون موقع location وليس قيمه ... هذا الكلام بالنسبه الى المتغيرات العاديه Primitive Data Type .. أما بالنسبه للكائنات فلن يكون أي خطأ هناك ، لأنه ناتج جمع كائن مع كائن(بعد اعاده تعرف المعامل +) سوف يكون كائن أخر ،وعمليه الأسناد تتم بشكل عادي (استدعاء لـ = operator) ، مثال بسيط : ```cpp MyObject a, b, c; // here put Any Value in Objects a + b = c; ``` العمليه صحيحه ولا يوجد خطأ بالترجمه ... ولكنه بالتأكيد خطأ منطقى قام به العميل لطبقتك .. لذلك عليك عند تصميم الطبقه Class أن تراعي مثل هذه الأخطاء بوضع const في القيمه الراجعه وبالتالي لن تكون هذه القيمه أبدا هي LValue .. ربما قد تقول يستحيل أن يقوم مبرمج بكتابه سطر مثل هذا : ```cpp a + b = c; ``` حسنا ما رأيك بهذا : ```cpp if (a + b = c) // any statement ``` لقد وقع المبرمج القدير بالفخ ، وخطأ مثل هذا قد يصعب تصحيحه .. لذلك لم لا تدع هذه المهمه للمترجم .. فقط أعلن عن القيمه الراجعه بconst وريح نفسك ... أحد الأشهر الأستخدامات لـ const أيضا هو في داخل الداول في الكلاس const member function ، هذا يعني أن تلك الداله لن تغير في قيمه أي متغير معرف داخل الكلاس (ماعدا static variable) ... أيضا هذه الدوال const لا يمكن أن يتم استدعائها الا من خلال الكائنات الثابته const object .. سؤال / ماالفائده من const Member Function بشكل عملي ؟ جواب / لأنها تجعل الـ Interface أكثر وضوحا .. أضافه الى أنها يتم استدعائها بواسطه الكائنات الثابته const object سؤال / ما الحاجه للكائنات الثابته const object ؟ جواب / استخدام const مع الكائنات لا توجد منه فائده كبيره ... لكن Reference to const هو أكثر أستخداما حيث كما نعلم أن التمرير بالمرجع أفضل من التمرير بالقيمه عند التعامل مع الكائنات حيث لن يتم استدعاء الـCopy Constructor ولا Destructor عند الأنتهاء من الداله .. لذلك دائما تمرير الكائنات يفضل بالمرجع : - اذا أدرت تغيير قيمته أرسله بالمرجع فقط By reference - اذا لم ترد تغيير قيمته أرسله By reference to const . وبما أنك تريد ارسال reference to const فلن تستطيع استدعاء أي داله الا أن تكون const .. تخيل أن لديك داله display للطباعه داخل كلاس A . و لكي تستخدم هذه الداله يجب عليك عمل كائن وتقوم باستدعائها .. في حال أحتجت لداله تأخذ كائن من A ثابت ، هنا لن تستطيع استدعاء display لأنها غير const .. الحل هو باعاده تعريف الداله display في الكلاس A وجعلها const ، هكذا يكون لدينا دالتين بنفس الأسم ، أحدهما عاديه والأخرى ثابته .. شاهد المثال : ```cpp #include using namespace std; class A { private: int x; public: A(int v = 5) : x(v) { } void display() { cout << x << endl; } }; void calculator(const A &a) { // simple statement here a.display(); } int main(int argc, char **argv) { A a; calculator(a); } ``` الأن ترجم البرنامج :وستجد الخطأ بأنك لا يمكن أن تستدعي display من خلال الثابت a .. الحل كما ذكرت باعاده تعريف الداله display ، ليكون لدينا : ```cpp void display() { cout << x << endl; } void display() const { cout << x << endl; } ``` في حال قمنا في الداله main بعمل كائن من A واستدعينا display.. في حال كان الكائن ثابت سوف يستدعي display الثابته ، واذا كان عادي سوف يستدعي الداله العاديه .. جرب المثال التالي وشاهد المخرجات : ```cpp #include using namespace std; class A { private: int x; public: A(int v = 5) : x(v) { } void display() { cout << "in Non-Const Function : " << x << endl; } void display() const { cout << "in Const Function : " << x << endl; } }; void calculator(const A &a) { // simple statement here a.display(); } int main(int argc, char **argv) { A a; calculator(a); A b(3); b.display(); const A c(4); c.display(); } ``` بعض الأحيان يكون الكائن ثابت ، والداله ثابته .. ولكن يمكنك تغيير القيمه ولن يشتكي المترجم .. والسبب لأنه لا يفكر ولا يعرف ماذا تفعل ، فقط هو يراعي الـ syntax ولا يفكر .. ففي حال أرجعت قيمه من داخل الكلاس بالمرجع ، يمكنك تغيير تلك القيمه التي داخل الكلاس عن طريق مؤشر .. وهو مايعرف بـ logical constness .. أنظر المثال التالي : ```cpp #include using namespace std; class Text { private: char *ch; public: Text(char *c) { ch = new char[strlen(c) + 1]; strcpy(ch, c); } void display() const { cout << ch << endl; } char &operator[](int position) const { return ch[position]; } }; int main(int argc, char **argv) { const Text t("My Name is Wajdy"); char *ptr = &t[0]; *ptr = 'X'; t.display(); return 0; } ``` لاحظ أننا أرجعنا Reference من المصفوفه ، وقمنا بتغيير هذه القيمه بواسطه مؤشر ... في بعض الحالات الشاذه ، قد تود أن تغير من قيمه متغير داخل الداله الثابته في الكلاس ، فقم باعلان المتغير بـ mutable وبالتالي لن يشتكي المترجم ان قمت بتغيير قيمه المتغير داخل تلك الداله .. ```cpp mutable int x; ``` ****Avoiding Duplication in const and Non-const Member Functions** الى هنا ، عرفنا أنه يجب ان نقوم بعمل overload للداله لكي تتعامل مع الconst وnon-const .. لكن المشكله قد تكون في حال كانت الداله كبيره ، سوف يكون هناك تكرار كثير في الكود : لاحظ : ```cpp const char &operator[](std::size_t position) const { ... // do bounds checking ... // log access data ... // verify data integrity return text[position]; } char &operator[](std::size_t position) { ... // do bounds checking ... // log access data ... // verify data integrity return text[position]; } ``` **ما الحل في مشكله تكرار الكود Duplication هنا ؟** أسهل حل بوضع تلك الأجزاء التي تتكرر في داله (private) ونقوم باستدعاء هذه الداله داخل الدالتين .. حل أخر وهو جعل الداله العاديه تستدعي الداله الثابته ، وذلك بتحويل الكائن الحالى الى ثابت ، واستدعاء الداله الثابته وعندما ترجع القيمه الثابته نقوم بازاحه الثابت cast const away .. كيف يمكن عمل ذلك ؟ ```cpp const char &operator[](std::size_t position) const { ... // do bounds checking ... // log access data ... // verify data integrity return text[position]; } char &operator[](std::size_t position) { return const_cast(static_cast(*this)[position]); } ``` السطر الأخير وقد يبدوا غريب بعض الشيء .. نقوم بشرحه : const\_cast( تعني أن مابين القوسين سوف نزيح منه const ) ، اي حذف الـ const static\_cast(\*this) هنا اضافه const الى الكائن الحالي \*this \[position\] هنا سوف يتم استدعاء الداله ذات const هذا هو الحل الأخر ، وبشكل عام عمليات Casting هي حلول سيئه في سي++ وعلينا أن نتجنبها بأي طريقه .. لكن بالمقابل تكرار الكود code duplication أمر لا يمكن السكوت عنه نهائيا .. لذلك اما أن تستخدم داله أخرى ، أو تستخدم الCasting .. **أخيرا** ، أستخدام const أمر جميل حقا ، فهو يضمن لك بأن المترجم سوف ينبهك بأي عمليه خاطئه لا تود أن يقع فيها عميل طبقتك ... لذلك حاول أن تكثر من أستخدامها في اي مكان .. سوف تكون حقا مسرور بذلك . **المصادر :** Effective c++ Thinking in C++ ### لماذا عليك أن تقلل من الأعتماد على Preprocessor ؟ URL: https://informatic-ar.com/dont_use_preprocessor/ Last updated: 2026-10-03T22:32:12.000Z موضوعنا الليله يتحدث حول التقليل من استخدام الـ Preprocessor في سي++ وأستخدام طرق أخرى للوصول الى الهدف ، وخاصه الـ #define والتي كما سنرى بعد قليل مساوئها أكثر من ميزاتها .. وبشكل عام دائما حاول الأعتماد على الCompiler وليس الـ Preprocessor ، أي ( Prefer Compiler Than Preprocessor ) في البدايه قبل أن نذكر لماذا علينا أن نقلل من أستخدام تعليمات الـ Preprocessor ، علينا أن نتحدث عن ماهيه الـ Preprocessor ، ودوره في لغه سي++ . الـ Preprocessor هو برنامج يقوم بوظيفه معينه قبل أن يبدأ المترجم عمله ، هذا البرنامج Preprocessor لديه تعليمات خاصه به تسمى Preprocessor Directive ، وفي سي++ أي جمله تبدأ بالعلامه # تعتبر Preprocessor Directive . بعدما يبدأ عمل الـ Preprocessor ويقوم بمعالجه الـ Directive ، سوف ينتهي عمله ويبدأ بعدها المترجم بالعمل ، طبعا المترجم لن يرى أي Preprocessor Directive وذلك لأن Preprocessor قد عالج تلك الأوامر ، أي أن المترجم سوف يرى حاصل العمليات بعد تشغيل الـ Preprocessor . وظيفه الـ Preprocessor غالبا تكون في أربعه أمور : 1- تعريف ثوابت Constants 2- تعريف ماكروز Macros 3- عمل تضمين لملفات الرأس Inclusion Guards 4- في عمليات compilation Control وتستخدم للتأكد من عدم اضافه ملف رأس أكثر من مره ، ومسح تعريف ثابت . في الحاله الأولى ، عندما أريد أن أعرف ثابت سوف أقول **:** ```cpp #define MAX 30 ``` أما في الحاله الثانيه ، لتعريف ماكروز (شبيه بالداله ، ولكنه أسرع في التنفيذ أولا لأنه لن يكون موجود في ملف Object الناتج لأنه كما ذكرنا أن الPreprocessor سوف يعمل له Substitute ، بعكس الداله التى لها ثقل Overhead حيث هناك Call والتي تسبب عملتي Push و pop ) : ```cpp #define MIN(x, y) (x) < (y) ? (x) : (y) ``` الماكروز أعلاه يقوم بارجاع العدد الأصغر من عددين . أما الحاله الثالثه ، فدائما ما نستخدمها لكي نصل للـ Deceleration للدوال التي نستخدمها والتي تكون في ملف رأس أخر ، مثل : ```cpp #include ``` الحاله الأخير وهي للتأكد من أن من سيستخدم هذا الملف لا يقوم بعمل Include أكثر من مره ، وذلك باستخدام الـ Inclusion Guards : ```cpp #ifndef WAJDY.HPP #define WAJDY .HPP // here Your Source #endif ``` بالنسبه للحاله الثالثه والرابعه ، فلن يمكنك الأستغناء عنها أبدا ، لأنها ضروريه جدا ، فلا يمكن أستدعاء داله موجوده في كلاس أخر الأ بعد عمل include لهذا الكلاس .. أما بالنسبه للحالتين الأولى والثانيه فهناك مساوئ في أستخدامهم ويجب أن نتجنبهم بقدر الأمكان واستخدام حلول سي++ الأكثر فعاليه ، كما سنرى بعد قليل . **لماذا لا يجب أن نستخدم #define لكي نعرف الثوابت ؟** السبب هو أن الـ Preprocessor أول ما يرى الثابت سوف يقوم بتغيير الأسم Substitue مباشره بالقيمه الخاصه بالثابت ، وبالتالي عندما يبدأ المترجم عمله سوف يرى القيمه فقط ، لنفرض أنك عرفت الثابت MAX بالقيمه 10 في الملف A.h ، المهم في الملف الأخر قمت باستخدام MAX وحصلت مشكله في هذا الثابت ، الرساله التي ستأتيك من المترجم هي خطأ في 10 وليس MAX (لأن المترجم لا يراه) ، تخيل أنك تعمل في مشروع كبير مكون من عشرات الملفات وعده مبرمجين يعملوا في المشروع ، هل تستطيع معرفه ماذا يقصد بالرقم 10 ؟ يمكن الأستغناء عن تعريف الثوابت بـ #define عن طريق const : ```cpp const int MAX = 10; ``` هنا وصلنا لنفس الهدف وبشكل أكثر كفائه ، حيث في حاله الخطأ سوف يكون واضح هو MAX ، أيضا الثوابت لا تدخل جدول الرموز Symbol Table الا في حال حصلت على عنوانها ، أو أحتاج المترجم الى الوصول الى عنوان الثابت فهنا في هذه الحاله تدخل الجدول . لكن عند التعامل مع الـ const يجب أعطائها قيمه مباشره ، وهنا علينا سوف نقع في ثلاثه خيارات : - ثابت عادي - ثابت معرف داخل كلاس - مؤشر الى ثابت لتعريف الثابت العادي (أقصد في أي داله) : ```cpp const int MAX = 10; ``` أما الثابت المعرف داخل الكلاس ، يجب أعطائه قيمه في داله البناء في مرحله الـ Initializing والا سوف يكون هناك رساله error : ```cpp class Demo { private: const int MAX; public: Demo() : MAX(0) { } }; ``` جرب أعطي الثابت قيمه داخل الـ Body في داله البناء وسوف تجد المترجم يشتكي منك . الان التعريف السابق ، يعني أن كل كائن سوف يحتوي على ثابت خاص به ، وفي الكثير من الأحيان نحن لا نريد ذلك ، مثلا كنا نريد عمل كلاس به مصفوفه من خانات ثابته ، فالأفضل عمل الثابت لجميع الكائنات (جميعا تتشارك في هذا الثابت) وهو عن طريق static .طبعا يمكن أن نعرف الثابت Global ونستخدم هذا الثابت داخل الكلاس ، ولكن سوف يهز مبدأ الحمايه والكبسله ، لذلك يفضل أن نعرف هذا الثابت داخل الكلاس ويكون static : ```cpp class Stack { private: static const int MAX = 10; int arrat[MAX]; }; ``` بشكل عام في سي++ ، جميع الأعلانات عن المتغيرات الـ Static داخل الكلاس تكون Declare أعلان فقط من غير قيمه ، ويتم اعطاء القيمه من خارج الكلاس . ماعدا في حاله واحده هي static const to Integral Data Type أي لثابت من نوع صحيح أو حرفي char وهنا في هذه الحاله سوف يتم اعتباره Declare & Definition في نفس اللحظه . \-ربما قد تشتكي المترجمات القديمه من الكود السابق ، وهنا عليك بتعريف هذا الثابت بالخارج واعطائه قيمه هناك- أو يمكنك أستخدام الـ Enum Hack (وهي أفضل ) حيث هي مشابه للـ #define من ناحيه أنك لا تستطيع أخذ عنوانها ، ومشابه لـ const في طريقه العمل حيث لها Range محدد . ```cpp class Stack { private: enum { MAX = 10 }; int arrat[MAX]; }; ``` باختصار هنا ، أستخدم const أفضل بكثير من الـ #define ، هذا الكلام في تعريف الثوابت . **نأتي الأن الى تعريف الـ Macro ؟** أولا في الماكروز هناك شروط لكي يعمل بشكل صحيح ، هي : لا يحتوي بعد أسم الماكروز على مسافه (في حال كانت هناك معاملات مرسله) يجب أن يكون كل متغير مرسل في قوس حتى نتعامل بشكل صحيح في حاله كان الأستدعاء عباره عن Expression . مثال : ```cpp #include using namespace std; #define MIN(x, y) (x) < (y) ? (x) : (y) int main() { int z = MIN(3, 4); cout << z << endl; return 0; } ``` سوف يطبع العدد الأصغر وهو 3 ، دعنا الأن نحاول ان نخل في الشروط لنرى مدى الخطوره .. في حاله غيرنا الماكروز (أضفنا مسافه بعد أسم الماكرو MIN ) : ```cpp #define MIN (x, y)(x) < (y) ? (x) : (y) ``` الأن عندما يقوم الـ Preprocessor بعمل Substitute للسطر : ```cpp MIN(3,4 ``` أي سيغير MIN بمحتوى الماكرو ويصبح السطر : ```cpp (x, y)(x) < (y) ? (x) : (y)(3, 4) ``` وسوف يكون خطأ بالترجمه ، وهو أن x غير معرفه .. الحاله الثانيه وهي بدون عمل أقواس ، سيتم فهم اي تعبير بشكل خاطئ : شاهد المثال التالي : ```cpp #include using namespace std; #define CUB(x) ((x) * (x) * (x)) #define CUB2(x) x *x *x int main() { int x = 3; int a = CUB(x); int b = CUB2(x); cout << a << " " << b << endl; a = CUB(x + 2); b = CUB2(x + 2); cout << a << " " << b << endl; return 0; } ``` الأن في المره الأولى (في كلا الماكروين ) سوف يطبع 27 مرتين ، وهو صحيح . -لا يوجد expression - في المره الثانيه ، حيث كان هناك عمليع جمع ، الماكرو CUB جاب النتيجه صحيحه لأنه يحتوي على أقواس ، أما الثاني طبع 17 وهي خاطئه ، حيث فهم CUB2 السطر كالتالي : x\*x\*x تستبدل كل x بـ x+2 : 3+2\*3+2\*3+2 والأولويه في العمليات الحسابيه للضرب : 2+6+6+3 12 + 5 = 17 !! الى هنا قد تقول سوف أستخدم ماكروز يحتوي على أقواس ومن غير مسافات ، هل هناك عيب أخر ؟ نعم في بعض الأحيان سوف تكون النتيجه غير التي تتوقعها تماما ، ليكن لدينا الماكرو التالي : ```cpp #define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b)) ``` يقوم هذا الماكروز باستدعاء داله f بالعدد الأصغر المرسل للماكرو .. ولدينا : ```cpp int a = 5, b = 0; ``` وأستدعائين : ```cpp CALL_WITH_MAX(++a, b); CALL_WITH_MAX(++a, b + 10); ``` في الأستدعاء الأول سوف يحصل زياده لa مرتين . مره عند عمليه المقارنه ، ومره أخرى في الجزء true في الجمله ، حيث قيمه a أكبر من b وبالتالي تحقق الشرط ، وذهب للجزء (a) وهنا ستتم الزياده للمره الثانيه (حيث تستبدل a بـ a++ ) . في الأستدعاء الثاني ، سوف تحصل زياده لـ a مره واحده فقط في عمليه المقارنه . بذلك تكون رأيت مشاكل الماكرو والتي يمكن حلها كلها بعمل داله inline (داله صغيره حبوبه ، لا تأخذ مساحه ويتم عمله Substitue لها أيضا ولكن بواسطه المترجم) . ويا حبذا لو كانت هذه الداله عباره عن Template Based (لا نعرف ما نوع القيمه المرسله) ، وياحبذا لو كانت القيمه المرسله هي Reference To Const وبالتالي لا يكون هناك استدعائات كثيره لCopy Constructor ```cpp template inline void callWithMax(const T &a, const T &b) { f(a > b ? a : b); } ``` هكذا نكون أنهينا مشاكل الMacro بهذه الدوال الinline وبشكل أكثر كفائه ، وأكثر حمايه حيث في الماكروز لا يوجد أي مراعاه لـ Data Type فهي Not Type Safe بعكس الدوال العاديه سواء inline أو لا . قبل أن أنهي ، أحب أوضح ماكروز جيد وهو الـ Stringizing حيث يقوم بطباعه ما تمرره لهذا الماكروز : ```cpp #define WRITESTRING(x) cout << #x WRITESTRING(This is a string); ``` قبل الترجمه سوف يصبح السطر الثاني : ```cpp cout << "This is a string"; ``` مثال بسيط : ```cpp #include using namespace std; #define print(x, y) cout << #x << " : " << y << endl; int main() { int b = 3; print(value of b, b); print(b * 2, b * 2); return 0; } ``` فقط يسهل بعض الأحيان وخاصه لطباعه نص وبعدها رقم . **الخلاصه في موضوع الماكرو:** - عند تعريف الثوابت أستخدم const - لتعريف الدوال الصغيره (ماكروز) أستخدم الدوال الخطيه Inline Function . ### الحصول على اسم الدولة من خلال عنوان الجهاز URL: https://informatic-ar.com/ip_to_country_java/ Last updated: 2026-10-04T04:35:47.000Z عند حصولك على IP شخص ما سواء من خلال تسجيل logging هذا العنوان القادم للسيرفر أو حتى عند قرائتك للSMTP Header من صندوق بريدك حتى تحصل على عنوان مرسل الرسالة الحقيقي أو غيرها من الحالات فإنك في الغالب تود الحصول على الدولة التي يوجد فيها هذا العنوان.. لحسن الحظ أي دولة لديها Range معين من العنواين وبالتالي يمكن بكل سهوله بعد الحصول على الIP هو التحقق من وجوده في المدى المعين للدولة وعند دخوله في ذلك المجال فمباشرة تستطيع القول بأن هذا الIP من الدولة الفلانية.. هناك بعض من المواقع تقدم هذه الخدمة كwhois ip ، وبعض من المواقع تقدم database صغيرة تستطيع تحميلها في جهازك مثلا: [http://ip-to-country.webhosting.info](http://ip-to-country.webhosting.info/?ref=informatic-ar.com "رابط خارجي") [http://software77.net/geo-ip/](http://software77.net/geo-ip/?ref=informatic-ar.com "رابط خارجي") [http://www.maxmind.c...p/geoip\_country](http://www.maxmind.com/app/geoip%5Fcountry?ref=informatic-ar.com "رابط خارجي") سوف نأخذ مثال على القاعدة الموجودة في الموقع الأول، فعند تحميلك للملف [ip-to-country.CSV](http://ip-to-country.webhosting.info/downloads/ip-to-country.csv.zip?ref=informatic-ar.com "رابط خارجي") ستجده بهذا الشكل: [![post-51148-12762812151352](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-12762812151352-300x169.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-12762812151352.png) العمود الأول هو أول IP في المجال والعمود الثاني هو أخر IP في المجال، اما العمود الثالث والرابع فما رمز الدولة بحرفين وثلاثه والعمود الأخير هو اسم الدولة لكل من العناوين الموجودة في العمود الاول والثاني.. لاحظ أن العنوان ليست مكتوبة بالصيغة التقليدية A.B.C.D لكنها مكتوبه على شكل عدد صحيح حتى يسهل تخزينها والمقارنة فيما بينهما.. ولكي يتم تحويل الIP على شكل عدد ، يتم استخدام المعادلة: > A.B.C.D = A\*256\*256\*256 + B\*256\*256 + C\*256 + D طريقة عمل الكلاس بسيطة، نقوم أولا بفتح هذا الملف والقرائه منه ثم تخزين هذه المعلومات في ArrayList ومن ثم ترتيبها ، وعندما تريد الاستعلام عن رقم IP ما ، فيتم البحث في هذه الArrayList باستخدام Binary Search ، أحب أن أشكر الCollections على توفير دوال Sort و BinarySearch للمبرمجين .. يمكن القرائه من الملف بالطريقة العادية BufferedReader ولكني استخدمت Memory Mapped في هذا المثال حيث هي أسرع من الأولى في القرائه وان كان فارق السرعه في هذا المثال ليس بكبير ولكنه يوضح في أمثلة أخرى خصوصا عند العمل في الملفات الضخمة .. عندما نريد عمل sort و binary search لأي كائن في ArrayList يجب أن يطبق implement هذا الكائن الComparable أو أن نرسل Comparator لهذه الدوال (sort,binarysearch) وفي داخل الComparator نوضح كيف تتم المقارنة في الكائن من النوع المعين.. وهذا ما قمت به في المثال using Comparator . هذه الطريقة تتطلب بضعه ثواني كstartup (حتى يتم تحميل الملف في الذاكرة وترتيبه ).. ولكنها سريعه في البحث (أجزاء من الثانية).. لاستخدام الكلاس: ```java public static void main(String[] args) throws FileNotFoundException, IOException { String ip = "94.96.84.100"; IP2Country ip2Country = new IP2Country(); Country country = ip2Country.getCountry(ip); System.out.println("Country Name: " + country.getName() + "\n" + "Country Code: " + country.getCode2() + "\n" + "Country IP : " + ip); } ``` يمكنك [تحميل الكلاس من هذا الرابط](https://informatic-ar.com/files/IP2Country.zip). ### حفظ إعدادت البرامج Storage Of Application Preferences URL: https://informatic-ar.com/java_preferences/ Last updated: 2026-10-04T04:35:48.000Z أي مستخدم User يفضل أن يحفظ البرنامج الذي يستخدمه الإعدادات والألوان وحجم الخطوط وأي Customization مباشره بعد أن يقوم تعديل خصائص البرنامج ، وبالتالي بمجرد تشغيل التطبيق مره أخرى يبدأ المستخدم في العمل بدلا من اعاده تجهيز تلك الإعدادات مره أخرى .. كمبرمج اذا أردت أن تطبيق مثل هذه الخاصية بدون أستخدام أي أدوات مساعدة سوف تضطر الى كتابة تلك الإعدادات في ملف في مكان ما على القرص ، ومن ثم تقوم بقرائه هذه الإعدادات عند كل مره تشغيل للبرنامج وتطبيق هذه الإعدادات في البرنامج ، الطريقة واضحه ولكنها سوف تستهلك الزمن خصوصا عند الكتابة والقرائه حيث يجب أن تكون وفق نسق معين تعرفه حتى تستطيع القرائه Parsing بشكل صحيح . في جافا وفرت صن عدة API لتسهيل المبرمجين على حفظ إعدادات البرنامج ابتدائا من الكلاس Properties والذي وجد في أوائل نسخ الجافا ، ولكن كما سنرى بعد قليل أن العمل مع هذا الكلاس غير مضمون تماما في جميع الحالات وفقط يفضل استخدام هذا الكلاس عند اراده حفظ معلومات بسيطة . بعد ذلك قامت صن في جافا 1.4 بتقديم الكلاس Preference الذي يحل كل المشاكل تماما واستخدامه أسهل بكثير أيضا من الProperties . في هذه المقالة سوف نتناول هذه الAPI الخاصه بحفظ اعدادات البرامج (سواء الProperties و الPreferences ) ، وفي الأخير سوف نطرح مثال عملي متكامل حول هذا الموضوع .. **Property Maps** الProperty هو map لتخزين مجموعه من المفاتيح وما يقابلها من القيم Key/Value Pairs . وجافا تطبق هذا المفهوم في الكلاس Properties وفي هذا الكلاس يجب أن تكون المفاتيح والقيم كلها من النوع String والذي يميزه عن استخدام الملفات العادية هو سهولة التخزين واسترجاع البيانات بسهوله ، اضافة الى أنك يمكن أن تقدم قيم افتراضية في حال لم تكن هناك قيم في الملف . مثال : ```java Properties settings = new Properties(); settings.put("title", "This is simple example"); settings.put("color", "Color.BLACK"); ``` وعند إراده تخزين هذه البيانات سوف نستخدم الدالة store والتي تستقبل FileStream حتى تكتب فيه البيانات والمعامل الأخر هو نص يُكتب في أول سطر في الملف ، كما المثال : ```java FileOutputStream out = new FileOutputStream("mySettings.properties"); settings.store(out, "This is My Settings"); ``` ومن المُفضل تخزين هذا الملف في مجلد المستخدم user home ، ويتم الإستعلام عن هذا المسار من خلال الدالة getProperty في الكلاس System : ```java String userDir = System.getProperty("user.home"); ``` وعند إراده القرائه من الملف ، سوف نستخدم الدالة load ويمرر لها FileStream يتم القرائه منه ، كما يلي : ```java FileInputStream in = new FileInputStream("mySettings.properties"); settings.load(in); ``` لكن قد يحدث أن يتم التغيير في هذا الملف أو حتى حذفه ، لذلك يمكن أن نضع قيمه افتراضيه بحيث اذا لم يجد القيم في الملف يستخدم الإفتراضيه ، ويمكن ان نضع هذه القيم الإفتراضيه بطريقتين : الأولى وهي من خلال استدعاء الدالة getProprty (في الكلاس Properties وليس System ) وتمرير المفتاح الذي نريد قيمته ويكون المعامل الأخر هو القيمه الإفتراضيه اذا لم يجد ذلك المفتاح : ```java String title = settings.getProperty("title", "new title"); ``` الطريقة الثانيه وهي أسهل وأسرع خاصه اذا كانت هناك كثير من الإعدادات تحتاج لقرائتها ، وفيها تقوم بعمل Properties أخر يحتوي على القيم الإفتراضيه وتقوم بتمريرها لدالة بناء الProperties الذي يحتوي على الإعدادت ، وحين يتم قرائه القيمه الأولى سيتجاهل الإفتراضيه بالطبع ، وهكذا لكل المفاتيح ، كما يلي المثال : ```java Properties defaultSettings = new Properties(); defaultSettings.put("title",new title"); defaultSettings.put("color","Color.BLACK); //.... Properties settings = new Properties( defaultSettings ); ``` المثال التالي لواجهه Swing تحفظ حجم الJFrame والإسم بعد أغلاق البرنامج ، ويعود الحجم عند فتحك للتطبيق مره أخرى : ```csharp // use Java Properties File to store Application size and Title import java.awt.EventQueue; import java.awt.event.WindowListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.swing.JFrame; import javax.swing.JOptionPane; import java.util.Properties; import java.io.File; import java.io.IOException; import java.io.FileInputStream; import java.io.FileOutputStream; public class PropTest { public static void main(String[] args) { EventQueue.invokeLater ( new Runnable() { public void run () { PropFrame frame = new PropFrame(); frame.setVisible(true); } }); } } class PropFrame extends JFrame { public PropFrame() { // get size,position,title from properties file String userDir = System.getProperty("user.home"); File propertiesDir = new File(userDir, ".myApp"); if (!propertiesDir.exists()) propertiesDir.mkdir(); propertiesFile = new File(propertiesDir, "here.test"); Properties defaultSettings = new Properties(); defaultSettings.put("left", "0"); defaultSettings.put("top", "0"); defaultSettings.put("width", "" + DEFAULT_WIDTH); defaultSettings.put("height", "" + DEFAULT_HEIGHT); defaultSettings.put("title", ""); settings = new Properties(defaultSettings); if (propertiesFile.exists()) { try { FileInputStream in = new FileInputStream(propertiesFile); settings.load(in); } catch (IOException ex) { ex.printStackTrace(); } } // set size, position setBounds(Integer.parseInt(settings.getProperty("left")), Integer.parseInt(settings.getProperty("top")), Integer.parseInt(settings.getProperty("width")), Integer.parseInt(settings.getProperty("height"))); // if no title , get title from user String title = settings.getProperty("title"); if (title.equals("")) title = JOptionPane.showInputDialog("please supply frame title"); if (title == null) title = ""; setTitle(title); addWindowListener ( new WindowAdapter() { public void windowClosing ( WindowEvent event ) { settings.put("left", "" + getX() ); settings.put("top", "" + getY() ); settings.put("width", "" + getWidth()); settings.put("height","" + getHeight()); settings.put("title",getTitle()); try { FileOutputStream out = new FileOutputStream(propertiesFile); settings.store(out, "Program Settings"); } catch ( IOException ex ) { ex.printStackTrace(); } System.exit(0); } }); } private Properties settings; private File propertiesFile; private static final int DEFAULT_WIDTH = 300; private static final int DEFAULT_HEIGHT = 200; } ``` **The Preferences API** بالرغم من الكلاس Properties أدى المهمه على أكمل وجه ، الا أنه قد تحدث مشاكل في حال وجد ملف بنفس إسم الملف الذي ستحفظ فيه الإعدادات ، اضافة الى انه يدخلك في تفاصيل التخزين الى ملف والقرائه منه وكثير من المبرمجين الكسالى يريدون تجريد أكثر من ذلك وأنا أولهم .. الكلاس Preferences أتي لحل هذه المشاكل ، فهو يقوم بحفظ الإعدادات في مكان ما على حسب البيئة التي يعمل عليها البرنامج ، فاذا عمل البرنامج تحت نظام ويندوز فسوف يخزن تلك الإعدادات في مسجل النظام Registry ، أما اذا عمل تحت لينوكس سوف يخزنها في مكان في نظام الملفات . ويمكن حفظ هذه الإعدادت للمستخدم الحالى User ، أما لأي مستخدم في النظام System . كما يبين المثال : ```java Preferences root = Preferences.userRoot(); // for user Preferences root = Preferences.systemRoot(); // for system ``` بعد ذلك يمكنك وضع الإعدادت في المكان الذي تريد ويفضل أن تكون كما في تقسيم الباكج ، المثال التالي يضعها في com/mycompany/myapp ```java Preferences node = root.node("/com/mycompany/myapp"); ``` اذا أردت أن تضع المسار كما في باكج الكلاس الحالي يمكنك أن تستخدم (فقط غير obj بthis ) : ```java Preferences node = Preferences.userNodeForPackage(obj.getClass()); Preferences node = Preferences.systemNodeForPackage(obj.getClass()); ``` لقرائه الإعدادت ، سوف تستخدم أحدى الدوال التالية ، وكلها تستقبل المفتاح والقيمه الإفتراضيه وترجع القيمه القيمه المناسبة (الإفتراضية اذا لم توجد قيمه للمفتاح والا القيمه الموجودة ) : ```java • String get(String key, String defval) • int getInt(String key, int defval) • long getLong(String key, long defval) • float getFloat(String key, float defval) • double getDouble(String key, double defval) • boolean getBoolean(String key, boolean defval) • byte[] getByteArray(String key, byte[] defval) ``` وللكتابة سوف تستخدم : ```java • void put(String key, String value) • void putInt(String key, int value) • void putLong(String key, long value) • void putFloat(String key, float value) • void putDouble(String key, double value) • void putBoolean(String key, boolean value) • void putByteArray(String key, byte[] value) ``` ويمكن اذا أردت أن تنقل البرنامج من بيئة لأخرى أن تقوم بعمل export للإعدادت في ملف xml ، وهناك قم بعمل import للإعدادات ، وذلك من خلال الدوال التالية: ```java void exportSubtree(OutputStream out) void exportNode(OutputStream out) void importPreferences( InputStream in) ``` **Text-To-Speech Application :** قمت بعمل مثال بسيط ، لبرنامج لقرائه النصوص باستخدام المكتبة [TTS ](http://freetts.sourceforge.net/docs/index.php?ref=informatic-ar.com "رابط خارجي")، أيضا استخدمت واجهات للبرنامج تسمى [Substance ](https://substance.dev.java.net/?ref=informatic-ar.com "رابط خارجي")، وفي حال قام المستخدم بتغيير الواجهه أو تغيير صوت القارئ ومن ثم خرج من البرنامج فسوف تعود الإعدادات مره أخرى بعد فتح التطبيق .. أيضا اذا تمت الكتابة في JTextArea وتم الخروج من البرنامج فسوف ترجع ما كان موجود مرة أخرى عند فتح التطبيق .. [![Prefer](https://informatic-ar.com/wp-content/uploads/2013/09/Prefer-270x300.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Prefer.png) لترجمه وتشغيل التطبيق ، حمل المكتبة Substance وضع الملف substance.jar في مسار البرنامج ، أيضا حمل المكتبة TTS وضع مجلد lib بالكامل في مسار البرنامج ، وأفضل أن تقوم بعمل ملف الباتش make.bat هذا للتسهيل وقم بتشغيله : ```batch cls @echo off javac -cp .;lib\freetts.jar TextToSpeech.java if not errorlevel 1 java -cp .;lib\freetts.jar;substance.jar TextToSpeech pause ``` أما من يريد الكود + المكتبة للتجربة فقط فيمكنه تحميل هذا الملف ( 10 ميغا ): [Code + Library](https://informatic-ar.com/files/TextToSpeech.rar "رابط خارجي") وقم بتشغيل ملف الmake.bat .. ### تصميم البرمجيات باستخدام نمط Model-View-Controller URL: https://informatic-ar.com/mvc_java/ Last updated: 2026-10-03T22:44:51.000Z من المعروف والمسَلَم به في عالم البرمجيات القاعده "*المتطلبات دائماً تتغير*" " *Software Requirement Always change* "، هذه القاعده تنص على أن متطلبات البرنامج سواء كانت General Software موجه للعامه أو Custom Software موجه لزبون معين سوف تتغير بمرور الزمن ، فالبرنامج الذي كان يشغل صوتيات MP3 يجب أن يشغل في الاصداره القادمه صوتيات ram,rm والا بالتأكيد سوف يتجاهل المستخدمين هذا البرنامج ويذهبوا الى ما هو أفضل ، بنفس الأمر البرنامج الذي صممته لاداره المدرسه القريبه منكم تغيرت متطلباتها حيث تريد الاداره أن يقوم البرنامج باصدار شهادات للطلاب بالاضافه الى امكانيه استعلام الطالب عن نتيجته من خلال موقع المدرسه ،، مع كل هذه التغييرات التي لا مفر بها ، عرف المبرمجين ومهندسي البرمجيات ضروره تصميم البرمجيات باستخدام أساليب جيده تجعل عمليه التغيير في البرنامج أمر سهل وغير مكلف بتاتا (ونقصد لا يحتاج الى تغيير في الكود الأصلي ، فقط اضافه المزيد) ، وبدأت تظهر منهجيات Methodology لا تخاف أبدا من التغيير بل تشجع الزبون على تغيير متطلباته أيضا :) . هذه المنهجيات هي عباره عن مجموعه من الخطوات التي يجب أن تلتزم بها عندما تبدأ في تصميم برمجياتك باستخدام المنهج المعين ، كما أنها تحتوي على مفاهيم وطرق تختلف من منهجيه لأخرى ، وبغض النظر عن المنهجيه المستخدمه في التطوير ، فما زالت هناك مشاكل تحدث دائما عند تصميم البرامج (أتحدث عن البرامج التي تستخدم أسلوب البرمجه الكائنيه Object Oriented ) فمثلا قمت بانشاء كائننين في البرنامج السؤال هو ما هو الكائن المسؤول عن توليد الأخر ، من الذي سوف تنشئه أولا ومن الذي ستنهيه أخرا ، مثال أخر لديك مثلا كائن عباره عن timer للعبه ما ، وأنت تريد أن تكون هناك timer واحده فقط في البرنامج ، كيف يمكن أن تمنع المستخدم للكائن Timer من أن ينشئ كائن أخر ،، هذه المشاكل وغيرها الكثير هي مشاكل في تصميم Design البرنامج أو الكلاسات في البرنامج ، ومع تقدم الوقت بدأت خبره المبرمجين تزداد وظهرت حلول عمليه لأشهر هذه المشاكل وكيف يمكن أن تحلها بأسلوب مبسط وأكثر كفائه ، هذه الحلول للمشاكل أصبح يطلق عليها حاليا في مصطلح جديد الا وهو الdesign pattern . علم تصميم الأنماط Design Pattern هو عباره عن تصاميم معينه لمشاكل لطالما سوف تظهر في أي مشروع برمجي (يستخدم بالطبع مفهوم OO) ، وهذه الحلول قدمها مهندسي البرمجيات والمبرمجين المخترفين على مر الزمن وأصبح من الأفضل للمبتدئ مباشره استخدام هذه الانماط في برنامجه لحل المشكله بدلا من التفكيير في حل قد يكون غير صحيح أو قد يُظهر مشاكل أخرى فيما بعد ، سنتناول في هذه المقاله البسيطه واحد من أشهر الأنماط ويستخدم بشكل كبير في تصميم واجهات البرامج OO GUI Design الا وهو نمط Model-View-Controller واختصارا MVC **ما هو الMVC ؟** نمط MVC يقوم على أساس فصل الواجهه Interface (سواء GUI,console) وتسمى View عن التطبيق والذي هو مشكله البرنامج الذي تقوم على حلها Business Logic وتسمى Model وعن التحكم في الواجهه (مثلا الحدث الفلاني عندما يتم التعامل مع أحد الأزرار في الواجهه) ويسمى Controller . بعباره مبسطه ينص نمط MVC على فصل الواجهه Interface من التطبيق نفسه ، بحيث في حال تغيير المتطلبات وتم تعديل الBusniess Logic فان الواجهه تكون كما هي بدون أي تغيير ، وبنفس الأمر في حال تغييرت المتطلبات وتم تحديث الواجهه (اضافه واجهه أكثر احترافيه) فإن طبقه الBusiness Logic تكون كما هي بدون تغيير ،، لنأخذ مثال بسيط لكي نوضح الفكره في تقسم البرنامج لعده طبقات ، مثلا تريد كتابه تطبيق شبكي يتصل بجهه معينه ويقوم بأخذ بيانات ما ويخرجها لديه في الشاشه ،،الطريقه الأعتياديه التي يقوم بها المبرمجين المبتدئين وهي كتابه التطبيق بالكامل في ملف واحد (التعامل الشبكي ، الواجهه ، الأحداث والتعامل معها) وبمجرد انتهاء البرنامج ستجد هذا المبرمج -المسكين- في قمه السعاده لأنه باع أول برنامج قام به وأخذ عمولته بالكامل $$ ، لكن ولنفرض بمرور أيام اتصل العميل وطلب من المبرمج أن يغير طريقه طلب الخدمه أو الأتصال الشبكي ،، من هنا ستبدأ رحله معاناه مبرمجنا المسكين حيث سيتوجب عليه تغيير طريقه تقديم الخدمه في الجزء الشبكي وليس هذا فقط بل تغيير جزء الواجهه المرتبط بالجزء القديم بالاضافه الى الجزء المتحكم والذي هو event-handler يجب أن يتغير أيضا ،، كل هذا بسبب التزواج والأعتماديه الخاطئه في تصميم الكلاسات من البدايه ،، لكن ماذا لو قام المبرمج من البدايه باتباع اسلوب MVC ، فقط عليه التغيير في الجزء Model (المتعلق بالتعامل الشبكي) ولن يحتاج لأي تغيير في الواجهه والتحكم VC ، والسبب أن Model لا يعلم أصلا كيف سيتم عرضه (GUI,Console) والجزء المتعلق بالعرض هو View . لذلك استخدام هذا الأسلوب من البدايه يجعل البرنامج قابل للتغيير والتطوير بشكل أفضل بكثير ،، بعض الأحيان أو في الكثير من الأحيان الجزء View والجزء Controller يكونان مع بعض والسبب أنهم قريبين جدا ويصعب الفصل بينهم ، فالزر Button يمثل الواجهه ، والحدث الخاص بالضغط عليه يمثل المتحكم وهو يقوم بالوظيفه المناسبه ، وحقيقه الفصل بينهم كل منهم في طبقه أمر يعقد البرنامج أكثر لذلك الكثير يجعل VC مع بعض وهكذا سوف نستخدم النمط M-VC . [![321px-ModelViewControllerDiagram.svg](https://informatic-ar.com/wp-content/uploads/2013/09/321px-ModelViewControllerDiagram.svg_-300x141.png)](https://informatic-ar.com/wp-content/uploads/2013/09/321px-ModelViewControllerDiagram.svg%5F.png) نأخذ مثال بسيط بلغه الجافا لكي يتضح المفهوم بشكل أكبر ، المثال عن برنامج لتربيع العدد في نفسه فقط ، وسنقوم أولا بعمل مثال بالطريقه العاديه لكي نرى محدوديه الطرق العاديه واستخدام MVC أو M-VC أفضل بكثير (المثال Toy example ولكن المفهوم واحد ، وفي الأمثله الكبيره ستتجلي فائده MVC بشكل أكبر) . ```java import java.awt.*; import javax.swing.*; import java.awt.event.*; import java.math.BigInteger; public class BigBlob { public static void main(String[] args) { JFrame window = new BigBlobGUI(); window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); window.setTitle("Simple Calc"); window.setVisible(true); } } class BigBlobGUI extends JFrame { //... Constants private static final String INITIAL_VALUE = "1"; //... Components private JTextField m_totalTf = new JTextField(10); private JTextField m_userInputTf = new JTextField(10); private JButton m_multiplyBtn = new JButton("Multiply"); private JButton m_clearBtn = new JButton("Clear"); private BigInteger m_total; // The total current value state. /** Constructor */ BigBlobGUI() { //... Initialize components and model m_total = new BigInteger(INITIAL_VALUE); m_totalTf.setText(INITIAL_VALUE); m_totalTf.setEditable(false); //... Layout the components. JPanel content = new JPanel(); content.setLayout(new FlowLayout()); content.add(new JLabel("Input")); content.add(m_userInputTf); content.add(m_multiplyBtn); content.add(new JLabel("Total")); content.add(m_totalTf); content.add(m_clearBtn); //... finalize layout this.setContentPane(content); this.pack(); //... Listener to do multiplication m_multiplyBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { m_total = m_total.multiply(new BigInteger(m_userInputTf.getText())); m_totalTf.setText(m_total.toString()); } catch (NumberFormatException nex) { JOptionPane.showMessageDialog(BigBlobGUI.this, "Bad Number"); } } }); //... Listener to clear. m_clearBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { m_total = new BigInteger(INITIAL_VALUE); m_totalTf.setText(INITIAL_VALUE); } }); } } ``` المخرج من البرنامج بعد تشغيله : [![SimpleCalc2](https://informatic-ar.com/wp-content/uploads/2013/09/SimpleCalc2-300x40.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/SimpleCalc2.gif) الان ماذا لو طلب منك تغيير الرقم من BigInteger الى BigDecimal ، هل يمكن التغيير بسهوله ؟ ماذا لو طلب تغيير الواجهه الى console أو تطبيق يعمل في الويب ، هل يمكن التغيير بسهوله ؟ الاجابه لا ، بسبب الاعتماديه في التصميم .. من هنا كان الفصل بين الModel والتطبيق مهم جدا ، ففي حال كنا نريد تغيير الواجهه ، فلن تكون هناك أي علاقه مع الModel والعكس أيضا ، نأخذ المثال باستخدام M-VC : ملف Main : ```csharp import javax.swing.*; public class CalcV3 { public static void main(String[] args) { JFrame presentation = new CalcViewController(); presentation.setVisible(true); } } ``` ملف CalcViewController (الواجهه The User Interface ) : ```java import java.awt.*; import java.awt.event.*; import javax.swing.*; public class CalcViewController extends JFrame { //Constants private static final String INITIAL_VALUE = "1"; //instance vars //... The Model. private CalcModel m_logic; //... Components private JTextField m_userInputTf = new JTextField(5); private JTextField m_totalTf = new JTextField(20); private JButton m_multiplyBtn = new JButton("Multiply"); private JButton m_clearBtn = new JButton("Clear"); //constructor /** Constructor */ CalcViewController() { //... Set up the logic m_logic = new CalcModel(); m_logic.setValue(INITIAL_VALUE); //... Initialize components m_totalTf.setText(m_logic.getValue()); m_totalTf.setEditable(false); //... Layout the components. JPanel content = new JPanel(); content.setLayout(new FlowLayout()); content.add(new JLabel("Input")); content.add(m_userInputTf); content.add(m_multiplyBtn); content.add(new JLabel("Total")); content.add(m_totalTf); content.add(m_clearBtn); //... Add button listeners. m_multiplyBtn.addActionListener(new MultiplyListener()); m_clearBtn.addActionListener(new ClearListener()); //... finalize layout and set window parameters. this.setContentPane(content); this.pack(); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setTitle("Simple Calc - Presentation-Model"); } //end constructor ////////////////////////////////////////// inner class MultiplyListener /** When a multiplication is requested. * 1. Get the user input number. * 2. Call the model to mulitply by this number. * 3. Get the result from the Model. * 4. Set the Total textfield to this result. * If there was an error, display it in a JOptionPane. */ class MultiplyListener implements ActionListener { public void actionPerformed(ActionEvent e) { String userInput = ""; try { userInput = m_userInputTf.getText(); m_logic.multiplyBy(userInput); m_totalTf.setText(m_logic.getValue()); } catch (NumberFormatException nfex) { JOptionPane.showMessageDialog(CalcViewController.this, "Bad input: '" + userInput + "'"); } } } //end inner class MultiplyListener //////////////////////////////////////////// inner class ClearListener /** 1. Reset model. * 2. Put model's value into Total textfield. */ class ClearListener implements ActionListener { public void actionPerformed(ActionEvent e) { m_logic.reset(); m_totalTf.setText(m_logic.getValue()); } } } ``` ملف الModel : ```java import java.math.BigInteger; public class CalcModel { //... Constants private static final String INITIAL_VALUE = "0"; //... Member variable defining state of calculator. private BigInteger m_total; // The total current value state. //constructor /** Constructor */ public CalcModel() { reset(); } //reset /** Reset to initial value. */ public void reset() { m_total = new BigInteger(INITIAL_VALUE); } //multiplyBy /** Multiply current total by a number. *@param operand Number (as string) to multiply total by. */ public void multiplyBy(String operand) { m_total = m_total.multiply(new BigInteger(operand)); } //setValue /** Set the total value. *@param value New value that should be used for the calculator total. */ public void setValue(String value) { m_total = new BigInteger(value); } //getValue /** Return current calculator total. */ public String getValue() { return m_total.toString(); } } ``` الأن تغير المتطلب وأردت أن تستخدم BigDecmial في Model ، فقط كل ما عليك التغيير في ملف Model فقط ، ولن يؤثر ذلك في الواجهه ، وبنفس الفكره لو طلب منك الواجهه تكون نصيه console فقط كل ما عليك استبدال ملف الواجهه بملف للتعامل مع الواجهه النصيه console ويكون الModel كما هو بدون أي تغيير . هناك الكثير من المكتبات تستخدم هذا النمط كقاعده اساسيه مثل مكتبات java swing و Qt و GTK وغيرهم الكثير ممن عرفوا فائده هذه النمط ،، أيضا هذا المفهوم يمكن أن يمتد لاسلوب تقسيم البرنامج لثلاثه طبقات تعمل كل منهم في جهاز منفصل (جهاز المستخدم الواجهه ، طبقه في الوسط business logic ، وطبقه بيانات في السيرفر للحفظ) ويستخدم في الغالب لدى مطورين الويب، أرجوا أن تكون المقاله مفهومه ، وأن تستخدموا هذا النمط في جميع برامجكم حيث سيضمن لكم القابليه للتغيير والتعديل بأبسط وأسهل شكل ممكن ،، **المصادر** : [Model-view-controller](http://en.wikipedia.org/wiki/Model-view-controller?ref=informatic-ar.com) [Big Blob Structure](http://leepoint.net/notes-java/GUI/structure/20bigblob.html?ref=informatic-ar.com) [Applying MVC in Visual Age for Java](http://www.javadude.com/articles/vaddmvc1/mvc1.htm?ref=informatic-ar.com) ### ما الفرق بين ال Composition و ال Aggregation URL: https://informatic-ar.com/aggregation_vs_composition/ Last updated: 2026-10-03T22:32:14.000Z عالم البرمجه له كثير من الأوجه أو الأقسام programming paradigm منها البرمجه الإجرائيه procedural programming مثل لغه السي والباسكال (البعض يطلق عليهم imperative language)، نوع أخر وهي الFunctional programming وهي تختلف عن الإجرائيه مثل لغه ليسب فالبرمجه هنا تستخدم مفهوم الlist بشكل كبير وتختلف طريقه كتابه البرامج عن الطريقه المعروفه بالاضافه الى وجود بعض نقاط تشابه بين هذا النوع والimperative ،، وجه أخر من أنواع البرمجه وهي البرمجه الكائنيه object oriented programming وهو يعتبر من أفضل الparadigm وخاصه في البرمجيات التجاريه والسبب أن استخدام هذا الأسلوب يسهل في حل المشكله بنسبه كبيره لأننا سنحاول تقليد العالم الحقيقي من خلال كائنات تشابه تلك الكائنات التي نحاول وضع حل لمشكله لها .. بشكل عام كيفيه استخدام البرمجه OO واستخدام المفاهيم الرئيسيه مثل الوراثه inheritance وتعدد الأوجه polymorphism والعموميه generic والكبسله وغيرها أمر ليس بتلك الصعوبه ويستطيع المبتدئ تعلمها في غضون أيام ،وتبقى المشكله في كيفيه معرفه متى يمكن أن تستخدم الشيء القلاني هو الموضوع الصعب ، مثلا كيف تقوم بعمل كلاس عمليه بسيطه في أي لغه ، ولكن مثلا كم كلاس ستقوم لبرمجه نظام Bank Account ، وما هي نوعيه العلاقات بين هذه الكلاسات ، ومن هو الكلاس الرئيسي الذي ينشئ البقيه ، ولماذا الكلاس الأخر يحتوي على متغير من هذا النوع ، هل يمكن جعل هذا المتغير كلاس قائم بذاته وووو الكثير من الأمور وهي مختصه بموضوع التحليل والتصميم الكائني Object Oriented Analysis & design. بعد تمكنك من فهم أساسيات موضوع OOAD فتكون أنجزت نصف الطريق ،، فما زلت عند طلب برنامج متكامل ستواجهه صعوبه في اتباع طريقه منطقيه لكل تبدأ من البدايه في كتابه البرنامج الى النهايه وبشكل ملائم لمتطلبات العميل وفي الوقت المحدد in deadline والcost المناسب .. وهنا يكمن أهميه الProcess أو الMethodology المستخدمه لتطوير البرنامج وهناك الكثير من المنهجيات ودائما في عالم الOO نفضل الincrement-iterative لأنها تسمح للعميل بأن يغير متطلباته بلا مشاكل ، وفي كل دوره بسيطه في المشروع -قد تكون من اسبوعين الى شهرين- نقوم بعرض اصدار جديد من المشروع يحتوي على ميزه جديده ، وهكذا في كل دوره يكبر المشروع الى وقت التسليم ،، وكل هذا بمشاهده العميل ومرافقته لكل الأحداث وبالتالي نضمن عدم رفض العميل للمشروع أو احداث تغيير كبير جدا لأنه كان مرافق لعمليه التطوير ،، هناك الكثير من الprocess المستخدمه مثل RUP و eXtreme Programming وهي أحد أبناء الAgile وهناك الripple وغيرها من الطرق والمنهجيات والتي يفضل أن تستخدم كل واحده في حاله من الحالات تكون هي الأفضل عن غيرها ،، سوف نتحدث قريبا عن هذا الموضوع بشكل موسع للغايه ان شاء الله بموضوع Object Oriented Software development ، وحاليا سنركز على جزئيه الكائنات ونصف أحد أهم العلاقات بين الكائنات وهي العلاقه Composition و العلاقه aggregation ونعرف الفرق بينهم ،، ونترك بقيه العلاقات (الوراثه inheritance و Association) والتفاعل بين الكائنات للموضوع الذي سنطرح فيما بعد بمشيئه الله .. ## الفرق بين ال Composition و ال Aggregation سبب طرحي للموضوع بشكل منفصل هو وجود لبس لدى البعض في معرفه الفرق بين الComposition والAggregation واستخدام مصطلحات ركيكه للتفرقه بينهم ، ومن هنا كان هذا المقال ،، في البدايه وبشكل عام ، لا يوجد فرق كبير من الComposition والAggregation حيث يعني هذا النوع من العلاقات بعلاقه أحتواء أو علاقه Has-A بمعنى كائن يحتوي على كائن ،، لنأخذ مثال السياره تحتوي على عده كائنات مثلا محرك السياره والماكينه ودواسه الوقود ووو كل هذه المكونات مع بعض تكون ما يسمى بالسياره .. أي أن السياره "تحتوي" على جميع هذه المكونات .. مثال أخر جهاز الحاسب Computer يحتوي على عده مكونات وهي الذاكره RAM والمعالج CPU ومزود الطاقه Power Supply واللوحه الأم motherboard ووو غيرها من المكونات ،، وبالطبع بما أن لكل مكون من هذه المكونات خصائص وسمات خاصه به فأننا لن نستطيع استخدام متغيرات عاديه primitive data type لوصف هذه المكونات ، أي أننا لا تستطيع أن نقوم بعمل متغير int لكي يمثل الذاكره أو المعالج لأن لكل من هذه المكونات وظيفه خاصه فيه ومتغيرات Internal بها خصائص هذا المكون ،، وبالتالى سيكون أي مكون من هذه المكونات عباره عن كائن من كلاس يمثل Model هذا المفهوم .. (ملاحظه : الكلاس هو Model فقط أو عباره عن قالب blue-print ومن خلاله نقوم بعمل كائنات Objects من هذا الكلاس ) . ملاحظه مهمه / ابتعد تماما عن ترجمه أي مصطلح مهما كان حتى لا تدخل في مشاكل في الفهم أنت في غني عنها ،، فمثلا سوف تجد في الكتب من يقول أن علاقه compoisition هي has-a وستجد من يقول هي use-a وستجد وستجد .. لذلك دع جميع هذه المصطلحات وقم بفهم العلاقه فقط وقم بصياغتها بطريقتك الخاصه .. بنفس الأمر هناك من يفرق بين الobject والobject instance !! مع أن العبارتين تعني عمل كائن من الكلاس ولا يوجد أي فرق بينهم .. ولكن محاوله الترجمه الحرفيه كما ذكرت سيدخلك في مشاكل أنت في غني عنها ،، نعود للComposition والAggregation ،، كما ذكرنا أنهم علاقه أحتواء (كائن يحتوي على كائن) فقط .. لكن هناك فرق بسيط بينهم (لدرجه أن هناك كتب ومقالات تتجاهل هذا الفرق عندما يتحدث عن التصميم بشكل High level ، لأنه فرق بسييط جدا).. الفرق هو أن الكلاس الذي يحتوي على بقيه الكائناتComposition هو المسؤول عن عمليه انشاء هذه الكائنات وعمليه انهائها ، ونقصد هنا هو المسؤول عن حجز مواقع لهذه الكائنات في الذاكره ، وهو المسؤول عن تحرريها ، بمعني أوضح أن الكلاس الكبير بمجرد انشائه سيتم انشاء جميع المكونات (الكائنات) بداخله ،، وبمجرد أنتهاء الكلاس الكبير سيتم فورا انهاء جميع الكائنات بداخله ،، لنأخذ مثال السياره لكي نوضح القصد ، هل يمكن أن تصبح السياره سياره بعد أخذ المحرك منها أو الماكينه ؟ بالطبع لأ .. اذا الكلاس الكبير لن يتم انشائه الا اذا تمت انشاء جميع ما يحتويه من كائنات .. لذلك يطلق على علاقه Composition بعلاقه قويه Strong Relationship . النوع الأخر وهو الAggregation وهو أيضا علاقه احتواء ولكن في هذه الحاله الكلاس الكبير هو غير مسؤول عن انشاء الكائنات (أي حجز موقع في الذاكره لها) وغير مسؤول عن عن تحرريها ،، اضافه الى أنه تم انهاء الكائن الكبير فهذا قد لا يؤثر على المكونات بداخله وقت تعمل بعد ذلك ،، مثلا قسم في جامعه يحتوي على عدد من المدرسين ، في حال تم نقل القسم أو الغائه فلن يتم الغاء المدرسين الذين عملوا بالقسم بل سيكونوا موجودين وربما ينتقلوا لقسم أخر أو جامعه أخرى .. الى هنا وقد يكون الفرق غير واضح بينهم ، لكن بقليل من الكود سيتضح المقصود بذلك ، وسوف تتفاجئ بأنك كنت تستخدم أحد النوعين بكثره في برامجك ، وربما كنت تستخدم طريقه hybrid أي تستخدمهم مع بعض في نفس الوقت ! نبدأ بقليل من الكود لكي نوضح الفكره ، بالاضافه لتوضيح كيف يتم رسم هذه العلاقات باستخدام UML (لمعرفة المزيد عن UML قم بقرائة [كيف تُستخدم ال UML بشكل صحيح؟](https://informatic-ar.com/using-uml/)) لنبدا الcomposition ، كما ذكرنا أن الكائن هو المسؤول عن جميع الكائنات بداخله وهو يتولى عمليه انشائها وانهائها ، بالتالي تكون هذه الكائنات موجوده لديه ، مثلا لنعد لعلاقه المحرك والسياره وننظر لمقطع الكود التالي (قد نستخدم في بقيه الأمثله نموذج خليط بين كود سي++ وجافا ، فالغرض من الموضوع توضيح النظريه والفكره وليس التطبيق الفعلي) : ```java public class Engine { ... } public class Car { Engine e = new Engine(); ....... } ``` بالنظر للكود ستجد أن Car هو سينشئ الEngine وبانتهاء Car سينتهي Engine ،، وهذا كل ما في موضوع الcomposition ، ويتم رسمه في UML باستخدام معين مغلق (ربما للدلاله على الصرامه :) ) [![composition](https://informatic-ar.com/wp-content/uploads/2013/09/composition.png)](https://informatic-ar.com/wp-content/uploads/2013/09/composition.png) بالنسبه للaggregation ، نأخذ مثال شخص person مع عنوانه ،، وسنجد أنه بانتهاء هذا الشخص فأن العنوان موجود وربما نقوم باعطائه لشخص أخر فيما بعد ،، ننظر للمقطع التالي : ```java public class Address { ... } public class Person { private Address address; public Person(Address address) { this.address = address; } ... } public static void main(string args[]) { Address address = new Address(); { Person person = new Person(address); } // say here person delete or end of its scope // address still can be used here } ``` بالنظر للمثال أعلاه سنجد أن بعد عمل person ، وانتهائه (لأنه خرج من الscope الذي عرف فيه) أن العنوان ما زال موجودا ويمكن أستخدامه مره ،، وهو ما يعرف بaggregation . وفي UML يكون بمعين لكن بدون لون ويدل على خفه العلاقه weak relationship ... [![aggregation](https://informatic-ar.com/wp-content/uploads/2013/09/aggregation.png)](https://informatic-ar.com/wp-content/uploads/2013/09/aggregation.png) مثال أخر لكي تتضح الفكره : ```java class Professor; class Department { ... private : // Aggregation Professor *members[5]; ... }; class University { ... private : // Composition Department faculty[20]; ... }; ``` وهنا سنجد أن Department أحتوى على مؤشرات لProfessor وبالتالي لن يكون مسؤولا عن انشائه ، وهنا تكون العلاقه بين الDepartment واللProfessor هو aggregation ، بعكس الDepartment والUniversity حيث الأخيره تحتوى على عدد من الأقسام ،، وهي المسؤوله عن انشائهم وانهائهم .. [![Aggregation-Composition3](https://informatic-ar.com/wp-content/uploads/2013/09/Aggregation-Composition3-300x42.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Aggregation-Composition3.png) أخيرا ستجد كم من المصطلحات لوصف هذه العلاقات ولكل منها معني قد يشوشك في حاله الترجمه ، فمن يقول بأن الcomposition يسمى Composition / Composition Aggregation ، و Aggregation يسمى Shareable Aggregation . وهناك مسمى بأن الcomposition هو Aggregation by value (والسبب أن الكلاس الكبير قد يأخذ قيمه value ويضعها في المكونات ولكنه هو المسؤول عن انشائهم وكانها شبيه بالتمرير بالقيمه) ، والAggregation هو Aggregation by reference (بمعني أن القيمه المرره للكلاس الكبير للمكونات هي موقع في الذاكره reference وأنه فقط سجعل تلك المكونات تؤشر لهذه القيم بالذاكره) .، ولكنها في النهايه هي مصطلحات من منظور شخص معين ، يمكنك بعد قرائتك للدرس وفهمك أن تخترع مصطلحاتك الخاصه اذا أردت .. لذلك تعامل مع المفهوم والعلاقه بشكل مبسط واترك ترجمه المصطلحات فقط بكل بساطه :). ## مصادر [Object composition](http://en.wikipedia.org/wiki/Object%5Fcomposition?ref=informatic-ar.com) [Aggregation](http://www.learncpp.com/cpp-tutorial/103-aggregation/?ref=informatic-ar.com) [Composition](http://www.learncpp.com/cpp-tutorial/102-composition/?ref=informatic-ar.com) [Aggregation versusComposition](http://www.prestwood.com/aspsuite/kb/document%5Fview.asp?qid=100035&ref=informatic-ar.com) ### ماذا تعرف عن الـ Ternary Search Tree URL: https://informatic-ar.com/ternary_search_tree/ Last updated: 2026-10-03T22:44:51.000Z ال Ternary Search Tree (أشجار البحث الثلاثية) هي بنية بيانات تستخدم لحفظ واسترجاع النصوص بسرعه كبيرة ، كما أنها يمكن اجراء عمليات خاصه في البحث مثلا البحث عن أي كلمه لها النمط a??b وهو ما يعرف Pattern Matching With Wildcards ، بالاضافة الى امكانية البحث عن طريق أول عده أحرف من النص وهو ما يسمى Prefix Searching . وهي مشابه لشجره البحث الثنائية Binary Search Tree في أن القيمه في كل عقدة تكون أكبر من القيمه الموجوده في الإبن الأيسر وأصغر من القيمه الموجودة في الإبن الأيمن ، ولكنها تختلف عنها في أنها لا تحتوي على القيم (النص) في العقده الواحده ولكن كل عقده سوف تحتوي على حرف واحد من النص وهذه العقده تحتوي على مؤشر للعقده التالية والتي تحتوي على الحرف التالي من النص وهكذا كل عقده تحتوي على الحرف وتحتوي على المؤشر للعقده التي تحتوي على الحرف التالي (وبسبب هذا المؤشر سميت ب Ternary Search Tree). الشكل (1) يبين كيف يمكن حفظ الكلمات cup و ape و bat و man و map في الشجره Ternary Search Tree ، وأحرف الكلمة في الشجره تتصل بعلامه (شرطه - ) لكي يتم التفرقه بينهم وبين العقد الأبناء. وفي الشكل نجد أن العقد الملونه تشكل لنا المسار للكلمة bat. [![الشكل يبين Ternary Search Tree ](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248621798.png "الشكل يبين Ternary Search Tree ")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248621798.png) الشكل 1 يبين Ternary Search Tree في الشكل نجد أن العقده c هي الجذر root والإبن الأيمن m هو أكبر من ال c والإبن الأيسر a هو أصغر من ال c والعقدة b هي أكبر من a (كما في الأشجار الثنائية Binary Search Tree) . وكل من العقد الأبناء (التي تحتوي على رابط - ) سوف تحتوي على الحرف التالي من الكلمة ( الحرف a يأتي بعد b ويأتي بعده t وهكذا نصل للكلمة bat). ## **البحث عن الكلمة Searching for Word** في كل مستوي level في الشجره سيتم عمل بحث ثنائي Binary Search مع الحرف الأول من الكلمة التي يراد البحث عنها. والبحث مثل البحث في الBinary Search Tree حيث تكون البدايه من الجذر Root ويتم الإنتقال الى الإبن الأيمن أو الأيسر حسب الحرف المناسب وبعد أن يتم ايجاده يتم الأنتقال للمستوي level التالي ويتم بدء البحث مره أخرى ولكن بالحرف الثاني من الكلمة ويتم تكرار هذه الخطوات الى أن يتم ايجاد كل الحروف (وبالتالي يكون هناك تطابق) أو تنتهي العقد بدون حصول مطابقة. نعود للشكل الأول ونرى كيف يتم البحث عن الكلمة bat في تلك الشجره ويتم ذلك أولا من خلال مقارنه الحرف الأول مع الجذر c ولأن الحرف b أقل من c سيتم الإنتقال للإبن الأيسر كما يوضح ذلك الشكل التالي. [![الشكل ‏2 يبين الإنتقال الى الإبن الأيسر](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622784-300x254.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622784.png) الشكل ‏2 يبين الإنتقال الى الإبن الأيسر بعد ذلك سيتم عمل المقارنه مرة أخرى ويتم مقارنه الحرف الأول من الكلمة b مع الحرف a ونظراً لأن الحرف b أكبر سوف يتم الإنتقال للأبن الأيمن وهو b. كما يوضح ذلك الشكل (3). [![الشكل ‏3 يبين الإنتقال للإبن الأيمن نظرا لأن الحرف b أكبر من العقده a](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622250-300x292.png "الشكل ‏3 يبين الإنتقال للإبن الأيمن نظرا لأن الحرف b أكبر من العقده a")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622250.png) الشكل ‏3 يبين الإنتقال للإبن الأيمن نظرا لأن الحرف b أكبر من العقده a بعد ذلك سيتم عمل المقارنة مره أخرى وسيتم مقارنه الحرف b مع الحرف الأول من الكلمة وهو b وسيحصل تطابق وهكذا سيتم الإنتقال للأبن الأول للحرف b. كما يوضح ذلك الشكل (4). [![الشكل ‏4 يبين أنه بعد التطابق يتم الإنتقال للأبن الأول من العقده التي حصل بها التطابق](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622306-300x285.png "الشكل ‏4 يبين أنه بعد التطابق يتم الإنتقال للأبن الأول من العقده التي حصل بها التطابق")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622306.png) الشكل ‏4 يبين أنه بعد التطابق يتم الإنتقال للأبن الأول من العقده التي حصل بها التطابق بعد ذلك سيتم مقارنه الحرف الثاني من الكلمة مع الإبن الأول وسيحصل التطابق وبعدها تتم مقارنه الحرف الثالث t مع العقده التالية t وسيحصل التطابق وهكذا نكون قد تحصلنا على تطابق في 5 محاولات (مقارنات) ، كما يوضح ذلك الشكل (5). [![الشكل ‏5 يبين توقف البحث بعد ايجاد أخر حرف من الكلمة](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248621798.png "الشكل ‏5 يبين توقف البحث بعد ايجاد أخر حرف من الكلمة")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248621798.png) الشكل ‏5 يبين توقف البحث بعد ايجاد أخر حرف من الكلمة في كل مستوى يتم البحث عن الحرف بالبحث كما في Binary Search Tree وفي حال تم ايجاد الحرف يتم الإنتقال الى المستوى التالي ويتم البحث مره أخرى عن الحرف الثاني من الكلمة ويتم تكرار ذلك لجميع الأحرف في الكلمة من خلال ذلك يمكن معرفه زمن التشغيل Run-Time المستغرق للبحث عن الكلمة في الTernary Search Tree والتي في بعض الأحيان تكون أعلى كفائه من الBinary Search Tree. لنأخذ مثال للبحث عن الكلمة man في الشجره السابقة ولنرى كم عدد المقارنات للبحث عن هذه الكلمة في الشجره Ternary Search Tree. أولا سوف نقارن الحرف c مع الحرف الأول من الكلمة وهو m وبما أنه أكبر سيتم الإنتقال للجهه اليمني (عدد المقارنات =1) سيتم مقارنه الحرف الأول m مع العقدة m وسنجد التطابق (عدد المقارنات=2) وسيتم الإنتقال للعقده التالية ويتم مقارنتها مع الحرف الثاني من الكلمة وهو a (عدد المقارنات=3( وسنجد أنهم متطابقان وسيتم الإنتقال للعقده التالية ويتم مقارنتها مع الحرف الثالث n وسنجد أنها غير متطابقه (عدد المقارنات=4) وهنا سوف نطبق البحث الثنائي Binary Search وسيتم الإنتقال للإبن الأيسر ويتم مقارنته مع الحرف n ويتم التطابق (عدد المقارنات=5). سوف نطبق البحث عن الكلمة man في شجره ثنائية Binary Search Tree ولنرى كم تكون عدد المقارنات في شجره البحث الثنائية والتي يوضحها الشكل (6) . [![الشكل 6 يبين شجره بحث ثنائية Binary Search Tree](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622434.png "الشكل 6 يبين شجره بحث ثنائية Binary Search Tree")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622434.png) الشكل 6 يبين شجره بحث ثنائية Binary Search Tree ستتم مقارنه الكلمة man مع الجذر cup ولن يتطابقا بعد أختلاف الحرف الأول في كل كلمة (عدد المقارنات=1) ، وسيتم الإنتقال للإبن الأيمن وتتم مقارنه map مع man وستتم مقارنه الحرف الأول في كل منهم (عدد المقارنات=2) وتحصل مطابقة ويتم مطابقة الحرف الثاني (عدد المقارنات =3) وتحصل مطابقة ويتم مطابقة الحرف الثالث n مع P ولن تحصل مطابقة (عدد المقارنات =4) لذلك سوف يتم الإنتقال للإبن الأيمن وسيتم اجراء 3 مقارنات ويحصل التطابق وبالتالي يكون مجموع المقارنات في شجره البحث الثنائية هو 7 مقارنات. بالرغم من بساطة الشجره أعلاه الا أن المثال أوضح أن عمليات المقارنه في الTernary أقل من الBinary والسبب أن أي كلمات لها نفس الأحرف الإبتدائية تكون موضوعه مع بعض Compressed وبالتالي اذا تمت زياره هذه الأحرف فلن تحتاج اعاده الزياره لها مره أخرى بعكس الBinary Search Tree والتي تقوم بعمل مقارنه لكل الأحرف في أي عقدة في الشجره . وبالإضافة الى كفائه الTernary Search على الBinary Search فالTernary لا تكمل البحث عند عدم المطابقة بأحرف أبتدائية Prefix Matching بينما في الBinary سوف تستمر بالبحث الى حين الوصول لأخر عقدة في الشجره. لكي نعرف أداء الTernary Search Tree نفرض أن كل مستوى يحتوي على كل الأحرف الهجائية وتكون في شكل عقد (كما في الشجره الثنائية)، واذا كان عدد الأحرف في اللغه (من A الى Z ) هو M فإن عدد المقارنات في كل مستوى ستكون O(log M) لكل حرف ، وبالتالي لإيجاد كلمة بطول N سوف تكون عدد المقارنات O(N log M) وبشكل عملي سنجد أن الأداء أفضل من ذلك دائما بسبب خاصيه Common Prefix وأنه في الغالب لا تكون جميع أحرف الهجائية في كل مستوى. ## **إدخال الكلمات Inserting Words** ادخال الكلمات للشجره Ternary عملية سهله كما في البحث عن الكلمة ، فكل ما في الأمر هو اضافة عقدة جديدة Leaf Node لأي حرف لا يتواجد ، ونفرض أننا نريد أن ندخل الكملة bats في الشجره السابقة Ternary Search Tree فكل ما علينا هو اضافه الحرف s بعد الكلمة bat ، وفي حال كنا نريد أن نضيف الكلمة mat سوف نضيف العقدة t كأحد أبناء العقدة p. (بمعنى أن الأبناء التي روابطهم -- سوف يكون أحد من هذه الأبناء هو الحرف التالي ، أما الأبناء التي روابطهم – فهم يكونوا الأحرف الأخرى ) . الشكل (7) يوضح اضافه الكلمة bats والكلمة mat للشجره السابقة. [![الشكل7 يبين اضافة الكلمة bats و mats وتمت اضافة عقدتين فقط](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622541-300x288.png "الشكل7 يبين اضافة الكلمة bats و mats وتمت اضافة عقدتين فقط")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622541.png) الشكل7 يبين اضافة الكلمة bats و mats وتمت اضافة عقدتين فقط بعد اضافة تلك الكلمتين سنجد أن هناك كلمات الأحرف الإبتدائية لها متشابه has common prefix مثلا الكلمة bat و bats تبدأن ب bat . فهنا كيف يمكن أن نعرف أن كل من bat و bats تمثل كلمة وأن b و ba و ap ليست كلمات ؟ ويكون ذلك عن طريق اضافة علامة في أي عقدة تكون هي الحرف الأخير في الكلمة ، فيمكن أن نضيف متغير Boolean يحدد لي هل هذه العقده هي عقده أخيره في الكلمة أم لأ وفي حال كنا مثلا نريد تطبيق قاموس Dictionary فستكون هذه القيمه هي ترجمه الكلمة أو تعريفها. وبأي حال في النهايه نريد تطبيق علامه توضح لنا أن هذه العقده هي الأخيره في الكلمة ، كما يوضح ذلك الشكل (8) . [![الشكل 8 يبين العقد (الملونه) التي تمثل الحرف الأخير في كل كلمة](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622603-255x300.png "الشكل 8 يبين العقد (الملونه) التي تمثل الحرف الأخير في كل كلمة")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622603.png) الشكل 8 يبين العقد (الملونه) التي تمثل الحرف الأخير في كل كلمة الشجره السابقة هي شجره متزنة Balanced Tree فكما أن الأشجار الثنائية يمكن أن تصبح غير متزنه Unbalanced فيمكن أن تصبح الTernary Search Tree أيضا غير متزنه. الشجره السابقة كانت متزنه والسبب أننا أدخلنا الكلمات بالترتيب التالي : Cup, ape, bat map, man ولكن في حال أدخلنا الكلمات مرتبة أبجديا أي بالترتيب: Ape,bat,cup,man,map سوف تكون الشجره غير متزنه ، كما يوضح ذلك الشكل (9). [![الشكل ‏9 يبين شجره Ternary Search Tree غير متزنه](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622637-226x300.png "الشكل ‏9 يبين شجره Ternary Search Tree غير متزنه")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622637.png) الشكل ‏9 يبين شجره Ternary Search Tree غير متزنه زمن البحث في الشجره المتزنه هي O(N log M) بينما في الشجره غير المتزنه يكون حوالي O(NM) وبشكل عملي وحتى لو لم تكن متزنه سوف يكون الأداء جيد بسبب تشارك الكلمات في بعض الأحرف الإبتدائية Prefix-Sharing بالإضافة الى أن كل مستوى لا توجد فيه جميع حروف اللغه. ## **Prefix Searching** أحد أهم أستخدامات الTernary Search Tree هي في البحث عن طريق مجموعه من الأحرف الإبتدائية وسيتم استخراج كل الكلمات التي تبدأ بهذه الأحرف والفكره تكون عن طريق البحث في Ternary Search Tree باستخدام هذه الPrefix (كما وضحناها سابقا في فقره البحث عن الكلمة) ، وبعد ذلك يتم زيارة الشجره بأسلوب In-Order والبحث عن العقده الأخيره (والتي تحتوي على علامة توضح بأنها نهايه الكلمة) في كل أبناء هذا الPrefix. الزياره الIn-Order تكون مثل الزياره في الأشجار الثنائية Binary Search tree ولكن يجب زياره أبناء العقدة أيضا ، وعند انتهاء البحث بالطريقة التي وضحناها أعلاه والوصول لأخر عقدة في الPrefix ، نقوم بعد ذلك بالخطوات التالية: • زياره الشق الأيسر left Subtree من العقدة. • زيارة العقده نفسها. • زيارة أبناء العقدة. • زياره الشق الأيمن right Subtree من العقدة. لنأخذ مثال للبحث عن الPrefix (ma) في الشجره السابقة ، وعند ايجاد هذا الPrefix سوف يتم اتباع الخطوات الأربعه السابقة حيث يتم أولا زياره الشجره الفرعيه اليسرى وسوف ترجع هذه الكلمة man وبعد ذلك يتم زياره العقدة نفسها ma ولكنها لا توجد بها علامة تدل على نهايه الكلمة وسنذهب للخطوه الثالثه وهو زياره ابناء العقدة اذا كان هناك أبناء وأخيرا سيتم زياره الشجره الفرعية اليمني ويتم ارجاع الكلمة map. الشكلين (10) و (11) يوضحوا تلك الخطوات . [![الشكل ‏10 يبين ايجاد النهايه الأولى للma وهي man](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622709-300x278.png "الشكل ‏10 يبين ايجاد النهايه الأولى للma وهي man")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622709.png) الشكل ‏10 يبين ايجاد النهايه الأولى للma وهي man [![الشكل ‏11 يبين ايجاد النهايه الثانية للكلمة ma وهي map](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622730.png "الشكل ‏11 يبين ايجاد النهايه الثانية للكلمة ma وهي map")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622730.png) الشكل ‏11 يبين ايجاد النهايه الثانية للكلمة ma وهي map أثناء التجول Traverse في الشجره يمكن طباعه الكلمات التي يتم ايجادها أو جمعها واستخدامها بطريقة ما مثلا طباعتها في قائمه list للمستخدم. ## **Pattern Matching** أحد تطبيقات البحث باستخدام الTernary Search Tree هو البحث باستخدام أحرف غير معروفة وهذا ما يسمى الWildcard Searching فمثلا اذا أردنا البحث عن الكلمة التي تتكون من النمط a-r---t (الشرطه – هي الحرف الذي لا نعرفه وتسمى Wildcard) ويمكن أستخدام أي حرف لتمثيل الWildcard (مثلا . أو ? ) . أحد أسهل الحلول لمطابقة نص للنص السابق a-r---t هو باستخدام الBrute-Force أي سيتم تجربة جميع الإحتمالات الممكنة بدءا من aaraaat وانتهاءا ب azrzzzt ولكن هذا الحل مستهلك للزمن والمصادر. وبدلا من ذلك يمكن البحث بطريقة أفضل من خلال الشجره نفسها ، فيكون البحث بالطريقة العاديه وعند الوصول للحرف الWildcard (بالطبع لن نجد مطابق له في الشجره) سيتم زياره كل عقدة على أساس أنها هي ذلك الحرف. لنأخذ مثال على البحث عن النمط -a- في الشجره السابقة ، وكما في البحث العادي وسنبدأ من الجذر c والحرف الأول وهو - وبما أن هو Wildcard فسيتم تجربة كل عقده في المستوي الحالي بالترتيب. كما يوضح ذلك الشكل (12). [![الشكل 12 يوضح أن استخدام الWildcard سوف يجعل البحث يبدأ بالترتيب من العقده الأولى](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622210-300x279.png "الشكل 12 يوضح أن استخدام الWildcard سوف يجعل البحث يبدأ بالترتيب من العقده الأولى")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622210.png) الشكل 12 يوضح أن استخدام الWildcard سوف يجعل البحث يبدأ بالترتيب من العقده الأولى سوف يتم اختبار الحرف الثاني من النص a مع كل من أبناء العقدة a وسيتم مقارنه a مع الحرف p ولن يتطابقها (لا يتطابق –a مع –p ) لذلك سيتم تجاهل هذا المسار ويوضح ذلك الشكل (13) . [![الشكل ‏13 يبين حرف غير مطابق وبالتالي يتوقف البحث في المسار الحالي](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622807.png "الشكل ‏13 يبين حرف غير مطابق وبالتالي يتوقف البحث في المسار الحالي")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622807.png) الشكل ‏13 يبين حرف غير مطابق وبالتالي يتوقف البحث في المسار الحالي يتم النظر في بقية الأبناء اذا كان هناك للعقده a ، وهناك ابن الأخر للعقدة a سيتم البحث به كما يوضح ذلك الشكل (14). [![الشكل 14 يبين زياره العقدة الثانية](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622832.png "الشكل 14 يبين زياره العقدة الثانية")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622832.png) الشكل 14 يبين زياره العقدة الثانية مره أخرى ولأننا نبحث عن Wildcard سيتم افتراض أن الحرف الأول تمت مطابقته وننتقل للحرف الثاني من النص a وللعقده التالية. [![الشكل 15 يبين تطابق والإنتقال للحرف التالي.](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622848.png "الشكل 15 يبين تطابق والإنتقال للحرف التالي.")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622848.png) الشكل 15 يبين تطابق والإنتقال للحرف التالي. كما يوضح الشكل السابق سيكون هناك تطابق بين العقدة a والحرف a في النص ، ولكن هناك عده أحرف في النص يجب مطابقتها فيتم الإنتقال للحرف التالي والإبن التالي. [![الشكل ‏16 يبين ايجاد أول كلمة صحيحه](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622874.png "الشكل ‏16 يبين ايجاد أول كلمة صحيحه")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622874.png) الشكل ‏16 يبين ايجاد أول كلمة صحيحه وسوف تستمر هذه العملية الى ايجاد كل النصوص المطابقة (سوف يتم تجربة العقدة c و m)، الشكل التالي يبين الكلمات المطابقة وغير المطابقة . [![الشكل ‏17 يبين كل الكلمات المطابقة وغير المطابقة](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622892.png "الشكل ‏17 يبين كل الكلمات المطابقة وغير المطابقة")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248622892.png) الشكل ‏17 يبين كل الكلمات المطابقة وغير المطابقة والكلمات التي تم ايجادها هي bat و man و map ، وتم ذلك في خلال 11 مقارنه فقط، وفي حال كنا استخدمنا طريقة الBrute Force لأيجاد كل الكلمات المطابقة للنمط –a- سيكون هناك (26\*26 احتمال) وبالتالي الكثير من المقارنات (676 مقارنة). تطبيق الTernary Search Tree Implementation: ```cpp // Ternary search tree #include #include using namespace std; #define WILDCARD '?' class Node { private: char c; Node *smaller, *larger, *child; string word; public: Node(char ch) { c = ch; smaller = larger = child = NULL; word = ""; } char getChar() { return c; } Node *getSmaller() { return smaller; } Node *getLarger() { return larger; } Node *getChild() { return child; } string getWord() { return word; } void setSmaller(Node *n) { smaller = n; } void setLarger(Node *n) { larger = n; } void setChild(Node *n) { child = n; } void setWord(const string &s) { word = s; } bool isEndOfWord() { return getWord() != ""; } }; class TernarySearchTree { private: Node *root; public: TernarySearchTree() : root(NULL) { } void add(const string &str) { Node *node = insert(root, str, 0); if (root == NULL) root = node; } bool contain(const string &word) { Node *node = search(root, word, 0); return node != NULL && node->isEndOfWord(); } void prefixSearch(const string &prefix, vector &result) { inOrderTraversal(search(root, prefix, 0), result); } void patternMatch(const string &pattern, vector &result) { patternMatch(root, pattern, 0, result); } private: Node *search(Node *node, const string &word, unsigned int index) { if (node == NULL) return NULL; char ch = word[index]; if (ch == node->getChar()) { if (index + 1 < word.length()) { node = search(node->getChild(), word, index + 1); } } else if (ch < node->getChar()) node = search(node->getSmaller(), word, index); else node = search(node->getLarger(), word, index); return node; } Node *insert(Node *node, const string &word, unsigned int index) { char c = word[index]; if (node == NULL) node = new Node(c); if (c == node->getChar()) { if (index + 1 < word.length()) node->setChild(insert(node->getChild(), word, index + 1)); else node->setWord(word); } else if (c < node->getChar()) node->setSmaller(insert(node->getSmaller(), word, index)); else node->setLarger(insert(node->getLarger(), word, index)); return node; } void patternMatch(Node *node, const string &pattern, unsigned int index, vector &result) { if (node == NULL) return; char c = pattern[index]; if ((c == WILDCARD) || (c < node->getChar())) patternMatch(node->getSmaller(), pattern, index, result); if ((c == WILDCARD) || (c == node->getChar())) { if (index + 1 < pattern.length()) patternMatch(node->getChild(), pattern, index + 1, result); else if (node->isEndOfWord()) result.push_back(node->getWord()); } if ((c == WILDCARD) || (c > node->getChar())) patternMatch(node->getLarger(), pattern, index, result); } void inOrderTraversal(Node *node, vector &result) { if (node == NULL) return; inOrderTraversal(node->getSmaller(), result); if (node->isEndOfWord()) result.push_back(node->getWord()); inOrderTraversal(node->getChild(), result); inOrderTraversal(node->getLarger(), result); } }; int main(int argc, char *argv[]) { TernarySearchTree tst; // test add tst.add("prefabricate"); tst.add("presume"); tst.add("prejudice"); tst.add("preliminary"); tst.add("apple"); tst.add("ape"); tst.add("appeal"); tst.add("car"); tst.add("dog"); tst.add("cat"); tst.add("mouse"); tst.add("mince"); tst.add("minty"); //test contain cout << (tst.contain("prefabricate") ? "found " : "not found ") << "prefabricate" << endl; cout << (tst.contain("presume") ? "found " : "not found ") << "presume" << endl; cout << (tst.contain("prejudice") ? "found " : "not found ") << "prejudice" << endl; cout << (tst.contain("cat") ? "found " : "not found ") << "cat" << endl; cout << (tst.contain("minty") ? "found " : "not found ") << "minty" << endl; cout << (tst.contain("pre") ? "found " : "not found ") << "pre" << endl; cout << (tst.contain("dogs") ? "found " : "not found ") << "dogs" << endl; cout << (tst.contain("UNKNOWN") ? "found " : "not found ") << "UNKNOWN" << endl; // test prefix string prefix = "pre"; cout << endl; cout << "Found All Word Begin with: " << prefix << endl; vector words; tst.prefixSearch(prefix, words); for (unsigned int i = 0; i < words.size(); i++) cout << words[i] << endl; // test pattern matching string pattern = "m???e"; cout << endl; cout << "Found All Word same : " << pattern << endl; vector lists; tst.patternMatch(pattern, lists); for (unsigned int i = 0; i < lists.size(); i++) cout << lists[i] << endl; return (0); } ``` وعند تنفيذ البرنامج سوف يتم طباعه الكلمات التي نبحث عنها باستخدام الدالة contain ، ثم تتم طباعه جميع الكلمات التي تبدأ بالأحرف pre ، ثم تتم طباعه كل الكلمات التي لها النمط m???e كما يوضح ذلك شكل المخرج (18) . [![الشكل ‏18 يبين مخرج تنفيذ البرنامج السابق.](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248624106-300x240.png "الشكل ‏18 يبين مخرج تنفيذ البرنامج السابق.")](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-1248624106.png) الشكل ‏18 يبين مخرج تنفيذ البرنامج السابق. ## **خاتمة** لا تكاد تخلو البرامج التي نستخدمها من أحد الهياكل القويه، والشجره التي تناولناها في هذه المقالة تستخدم كثيرا في البرامج والIDE والتي تعرض كل الدوال والتي تبدأ بالحرف الذي تقوم بكتابته ، محرك Google يستخدم هذه الخوارزمية لكي يخرج جميع الكلمات التي تبدأ بالحرف الذي تكتبه في الصندوق ..تطبيق أخر لها مثلا في حل لعبه الكلمات المتقاطعه Crossword حيث ستبحث في قاعده بيانات الكلمات عن كلمة مكون من عدد من الأحرف ؟؟؟ ويكون الحرف الثاني منها هو الحرف الثالث في الكلمة ؟؟ وهكذا من شروط .. وستحل اللعبه في غضون ثواني :) . ## **المصادر** [Beginning Algorithms by Simon Harris and James Ross](http://www.amazon.com/Beginning-Algorithms-Wrox-Guides/dp/0764596748?ref=informatic-ar.com "رابط خارجي") [Ternary Search Trees](http://www.ddj.com/windows/184410528?ref=informatic-ar.com "رابط خارجي") [Plant your data in a ternary search tree](http://www.javaworld.com/javaworld/jw-02-2001/jw-0216-ternary.html?ref=informatic-ar.com "رابط خارجي") ### مقدمه في الشبكات العصبيه Neural Networks URL: https://informatic-ar.com/neural-networks/ Last updated: 2026-10-03T22:32:15.000Z **مقدمه Introduction :**حبانا الله بالعقل وميزنا عن سائر المخلوقات ، وهي نعمه من نعم الله عليك أيها الإنسان فينبغي أن تحمد الله عليها ،، فبدون العقل لن نميز بين الصواب والخطأ ، ولا النافع من الضار .. علوم الذكاء الأصطناعي بدأت وانصبت منذ أول بدايتها عن كيف يمكن أن نحاكي ذلك العقل ، كيف يمكن أن تكون للأجهزه القدره على التمييز ،، كيف يمكن أن نصنع ذلك الجهاز الذي يفرق بين الماء والنار .. هذه الأجهزه وبالرغم من سرعتها الفائقه وقدرتها على معالجه الملايين من البيانات في أجزاء من الثانيه لم تستطيع القيام بالكثير من الأمور التي يقوم بها الإنسان ، فمثلا أجلب طفل صغير في العاشره، وأعرض عليه مجموعه من الصور ، بكل بساطه سوف يعرف هذا الطفل صوره القط ، ويعرف صوره الشجره وغيرها .. هذه العمليه بسيطه ربما تكون معقده جدا جدا للجهاز وحتى الخارق Super-Computer أيضا ! من هنا بدأ العلماء بالتسائل وبالبحث ، كيف يمكن لهذا الأنسان القيام بتلك الأمور ، وهنا يكون العقل هو الجواب ، أي محاوله تقليد العقل ومحاكاته ،، لكن كلمه العقل هذه بالرغم من بساطتها الإ أن عقل الأنسان معقد جدا ، ولذلك كان على العلماء البحث أكثر ومحاوله ايجاد الأشياء التي يتكون منها العقل ،، ومن هنا كانت البدايه لدراسه الخليه العصبيه والتي هي أبسط Model موجود بالعقل ،، وجمع تلك الخلايا مع بعض تصبح لدينا شبكه من الخلايا ،، ومن هنا بدأت دراسه الشكبات العصبيه (لاحظ أننا نتحدث الأن عن الشبكات العصبيه الموجوده داخل الدماغ Biological Neural Network ) .. من خلال دراسه تلك الخلايا العصبيه ، ومحاوله محاكاتها ، نستطيع بناء شبكه تعمل بنفس المبدأ ، وبالتالي نستطيع الحصول على ذلك الحاسوب الذكي القادر على التمييز .. ومن هنا بدأت الشكبات العصبيه (وأقصد هنا الإصطناعيه منها artificial neural network واختصارا ANN) . \*ملاحظه ،عندما يتم ذكر "الشبكه العصبيه" فالحديث في الغالب حول الإصطناعيه منها ANN - أغلب المراجع والكتب التي تتحدث حول الموضوع بهذا الشكل- ، وبالتالي دروسنا البسيطه لن تخالف المبدأ، وستكون على ذلك الأساس .. وعندنا نحدد بذكر الحيويه فنعني تلك التي توجد في رأسك :) **ما هي الشبكه العصبيه الحيويه Biological Neural Network** في أبسط الأشكال ، تتكون الشكبه من خلايا ، وتتكون الخلايا من عده أمور -بالطبع الحديث عنها بالتفصيل أمر خارج نطاق دروسنا السريعه- ، الصوره التاليه توضح لك الخليه العصبيه في أبسط الحالات : [![ann](https://informatic-ar.com/wp-content/uploads/2013/09/ann-300x193.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/ann.jpg) هذه الخليه التي تشاهدها أعلاه ، تبدأ عملها عندما تستقبل تلك الأشاره signal التي تأتي من خليه أخرى ، وتستقبل الخليه العصبيه تلك الإشاره من خلال الdendrites -أعذرني أخي القارئ ، لن نترجم المصطلحات حفاظا على جوده الدروس- ،، وعندما تستقبل الخليه الإشاره فتقوم الخليه بتحديد هل تلك الإشاره تقع ضمن نطاق معين (الإشاره التي تستقبلها من نوع Analog والإشارات هنا تكون متفاوته) ، فاذا كانت الأشاره ضمن ذلك النطاق فإن الخليه تقوم بعمل fire بمعنى ان تقوم هذه الخليه أيضا بارسال اشاره الى بقيه الخلايا في الشبكه العصبيه ويكون مجرى الإرسال كما هو واضح في الصوره الAxon ، وترتبط الخلايا فيما بينها من الsynapses . **حل المشاكل باستخدام الشبكات العصبيه** لماذا كل هذا العناء ؟ لماذا نريد محاكاه هذه الشبكه العصبيه ؟ ما الفائده من عمليه المحاكاه ؟ ربما هذه أسئله تخطر على بالك ، أحد أهم الأسباب هو تحسين قدرتك على حل المشاكل ، وفهم الأمور التي يمكن عملها بANN والأمور التى لا يمكن عملها بها .. كمبرمج ، عليك أن تحدد متى تستخدم أساليب برمجيه بحته مثل Function أو OO ومتى تستخدم أساليب مخصصه لنوع معين من المشاكل ANN و Expert System ,, بالتالي تزيد من انتاجيتك بأقل قدر من الخسائر ،، نكمل معا حول نوعيه المشاكل التي ينبغي عليك أن تستخدم فيها الشبكات العصبيه ونوعيه المشاكل التي ينبغي عليك أن تستخدم طرقاً أخرى ،، عندما تكون لديك مهمه ما ، وكانت هناك خطوات واضحه لحل هذه المشكله (سواء توفر خوارزميه أم Flow Chart) فهذه المشكله ليست من ضمن الANN ، قم بحلها بأي طريقه أخرى ،، مثلا لديك مهمه تتطلب أن تكون البرنامج ثابت لا يتغير أبدا ،، مثلا للبحث أو الترتيب ، هنا بالطبع أيضا الANN غير مناسب بالمره وقد تؤدي الى نتائج غير مرغوبه بها .. من الجهه الأخرى ، البرامج التي لا توجد بها خوارزميه واضحه لحلها تكون مؤشر جيد لاختيار الANN كوسيله للحل ، أيضا مسائل التعرف على الأنماط Pattern recognition كأن يعطيك نمط معين (صوره ، ملف صوتي ، أرقام) ، ويقوم برنامجك بالتعرف على هذا النمط (هل هذا النمط يقع ضمن الأنماط التي تم تدريبها عليه) ،، -الدرس المقبل بمشيئه الله سنبدأ مباشره بهذا النوع ونبدأ في كتابه برنامج بسيط للتعرف على الأنماط وذلك باستخدام الHopfield Nets- ، نوع أخر من المشاكل التي يقدم لها ANN حلا ممتازا ، هي مسائل الأمثله Optimization وهنا يمكن أن تختار الطريق الأمثل لأداء مهمه ما ، مثال على مشكله البائع الذي يريد زياره عده مدن ويريد أختيار أقصر طريق يحعله يتمم الزياره traveling salesman problem ، بالطبع يمكن حل هذه المشكله باستخدام الطرق التقليديه "لكنها تأخذ وقتا كبيرا ، ولن تخرج الحل الأمثل !" ،، أيضا نوع أخر من المسائل وهو قريب من التعرف على النمط وهو الClassification -اعذروني مره أخرى لا توجد ترجمه ، من يجتهد ويقدم لي ترجمه جيده؟- في هذا النوع من المسائل يكون لديك مجموعه من الأنماط (صور ، صوت ،الخ) وتريد تقسيمها الى مجموعات ، أي يذهب كل نمط الى القسم المناسب له ، مثال على ذلك نظام لعمل تقسيم للبريد وتقسيم البريد الى ضار (يحتوي على Spam) وغير ضار .. أيضا التوقع هو أحد المسائل المهمه التي تحل باستخدام ANN فتقوم الشبكه بأخذ مدخلات ، ثم تتوقع النتائج مثلا في الأسبوع القادم بناء على أمور عده . لكن الشبكه العصبيه لا يوجد بها "تعليل الحل" ، اي لن تجدها تقول لك "حصلت على الحل الفلاني باستخدام ال وال وال" ، لا يوجد في الشبكات العصبيه مثل هذه خاصيه والتي هي من نطاق الأنظمه الخبيره حيث تتميز بReasoning .. بعض الأنظمه دمجت بين النوعين فيما يسمى Hybrid System وهنا سوف تقوم بعمليه التعليل هذه .. **Hello World Artificial neuron** ربما لا يوجد شيء بهذا الأسم الا هنا ، والغرض القاء نظره بسيطه حول تركيبه هذا المسى بخليه neuron ومعرفه أهم الأمور المرتبطه به .. أنظر للصوره التاليه ، [![ch1fig6.graffle](https://informatic-ar.com/wp-content/uploads/2013/09/ann2-140x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/ann2.jpg) الصوره أعلاه أحتوت على مفهومين من أهم المفاهيم وهي الوزن weight والحد threshold (حاولت الترجمه هنا ، هناك من يطلق عليها عتبه ، لكني أفضل ترجمتي المتواضعه) . الوزن في الصوره السابقه هي 1.5 أما الحد هو 2.5 ... فكره العمل سوف تكون بأن تأتي الأشاره ويتم ضربها مع الوزن ، ومن ثم تتم مقارنتها مع الحد ، فاذا تجازوت هذا الحد فإن الخليه تقوم بعمل fire أي ارسال الإشاره الى بقيه الخلايا ... لنفرض أن قيمه المدخل (الاشاره) كانت 1، نقوم بضربها مع الوزن والناتج هو 1.5 ، ثم نقارن هل القيمه أكبر من 2.5 ؟ الجواب للأسف لا ، بالتالي لن تقوم الخليه بعمل شيء .. من الملاحظ أيضا أن هذه الخليه لن تعمل fire أيضا مع الصفر .. (يعني لا تتعامل مع المدخلات المنطقيه Boolean value) .. ليست كل الخلايا تتعامل فقط بمدخلات منطقيه ،، لكن بقيه الدرس سنكمل على هذا الأساس لغرض التوضيح فقط وسوف تتضح المزيد من المفاهيم مع تقدم الدروس -ان شاء الله -. الخليه بالأعلى ليست مفيده (أليس كذلك) ، وكذلك أي خليه أخرى منفصله بمفردها ، لكن في حاله تجمعت الخلايا مع بعض (شكبه خلايا) فالوضع يختلف بالتأكيد ، لنأخذ مثال على شبكه عصبيه لتمثيل بوابه AND .. مراجعه بسيطه : [![ann3](https://informatic-ar.com/wp-content/uploads/2013/09/ann3.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/ann3.jpg) الأن أنظر للشبكه العصبيه البسيطه AND logical operation : [![ch1fig7.graffle](https://informatic-ar.com/wp-content/uploads/2013/09/ann4-234x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/ann4.jpg) هنا لدينا خليتين بوزن 1 ، والحد 1.5 ،، كم برأيك تكون قيمه المدخل لكي تقوم هذه الخليه بعمل fire ؟؟ بتفكير بسيط نجد أنه يجب أن يكون المدخلين 1 ، بالتالي حتى يتكون لدي المجموع 2 ، وبالتالي تستطيع الخليه اطلاق fire الإشاره (نعم ، كلمه اطلاق هي الكلمه المناسبه بالتأكيد !) . نشاهد شبكه لعمليه أو OR ، أولا مراجعه بسيطه للعمليه : [![ann5](https://informatic-ar.com/wp-content/uploads/2013/09/ann5.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/ann5.jpg) نشاهد الأن الشبكه العصبيه : [![ch1fig8.graffle](https://informatic-ar.com/wp-content/uploads/2013/09/ann6-235x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/ann6.jpg) لاحظ الحد صغير ، بالتالي خليه واحده تكفى ، اذا هنا يجب أن تكون واحده على الأقل true بالقيمه 1 .. ماذا عن عمليه الXOR ، مراجعه بسيطه : [![ann7](https://informatic-ar.com/wp-content/uploads/2013/09/ann7.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/ann7.jpg) شاهد الشبكه العصبيه ، وهي أعقد قليلا من الشكبتين السابقتين ، وبالتالي قد تحتاج لاستخدام طبقه Layer أخرى في المنتصف (تسمى Hidden Layer) ، وكل ما زادت الشبكه في التعقيد كل ما زاد عدد الـHidden Layer .. [![ch1fig8.graffle](https://informatic-ar.com/wp-content/uploads/2013/09/ann8-156x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/ann8.jpg) مثلا لو كان المدخل 1 و 1 ، الطبقه في المنتصف سوف يصل لكل خليه منها 2 ، وبالتالي تقوم بعمل fire ، بالقيمه -1 و 1 ، ومجموعهم 0 ، بالتالي هو أقل من الحد 0.5 لذلك لن تكون هناك أطلاق أخر .. (وهو مطابق لعمليه 1 أكسور 1 = 0) .. لكن في حاله كان المدخل 0 و 1 ، الطبقه في المنتصف سوف يصل لها المدخل 1 ، والخليه باليمين سوف تقوم بعمل fire لأنه 1 أكبر من 0.5 بينما الخليه الثانيه لن تقوم بذلك ، وهنا سوف يصل للطبقه الأخيره 1 ، وهو أعلى من 0.5 وبالتالي fire . لاحظنا في الأمثله السابقه أن الأوزان هي التي تحدد لي المخرج من الشبكه ، السؤال هنا كيف يتم توليد هذه الأوزان وكيف يتم الحصول عليها ؟ هذا عن طريق ما يعرف بتدريب الشكبه العصبيه ، فتقوم بتدريبها على مجموعها من المدخلات وتقوم كل مره بتعديل الوزن (أول مره قد تكون الأوزان مولده عشوائيا) حتى تحصل على المخرج المطلوب tranning .. هناك نوعين من التدريب الأول Supervised training وهو باعطاء مجموعه مدخلات وما يقابلها من مخرج ويتم التدريب حتى تحصل على ذلك المخرج (التدريب نقصد به تعديل الأوزان) ، النوع الأخر Unsupervised training وهي تدريب وتعديل الأوزان من غير الحاجه لمخرجات معينه في البدايه .. وتقريبا النوع الاول هو الأشهر استخداما ، أيضا بعد التدريب يجب أن تتأكد validation من أن هل الأوزان مضبوطه أم تحتاج الشبكه الى تدريب أكثر .. **نظرة أكثر تفصيلاً عن ال ANN**: كما ذكرنا قبل قليل ، الشبكه العصبيه تتكون من مجموعه من الخلايا تتصل مع بعضهم البعض ، كل خليه من هذه الخلايا تكون لديها القدره على حساب الأوزان القادمه اليها من الخلايا السابقه ، بالاضافه الى المقدره على حساب المخرج من هذه الخلايا بعد اجراء عمليه معينه .. الأوزان عاده يمكن أن تكون excite أو inhibit بمعنى أنها قد تقوى الأشاره(اذا كان الوزن موجب) أو قد تضعف الاشاره (اذا كان سالب) التي تأتي اليها ،،الصوره التاليه تبين الخليه بشكل مبسط : [![Neruo](https://informatic-ar.com/wp-content/uploads/2013/09/Neruo-300x246.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/Neruo.gif) كما ذكرنا أن الخليه بمفردها لن تقوم بأي عمل مفيد ، لذلك نجمع العديد من الخلايا ونربطهم مع بعضهم البعض بحيث يشكلوا شبكه من الخلايا ، طريقه الربط بين هذه الخلايا أو الConnectivity of patterns فيه العديد من الأمور التي ينبغى لمصمم الشبكه أن يراعيها مثل طريقه توصيل الخلايا مع بعضهم وفي أي أتجاه وما هي طريقه تعلم الشبكه (طريقه تعديل الأوزان ) . قبل قليل كنا قد وضعنا نموذج لشكبه تحل AND Gate ، وكنا قد وضعنا الأوزان مباشره في الخلايا ،، لكن في الحقيقه من أهم الأمور في الشبكات العصبيه هي القدره على التعلم بمعني أن الشبكه بنفسها تقوم بتعديل الأوزان على حسب الأمثله التي قدمت لهذه الشبكه ، فكل ما تدخل المزيد من الأمثله للشبكه كل ما تعلمت الشكبه وصارت تقوم بالمهمه بشكل أفضل ،، لذلك طريقه تعديل الأوزان من أهم الأمور التي ينبغي لمصمم الشبكه أن يضعها في الأعتبار وقت تصمميمه الشبكه ،، في الحقيقه هناك الكثير من أنواع الشبكات العصبيه ، لكن جميعهم يتشاركوا في عده أمور هي : 1) تتكون من مجموعه من الخلايا البسيطه set of simple processing unit 2) طريقه التوصيل Patten of Connectivity 3) طريقه لارسال الأوزان عبر الشبكه propagate singal 4) طريقه لحساب المدخل للخليه Combining input signal 5) طريقه لحساب المخرج للخليه Calculating output signal 6) طريقه لتعديل الاوزان بالشبكه Learning Rule to Adapt weight **1) مجموعه من الخلايا البسيطه :** كما ذكرنا أن الخليه تقوم باستقبال جميع الاشارات التي تأتي اليها ، ومن ثم تقوم باخراج الاشاره للخلايا الأخرى عبر الشبكه ، والخلايا التي تبدأ العمل بها (التي تستقبل الاشاره من البيئه environment) تسمى خلايا الادخال input unit . والخلايا التي تخرج المخرج في النهايه الى البيئه environment تسمى خلايا الاخراج .. يمكن أن تكون البيئه التي تستقبل الخليه منها الاشاره عباره عن مدخلات من ملف عادي pre-processed ودائما ما نستخدم هذه الطريقه لأننا في الحقيقه نقوم بعمل محاكي للواقع ، ولكن يمكن أن تكون هذه المدخلات قادمه من محسسات أو حتى رادار يتصل بالنظام وفي تلك الحاله قد نحتاج معالجه هذه المدخلات لأنها قد تكون مشوشه أو غير دقيقه فنقوم باستخدام Fuzzy gate تقوم بالتعامل مع تلك المدخلات غير الدقيقه ،، حاليا بما أننا نقوم فقط بعمل محاكيات بسيطه ، سوف يكون الادخال للشبكه في ملف عادي ، والاخراج سوف يكون على الشاشه . **2) طريقه أو نمط التوصيل Pattern of Connectivity :** هناك الكثير من أنواع الشبكات Model of Nets وكل منهم لديه توصيل معين للخلايا ، وبشكل عام هناك أحد الmodel يكون فيه أن كل خليه بالشبكه تتصل بالخلايا الأخرى الموجوده بالشبكه ، نوع أخر من الModel وهو أن الخلايا تقسم لمستويات layer كل من خليه في المستوى الأول تتصل بجميع الخلايا في المستوي التالي فقط ، وكل خليه في المستوى التالي تتصل بجميع الخلايا في المستوى الذي يليه وهكذا .. نوع أخر من الModel وهو أن يسمح بخليه في مستوى لاحق بأن تتصل بخليه في مستوى سابق Backward ، نوع أخر من الModel وهو بأنه يسمح للخليه بأن تتصل مع نفسها ، وغيرهم من الأنواع الكثيره من الModel . وتوصف طريقه التوصيل في مصفوفه من بعدين بحيث كل خانه [![mimetex](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex.gif) تمثل الوزن من الخليه i الى الخليه j . وفي حال كان هناك أكثر من مستوى Layer يمكن أن تكون هناك أكثر من مصفوفه ،، وبشكل عام مصفوفه الأوزان هذه يمكن أن نقول أنها صلب الشبكات العصبيه حيث أنها تمثل الذاكره للشكبه ، فبدونها لن تعرف الشبكه كيف تخرج النتيجه الفلانيه وبوجودها تستطيع الشبكه أن تقوم بالمهمه التي صممت من أجلها .. الصور التاليه ـ توضح أحد أشكال الشبكات بالاضافه الى شكل مصفوفه الأوزان للModel المعين . [![Neruo2](https://informatic-ar.com/wp-content/uploads/2013/09/Neruo2-300x206.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/Neruo2.gif) وتكون شكل مصفوفه الأوزان : [![Neruo3](https://informatic-ar.com/wp-content/uploads/2013/09/Neruo3.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/Neruo3.gif) شكل أخر Feed-Forward ولكن لم نكمل كل التوصيلات بين الخلايا ، ولكنها بنفس المفهوم : [![Neruo4](https://informatic-ar.com/wp-content/uploads/2013/09/Neruo4-300x266.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/Neruo4.gif) **3) طريقه لارسال الأوزان عبر الشبكه propagate signal :** وهنا نقصد متي نقوم بعمليه تعديل بتحديث الخليه (حساب المدخلات لها بالاضافه الى حساب المخرج للخليه) ، ومتى نرسل اشاره من خليه لأخرى ، في بعض الModel يتم أختيار الخلايا في التحديث بشكل عشوائي ، ولكن في البعض الأخر يجب تحديث مجموعه من الخلايا قبل مجموعه أخرى بمعنى أنه بشكل متسلسل .. **4) طريقه لحساب المدخل للخليه Combining input signal :** عاده يتم حساب المدخل للخليه عن طريق ضرب المدخلات في الأوزان ويتم جمع الناتج ليكون هو المدخل للخليه j ، انظر الشكل التالي : [![Neruo5](https://informatic-ar.com/wp-content/uploads/2013/09/Neruo5.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/Neruo5.gif) الان نقوم بجمع حاص بضرب كل مدخل مع الوزن المقابل له بالمعادله التاليه : [![mimetex(1)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex1.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex1.gif) نعوض الأن بالمعادله لكي نحصل على : [![mm2](https://informatic-ar.com/wp-content/uploads/2013/09/mm2-300x33.png)](https://informatic-ar.com/wp-content/uploads/2013/09/mm2.png) أو باستخدام تمثيل المتجهات vector notation : [![mimetex(3)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex3.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex3.gif) بالضرب في : [![mimetex(4)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex4.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex4.gif) وينتج 0.25 . أغلب الأحيان يستخدم مدخل جديد يسمى الbais في الشبكه (ولا أدري لماذا؟ ) ويعطي الوزن 1 دائما ، المهم يتم حساب المدخلات في حال كان ال bais موجود بالمعادله التاليه : [![mimetex(5)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex5.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex5.gif) نفس المعادله ولكن بدئا من 0 : [![mimetex(6)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex6.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex6.gif) ويكون الوزن الأول w0 دائما 1 . **5) طريقه لحساب المخرج للخليه Calculating output signal** بعد أن تم حساب المدخل للخليه ، وحتى تمرر الخليه الناتج للخليه التي تليها او للبيئه (في حال كانت هي أخر خليه في الشبكه output unit) سنقوم بحساب المخرج للخليه وهو ما يعرف بالتنشيط Activation ، هناك عده طرق أقصد دوال تنشيط Activation Function منها ما يخرج مخرج يكون رقم real محصور بين 0 و 1 ، ومنها ما يكون فقط أما 1 أو 1 (متقطع Discrete) ، أو -1 و 1 ، وهناك الكثير من دوال التنشيط منها Identity function و Binary Threshold function و Sigmoid Function ، نأخذ نظره بسيطه على من هذه الدوال والتي مهتما تكون في استقبال المدخل الذي حسبناه سابقا ومن ثم تقوم باخراج النتيجه .. الداله Identity لا تقوم بعمل شيء حقيقه ، فقط تأخذ المدخل للخليه وتجعله هو المخرج لهذه الخليه .. وتقريبا جميع خلايا الأدخال يمكن أعتبارها على أنها تستخدم هذه الداله ، حيث أنها تستقبل المدخلات (سواء من ملف أو محسسات خارجيه) وتقوم فقط بتمرريها الى الخلايا التاليه ،، على العموم أغلب الكتاب والمؤلفين لا يعتبروا أن الخلايا في طبقه الأدخال جزء من الشبكه ،، اضافه الى أن أغلب الModel لا تعتمد على دوال خطيه كهذه .. الداله Binary Threshold function تقوم بجعل المخرج محصور في قيمتين فقط ، وذلك بالأعتماد على قيمه ما [![mimetex(7)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex7.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex7.gif) حيث ننظر للمدخل فاذا كان مثلا أكبر من ال [![mimetex(7)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex7.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex7.gif)فيكون 1 واذا كان أصغر فيكون 0 . [![mimetex(8)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex8.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex8.gif) [![mimetex(9)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex9.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex9.gif) الداله الثالثه Sigmoid وهي تجعل المخرج محصور بين عده قيم في المدى من 1 الى 0 ، ومعادلتها بهذا الشكل : [![mimetex(10)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex10.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex10.gif) **6) طريقه لتعديل الاوزان بالشبكه Learning Rule to Adapt weight** هناك طريقتين للتعليم وهم التعليم بموجه أو دليل Supervised بمعنى تقديم المخرج مع كل مثال نقدمه للشبكه ، والنوع الأخر وهو بدون موجه Unsupervised ولن نقدم للشبكه أي مخرج لأي مثال ،، التعليم من غير موجه Unsupervised يستخدم في الشبكات التي تقوم بعمل تصنيف Classification ، بحيث نقدم للشبكه عده أمثله ومن ثم تقوم الشبكه بمعرفه ما يميز هذه الأمثله وتقوم بتقسيمها الى مجموعات ، وستضع الأمثله المتشابهه مع بعضها البعض في مجموعه واحده ،، ومن الأمثله على تطبيقات هذا النوع من التدريب هو في الData Mining فقد نحتاج الى البحث في كميه ضخمه من النصوص ، وقد لا نعلم عن ماذا نبحث بالضبط ، فنستخدم هذا النوع من التدريب وسوف يقوم بتنصيف هذه النصوص أو البيانات الى أقسام ويضع البيانات أو النصوص المتشابهه في القسم المناسب لهم،، شبكه الSOM أو ما تعرف بكونين Kohonen NN تستخدم هذا النوع من التدريب .. أيضا خورازميه Hebb تستخدم للتعليم من غير موجه ، الصوره التاليه توضح كيفيه تدريب الشبكه باستخدام التعليم من غير موجه : [![1604390085c4f1.graffle](https://informatic-ar.com/wp-content/uploads/2013/09/unsupervised-300x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/unsupervised.jpg) في التعليم باستخدام موجه Supervised سوف نقوم بتقديم المخرج المعين لكل مثال نقدمه للشبكه ، وبالتالي تتعلم الشبكه (تعدل الأوزان) عن طريق معرفه الفرق بين المخرج الحالي والمخرج الهدف target output ، ومن أشهر خوارزميات التعلم بموجه طريقه ال Bakpropgation ، أيضا طريقه Delta Rule . والطريقه الأولى في الحقيقه تستخدم نسخه معدله من طريقه Delta Rule ،، أيضا يمكن تعليم الشبكه بدون موجه باستخدام الخوارزميه الجينيه Genetic Algorithm أيضا طريقه الSimulating Annealing . الصوره التاليه توضح كيفيه تدريب الشبكه باستخدام التعليم موجه : [![1604390085c4f2.graffle](https://informatic-ar.com/wp-content/uploads/2013/09/supervised-300x265.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/supervised.jpg) **Delta Rule** سنتطرق الأن لأشهر طريقه لتعديل الأوزان في النوع التعليم بموجه و هي Delta Rule وفكرتها تكون عن طريق معرفه الفرق بين المخرج الحالي للشبكه مع المخرج الأصلي (الذي من المفترض أن تكون الشبكه قد أخرجته) ومن ثم تقوم بضرب هذا الفرق في رقم يمثل لي معدل التعليم Learning rate بالاضافه الى ضربه مع المدخل لهذه الخليه ومن ثم نجمع الناتج للوزن القديم ، وهكذا يكون الوزن قد تم تعديله .. المعادله التاليه هي للتعليم باستخدام Delta Rule : [![mimetex(11)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex11.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex11.gif) حيث o هو المخرج الحالي ، و t هو المخرج المستهدف أو الأصلي target . [![mimetex(12)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex12.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex12.gif) mu هي معدل التعليم وهو رقم حقيقي عاده يكون 0.1 ، beta هي مقدر الخطأ الناتج من المعادله السابقه ، وx هو المدخل لهذه الخليه ، [![mimetex(13)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex13.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex13.gif) نقوم بجمع الوزن القديم مع التعديل الجديد وبالتالى يتغير الوزن للخليه . ملاحظه هذه الطرق لها عده مسميات منها : Adaline Rule Widrow-Hoff Rule Least Mean Squares (LMS) Rule **Hebb Rule** تستخدم طريقه Hebb في تعليم الشبكات التي تستخدم التعليم من غير موجه ، ويتم تعديل الوزن عن طريق المعادله : [![mimetex(14)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex14.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex14.gif) حيث x هو المدخل ، و o هو المخرج الحالي ، و mu هو معامل التعليم learning rate . ومن ثم يتم جمع هذا الناتج مع الوزن القديم : [![mimetex(13)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex13.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex13.gif) **حساب الخطأ في الشبكه العصبيه :** أثناء عملية تدريب الشبكه ، يجب أن نختبر الشبكه فاذا أخرجت القيم الصحيحه مباشره لجميع المدخلات فهذا يعني أن الأوزان الحاليه هي الأوزان المثاليه ولا تحتاج لتغيير ، لكن في أغلب الأحيان القيم المخرجه لن تكون بالضبط كما هي القيم المستهدفه target output (أتحدث عن التدريب بموجه) ولكن تكون هناك نسبه من الخطأ (نسبه سماح ) وبالطبع يجب أن تكون النسبه قليله ، والا في حال كبرت فيمكن أن تقدم للشبكه صوره شاب وتتعرف الشبكه على أنها صوره شجره !! والعكس أيضا ففي حال قللت نسبه الخطأ بشكل قليل جدا فيمكن أن لا تتعرف الشبكه على المثال التي قدمته والذي يختلف قليلا عن المثال الذي تعرفه الشبكه ،، لذلك نسبه الخطأ أو السماح في الشبكه يجب أن تكون متوسطه القيمه ،، وعندما نحسب الخطأ في الشبكه سوف نعرف أنها ما زالت بحاجه الى المزيد من التدريب (تغيير الأوزان) في حال كانت نسبه الخطأ كبيره ، ونستمر في عمليه التدريب إلى أن تقل درجه الخطأ للحد المعين الذي حددته أنت المصمم (حد السماحيه) .. ويتم حساب الخطأ في الشبكه Root Mean Square RMS بالمعادله : [![mimetex(15)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex15.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/mimetex15.gif) وذلك بحساب الفرق بين المخرج المثالي والحالي للمدخل الأول ، ومن ثم تربييعه ، وبعدها جمعه في sum والقيام بهذه العمليه لجميع المدخلات للشبكه ، بعدها نقوم بتقسيم sum على عدد المدخلات ونأخذ الجذر التربيعي للناتج وهكذا ينتج لدينا الRMS . **خلاصه :** تعرفت أخي القارئ الكريم ، أن الشبكه العصبيه ANN هي محاوله لمحاكاه عمل الشبكه العصبيه الموجوده في عقل الأنسان ، وذلك لحل مشاكل يصعب حلها بواسطه الطرق التقليديه ،، كما تعرفت على مكونات الخليه (وزن ، حد) ، وعرفت أن الخليه تقوم باطلاق القيمه fire عندما تكون القيمه المدخله\*وزن أكبر من قيمه الحد ، والا فلن تقوم بعمل هذا الfire . كما تعلمت أن الشبكه العصبيه يجب أن تتمرن بحيث تتغير هذه الاوزان في كل مره حتى تحصل على النتيجه المطلوبه ، وبعدها تقوم بالتأكد ، لأنك بعد الأنتهاء لن تستطيع تدريبها مره أخرى .. **المصادر التي أقرأها وأستفيد منها :** [Introduction to Neural Networks for Java, Second Edition](http://www.heatonresearch.com/online/introduction-neural-networks-java-edition-2?ref=informatic-ar.com) C++ Neural Networks and Fuzzy Logic ### شبكات ال Perceptron المكونة من طبقة واحدة URL: https://informatic-ar.com/single-layer-perceptron/ Last updated: 2026-10-03T22:32:16.000Z شبكه الPerceptron تعد من أقدم وأسهل أنواع الشبكات العصبيه ، وهي نوع مبسط من الFeed-Forward Neural Network حيث هناك نوع منها يحتوي على طبقه واحده Single Layer والأخر يحتوي على أكثر من طبقه Multi-Perceptron Layer واختصارا MPL . وبشكل عام مهمه هذا النموذج هي في التصنيف Classification . موضوعنا اليوم حول النموذج البسيط Single Layer وباذن الله نتحدث حول MPL المره القادمه ،، وشبكه الSingle Layer Perceptron تسمى عاده linear classifier بمعنى أن هذه الشبكه تحل المشاكل التي يمكن فصلها بشكل خطي linearly separable ، انظر للصوره التاليه وستجد أن الخط الأخضر قد فصل بين اللون الأزرق والأحمر ،، وهذه الشبكه تستطيع حل المشاكل مثل هذه . [![Linear](https://informatic-ar.com/wp-content/uploads/2013/09/Linear-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Linear.png) سنقوم في موضوعنا الليله بكتابه برنامج باستخدام الشبكه Perceptron نقوم باعطائه أي بعد في الصوره السابقه وتقوم الشبكه بالرد اما أن البعد في المنطقه التي في النقاط باللون الأزرق أو الأخرى باللون الأحمر ،، نبدأ على بركه الله، أولا الشبكه Single Layer Perceptron سوف تتكون من مدخلين يمثلان البعدين اللذان سندخلانه ، والمخرج سوف يكون اما 1 (أزرق) أو -1 (أحمر) ، وبهذا يكون شكل الشبكه كالتالي : [![Network](https://informatic-ar.com/wp-content/uploads/2013/09/Network-300x105.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Network.png) سنستخدم Delta Rule لتعديل الأوزان والتي ستكون في البدايه 0.0 ، أيضا الActivation function سوف نستخدم bipolar threshold function تعطي 1 للون الأزرق ، و-1 للون الأحمر ، أيضا الLarning rate سوف يكون بمقدار 0.1 ويمكن تغييره في البرنامج ، بالاضافه الى الThreshold سوف يكون بقيمه 0.1 ويمكن تغيير أيضا ، في البدايه سوف نقدم 30 مثال (نقطه) للشبكه بالاضافه الى الناتج من كل نقطه target output ، بعد أن تتدرب الشبكه على ذلك (وسوف تستغرق عدد محدد من الepoch الدورات) ، سنقدم عده أمثله جديده لكي نختبر البرنامج ولنرى مدى كفائه الشبكه ،، البرنامج بسيط ويستخدم [مكتبه الMatrix التي سبق وان صممناها في الHopfeild ](https://informatic-ar.com/?p=57)، اترككم مع الكود : ```cpp // Perceptron.cpp #include "Perceptron.h" #include using namespace std; #define NUM 30 // 30 sample example #define SIZE 2 // two input void printData(double i, double j, int output); int main(int argc, char *argv[]) { // traning data double input[NUM][SIZE] = { {0.72, 0.82}, {0.91, -0.69}, {0.46, 0.80}, {0.03, 0.93}, {0.12, 0.25}, {0.96, 0.47}, {0.79, -0.75}, {0.46, 0.98}, {0.66, 0.24}, {0.72, -0.15}, {0.35, 0.01}, {-0.16, 0.84}, {-0.04, 0.68}, {-0.11, 0.10}, {0.31, -0.96}, {0.00, -0.26}, {-0.43, -0.65}, {0.57, -0.97}, {-0.47, -0.03}, {-0.72, -0.64}, {-0.57, 0.15}, {-0.25, -0.43}, {0.47, -0.88}, {-0.12, -0.90}, {-0.58, 0.62}, {-0.48, 0.05}, {-0.79, -0.92}, {-0.42, -0.09}, {-0.76, 0.65}, {-0.77, -0.76}}; // target output int output[NUM] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; // perceptron with (input,output,weight,TH,LR) Perceptron perceptron(input, output, NUM); // print data cout << "Traning Data Input : " << endl; cout << perceptron.getInput() << endl; cout << "Target Data Output : " << endl; cout << perceptron.getOutput() << endl; cout << "Weight : " << endl; cout << perceptron.getWeight() << endl; system("pause"); cout << endl << endl; // train ANN perceptron.train(); // test ANN from number (-1,-1) to (1,1) cout << "input 1 , input2 > output " << endl; cout << "----------------------------" << endl << endl; for (double i = -1; i <= 1; i += 0.5) { for (double j = -1; j <= 1; j += 0.5) { int output = perceptron.classify(i, j); printData(i, j, output); } } printData(5, 5, perceptron.classify(5, 5)); // test (5,5) printData(-15, 5, perceptron.classify(-15, 5)); // test (-15,5) cout << endl << endl; cout << "Weight : " << endl; cout << perceptron.getWeight() << endl; cout << "Number of iteration : " << endl; cout << perceptron.getIteration() << endl; return (0); } void printData(double i, double j, int output) { cout << setw(6) << setfill(' ') << i << setw(10) << setfill(' ') << j << setw(10) << setfill(' '); if (output == 1) cout << "Blue"; else cout << "Red"; cout << endl; } ``` لنقم الان بتشغيل البرنامج ، وسنرى الجزء الأول من المخرج وهي البيانات التي قدمناها كأمثله للتدريب بالاضافه الى النتيجه المقابله لها ، وسوف يعرض الأوزان الأبتدائيه ،، [![Percep](https://informatic-ar.com/wp-content/uploads/2013/09/Percep-300x277.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/Percep.gif) بعد الضغط على enter تبدأ عمليه التدريب ، الى أن تنتهي وسيخرج الناتج كما توضح الصوره التاليه ،، (يمكن ادخال جمله طباعه cout لطباعه كل من الخطأ والدوره الحاليه والوزن الجديد داخل داله التدريب حتى يسهل تتبع البرنامج) . [![Percep1](https://informatic-ar.com/wp-content/uploads/2013/09/Percep1-257x300.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/Percep1.gif) راجع الصوره الأولى الخاصه بالاحداثيات لكي تتأكد من النتيجه ،، هناك بعض الأحيان مشاكل غير قابله للفصل الخطي مثل XOR ، انظر للصوره التاليه لكي توضح لك : [![Non_Linear](https://informatic-ar.com/wp-content/uploads/2013/09/Non_Linear-300x300.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Non%5FLinear.png) في هذه النوع من المشاكل يجب أن نستخدم أكثر من طبقه في شبكه وهو نوع MPL وباذن الله نتناوله المره المقدمه ،، في هذا الرابط المثال كاملاً ([حمله من هنا](https://informatic-ar.com/files/Perceptron.rar)) ،، ### شبكات الAssociator - هوبفلد HopField Neural Network URL: https://informatic-ar.com/hopfield-neural-network/ Last updated: 2026-10-03T22:32:17.000Z ان البرامج التقليديه تستطيع ايجاد الحلول للمشاكل التي يوجد لها خوارزميه واضحه للحل ، أو التي يكون حلها ثابت لا يتغير أبدا ... وتستطيع هذه البرامج حفظ مئات أو ملايين الصور ، لكن هل يمكن لهذه البرامج التعرف على محتويات الصوره التي يحتفظ بها .. الشبكه العصبيه أتت لحل هذه المشاكل ، حيث تعني بمحاوله تقليد طريقه تفكير الأنسان ، وبالتالى عندما تبدأ برمجه الشبكه العصبيه يجب أن تقوم بتدريبها على مجموعه كبيره من الانماط ، بحيث فيما بعد لو أتى لها نمط مشابه أو قريب منها تقوم الشبكه العصبيه بالتعرف على هذا النمط .. بشكل عام أغلب مهمات الشكبات العصبيه في نوعين هم الClassification التصنيف أو الAssociation ويمكن أن نقول عنه التعرف recognition ، في النوع الأول الClassification يكون لدينا مدخل ونقوم بتصنيفه الى أحد الأفسام الموجوده ، مثلا صوره حيوان ويتم تصنيفها الى الثديات أو الزواحف أو أي نوع أخر ،،مثال على تلك الأنواع شبكات Perceptron وشبكات الAdline . في الAssociator يكون المدخل input vector وتخرج الشبكه متجه أخر output vector ، وهناك نوعين من الAssociator الأول هو hetero-associative HA والأخر هو auto-associative AA ، في الأول HA يأخذ المدخل ويخرج مخرج مختلف تماما عن المدخل مثلا الشبكه أخذت ملف صوتي والمخرج يكون عباره عن نصوص text تمثل الأمور التي فهمتها الشبكه من الصوت .. النوع الأخر AA ويكون المخرج بالضبط كما هو المدخل ، ويمكن أستخدامها في حال تم تدريب الشبكه على عده صور ، ثم تم أدخال صوره مشوهه قليلا ، فسوف تتعرف الشبكه على الصوره وترجع النسخه الأصليه ،، الشبكه Hopfield هي من النوع AA . **Hopfield Neural Network** هذا النوع من الشبكات ربما يعد من أسهل الأنواع على الإطلاق ، وتركيبته architecture تكون بأن كل خليه neuron في الشبكه يتصل بالأخر لذلك يطلق عليه Fully Connected Neural Network ، أيضا هذا النوع يعتبر Auto-associative وذلك لأن الشبكه ترجع النمط فور التعرف عليه مباشره ،، في هذا المقال سوف نتناول شبكه Hopfield تتكون من أربع خلايا للتبسيط وحتى تتضح الصوره بشكل كبير ،، ,وهذا النوع Hopfieldيستخدم في الغالب للتعرف على الأنماط Pattern Recognition . نشاهد الصوره التاليه والتي توضح لي شبكه Hopfield تتكون من أربع خلايا : [![hnn](https://informatic-ar.com/wp-content/uploads/2013/09/hnn.bmp)](https://informatic-ar.com/wp-content/uploads/2013/09/hnn.bmp) كما نلاحظ بالصوره أعلاه ، تتكون الشبكه من 4 خلايا ، كل منها يرتبط بالأخر عن طريق connection (رابط) ، هذا الرابط يحتوي على قيمه والتي تمثل الوزن weight . عدد الروابط هنا هي 12 رابط .. ويمكن تمثيل هذه الروابط باستخدام مصفوفه من بعدين Matrix كما توضح الصوره التاليه : [![hnn2](https://informatic-ar.com/wp-content/uploads/2013/09/hnn2-300x135.png)](https://informatic-ar.com/wp-content/uploads/2013/09/hnn2.png) لاحظ أن قطر الMatrix (كل خليه مع نفسها) يساوي 0 ، لأنه لا يوجد رابط مع كل خليه بالأخرى ، بقيه القيم في الMatrix تكون على حسب الوزن الموجود بين القيمتين ، المثال التالي سوف يوضح العمليه .. ليكن لدينا الأوزان التاليه ، وهي تتعرف recall على النمط Pattern ال0101 و1010 : [![hnn3](https://informatic-ar.com/wp-content/uploads/2013/09/hnn3.png)](https://informatic-ar.com/wp-content/uploads/2013/09/hnn3.png) نبدأ بالنمط الأول 0101 كيف يمكن لهذه الشبكه العصبيه أن تتعرف على هذا النمط ، الخطوات بسيطه ، فكل ما في الأمر أن تقوم بحساب التنشيط لكل خليه ، ومن ثم تقوم بمعرفه هل الناتج من الخليه أكبر من داله النتشيط (وفي حالتنا هي Threshold وهي في hop يكون 0 ) ، وفي حال كان أكبر أو يساوي فإن الخليه تقوم بعمل fire (قيمه 1 ) والا العكس (قيمه 0) .. لحساب تنشيط كل خليه ، نقوم بضرب المدخل (النمط) مع العمود الأول وجمع النتائج (العمليه تسمى dot product) وهكذا نكون عرفنا تنشيط الخليه الأولى ، ونفس الأمر بالنسبه للخليه الثانيه (العمود الثاني) نقوم بضربها مع المدخل وحساب الناتج .. وهكذا للخليه الثالثه والرابعه ،، نبدأ بالنمط الأول كما ذكرنا 0101 ، ونبدأ بالعمود الأول (خليه 1) في الصوره ، ونقوم بعمليه الضرب وجمع الناتج ، والنتيجه سوف تكون -2 .. هناك طريقه سريعه وهي جمع الرقم الذي يقابل الواحد في المدخل ، المدخل : 0 1 0 1 الخليه : 0 -1 1 -1 الجموع : -1 + -1 = -2 . هكذا نكون أخرجنا التنشيط activation للخليه الأولى ، بنفس الأمر لباقي الخلايا ويخرج لدينا الناتج التالي : N1 = -1 + -1 = -2 N2 = 0 + 1 = 1 N3 = -1 + -1 = -2 N4 = 1 + 0 = 1 المخرج النهائي هو –2, 1, –2, 1 ، الأن نقوم بحساب داله التنشيط activation function (الحد Threshold) ، وهنا أي خليه ناتجها يساوي أو أكبر من صفر سوف تكون نشطه fire بالقيمه (1) ، وغير ذلك بالقيمه (0) . خليه 1 تنشيطها -2 وبالتالي لن تكون نشطه (0) خليه 2 تنشيطها 1 وبالتالي تكون نشطه (1) خليه 3 تنشيطها -2 وبالتالي لن تكون نشطه (0) خليه 4 تنشيطها 1 وبالتالي تكون نشطه (1) وبالتالي الناتج يكون 0101 ، وعند مقارتنه مع المدخل الأصلى 0101 سوف نجد أنهم متابطقين وبالتالى تكون الشبكه العصبيه قد تعرفت على النمط المدخل .. **ايجاد الأوزان Deriving the Weight Matrix** لعلك تتسائل كيف أمكن ايجاد تلك الأوزان في المثال السابق ، حيث كما تلاحظ أنه اذا كانت الأوزان صحيحه بالتالي التعرف على النمط أمر في غايه السهوله ،، اذا المشكله كيف يمكن أن نوجد هذه الأوزان ... وهو ما يعرف بتدريب الشبكه العصبيه .. العمليه بشكل مجمل تكون على تدريب الشبكه العصبيه على نمط معين وسوف يكون الناتج هو مصفوفه Matrix تتعرف على هذا النمط contribution matrix ، ونقوم بجمع هذه المصفوفه مع مصفوفه الأوزان ، في حال أردت أن تتعرف على نمط أخر نقوم بجعل الشبكه بتدريب الشبكه على ذلك النمط ونقوم بجمعها مع مصفوفه الأوزان ، وهكذا سوف نضيف الcontribution matrix كل مره لwieght Matrix وهكذا تكون كل مره تعلمت أنماط جديد .. الى أن تنتهي عمليه التدريب . في المثال السابق كان المدخل 0101 ، لنرى الأن كيف يمكن توليد الأوزان (تدريب الشبكه على هذا المدخل) ، أولا نقوم بعمل Matrix خالي في البدايه ، كما توضح الصوره التاليه : [![hnn4](https://informatic-ar.com/wp-content/uploads/2013/09/hnn4.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/hnn4.jpg) **بعد ذلك نقوم بتطبيق الثلاثه خطوات التاليه :** 1) حساب الBipolar للمدخل 2) ضرب الناتج من 1 مع الtransposition لنفس المصفوفه 3) تصفير الأقطار northwest diagonal (التقاء الخليه مع نفسها) تذكر أن في شبكه hobfield لا توجد خليه تتربط مع نفسها . نبدأ بالخطوات ،، 1) أولاً الBipolar هو وسيله لتمثيل الtrue والfalse ، فبدلا أن تكون قيمه false هي 0 ، تكون false تساوي -1 ، وtrue هي نفسها 1 (سواء في binary أو bipolar) . لكي نحول من binary الى bipolar يمكن أستخدام القانون التالي : [![hnn5](https://informatic-ar.com/wp-content/uploads/2013/09/hnn5.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/hnn5.jpg) ولكى نقوم بالعكس ، تحويل من bipolar الى binary نستخدم : [![hnn6](https://informatic-ar.com/wp-content/uploads/2013/09/hnn6.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/hnn6.jpg) الأن نعود للمدخل 0101 ، نقوم بتحويله : 0 = -1 1 = 1 0 = -1 1 = 1 النتيجه هي -1 1 -1 1 وهذه المصفوفه Vector سوف نستخدمها في المرحله التاليه . 2) ضرب الناتج مع الtransposition لنفس المصفوفه الناتج السابق ضرب المعكوس له الtransposition : [![a](https://informatic-ar.com/wp-content/uploads/2013/09/a.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/a.jpg) ينتج لدينا : [![hnn9](https://informatic-ar.com/wp-content/uploads/2013/09/hnn9.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/hnn9.jpg) 3) نقوم الأن بتصفير الأقطار في المنتصف ، وينتج لدينا [![hnn10](https://informatic-ar.com/wp-content/uploads/2013/09/hnn10.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/hnn10.jpg) (يمكن أن تكون العمليه عن طريق طرح الناتج من (2) مع الIdentity Matrix) . الصوره السابقه تمثل لي contribution matrix التي تتعرف على النمط 0101 . ونقوم بجمعها مع مصفوفه الأوزان weight matrix . واذا كنا نريد الشبكه تتعرف فقط على هذا النمط بالطبع سوف تصبح الcontribution matrix هي نفسها الweight matrix . واذا أردنا أن تتعرف الشبكه على أنماط أخرى نقوم بنفس الخطوات الثلاثه ، ثم نجمع الcontribution matrix مع الweight matrix ، وهكذا نستمر، **الجزء العملي**: الكود يعتمد على مكتبة Matrix وهي تدعم عمليات المصفوفات ، يمكنك تحميل الكود مرفقة مع مثال شبكة هوبفليد وترجمتها وتشغيلها [![HopF](https://informatic-ar.com/wp-content/uploads/2013/09/HopF-300x224.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/HopF.gif) مثال على شبكه Hopfield من 4 خلايا ، ونقوم بتدريبها على المدخل1100 ، ثم نقدم لها نفس المدخل وسنجد أنها تعرفت عليه ،، وسنقدم لها مدخل ثاني 1000 سنجد أنها لم تتعرف عليه وقدمت أقرب نمط موجود لديها وهو 1100 .. ```cpp // Hopfield Example #include "Utility.h" #include "Hopfield.h" #include using namespace std; int main(int argc, char *argv[]) { try { Hopfield hp(4); cout << "\nNow weight Matrix is : \n" << hp.getMatrix() << endl; bool pattern1[] = {true, true, false, false}; bool pattern2[] = {true, false, false, false}; cout << "\nTraining Nets With Pattern : " << Utility::print(pattern1) << endl; hp.train(pattern1); //hp.train(pattern2); // now input pattern1 to see if it recogonized bool *result = hp.present(pattern1); cout << "\nPrensent : " << Utility::print(pattern1) << " And Got : " << Utility::print(result) << endl; // now input pattern1 to see if it recogonized bool *r2 = hp.present(pattern2); cout << "\nPrensent : " << Utility::print(pattern2) << " And Got : " << Utility::print(r2) << endl; cout << "\nNow weight Matrix is : \n" << hp.getMatrix() << endl; } catch (const exception &exp) { cout << exp.what(); } return 0; } ``` المخرج كالتالي : [![Hopfield](https://informatic-ar.com/wp-content/uploads/2013/09/Hopfield-300x187.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Hopfield.png) الأن أزل التعليق من السطر 26 من البرنامج ، وهو لكي ندرب الشبكه على المدخل 1000 ، وبعدها سنجد أنها تعرفت عليه .. كما يوضح الشكل التالي : [![Hopfield2](https://informatic-ar.com/wp-content/uploads/2013/09/Hopfield2-300x236.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Hopfield2.png) لاحظ أنك يجب أن تضيف ملفات الرأس كما هي ، وأن تقوم بعمل ترجمه لجميع الملفات وتشغل الملف الرئيسي وهو TestHop.cpp .. [جميع الملفات (Matrix , Utility , Hopfield ) سواء ملف رأس أم cpp موجودين في في الرابط](https://informatic-ar.com/files/HopField%200.1.zip). **نأخذ مثال أخر للتعرف على علامه + و - ، أنظر الشكل التالي :** [![hopplus](https://informatic-ar.com/wp-content/uploads/2013/09/hopplus-300x156.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/hopplus.jpg) الأن كل مربع من هذه المربعات يمثل لي خليه كامله ، بالتالى نحتاج الى 9 خلايا لكي نتعرف على العلامه ،، الأن في البدايه يجب أن ندرب الشبكه على العلامتين ، المربع الأبيض يكون false والأسود true بالتالي الحرف الأول : \-1,1,-1,1,1,1,-1,1,-1 أما الثاني : \-1,-1,-1,1,1,1,-1,-1,-1 الأن نقوم بتدريب الشبكه على هذين المدخلين ، وسوف نستخدم البرنامج للتسهيل طبعا ،، لاحظ البرنامج الأن : ```cpp // Hopfield Example #include "Utility.h" #include "Hopfield.h" #include using namespace std; #define num 9 // neuron number int main(int argc, char *argv[]) { try { Hopfield hp(num); cout << "\nNow weight Matrix is : \n" << hp.getMatrix() << endl; bool pattern1[] = {false, true, false, true, true, true, false, true, false}; bool pattern2[] = {false, false, false, true, true, true, false, false, false}; cout << "\nTraining Nets With Pattern : " << Utility::print(pattern1, num) << endl; hp.train(pattern1, num); hp.train(pattern2, num); // now input pattern1 to see if it recogonized bool *result = hp.present(pattern1, num); cout << "\nPrensent : " << Utility::print(pattern1, num) << " And Got : " << Utility::print(result, num) << endl; // now input pattern1 to see if it recogonized bool *r2 = hp.present(pattern2, num); cout << "\nPrensent : " << Utility::print(pattern2, num) << " And Got : " << Utility::print(r2, num) << endl; cout << "\nNow weight Matrix is : \n" << hp.getMatrix() << endl; } catch (const exception &exp) { cout << exp.what(); } return 0; } ``` والمخرج : [![hopplus1](https://informatic-ar.com/wp-content/uploads/2013/09/hopplus1-300x240.png)](https://informatic-ar.com/wp-content/uploads/2013/09/hopplus1.png) الأن انظر لهذا الشكل : [![hopplus2](https://informatic-ar.com/wp-content/uploads/2013/09/hopplus2.png)](https://informatic-ar.com/wp-content/uploads/2013/09/hopplus2.png) وقم مباشره بادخاله للشبكه بدون تدريب ،ماذا تتوقع النتيجه ؟ بالطبع على الحرف الثاني - لأنه أقرب اليه ،، قم بتجربته في البرنامج السابق ولاحظ النتيجه . ### لماذا مساحات الأقراص أقل مما هو موجود بالإعلان URL: https://informatic-ar.com/disk-capacity/ Last updated: 2026-10-03T22:32:17.000Z لماذا عندما تشتري أي وحدة تخزينية Hard disk/Flash تجد مساحتها أقل من ما هو موجود في الإعلان ؟ [![flash](https://informatic-ar.com/wp-content/uploads/2013/09/flash-300x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/flash.jpg) السبب هو في كيفية حساب البايت Byte ، مصنعي وحدات التخزين Hardware Manufacture يحسبوا الكيلوبايت على انه 1000 بايت ، بينما نحسبها نحن المبرمجين ونظام التشغيل ايضاً على أنها 1024 بايت. **فمثلاً اذا جلبت فلاش 2 GB، فمصنعي الواحدات يصنعوا الجهاز ب 2000000000 بايت ويقوموا بالاعلان عن 2 قيقا بالطريقة بالطريقة التالية:** قسمة 2000000000 بايت على 1000 لكي يتم تحويلها الى كيلوبايت (الناتج هو 2000000 ) الناتج 2000000 يتم قسمته على 1000 لكي يتم تحويلها الى ميغا بايت (الناتح هو 2000) ويتم قسمة التانج على 1000 لكي يخرج 2 قيقا بايت. أو بالعكس: 2 GB نضربها في 1000 لنحصل على 2000 MB، نضربه في 1000 لنحصل على 2000000 كيلوبايت، نضربه في 1000 لنحصل على 2000000000 بايت.. **أما ويندوز فهو يري 2000000000 بايت ولكن يحسبها على أنه 1.86 قيقا على أساس ان الكيلو فيه 1024 بايت** ، الطريقة كالتالي: حجم الفلاش هو 2000000000 بايت ، يتم تقسيمها على 1024 والناتج هو 1953125 كيلوبايت، ومن ثم نقسمه مرة اخرى على 1024 ليكون 1907.34 ميغابايت ومن ثم نقسمها مره اخرى ويخرج لدينا 1.86264514923095703125 قيقابايت ومن ثم نأخذ اول رقمين بعد الفاصلة precision of 2 يخرج لدينا 1.86 GB. **هناك طريقة سريعة لحساب المساحة الحقيقية لأي قرص** ، فقط قم بالقسمه على 1.024 ثلاث مرات ، لو قسمت 2 على 1.024 ثلاث مرات سوف تجد الناتج الصحيح 1.86 ### لماذا كتب علوم الحاسب الكلاسيكية تحتوى بعضاً منها على ديناصورات؟ URL: https://informatic-ar.com/computer_science_books/ Last updated: 2026-10-03T22:32:17.000Z القصه تبدأ مع معاناه عالم الحاسب و مهندس البرمجيات فريد بروك [Fred Brooks](http://en.wikipedia.org/wiki/Fred%5FBrooks?ref=informatic-ar.com) مع نظام OS/360 ، حيث هذا النظام كان من المفترض أن يكون من افضل الأنظمه وأكبرها لدعمه time-sharing ولكن لكبر هذا النظام وتعقيده فكل ما كانت تحل مشكله يظهر مكانها عشرات المشاكل الأخرى Bugs . أحد أخطاء فريد هو أعتقاده كلما زاد عدد الأشخاص في برمجه النظام فإنه سوف يقلل الزمن المستغرق وبعدها عرف العكس تماما . وهذا يسمى الآن بقانون بروك Brooks's law "اضافة اليد العاملة في مشروع متأخر سوف يأخره ايضاً" بعدما أنتهت معاناته في ذلك النظام، قام بكتابه كتاب يصف فيها تجربته في تطوير البرمجيات فكتب كتاب أسماه The Mythical Man-Month وهذا الكتاب يعتبر من كتب هندسه البرمجيات الجيدة ويطلق عليه The Bible of Software Engineering [![os](https://informatic-ar.com/wp-content/uploads/2013/09/os-279x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/os.jpg) لاحظ غلاف الكتاب الأسود حيث عباره عن هو عباره عن مجموعه من الدينصورات القديمه والتي تدل على المعاناه ، بعدها أصبحت عاده مؤلفين بعض الكتب مثل Silberschatz صاحب كتاب operating system concepts الشهير بكتاب "الديناصور" حيث وضع عده ديناصورات في غلافه بسبب معاناه انظمة التشغيل وتطورها مع الوقت حيث تناولت أول نسخه الحديث عن الأنظمة الشهيرة ذلك الوقت "OS/360, Multics, Scope, OS/MVS, VMS, UNIX, and CP/M" بنفس الفكرة قام العالم Alfred Aho صاحب كتاب Compilers: Principles, Techniques, and Tools المشهور بكتاب التنين Dragon Book قام بوضع غلافه بنفس الصورة ليوضح صعوبة وتعقيد بناء المترجمات. ### مقدمه في ال Hash Table URL: https://informatic-ar.com/hash_table/ Last updated: 2026-10-03T22:32:18.000Z تعد الـ Hash Table من أهم وأسرع هياكل البيانات على الإطلاق، وكثير من التطبيقات تستخدم مثل هذه البنيه مثل Spell Checker أو Symbol Table في المترجمات، حيث تضمن لنا هذه البنيه الوصول السريع جدا لأي بيانات نريدها مهما كان حجم تلك البيانات، بالاضافه الى ادخال البيانات أيضا يتم في سرعه كبيره .. زمن التنفيذ لها هي ((O(1) وبالتالى تعد من أسرع هياكل البيانات على الإطلاق (مثلها مثل المصفوفه) ،، اضافه الى ميزه السرعه هناك ميزه جيده بها وهي أنها سهله التطبيق حيث أننا سنطبق هذه البنيه من خلال مصفوفه عاديه أو vector. بالرغم من ميزات هذه البنيه ، الا أنها يجب أن تستخدم في الأوقات المناسبه وقد لا تصلح لكل حاله ،، لأن الHash Table أولا يتم تطبيقه باستخدام المصفوفه Array والتي يصعب توسيعها (لكننا سنتجاوز هذه المشكله لأننا سنستخدم vector) ، ثانيا وهو الأهم أن أداء هذه البنيه يقل تدريجيا كلما أمتلئت المصفوفه table ، لذلك من البدايه يجب أن نحدد حجم البيانات التي سوف توضع في هذه المصفوفه ونقوم بحجز مساحه مناسبه لهذا الحجم ..ثالثا باستخدام الHash Table لا يمكن زياره جميع العناصر في المصفوفه (بمعنى أصح لا توجد فائده من هذه العمليه Visiting لأن البيانات مخزنه في أماكن عشوائيه بلا ترتيب) ، وبالتالى لا يمكنك مثلا اجراء عمليه ايجاد القيمه الأكبر في هذه البنيه Find Max ، وفي حال أردت أن تستخدم بنيه توفر لك هذه الميزه فإن الBinary Tree بكل تأكيد هي الأنسب في هذه الحاله . الHash Table يفضل أن تستخدم في حال كانت البيانات التي تتعامل معها ثابته ولا تتغير ، وتريد الوصول لها في كل مره بشكل سريع ، مثلا البرامج التي تستخدم القاموس Dictionary يمكن أن تقرأ هذه القاموس وتضعه في بنيه Hash Table في الذاكره وقت تشغيل البرنامج. ## برمجة ال Hash Table أحد أهم المفاهيم في الHash Table والHashing بشكل عام هو كيف يمكن تحويل مجموعه من المفاتيح الى مواقع معينه Index في المصفوفه ، في أبسط الأحوال لا تكون هناك أي عمليه تحويل ويكون المفتاح هو نفسه مباشره الموقع Index في المصفوفه ، لكن هناك حالات أخرى كثيره لا يكون هناك مفتاح من أساسه وبالتالى عمليه التحويل من أي قيمه الى موقع index سوف تتم باستخدام الداله المعروفه بالHash Function . نبدأ بأبسط حاله ممكنه وهي استخدام المفتاح كموقع index ، ولنفرض أنك تريد برمجه نظام لشركه صغيره تحتوي على 1000 عميل وكل سجل عميل يتطلب حوالى 1000 بايت ، بالتالى المجموع الكلي للبيانات هي 1 Megabyte وبكل تأكيد يمكن تحميل هذه القاعده الى الذاكره مباشره وقت تشغيل البرنامج ،، أسهل حل ممكن وأسرع حل أيضا هو عمل مصفوفه من السجلات تتكون من 1000 خانه ،، وفي حال أردنا أن نستعلم عن العميل محمد صاحب الرقم 300 فكل ما في الأمر : ```cpp Record mohmmed = dataBaseArray[300]; ``` أيضا في حال جاء عميل جديد للشركه فيمكن أن نضيف بياناته على النحو : ```cpp Record ahmed = new Record; dataBaseArray[count++] = ahmed; ``` [![الشكل يبين استخدام المفتاح كindex في المصفوفة](https://informatic-ar.com/wp-content/uploads/2013/09/hash1-300x150.png "الشكل يبين استخدام المفتاح كindex في المصفوفة")](https://informatic-ar.com/wp-content/uploads/2013/09/hash1.png) الشكل يبين استخدام المفتاح كindex في المصفوفة في مثل هذه الحاله أو أي حاله مشابه لها وهو وجود مفتاح معين لكل سجل فيمكن أن نستخدم هذا المفتاح كموقع في المصفوفه Keys Are Index Numbers وبالتالى الوصول لأي موقع سيكون أسرع ما يكون على الإطلاق Big-o (1) . لكن بالطبع في الكثير من الأحيان لن تجد لديك أي مفتاح وستجد قيم مختلفه (نص string مثلا) وهنا سوف تكون مهتمك هو إيجاد أي وسيله لكي تحول هذا النص الى موقع Index .نأخذ مثال صغير على قاموس يحتوي على 50000 كلمه ، ونريد أيجاد أسرع طريقه يمكن من خلالها ايجاد كلمه معينه . في البدايه سنقوم بعمل مصفوفه من 50000 خانه ،، الأن مثلا نريد أن نبحث أو ندخل الكلمه person ، السؤال هنا كيف يمكن أن نجد الموقع المناسب index لهذه الكلمه؟ الحل الأول والأسهل اطلاقا هو تحويل كل حرف الى رقم ومن ثم جمع هذه الأرقام (Add the Digits) ، ومن ثم تمثل لي مجموع هذه الأرقام الموقع في المصفوفه index (سوف نعطي الحرف a القيمه 1 وb القيمه 2 وهكذا ..اضافه الى حرف المسافه space سوف نعطيه مثلا القيمه 0 ، وبالتالى مجموعه الحروف هنا 27). مثلا الكلمه cats سوف نقوم بتحويلها بهذ الطريقه وينتج لدينا : ```cpp 3 + 1 + 20 + 19 = 43 ``` وهكذا الكلمه cats سوف تكون موجوده أو سوف نقوم بتخزينها في الموقع 43 .. وهكذا لبقيه الكلمات ،، نأخذ كلمه أخرى ولنفرض أن القاموس الذي نحن بصدده تتكون فيه الكلمات من حرف الى 10 حروف على الأكثر ، بالتالي تكون الكلمه الأولى في القاموس a مخزنه في الموقع 1 : ```cpp 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 = 1 ``` والكلمه الأخيره zzzzzzzzzz مخزنه في الموقع : ```cpp 26 + 26 + 26 + 26 + 26 + 26 + 26 + 26 + 26 + 26 = 260 ``` بدأت المشاكل بالظهور !! لدينا قاموس يتكون من 50000 كلمه ،، والكلمه الأولى تخزن في الموقع الأول ، والأخيره تخزن في الموقع 260 .. اذاً جميع الكلمات سوف تخزن في الخانات ما بين 0 الى 260 (أي أن لكل خانه في المصفوفه هناك 192 كلمه يجب أن تخزن وذلك بقسمه 260 على 50000) . هنا يمكن تطبيق هذه العمليه بجعل المصفوفه تكون مصفوفه من المؤشرات ، وكل من هذه المؤشرات يرتبط بمؤشر وهكذا ، أي مصفوفه من الLinked list وكل واحده من هذه الlist سوف نخزن فيها ال192 كلمه .. بالرغم من أننا حللنا المشكله ، ولكن عمليه البحث في Linked List مكلفه أيضا وخصوصا في حال كبرت البيانات O (m) .لذلك في الحل الأول Add the Digits بالرغم من صغر حجم المصفوفه الى أننا نريد أن نسرع عمليه البحث بأكبر شكل ممكن .. الحل الأخر وهو بأن نضرب كل حرف ب26 مرفوع له القوى i حيث هي موقع الحرف ، مثال للكلمه cats : ```cpp 3 * 27 ^ 3 + 1 * 27 ^ 2 + 20 * 27 ^ 1 + 19 * 27 ^ 0 = 60, 337 ``` على ما يبدوا أن هذا الحل سوف يولد لكل كلمه ممكنه موقع فريد ، ننظر الى الكلمه zzzzzzzzzz وفقط نحسب الz الأخيره وهي 26\*27^9 وهذا الرقم فقط أكثر من 7,000,000,000,000 !! للأسف مشكله هذه الطريقه أنها تولد خانات لأي كلمه ممكنه مثلا aaaaa,aaaaaa,aaaaaa وهكذا سينتج الكثير من الكلمات غير صحيحه من الأساس . [![الشكل بين طريقة توليد موقع فريد لكل كلمة ممكنة وحتى اذا لم تكن صحيحه](https://informatic-ar.com/wp-content/uploads/2013/09/hash2-300x157.png "الشكل بين طريقة توليد موقع فريد لكل كلمة ممكنة وحتى اذا لم تكن صحيحه")](https://informatic-ar.com/wp-content/uploads/2013/09/hash2.png) الشكل بين طريقة توليد موقع فريد لكل كلمة ممكنة وحتى اذا لم تكن صحيحه الحل الأول ولَد القليل من المواقع أقل من ما نحتاجه ، والحل الثاني ولد أكثر من ما نحتاجه بكثيير ،، ما العمل ؟ ## **Hashing** نحن بحاجه الى طريقه نستطيع من خلالها تحويل هذا الرقم الضخم الى رقم صغير مناسب يكون كindex في المصفوفه ،، السؤال هنا كم خانه نحن بحاجه اليها من الأساس ؟ بما أن القاموس يتكون من 50000 كلمه فنحن بحاجه الى عدد كافي من الخانات ،، لكن كما ذكرت من البدايه وسيتضح السبب فيما بعد يجب أن يكون الحجم الممتلئ من الhash table حوالى النصف أو 2/3 على الأكثر ، لذلك نقوم بحجز عدد كافي وليكن 100000 خانه .. الأن نريد أن نحول الرقم الضخم الكبير 7,000,000,000,000 الى رقم صغير في حدود ال100000 ، وهذه هي الفكره الأساسيه من وراء الhashing ، وسوف نستخدم المعامل modulo باقي القسمه % لضمان وقوع أي رقم مهما كان في مجال معين ،، ```cpp arrayIndex = hugeNumber % arraySize; ``` [![الشكل يبين عمليه تحويل المواقع Range Conversion](https://informatic-ar.com/wp-content/uploads/2013/09/hash3-204x300.png "الشكل يبين عمليه تحويل المواقع Range Conversion")](https://informatic-ar.com/wp-content/uploads/2013/09/hash3.png) الشكل يبين عمليه تحويل المواقع Range Conversion نقوم بتطبيق بسيط داله تقوم بحساب الهاش للstring المرر لها : ```cpp int hashString(const string &str) { int hashValue = 0; int power = 1; for (int i = str.length() - 1; i >= 0; --i) { int letter = str[i] - 96; // get char code , assume char in small letter hashValue += power * letter; power *= 27; // next power } return hashValue % size; } ``` داله الهاش يجب أن تكون سريعه لأننا سنجري هذه العمليه في كل مره نريد البحث أو الأدخال وبالتالى يجب أن نقلل من عمليات الضرب والقسمه ، ويمكن أن نستخدم عمليات الShifting بدلا من الضرب والقسمه لأنها الأسرع ، حاليا سوف نستخدم طريقه بسيطه لتحويل عمليه الضرب والرفع لأس الى عمليه ضرب وجمع ، تسمى طريقه هورنر نسبه الى عالم رياضيات ، وهي أن المعادله : ```cpp var4 *n ^ 4 + var3 *n ^ 3 + var2 *n ^ 2 + var1 *n ^ 1 + var0 *n ^ 0 ``` يمكن أن تكتب : ```cpp (((var4 * n + var3) * n + var2) * n + var1) * n + var0 ``` الداله السابقه بعد تطبيق طريقه هورنر : ```cpp int hashString2(const string &str) { int hashValue = 0; for (int i = 0; i < str.length(); i++) { int letter = str[i] - 96; hashValue = (hashValue * 27 + letter) % size; } return hashValue; } ``` على العموم فأي داله hash كما تبين الصوره السابقه ستقوم بوضع أكثر من رقم في نفس الموقع وهذه المشكله تسمى التصادم Collisions وهذا هو الثمن الذي ستدفعه من جراء عمليه الhashing .ويستحيل بالطبع عمل perfect hashing وذلك بجعل كل كلمه في القاموس في موقع فريد ، لكن الذي نأمله هو عدم وجود كلمات كثيره في كل موقع . الصوره التاليه توضح عمليه التصادم ، حيث يحتوي الhash table على الكلمه demystify ، ونريد اضافه الكلمه الجديده melioration وبعد عمل hash لها تبين أن لها نفس الindex للكلمه demystify . [![الشكل يبين التصادم Collisions](https://informatic-ar.com/wp-content/uploads/2013/09/hash4-245x300.png "الشكل يبين التصادم Collisions")](https://informatic-ar.com/wp-content/uploads/2013/09/hash4.png) الشكل يبين التصادم Collisions كيف سندخل هذه الكلمه الجديده للجدول ؟ بمعنى أصح كيف يمكن أن نحل التصادم هذا ؟ هناك طريقتين في الحل وسنتحدث عنهم في هذه المقاله ان شاء الله ،، الطريقه الأولى وتسمى open addressing تبدأ بالبحث من ذلك الموقع (المحجوز)عن أي خانه تاليه تكون فارغه وندخل فيها الكلمه الجديده ، لذلك ذكرنا قبل قليل أن حجم الhast table يجب أن يكون كبير أكبر من حجم البيانات الحقيقي ،، الطريقه الثانيه وتسمى separate chaining وهي بجعل خانات المصفوفه تؤشر الى linked list of words أي كل خانه في المصفوفه لديه قائمه مرتطبه به ، وفي حال وجد تصادم في الموقع 10 نقوم بادخال هذه الكلمه في أخر القائمه في الموقع 10 . الطريقه الأولى open addressing كما ذكرنا تعمل بالبحث من الموقع الحالى عن أي خانه فارغه لكي تضيف الكلمه الجديده ، وهناك 3 طرق أيضا لهذه العمليه وكل منها تحتلف في كيفيه ايجاد الخانه الفارغه التاليه ، فالطريقه الأولى Linear Probing تقوم بالبحث خانه خانه بدءا من الخانه التي حصل فيها التصادم ، أما الطريقه الثانيه quadratic probing تقوم بزياده خطوه البحث بشكل متصاعد يكون عباره عن مضاعفات كل خانه بالشكل :1 4 9 25 وهكذا ،، الطريقه الثالثه double hashing وهي الأفضل تقريبا ، حيث تقوم باعاده حساب الhash مره أخرى (بداله أخرى) لكي يتم ايجاد الموقع الجديد للكلمه ،، ستتضح الأمور مع الأمثله الأن . ## **Linear Probing** في حال حدث التصادم في الخانه i ، نقوم بالبحث من الخانه i+1 عن أي خانه فارغه لكي ندخل فيها هذه القيمه الجديده ،، هذه العمليه قد تسبب مشكله ما ، مثلا الكلمه الجديده حصل لها تصادم وأضفناها في الموقع i+1 ، وجائت كلمه جديده أخرى وحصل تصادم في نفس الموقع i سوف تضاف في الخانه i+2 .. لنفرض جائت كلمه جديده وكان لها hash جديد لكنه للأسف كان يساوي i+2 وبالتالى يحدث تصادم ، وهكذا سيكبر عدد التصادمات وتضاف القيم بشكل متسلسل ، هذه المشكله تعرف بClustering [![الشكل يبين عملية ال Clustering](https://informatic-ar.com/wp-content/uploads/2013/09/hash5-145x300.png "الشكل يبين عملية ال Clustering")](https://informatic-ar.com/wp-content/uploads/2013/09/hash5.png) الشكل يبين عملية ال Clustering وهكذا كل ما تمتلئ المصوفه كل ما زاد الClustering ، لذلك من البدايه ذكرنا يجب أن تكون حجمها كبير حتى نتلافي هذه المشاكل . نقوم الأن بتطبيق Linear Probe Hash Table ، وحاليا للتبسيط المصفوفه سوف تكون من Data وهو كلاس يحتوي على int : ```cpp // Linear Probe Hash Table #include #include using namespace std; class Data { private: int value; public: Data(int d) : value(d) { } void setData(int d) { value = d; } int getData() const { return value; } }; class LinearHashTable { private: vector hashTable; int size; public: LinearHashTable(int size); // void insert(int key); Data *remove(int key); Data *find(int key); // friend ostream &operator<<(ostream &ostr, const LinearHashTable &tbl); private: int hashFunction(int key); }; LinearHashTable ::LinearHashTable(int size) { hashTable.resize(size); this->size = size; // for (int i = 0; i < size; i++) hashTable[i] = NULL; } void LinearHashTable ::insert(int key) { Data *data = new Data(key); int hash = hashFunction(key); while (hashTable[hash] != NULL && hashTable[hash]->getData() != -1) { hash++; hash %= size; } hashTable[hash] = data; } Data *LinearHashTable ::remove(int key) { int hash = hashFunction(key); while (hashTable[hash] != NULL) { if (hashTable[hash]->getData() == key) { Data *tmp = hashTable[hash]; delete hashTable[hash]; hashTable[hash] = new Data(-1); return tmp; } hash++; hash %= size; } return NULL; } Data *LinearHashTable ::find(int key) { int hash = hashFunction(key); while (hashTable[hash] != NULL) { if (hashTable[hash]->getData() == key) return hashTable[hash]; else { hash++; hash %= size; } } return NULL; } int LinearHashTable ::hashFunction(int key) { return key % size; } ostream &operator<<(ostream &ostr, const LinearHashTable &tbl) { cout << "Content: "; for (int i = 0; i < tbl.size; i++) { if (tbl.hashTable[i] != NULL) cout << tbl.hashTable[i]->getData() << " "; else cout << "** "; } cout << endl; return (ostr); } int menu() { cout << "[1] insert value " << endl << "[2] find value " << endl << "[3] remove value " << endl << "[4] display table" << endl << "[5] exit " << endl << " >> "; int choice; cin >> choice; return choice; } int main(int argc, char *argv[]) { cout << "Enter Size of hash : "; int size, key; cin >> size; // LinearHashTable hashTable(size); // time_t aTime; srand(static_cast(time(&aTime))); for (int i = 0; i < size / 2; i++) hashTable.insert(rand() % (10 * size)); // bool state = true; while (state) { switch (menu()) { case 1: cout << "Enter key to insert: "; cin >> key; hashTable.insert(key); break; case 2: cout << "Enter key to find: "; cin >> key; if (hashTable.find(key) != NULL) cout << "Found key " << key << endl; else cout << "cannot found " << key << endl; break; case 3: cout << "Enter key to remove : "; cin >> key; if (hashTable.remove(key) != NULL) cout << "remove key " << key << endl; else cout << "cannot found " << key << endl; break; case 4: cout << hashTable << endl; break; case 5: state = false; break; } } return (0); } ``` للأسف الطريقه الأولى Linear Probing تقوم بعمل كلستر وخاصه في حال أمتلئت المصفوفه (عمليه الإمتلاء تعرف بload factor ويتم حسابها عن طريق loadFactor = nItems / arraySize ) ومن هنا أتت الطرق الأخرى لحل هذه المشكله ،، ## **Quadratic Probing** وهذه الطريقه تعمل بنفس مبدأ الطريقه السابقه ولكن الزياده هنا لن تكون بمقدار واحد ، بل تكون بمضاعفه الموقع الحالى x بالشكل : ```cpp x + 1, x + 4, x + 9, x + 16, x + 2 ``` اي الزياده في المحاوله الأولى هي 1 ، ومن ثم في حال وجد ذلك المكان ممتلئ فتكون الزياده 4 وذلك لتجنب الكلستر في حال كان هناك كلستر موجود..وهكذا كل مره يتم القفر بخطوه كبيره ،، بالرغم من أنها حلت مشكله الكلستر ، ولكنها أيضا صنعت مشكله كلستر أخرى secondary clustering وهي أن أي كلمه جديده لها نفس الهاش في الموقع i سوف تمر بجميع الخطوات 1و4و9 لكي تجد مكان فارغ ،، على العموم أداء الطريقه أفضل من الأولى ولكنها غير جيده أيضا لذلك لن نقوم بتطبيقها .. لنرى الطريقه الثالثه . ## **Double Hashing** مشكله الكلستر الأولى أن أي كلمه تصادمت سوف يتم القفز للخانه التاليه وهكذا ،، مشكله الكلستر الثانيه وهي أن كل تصادم يحدث سوف يتم القفز أيضا للخانه 1 ثم 4 ثم 9 وهكذا أي العمليه متسلسله وهكذا ينتج الكلستر ،، لحل هذه المشكلتين سنقوم بأخذ هاش أخر لنفس الكلمه بواسطه داله أخرى وبالتالى سينتج موقع جديد سوف يكون هو مقدار القفز step size لكل كلمه تصادمت وهذه العمليه تعرف باعاده الهاش Re-hashing ، هذه الداله الثانيه يجب أن لا ترجع قيمه 0 مهما كان (لأن القيمه ستكون قيمه قفز لموقع جديد) اضافه الى أنها بالطبع يجب أن تكون مختلفه عن الداله الهاشيه الأولى ،، وعاده يتم استخدام هذه الداله step: ```cpp stepSize = constant - (key % constant); ``` ويكون الconstant عدد أولى prime أقل من حجم المصفوفه ، اضافه الى أن حجم المصفوفه يجب أن يكون عدد أولى ، فمثلا في حال كان حجم المصفوفه 15 -غير أولى- وكان الثابت هو 5 ، فكل مره نستدعى داله الهاش الثانيه step سوف يكون الناتج أما 0 أو 5 أو 10 ولن تخرج بقيه العناصر الفارغه ، أما اذا العدد أولى مثلا 13 فسوف تخرج جميع العناصر . نقوم الأن بتطبيق Double Hashing وسوف تلاحظ أن الأختلاف مع المثال السابق هو سطرين فقط في كل داله واضافه الداله الجديده : ```cpp // Double Hash Table #include #include using namespace std; class Data { private: int value; public: Data(int d) : value(d) { } void setData(int d) { value = d; } int getData() const { return value; } }; class DoubleHashTable { private: vector hashTable; int size; public: DoubleHashTable(int size); // void insert(int key); Data *remove(int key); Data *find(int key); // friend ostream &operator<<(ostream &ostr, const DoubleHashTable &tbl); private: int hashFunction(int key); int hashFunction2(int key); }; DoubleHashTable ::DoubleHashTable(int size) { hashTable.resize(size); this->size = size; // for (int i = 0; i < size; i++) hashTable[i] = NULL; } void DoubleHashTable ::insert(int key) { Data *data = new Data(key); int hash = hashFunction(key); int step = hashFunction2(key); while (hashTable[hash] != NULL && hashTable[hash]->getData() != -1) { hash += step; hash %= size; } hashTable[hash] = data; } Data *DoubleHashTable ::remove(int key) { int hash = hashFunction(key); int step = hashFunction2(key); while (hashTable[hash] != NULL) { if (hashTable[hash]->getData() == key) { Data *tmp = hashTable[hash]; delete hashTable[hash]; hashTable[hash] = new Data(-1); return tmp; } hash += step; hash %= size; } return NULL; } Data *DoubleHashTable ::find(int key) { int hash = hashFunction(key); int step = hashFunction2(key); while (hashTable[hash] != NULL) { if (hashTable[hash]->getData() == key) return hashTable[hash]; else { hash += step; hash %= size; } } return NULL; } int DoubleHashTable ::hashFunction(int key) { return key % size; } int DoubleHashTable ::hashFunction2(int key) { return 5 - key % 5; } ostream &operator<<(ostream &ostr, const DoubleHashTable &tbl) { cout << "Content: "; for (int i = 0; i < tbl.size; i++) { if (tbl.hashTable[i] != NULL) cout << tbl.hashTable[i]->getData() << " "; else cout << "** "; } cout << endl; return (ostr); } int menu() { cout << "[1] insert value " << endl << "[2] find value " << endl << "[3] remove value " << endl << "[4] display table" << endl << "[5] exit " << endl << " >> "; int choice; cin >> choice; return choice; } int main(int argc, char *argv[]) { cout << "Enter Size of hash : "; int size, key; cin >> size; // DoubleHashTable hashTable(size); // time_t aTime; srand(static_cast(time(&aTime))); for (int i = 0; i < size / 2; i++) hashTable.insert(rand() % (2 * size)); // bool state = true; while (state) { switch (menu()) { case 1: cout << "Enter key to insert: "; cin >> key; hashTable.insert(key); break; case 2: cout << "Enter key to find: "; cin >> key; if (hashTable.find(key) != NULL) cout << "Found key " << key << endl; else cout << "cannot found " << key << endl; break; case 3: cout << "Enter key to remove : "; cin >> key; if (hashTable.remove(key) != NULL) cout << "remove key " << key << endl; else cout << "cannot found " << key << endl; break; case 4: cout << hashTable << endl; break; case 5: state = false; break; } } return (0); } ``` الى الان تعرضنا الى طرق open addressing لحل مشكله الCollisions ، وسنعرض الأن الحل الأخر وربما الأفضل لحل مشكله التصادم وهو Separate chaining. ## **Separate chaining** هنا سوف تكون لدينا مصفوفه ولكن كل خانه منها تؤشر الى قائمه مرتبطه linked list ، وبالتالى بعد عمل hash لأي كلمه ومعرفه الindex سنقوم بادخال هذه الكلمه في القائمه الموجوده في الindex ، وفي حال حدث التصادم سوف نضيف الكلمه أيضا في هذه القائمه ، وبالتالى سنكون أنهينا عمليه البحث عند حدوث التصادم ، [![الشكل يبين طريقة ال Separate chaining](https://informatic-ar.com/wp-content/uploads/2013/09/hash6-300x260.png "الشكل يبين طريقة ال Separate chaining")](https://informatic-ar.com/wp-content/uploads/2013/09/hash6.png) الشكل يبين طريقة ال Separate chaining الLoad Factors هنا لا يهم اطلاقا ، وليس كما كان عندما كنا نستخدم أحد طرق الـopen addressing حيث يجب أن تكون عدد الخانات الممتلئه فيها على الأكثر2/3 ، أما في الSeparate chaining فلا مشكله وحتى اذا كانت المصفوفه ممتلئه كليا لأن أي خانه تحتوي على العديد من القيم (في القائمه) ،، لذلك نستخدم هذه الطريقه خصوصا في حال كنا لا نعلم عدد العناصر التي يجب وضعها في المصفوفه قبل البدء في البرنامج لأن الأمتلاء هنا لا يمثل مشكله ،، تطبيق بسيط لل HashTable باستخدام Separate Chaining : ```cpp //separate chaining #include #include using namespace std; class Data { private: int value; Data *next; public: Data(int d) : value(d), next(NULL) { } // void setData(int d) { value = d; } int getData() const { return value; } // void setNext(Data *d) { next = d; } Data *getNext() const { return next; } }; class SortedLinkedList { private: Data *first; public: SortedLinkedList(); // void insert(int key); void remove(int key); Data *find(int key); // friend ostream &operator<<(ostream &ostr, const SortedLinkedList &list); }; SortedLinkedList ::SortedLinkedList() { first = NULL; } void SortedLinkedList ::insert(int key) { Data *data = new Data(key); // Data *prev = NULL; Data *current = first; while (current != NULL && key > current->getData()) { prev = current; current = current->getNext(); } if (prev == NULL) first = data; else prev->setNext(data); data->setNext(current); } void SortedLinkedList ::remove(int key) { Data *data = new Data(key); // Data *prev = NULL; Data *current = first; while (current != NULL && key != current->getData()) { prev = current; current = current->getNext(); } if (prev == NULL) first = first->getNext(); else prev->setNext(current->getNext()); } Data *SortedLinkedList ::find(int key) { Data *current = first; while (current != NULL && current->getData() <= key) { if (current->getData() == key) return current; current = current->getNext(); } return NULL; } ostream &operator<<(ostream &ostr, const SortedLinkedList &list) { ostr << "Content: "; Data *current = list.first; while (current != NULL) { ostr << current->getData() << " "; current = current->getNext(); } ostr << endl; return ostr; } class HashTable { private: vector hashTable; int size; public: HashTable(int size); // void insert(int key); void remove(int key); Data *find(int key); // friend ostream &operator<<(ostream &ostr, const HashTable &tbl); private: int hashFunction(int key); }; HashTable ::HashTable(int size) { hashTable.resize(size); this->size = size; // for (int i = 0; i < size; i++) hashTable[i] = new SortedLinkedList; } void HashTable ::insert(int key) { int hash = hashFunction(key); hashTable[hash]->insert(key); } void HashTable ::remove(int key) { int hash = hashFunction(key); hashTable[hash]->remove(key); } Data *HashTable ::find(int key) { int hash = hashFunction(key); return hashTable[hash]->find(key); } int HashTable ::hashFunction(int key) { return key % size; } ostream &operator<<(ostream &ostr, const HashTable &tbl) { for (int i = 0; i < tbl.size; i++) { if (tbl.hashTable[i] != NULL) cout << i << " : " << *tbl.hashTable[i] << " "; else cout << "** "; } cout << endl; return (ostr); } int menu() { cout << "[1] insert value " << endl << "[2] find value " << endl << "[3] remove value " << endl << "[4] display table" << endl << "[5] exit " << endl << " >> "; int choice; cin >> choice; return choice; } int main(int argc, char *argv[]) { cout << "Enter Size of hash : "; int size, key; cin >> size; // HashTable hashTable(size); // time_t aTime; srand(static_cast(time(&aTime))); for (int i = 0; i < size / 2; i++) hashTable.insert(rand() % (100 * size)); // bool state = true; while (state) { switch (menu()) { case 1: cout << "Enter key to insert: "; cin >> key; hashTable.insert(key); break; case 2: cout << "Enter key to find: "; cin >> key; if (hashTable.find(key) != NULL) cout << "Found key " << key << endl; else cout << "cannot found " << key << endl; break; case 3: cout << "Enter key to remove : "; cin >> key; hashTable.remove(key); break; case 4: cout << hashTable << endl; break; case 5: state = false; break; } } return (0); } ``` ## **خلاصة** - hash table من أسرع هياكل البيانات ويجب أستخدامها في حال أردت سرعه كبيره في عمليه البحث والإدخال ،، - تستخدم هذه البنيه داله هاشيه hash function تقوم بتحويل المدخلات الى قيم محصوره تكون عباره عن مواقع Index في الجدول table . - عند حصول التصادم يمكن حله باستخدام طريقه الopen addressing أو طريقه الSeparate Chaining ، في حال أستخدمت الطريقه الأولى فطريقه الDouble Hashing هي الأفضل ما عدا في حاله كان الجدول لا يتغير ولا توجد فيه عمليات ادخال بعد انشائه فيمكن استخدام Linear hashing ، الطريقه الثانيه Separate Chaining هي الأفضل بشكل عام من طرق open addressing حيث هي غير متعلقه بالLoad facotr ، كما أننا يمكن أن نجري أي عمليات على الجدول بعد انشائه بكل سهوله ،، [في هذا الرابط نفس البرامج الموجودة في المقالة ولكن بلغه الجافا](https://informatic-ar.com/files/Implement%5FHashTable%5Fin%5Fjava.rar) ## **تمرين** **عمل Spell Checker باستخدام الHash Table** لديك في هذا المجلد ([قم بتحميله من هنا](https://informatic-ar.com/files/exersice%5Fhashtable.rar)) الملف dict.txt يحتوي على 25000 كلمه ،، ولديك ملف أخر example.txt يحتوي على فقره بها بعض الكلمات الخاطئه ، المطلوب هو تطوير برنامج يقوم بطباعه الكلمات الخاطئه على الشاشه ،، ويمكنك أن تطلب من المستخدم اعاده ادخالها مره أخرى وبعد ذلك قم بحفظ التعديل في الملف example.txt .. ## **المصدر Reference** Teach yourself data structure in 21 days ### Function Object Adapter URL: https://informatic-ar.com/32/ Last updated: 2026-10-03T22:32:19.000Z في عالم البرمجة بشكل عام عندما يتم ذكر المصطلح Adapter فهذا يعني أن لدينا implementation ما ونريد التعديل قليلا على هذا الimplementation سواء بمنع مجموعه من الدوال أو باضافه دوال جديدة ، وهنا سنقدم للمستخدم كلاس جديد Wrapper Class بمجموعه من الدوال والمستخدم لن يعلم شيئا عن الUnderlying implementation ، وهنا تكمن الفائده في أمكانية تغيير هذه الlower data structure بدون تغيير اي سطر في كود المستخدم . أشهر استخدامات نمط الAdapter في سي++ هو في الSTL ، حيث نجد أن هناك الAdapter Container مثل الstack و الqueue والتي تستخدم كل منها (list or deque ) في الوضع الإفتراضي ، ولكن طبيعة مكتبة الSTL المرنة تمكنك من تغيير الunderlying Container بكل سهوله كما تريد ، وذلك بفضل الtemplate ، يمكنك مراجعه مقال الأخ خالد الشايع ( [نظرة حول مكتبات الـ Data Structre في ++C](http://www.arabteam2000-forum.com/index.php?showtopic=203811&ref=informatic-ar.com) ) وهو مقال رائع بلا أدنى شك. وكما ان هناك Adapter للكلاسات في اللغه ، فهناك Adapter للدوال ، ولكنها تختلف قليلا في الفكرة حيث تكمن مهمه الFunctor Adapter في ربط عدة دوال مع بعضهم ليتم تحقيق هدف معين . وقبل أن نتناول هذه الAdapter سوف نتناول بعضا من الدوال في الSTL ثم نشرح المشكلة التي تحلها هذه الAdapter حتى ندرك تماما متى نستخدمها وهل يمكن أن نتجاهلها أم لأ. أحد أهم الخوارزميات في الSTL هي خوارزميات البحث find و find\_if واللتان تبحثان عن المفتاح في الContainer وترجع مؤشر iterator لأول عنصر كان مشابه للمفتاح ،، المثال التالي يوضح ذلك ```cpp // find and find_if exmaple #include #include #include #include using namespace std; bool isPass(int degree) { return (degree >= 50); } int main(int arcg, char *argv[]) { vector v1; v1.push_back(10); v1.push_back(56); v1.push_back(23); v1.push_back(12); // print vector cout << "content: "; copy(v1.begin(), v1.end(), ostream_iterator(cout, " ")); cout << endl; // search for key using find int key = 23; vector::iterator itr = find(v1.begin(), v1.end(), key); if (itr != v1.end()) cout << "find key at position: " << (itr - v1.begin()) << endl; else cout << "cannot find key ." << endl; // search for first passing degree ( >= 50 ) itr = find_if(v1.begin(), v1.end(), isPass); if (itr != v1.end()) cout << "find passing degree at position: " << (itr - v1.begin()) << endl; else cout << "cannot find passing degree ." << endl; return (0); } ``` بعد النظر للمثال فقد يبدوا هذا السطر محيرا قليلا : ```cpp cout << "content: "; copy(v1.begin(), v1.end(), ostream_iterator(cout, " ")); cout << endl; ``` الجمله التي في الوسط هي عباره عن استدعاء للدالة copy في STL تقوم هذه الدالة بأخذ عنصر عنصر من الContainer وهو في هذه الحالة v1 الى الDestination وهو عباره عن iterator يأخذ العنصر ويقوم بطباعته ثم طباعه مسافه بعد ذلك ،، سوف نستخدم هذا السطر لطباعه اي Sequential Container ، ويمكن اذا لم يعجبك أن تطبع بالطريقة التي تريد ، كما يلي : ```cpp for (vector::iterator itr = v1.begin(); itr != v1.end(); ++itr) cout << *itr << " "; ``` أو : ```cpp // function defnition void myPrint(int x) { cout << x << " "; } // in main function for_each(v1.begin(), v1.end(), &myPrint); ``` لدالة for\_each أيضا هي من دوال الSTL تأخذ عنصر عنصر الى الCallback function والتي تقوم بطباعه العنصر ،، الى هذه اللحظه سوف نجد أن هناك الكثير من الدوال في الSTL تتعامل مع أي دوال Callback Function ويمكنك أن تصنع دوال هكذه عن طريق وضع مؤشر للدالة كما في for\_each أو أن تعيد تعريف المعامل ( ) في الكائن الذي تريده أن يعمل كCallback . نرجع للمثال السابق (الأول) ، بعد سطر الطباعه سنجد أننا بحثنا عن المفتاح أولا باستخدام find ، وهي ترجع مؤشر يساوي الخانه الأخيره من الcontainer (تذكر أن الخانه الأخيره هي one-past أي أنها لا تؤشر لشيئ ) .. بعد ذلك استخدمنا الدالة الثانية find\_if وسنجد أن هذه الدالة (وكل دوال STL التي تنتهي بif) سوف تتطلب Callback funcation يرجع قيمه منطقية boolean فاذا كان أحد العناصر مساويا لذلك ، أرجعت الدالة true وهنا سوف تتوقف الدالة find\_if وترجع العنصر الصحيح . (الدالتين find و find\_if ترجع قيمه واحده فقط ، اذا أردت يمكن أن تكتب دالة مشابه ترجع جميع العناصر وجعلها STL-Compitable بحيث تستخدمها مع أي container ) . الى هنا سنجد أن الCallback يعمل جيدا ، ولكن ،، هناك Functor جاهز في سي++ للقيام بالعديد من المهام ، فلماذا لا نستخدمها بدلا من اعاده العجلة من جديد ؟ الFunctor الموجود في سي++ هو للعمليات الحسابية جمع وطرح وقسمه ... ، ولعمليات المقارنه (أكبر أصغر يساوي ..) وأيضا للعمليات المنطقية and or not . المثال التالي يبين استخدام هذه الكائنات: ```cpp // Pre-defined Functor //Arithmetics Functor: //--------------------- //Binary operator : plus , minus , multiplies , divides , modules //Unary operator : negate //comparsion Functor: //Binary operator : less, equal_to , not_equal_to, greater, less_equal, greater_equal //Logical Functor : //logical_not, logical_and , logical_or #include #include using namespace std; #define print(x) cout << #x << " : " << x << endl; int main(int argc, char *argv[]) { plus p; int sum = p(2, 45); print(sum); multiplies m; int x = m(sum, 3); print(x); less le; bool rslt = le(900, 3); print(rslt); greater gr; rslt = gr(900, 3); print(rslt); return (0); } ``` ال#define في المثال اعلاه هي تقوم بطباعه اسم المتغير وقيمته وهي مفيده في البرامج الصغيره ،، تسمى Stringizing وليس لها دخل طبعا بSTL فهي موجودة من أيام لغه السي .. المثال تقريبا واضح ، وبالرغم من ذلك فإن استخدام هذه الكائنات بهذا الشكل أمر لا فائده مرجوه منه ، ولكن في بعض الأحيان قد تضطر الى استخدامها فمثلا في الpriority\_queue معامل المقارنة الإفتراضي هو less فاذا أردت تغيير ذلك فيمكنك أن تستخدم مثلا greater ، وهنا سوف تكون طريقة الإنشاء بهذا الشكل : ```cpp priority_queue, greater> myQueue ``` أعلاه اضطررنا أن نحدد الContainer المستخدم بسبب أنه في حال أردنا تغيير الdefault argument فيجب أن نغير كل ما قبله اذا كان هناك . بعد كل هذه المقدمة الطويلة نعود لمثالنا find\_if ، وهنا هذه المرة نريد استخدام الCallback الموجوده مثل greater أو less ، فاذا كتبت العباره : ```cpp vector::iterator itr = find_if(v1.begin(), v1.end(), greater()); ``` فسوف تحصل على رسالة خطأ مشفره خلاصتها أن الكائن greater يحتاج لمعاملين ، وأن الدالة find\_if ترسل عنصر واحد في كل مرة ،، اذا ما الحل ؟ هل هناك طريقة تمكننا من جعل الكائن graeter يستقبل عنصرين بدون التأثير على find\_if ؟؟ نعم وهذه هي وظيفة الAdapter أو الFunctional Compsition ، ومرحبا بك في عالم التعقيد .. لنبدأ بأول Adapter وهو bind2nd ، وسنستخدمه لحل المشكلة السابقة ، وبالتالي برنامجنا سوف يصبح : ```cpp // find_if exmaple #include #include #include #include using namespace std; int main(int arcg, char *argv[]) { vector v1; v1.push_back(10); v1.push_back(56); v1.push_back(23); v1.push_back(12); // print vector cout << "content: "; copy(v1.begin(), v1.end(), ostream_iterator(cout, " ")); cout << endl; // search for first passing degree ( >= 50 ) vector::iterator itr = find_if(v1.begin(), v1.end(), bind2nd(greater_equal(), 50)); if (itr != v1.end()) cout << "find passing degree at position: " << (itr - v1.begin()) << endl; else cout << "cannot find passing degree ." << endl; return (0); } ``` وظيفة الbind2nd هي اضافة الرقم 50 للمعامل الثاني لgreater\_equal وهكذا سيعمل المثال ويتم ارجاع القيمة الصحيحه ، هناك bind1nd وهي اضافة الرقم الفلاني للمعامل الأول (لاحظ 1 في اسم الدالة للإشاره الى المعامل الأول ) . الAdapter الأخر وهو not1 وهو لنفي النتيجه بالكامل ، فاذا أردنا في المثال السابق أن نرجع أول عنصر هو ليس بأكبر من أو يساوي 50 ، فيكون ذلك كما يلي (لن نضيف المثال بالكامل ، فقط سطر الfind\_if ) : ```cpp vector::iterator itr = find_if(v1.begin(), v1.end(), not1(bind2nd(greater_equal(), 50))); ``` هكذا سيعود بأول نتيجه أقل من 50 ، بالطبع يمكن استخدام الكائن less بدلا من ذلك : ```cpp vector::iterator itr = find_if(v1.begin(), v1.end(), bind2nd(less(), 50)); ``` مثلا ، اذا أردت جمع 100 لكل عنصر : ```cpp transform(v1.begin(), v1.end(), v1.begin(), bind2nd(plus(), 100)); ``` مثلا أردت حصر قيمه أي عنصر بين 0 الى 100 ، يمكنك أن تستخدم : ```cpp replace_if(myVector.begin(), myVector.end(), bind2nd(less(), 0), 0); replace_if(myVector.begin(), myVector.end(), bind2nd(greater(), 100), 100); ``` الى هنا كان الAdapter يتعامل مع الكائنات الجاهزة Functor مثل less أو greater ، لكن ماذا اذا أردنا أن نستخدم هذه الAdapter مع الدوال العاديه أو حتى الكائنات التي تعرف () ؟ هذا ما سنتناوله الأن .. **استخدام الAdapter مع الدوال العادية** : \----------------------------------------- أولا هذه الAdapter تتطلب بعض من الtypedef المعرفة في الكائن الذي تعمل عليه ، لذلك اذا أردنا أن نستخدم هذه الAdapter مع الدوال العاديه يجب أن نستخدم ptr\_func والذي مهمته تحويل مؤشر الدالة الى طريقة يمكن أن تستخدمه الدوال bind2nd و not1 ، لنشاهد المثال : مثلا لدينا string ونريد معرفه هل هذا النص هو عدد أم لأ : ```cpp // isNumber.cpp #include #include // for isdigit #include #include using namespace std; bool isNumber(const string &s) { string::const_iterator itr = find_if(s.begin(), s.end(), ::isdigit); if (itr == s.end()) return false; else return true; } int main(int argc, char *argv[]) { string s = "42566"; string s1 = "ahemd"; cout << isNumber(s) << endl; cout << isNumber(s1) << endl; return (0); } ``` المثال جيد ، ولكن لنفرض أننا نريد أول حرف ليس رقم ، بالتالي سنحتاج نفي العباره بnot1 ، وكما ذكرنا أن الدوال العادية لا يمكن استخدامها مع هذه الAdapter الا عند اضافتها ل ptr\_fun ، وهنا سوف يكون التغيير في الدالة isNumber كما يلي : ```cpp bool isNumber(const string &s) { string::const_iterator itr = find_if(s.begin(), s.end(), not1(ptr_fun(::isdigit))); if (itr == s.end()) return true; else return false; } ``` **استخدام الAdapter مع الكائنات التي تعرف () :** \--------------------------------------------------- أيضا اذا أردنا أن نستخدم الكائنات التي تعيد تعريف () مع هذه الAdapter ، فإنه يجب تعريف بضعه typedef أو الطريق الأسهل وهو الوراثه من أحدى الكائنين unary\_function أو binary\_function على حسب الدالة هل تأخذ وسيط أو اثنين ، كما يبين ذلك المثال : ```cpp // isNumber.cpp #include #include // for isdigit #include #include using namespace std; class MyIsDigit : public unary_function { public: bool operator()(char c) const { return (::isdigit(c)); } }; bool isNumber(const string &s) { string::const_iterator itr = find_if(s.begin(), s.end(), not1(MyIsDigit())); if (itr == s.end()) return true; else return false; } int main(int argc, char *argv[]) { string s = "42566"; string s1 = "ahemd"; cout << isNumber(s) << endl; cout << isNumber(s1) << endl; return (0); } ``` قبل أن تنتهي لنتعرف على Adapter أخر وظيفته جعل دالة داخل الكلاس تعمل كCallback ، وهنا في هذه الحالة سوف نستخدم mem\_fun\_ref اذا كان الcontainer من كائنات عادية ، أما اذا كان الContainer من مؤشرات فسوف نستخدم mem\_fun . مثلا لدينا vector of string ونريد حذف أي string فارغ من هذا الvector ، نشاهد المثال : ```cpp // remove empty string #include #include // vector #include // for find #include // ostream_iterator using namespace std; void removeStrings(vector &strings); int main(int argc, char *argv[]) { vector strings; strings.push_back("ahmed"); strings.push_back(""); strings.push_back(""); strings.push_back("ali"); cout << "Before Remove : " << endl; copy(strings.begin(), strings.end(), ostream_iterator(cout, "\n")); removeStrings(strings); cout << endl << "After Remove : " << endl; copy(strings.begin(), strings.end(), ostream_iterator(cout, "\n")); return (0); } void removeStrings(vector &strings) { vector::iterator itr = remove_if(strings.begin(), strings.end(), mem_fun_ref(&string::empty)); strings.erase(itr, strings.end()); // erase removing elements } ``` لاحظ تم استخدام mem\_fun\_ref وهي بمثابة جعل الدالة empty تعمل كcallback للدالة remove\_if . أخيرا وليس أخر ، فإن boost قدمت المكتبة bind للقيام بنفس مهمه الadapter في STL ، ولكنها أسهل وأيسر استخداما ، فكما لاحظنا أنه لجعل الدوال داخل الكلاس تعمل ك callback قد نستخدم mem\_fun\_ref أو mem\_fun (وذلك حسب محتوي الcontainer هل يتعامل مع الvalue أو الpointer) ولكن في boost.bind فهي تتعامل مع الحالتين من خلال داله واحده boost::bind . أرجوا أن يفيد الموضوع من قرر استخدام الadapter في برامجه . ### كيف تُستخدم ال UML بشكل صحيح ؟ URL: https://informatic-ar.com/using-uml/ Last updated: 2026-10-03T22:32:19.000Z كون أن لغه UML لها مقياس Standard وهناك جهه (ال ISO) تتولى الاهتمام بذلك، الا أن هذا لا يعارض وجود أكثر من استخدام لها أثناء فترة تطوير المشروع، وهذا الاختلاف بين طرق الاستخدام قد يدخلك في صراع أو نقاشات مطوله خصوصاً لو عملت مع شخص يستخدمها بطريقة ما ولا يدري الطرق الاخرى التي يمكن ان تكون لل UML فائده منها. سأتحدث عن طريقتين الآن الأولى وهي توضيح كل العلاقات والكلاسات وبما داخلها بشكل مفصل جداً (سوف نسمية Blueprint) ، أما الثانية وهو توضيح جزء معين من تصميم المشروع Designing فقط بدون حتى الدخول في تفاصيله الدقيقة (سوف نسميه Sketch ) هذه الطرق يمكن ان تستخدمها قبل كتابه المشروع Forward Engineering، أو بعد كتابه المشروع Reverse Engineering، ولكل منها سبب، لنتحدث عن طريقة عمل التفاصيل لكل شيء وهي المفضلة لدى المناهج الأكاديمية Blueprint فمثلاً يقوم الطالب أو المبرمج بعمل المشروع بالكامل ومن ثم في الأخير يقوم بتوليد أو رسم ال UML لهذا المشروع (هنا فائده ال UML أصبحت قليله والسبب أن المشروع انتهي والتوثيق ورسومات ال UML قد تكون اعقد من الكود نفسه) ، لذلك في غالب مشاريع تخرج الطلاب نجد أنهم يتبعوا Reverse Engineering Blueprint. الطريقة الأخرى في ال Blueprint وهي أن تقوم بعمل ال UML قبل المشروع، وهي لها فوائد مثلاً كنت مبرمج محترف ولديك مبرمج مبتدئ تريده أن يعمل فيمكن أن تقوم بعمل Forward Engineering وعمل ما يلزم لهذا المبرمج حتى يستطيع العمل والبرمجة بمفرده. في الغالب القيام بعمل التفاصيل Blueprint يستلزم أدوات لتوليدها سواء كانت ضمن بيئتك البرمجية IDE أو أداة خارجية Third-Party تقوم بها (سواء قبل Forward أو بعد المشروع Reverse). الكتب الأكاديمية تسمى هذه الأدوات CASE Tools وقد سمى هذا المصطلح Martin Fowler (مهندس البرمجيات المعروف) بال Dirty Word وأن كثير من الشركات والمصنعين Vendors أصبحوا لا يستخدمونه ، واتفق معه في ذلك .. الطريقة الثانية للاستخدام UML وهو ما افضله واستخدمه وهو رسم ما يلزم لايصال الفكرة بدون الحاجه للدخول في التفاصيل Sketch. مثلاً كنت تريد ان تحل مشكلة عمل فهرسه للملفات فسوف تقوم بوضع ال High Level Design مع الفريق أو مع نفسك ومن ثم ترى هل سيحل هذا التصميم المشكلة ؟ فاذا كان كذلك والا فتقوموا بالتعديل عليه الا حين الوصول للشيء المناسب (طريقة الاستخدام هذه Forward Sketch تصلح ايضاً في اجتماعات المبرمجين في الفريق )، أو مثلاً لنقل أنك انهيت حل مشكلة ما وتريد توثيق الDesign أو أن هناك مبرمج جديد انضم وتريد شرح الفكرة العامه من هذا الاجزاء من الكود فال Reverse Sketch هنا أيضاً مفيد في تلك الحالة. [![uml](https://informatic-ar.com/wp-content/uploads/2013/09/uml-264x300.png)](https://informatic-ar.com/wp-content/uploads/2013/09/uml.png) عندما تقوم بال Sketcher فقد لا تلتزم بأي Standard للرسم ، بمجرد أن تصل فكرتك المطلوبة للفريق وهذا هو الهدف Communication Ideas فهذا يكفى ولو لم تكن مطابق لل UML Standards (الصورة المرفقة توضح Sketch قديم قمت بعمله وهو يوضح فكرة معينة بغض النظر عن الالتزام الحرفي بالمعيار وطريقة الرسم). فأسأل نفسك هل انت Sketcher أم Blueprinter ، ولا تتعصب لطريقة واستخدم كل منها في الوقت المناسب. ### ماذا تعرف عن ال Internationalization & Localization ؟ URL: https://informatic-ar.com/internationalization-localization/ Last updated: 2026-10-03T22:32:20.000Z [![localization](https://informatic-ar.com/wp-content/uploads/2013/09/localization.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/localization.jpg) ال Internationalization يقصد بها جعل البرنامج يتعامل مع مستخدمين بلغاتهم (أي يدعم البرنامج أكثر من لغه مثلاً العربية والانجليزية والفرنسية) ال Localization ويقصد به جعل البرنامج يتعامل مع المنطقة بطريقة كتابتها للأرقام والعملات والتواريخ والأزمان (مثلاً الInternationalization يكون اللغه العربية ولكن ال Localization يكون Saudi Arabia وسنجد أن العملة أصبحت ريال SR وهكذا). لذلك نجد دائماً المصطلحين مع بضعهما البعض : Internationalization & Localization، وفي الغالب يتم اختصارهم ب i18n و L10n (وهي الحرفين الأولين من كل كلمة مفصوله بالمسافة بين الحرف الأول والثاني، مثلاً الكلمة Internationalization المسافة بين الحرف i والحرف n هي 18 لذلك تختصر الكلمة ب i18n وهكذا دواليك بالنسبة ل L10n ### ماذا أستخدم: AWT أم Swing ؟ URL: https://informatic-ar.com/java-awt-or-swing/ Last updated: 2026-10-03T22:32:21.000Z لطالما تسائل كثير من مبرمجي الجافا عن سبب وجود مكتبتين خاصه برسم المكونات GUI Components ، ومن هي الأفضل في التطبيقات؟ الجواب وكغيره في بعض الأسئلة هو عن طريق الرجوع لأصل وسبب نشأه تلك المكتبات،، فمنذ صدور نسخه جافا الأولى Java 1.0 وهي تحتوي على مكتبة Abstract Window Toolkit (الAWT) وهي كانت الأساس في تطوير الواجهات الرسومية GUI، وتقوم بعمليات أخرى رسم نافذه البرنامجالرئيسية Window ، استقبال أحداث الماوس والكيبورد Event Handling، وتقوم بذلك عن طريق الاعتماد على نظام التشغيل الذي يعمل عليه البرنامج Windowing System ، لذلك ال AWT هي Platform Specific وبرنامجك سوف يأخذ شكل النظام الذي يعمل عليه، مثلاً برنامجك عمل في Solaris سوف يأخذ شكل بقية البرامج Look & Feel، وبالفعل كانت البدايه كما هو الشعار Write Once, Run Any Where. لكن بعد ذلك، حدثت بعض المشاكل في ال AWT، حيث هناك منصات لا تحتوي دعم لكل ال Components كما في غيرها من المنصات (مثلاً X11/Motif) وسوف تجد أن برنامجك يتصرف تصرفاً غريباً احياناً ووجد بعض من المشاكل البرمجية ايضاً بها Bugs. حتى أن برامج ال AWT لا تأخذ شكل النظام كما بقية البرامج ال Native الأخرى. لذلك اشتهر الشعار Write Once, Debug Everywhere. شركة Netscape كانت لديها الكلمة، حيث طورت مكتبة جديدة لل GUI سمتها ذلك الوقت Internet Foundation Classes (الIFC) والتي أعتمدت على منهجية مختلفة في التعامل مع المكونات، حيث بدلاً من الاعتماد على نظام التشغيل فيها قامت برسم كل تلك المكونات سواء كانت Buttons, Menu الخ ، واعتمدت على نظام التشغيل في رسم النافذه الأولى Top Window والتعامل مع الأحداث. وبالتالي اصبحت المكتبة تعمل على جميع المنصات بنفس الشكل. شركة Sun عجبها الأمر وعملت مع نت سكيب لأكمالها حتى خرجت لنا المكتبة Swing وتم اضافتها كاضافه للغه الجافا Extension في النسخه 1.1 وادخلت ضمن اللغه في الاصدار Java 1.2 (لذلك عندما ترى الباكج javax.swing فال x هي ترمز للاضافات Extensions في اللغه ومازالت تستخدم حتى هذا اليوم). المكتبة Swing ادخلت مع مجموعه من المكتبات تحت مسمى Java Foundation Classes (وهي تضم Swing و مكتبات ال 2D، وال Drag and Drop API وغيرها). [![swing](https://informatic-ar.com/wp-content/uploads/2013/09/swing-300x176.png)](https://informatic-ar.com/wp-content/uploads/2013/09/swing.png) أحتوت Swing على مكونات أكثر، وبسبب أنها ترسم على النافذه Painting باستخدام Java 2D API فانها تعمل على جميع المنصات بنفس الشكل Look and Feel، وان لم ترغب بذلك ورغب بشكل يشبه ال Native Feel للنظام الذي تعمل فيه فهذا أمر سهل، حيث وفرت Sun دوال لتغيير ال Look and Feel وجعل مثلاً برنامجك الذي يعمل في ويندوز يأخذ ال Windows Look and Feel أو مثلاً GTK look and feel. ليس هذا فقط بل هناك بعضاً من الأشكال لا تتعلق بأي نظام Platform Independent مثل الشكل الافتراضي لبرامج الجافا Metal (يطلق عليه احياناً Java L&F). خلاصه المكتبة Swing بنيت فوق ال AWT ، وحالياً Swing تستخدم للمكونات نظراً لوفرتها وعدم اعتمادها على النظام Lightweight Library، لكن ال AWT مازالت هي التي تقوم بالأمور المتعلقة بالنظام Windowing Toolkit ورسم النافذه الاولى واستقبال الأحداث وهي أمور بلا شك ضرورية في اي تطبيق. اذاً اجابه على السؤال السابق، ماذا أستخدم: AWT أم Swing ؟؟؟ فالجواب: الاثنين معاً. ### ماذا تعرف عن ال Performance Tuning ؟ URL: https://informatic-ar.com/performance-tuning/ Last updated: 2026-10-03T22:32:21.000Z في الحقيقة نحن لا نسمح هذه الكلمة "الأداء Performance" الا عندما تكون هناك مشاكل، ويخبرك المستخدم بأن التطبيق بطئ وهناك مشكلة في أدائه Performance Issue. ومن المعلوم ان حل مشاكل الأداء من المهام الصعبة في البرمجة، خصوصاً في الأنظمة الكبيرة التي تخدم الكثير من المستخدمين Large Scale Systems أو حتى الأنظمة الحساسه Missions Critical Systems، حيث تمكن الصعوبة في أن المشكلة قد تكون في عدة أجزاء مختلفة، مثلاً في كيفية كتابتك للخوارزميات Algorithms Structure أو في طريقة تعاملك مع الذاكرة Memory Allocations أو في التعامل مع القرص والملفات Disk I/O أو حتى في التعامل مع الشبكة Network I/O. فتحديد مكمن المشكلة أمر ليس في السهوله وحتى الخبراء قد يخطئوا ويشخصوا المشكلة بطريقة خطأ. لذلك فالPerformance Tuning هو علم تجريبي أو حتى فن Art أكثر من مكونه علم نظري بحت Experimental Science ولكي تقوم به بشكل صحيح يجب عمل التجربة ومشاهده النتائج حتى تصل للمشكلة أو ال Root Cause وبعد ذلك تبني النتيجة وتقوم بحل المشكلة. فكلما تعمل في هذا المجال أكثر كلما تكتسب خبرة أفضل (حتى أن الشركات الكبيرة لديها وظائف كثيرة مختصه بهذا المجال Performance Engineer). قبل محاولة حل المشكلة، هناك نقطة مهمه خصوصاً في البرامج أو المواقع التي يحصل بها Load عالي، وهو أنه يجب أن تتحدث مع صاحب المشروع وتعرف ما هي المتطلبات المتعلقة بالPerformance وحتى اذا كانت هناك خطط لتوسعه المشروع Scalability فيجب عليك معرفتها. حيث سوف يصعب عليك بعد أن تنتهي من المشروع ان تلتفت لتلك المتلطبات وقد يجعلك في موقف حرج للغايه (حدثت معي في أحد المشاريع حيث كنا نتوقع ان يعمل المشروع على dataset بأعلى تقدير 1GB ولكن بعد فترة تغيرت المتطلبات وأصبح الدعم يجب أن على الأقل 1TB). لذلك ال Performance وال Scalability يجب أن تحدد في وثيقة متطلبات المشروع بشكل جيد، حتى تستطيع اختبارهم بعد كل Iteration في التطوير وتتأكد هل انت مراعي ال Goals المطلوبة. اذا لم تستطيع ذلك، فيمكنك السؤال عن هذه الأسئلة: 1) ما هو ال Throughput المتوقع (عدد الأمور التي تنجز، مثلاً عدد الTransaction التي تتم في الموقع ، مثلاً 200 كل ثانية). 2) ما هو التأخير Latency المتوقع بين أي حدث Stimulus والاستجابه لهذه الحدث؟ 3) كم عدد المستخدمين يستطيعوا العمل في آن واحد في هذا التطبيق Concurrent Users أو حتى المهام العادية Concurrent Tasks ؟ 4) ما هو الThroughput وال Latency المتوقع عندما تكون الخدمه تعمل بأقصى امكانياتها Maximum Users or Tasks ؟ 5) ما هو أقل وأسوء تأخير مقبول Maximum worst case latency؟ مثل هذه الأسئلة تسهل لك عمل التجارب Benchmark وعمل اختبارات Performance Test وتتأكد انك في الطريق الصحيح اثناء التطوير. بشكل عام هناك طريقتين للنظر في مشاكل الأداء، نظرة تبدأ من الأعلى من طبقة التطبيق Top Down ، ونظرة تبدأ من أسفل حيث العتاد Bottom Down، والغالب أن طريقة التحليل Top Down هي الأكثر انتشاراً بين المطورين بينما ال Bottom Down فهي مختصه لفئة معينة من مختصى قياس الأداء حيث ينحصر عملهم على تشغيل المشروع على منصات مختلفة Different Platforms بعماريات مختلفة Different Architecture بأنظمة تشغيل مختلفة Different Operating System (على سبيل المثال أنظمة المترجمات Compilers وال VM وغيرها). حيث الهدف منها تشغيل التطبيق بنفس الاداء على تلك الانظمة فسوف تحتاج لعمل قياسات تبدأ من العتاد مثلاً عدد مرات عدم وجود المعلومات في الكيش CPU Cache Miss (فال Cache Miss يعني ان المعالج سوف يضيع بعضاً من ال Cycles في انتظار البيانات التي سيتم جلبها من الذاكرة فكلما تقل كلما يقل ال waiting time للمعالج) وغيرها من المعاملات التي يتم فحصها. في طريقة ال Top Down سوف تبدأ من التطبيق أو الكود لديك، وفي الغالب يكون العميل أخبرك بأن هناك مشكلة في الأداء Performance Issue عندما يعمل النظام تحت الضغط Under Load، أو مثلاً تقوم بتغيير اعدادت في النظام أو البيئة التي يعمل عليها برنامجك فتؤثر سلباً على الأداء Performance Degradation أو (كما حصل معي) هو أن يغيير العميل أو مديرك في العمل متطلباته في ال Performance & Scalability وعليك أن تفى بتلك المتطلبات. لكي تبدأ العمل، فهناك 3 خطوات عليك القيام بها بشكل متسلسل: 1) مراقبة التطبيق وجمع المعلومات Performance Monitoring: هذه أول خطوه تقوم بها بعد ابلاغك عن المشكلة بدون اي تفاصيل عنها أو أي مؤشر عن المسبب لها، وسوف تقوم بجمع كل المعلومات حول هذا التطبيق (مثل نسبة استهلاكه للمعالج، والذاكرة، وكرت الشبكة، والقرص) وهذه الخطوات في العادة تتم والبرنامج يعمل أي في Production Environments أو اذا استطعت تشغيله في بيئتك البرمجية Development Environments. 2) تشغيل التطبيق في بيئة للفحص Performance Profiling: وهنا ستقوم بجمع بعض المعلومات ايضاً عن التطبيق ولكنها اكثر شموليه من ال Monitoring وايضاً سوف يستوجب عليك القيام بها في بيئتك البرمجية فقط لأن تشغيل ال Profiler سوف يؤثر على التطبيق في أدائه واستجابته Effect Responsiveness and Throughput. 3) القيام بالتعديلات لتصحيح المشكلة Performance Tuning: سواء كان ذلك في تغيير الكود أو اعدادات النظام حتى تقوم بتصحيح الخلل سواء كان بطئ في الاستجابه Responsiveness أو بطئ في تنفيذ العمل Throughput. مثال بسيط لمراقبة اداء التطبيق Performance Monitoring وهو مراقبة استهلاك المعالج CPU Utilizations، فحتى تحصل على اداء عالي يجب الاستفاده من كل CPU Cycle بشكل صحيح سواء كانت في التطبيقات العادية أو في التطبيقات متعدده المهام Multi-threading Applications والتي تعمل على جهاز في أكثر من معالج Multi-core او Multi-processes فهذا هو المنتشر حالياً. [![اداء البرنامج في نظام ويندوز](https://informatic-ar.com/wp-content/uploads/2013/09/Performance-278x300.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Performance.jpg) اداء، برامج، ويندوز لمعرفة استهلاك المعالج، في ويندوز أسهل طريقة هو ال Task Manager حيث ستوضح النسبة المستهلكه من كل المعالجات في اليمين، وفي اليسار ترى معدل الاستهلاك في كل كور فيه. الصورة الموجودة بها لونين ، اللون الأخضر يبين مدى استهلاك المعالج في اكواد التطبيقات User Utilization في العمليات مع مدى استهلاك تنفيذ أكواد النظام System Utilization (أو تسمى Kernel Utilization) ، أما اللون الأحمر يبين استهلاك ال Kernel ، والمسافه التي بينهم تبين مدى استهلاك ال User في المعالج. (لأظهار ذلك لديك من قائمة View أختر Show Kernel Time). لكي تحصل على اداء عالي، فعليك بتقليل ال Kernel Utilization وجعله صفر% حتى يتم الاستفاده من وقت المعالج في تنفيد أكوادك في التطبيق. مثلاً اذا كنت تبرمج بجافا وكنت تستخدم Stream للقرائه والكتابه فعليك استخدام Buffered\*Stream حيث أن ال Stream العادي يقوم بعمل استدعاء لدوال النظام بشمل مستمر اثناء العملية وهكذا يزيد ال Kernel Utilization بينما في الBuffered سوف يحفظ في مخزن بحجم معين ويتم كتابتها مرة واحدة ، هكذا قللنا ال Kernel Utilization بسهوله. ويمكن ايضاً الاستفاده من Java NIO في بعض العمليات لأن ادائها عالي. في ويندوز هناك ادوات اخرى مثلاً Performance Monitoring أو اداة الكونسول typepref والتي تقيس كثير من الأمور وليس ال CPU Utilization فقط. أيضاً انظمة Linux و يونكس لها ادوات مشابه للقياس. نصل لنهايه البوست، هناك بعضاً من الأمور الأخرى المهمه مثلاً Memory Utilization وال Network Utilization والتي تحتاج للنظر فيها عندما تراقب تطبيقك، والمرة القادمة ان شاء الله سنتناول الحديث عن Performance في الجافا وبالأخص حول الصندق السحري JVM ، والذي وصل لدرجة عالية من المرونة تمكنك تطبيق اي Performance Requirement تريدها. لهذا السبب ظهرت العديد من اللغات تعمل فوق منصه الجافا JVM تسمى Polyglot Languages مثل جايثون , Groovy والرائعه Scala وغيرهم، وهذا السبب هو الذي جعل تويتر تنقل اكوادها من روبي الى جافا في 2011 [http://www.infoq.com/articles/twitter-java-use](http://www.infoq.com/articles/twitter-java-use?ref=informatic-ar.com) الى لقاء قريب ### مقدمة في تحليل جرائم الانترنت Internet Forensics URL: https://informatic-ar.com/internet-forensics/ Last updated: 2026-10-03T22:32:22.000Z **ماذا سوف تتعلم بعد قراءتك هذه المقالة:** - أهمية تخصص الأدلة الجنائية وتوضيح المهارات التي يجب أن يمتلكها محقق الجرائم الالكترونية - كيف يعمل متصفح Firefox وكيف يمكن استرجاع المعلومات التي تفيد أثناء التحقيقات - كيف يمكن استرجاع المحادثات من برنامج المحادثة الياهو Yahoo Messenger - كيف تقوم بعمل برامج مشابه لتلك البرامج مع انتشار استخدام الحواسيب وشبكة الانترنت بين المستخدمين ، تزايدت طردياً عمليات تبادل المواد الممنوعة وتزايد عمليات الاحتيال والسرقة والابتزاز على الشبكة بين المستخدمين ، وكان لا بد من أن تبدأ المنشآت الأمنية والحكومية بالاهتمام بتخصص الأدلة الجنائية Computer Forensics حتى يمكن لتلك الجهات التحقق من الجرائم الحاصلة على الشبكة ومن مرتكبيها حتى يثبتوا إدانتهم فعلاً لكي تتم محاكمتهم مثلهم مثل المجرمين على أرض الواقع. تخصص الأدلة الجنائية المتعلقة بالحاسب تخصص واسع وكبير ويشمل الكثير من المهارات سواء كانت في المنهجيات والأساليب المتبعة للتحقيق في جريمة أو المعرفة في تفاصيل الأنظمة وكيف تعمل من الداخل حتى يمكن للمحقق اكتشاف واسترجاع المعلومات التي يمكن أن تفيده في عملية التحقيق. وقد لا تخلوا الجرائم الحاسوبية من الاتصال بالويب والاستفادة من خدماتها مثلاً الانترنت أو البريد الالكتروني وتبادل الملفات وخدمة المحادثات بين الأشخاص والمحادثات الهاتفية عبر الانترنت. كل من هذه الخدمات التي تقدمها لنا الويب قد تستخدم بشكل غير لائق أو بأشكال ممنوعة شرعياً، مثلاً عملية تصوير الفتيات من خلال المحادثات عبر برامج المحادثة والتي تسمح بتبادل الصور وملفات الفيديو ثم البدء بعمليات الابتزاز، أو حتى تحميل مواد ممنوعة من مواقع غير مسموح بها والبدء بالمتاجرة بها، وطرق الاستخدام غير الشرعية قد لا تعد ولا تحصى. من هنا بدأت كثير من الشركات المتخصصة في أمن المعلومات بتقديم حلول برمجية للمحققين حتى تسهل عليهم في عملهم وتيسر لهم الحصول على المعلومات وتجهيزها في أشكال قابلة للفهم من قبل المحقق حتى يقوم بفهمها واستخراج الأدلة التي تثبت فعل الجريمة. ولكن المحققين المحترفين عليهم أن يكونوا على دراية بالتفاصيل في كل ما يحققون فيه حتى يضمنوا أنهم يقوموا بعملهم في أكمل وجه، فالمحقق الذي يعمل على أنظمة الويندوز عليه بتعلم كيف يعمل ذلك النظام، وما هو نظام الملفات في ذلك النظام وكيف يعمل وأين تذهب الملفات المحذوفة فيه، والمحقق المتخصص في التعامل مع الجرائم المتعلقة بالانترنت والتي استخدمت فيها متصفحات الانترنت أو برامج المحادثة عليه معرفة كيفية استرجاع هذه المعلومات بشكل كامل من هذه البرامج وحيث أن هذه البرامج تختلف فيما بينها من الناحية الداخلية وطريقة تخزينها للمعلومات وطريقة تشفير هذه المعلومات ، فإن على المحقق دراسة كل من هذه البرامج على حده حتى يضمن أن سيقوم باسترجاع جميع المعلومات المتعلقة بالقضية. هذه المقالة موجه للمحققين المتخصصين في التعامل مع الجرائم المختصة بالانترنت ومتخصصين أمن المعلومات بشكل أمن والباحثين والمبرمجين في مجال الأدلة الجنائية، ولأن مجال الحديث واسع والبرامج التي تستخدم الانترنت كثيرة جداً فسيتم الحديث عن أحد البرامج التي تتعامل مع الاتصال بصفحات الانترنت (متصفح Firefox) وسيتم الحديث عن احد برامج المحادثة (برنامج Yahoo Messenger). الغرض من هذه المقالة هو توضيح كيفية عمل هذه البرامج وخصوصاً عند تخزينها للبيانات حتى تسهل للمحقق استرجاع هذه البيانات عندما يبدأ في التحقيق. وتهدف أيضاً هذه المقالة إلى تدريب المحققين على استخدام الأساليب اليدوية في التحليل بعد فهم آليات العمل والتي قد يتطلب إليها في بعض الأحيان. عند التحقيق في جريمة تتعلق بالاشتباه في علاقة شخص بجهة معينة وذلك من خلال البحث عن عناوين هذه الجهة في العناوين التي قام المشتبه به في دخولها، وبالرغم من أنه يمكن من خلال برامج المتصفحات نفسها أن تسترجع عناوين المواقع التي دخلها المشتبه به إلا أن هذه المعلومات لا تكفي في التحقيق، ويمكن للمحقق الحصول على المزيد من المعلومات كتاريخ زيارة الموقع المعين وعدد المرات التي زار هذه الموقع وهل يحفظ الموقع في قائمة المفضلة لديه وما هي البرامج والأدوات التي قام المشتبه به بتحميلها موثقة بتاريخ وأوقات كل من هذه العمليات، وبالرغم من أهمية هذه المعلومات إلا أن برامج المتصفحات لا تعطيها أي أهمية وتقوم بتخزين هذه المعلومات بشكل متباعد سواء في ملفات مختلفة أو في جداول مختلفة داخل قاعدة بيانات، مما يستوجب للمحقق أن يدرس طرق التخزين هذه حتى يقوم باسترجاع ما يريد من المعلومات. أما الجرائم التي يراد أن يطلع فيها على المحادثات بين شخص مشتبه به وشخص مدان مسبقاً فإن من أهم المعلومات التي يريدها المحقق هي المحادثات نفسها بين الطرفين وأوقات جميع هذه المحادثات حتى يستطيع أن يربط بين الإحداث. المشكلة أن برامج المحادثة في الغالب تحفظ هذه المعلومات في ملفات غير معروفة البنية ، أو قد يستوجب استخدام برنامج المحادثة نفسه لكي يتم قرائه تلك الملفات (مثل برنامج المحادثة من Yahoo والذي ستتم دراسته فيما بعد) لأنها تحفظ الملفات بصيغة غير معروفة إضافة إلى استخدامها لطرق من الترميز لضمان عدم فهم هذه الرسائل. لذلك على المحقق أن يستطيع فهم هذه الملفات ومعرفة بنيتها حتى يستطيع استخراج المعلومات منها بكل يسر وسهولة. **استخراج المعلومات وتحليلها من المتصفح Firefox :** بدئاً من الإصدارة الثالثة من متصفح فايرفوكس الشهير فقد تم تغيير طريقة تخزين المعلومات التي يخزنها المتصفح على الجهاز حيث كانت تحتفظ في ملف بصيغة mork وأصبح المتصفح يستخدم قاعدة بيانات SQLite في تخزين هذه المعلومات. وتختلف مواقع هذه الملفات باختلاف نظام التشغيل المستخدم واصدارة النظام حيث لكل نظام مواقع معروفة تستخدمها البرامج في التخزين. الأسطر التالية تبين مواقع تخزين قاعدة البيانات هذه في أنظمة التشغيل ويندوز: Windows XP C:Documents and SettingsApplication DataMozillaFirefoxProfilesplaces.sqlite Windows Vista C:UsersAppDataRoamingMozillaFirefoxProfilesplaces.sqlite قواعد البيانات هذه تكون بشكل ثنائي binary وتحتاج لكي تعرف تركيبتها أن تدرس بنية هذه الملفات باستخدام أي Hex Editor أو استخدام الطريقة الأسهل وذلك باستخدام أوامر قاعدة بيانات SQLite والتي يمكن من خلالها معرفة بنية القاعدة والجداول التي بداخلها. ويمكن استخدام سطر الأوامر الذي يأتي مع القاعدة SQLite لغرض توضيح تركيبة القاعدة schema أو استخدام أي من البرامج التي تستخدم واجهة رسومية لها وذلك لتسهيل دراسة القاعدة وإيجاد المعلومات التي يمكن استخراجها منها. الشكل رقم 1 يوضح محتوى ملف قاعدة البيانات places.sqlite داخل محرر بالنظام السادس عشر. [![post-51148-025949000 1291234822](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-025949000-1291234822.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-025949000-1291234822.png) الشكل (1) يبين تركيبة قاعدة بيانات متصفح فايرفوكس المعلومات التي توجد في هذه القاعدة كثيرة ومن أهمها الزيارات التي قام بها المستخدم للمواقع، وعدد الزيارات لكل موقع ووقت وتاريخ هذه الزيارات، ويخزن جميع أسماء المواقع التي تمت زيارتها ويستفيد من تلك المعلومات عن طريق إظهار قائمة منسدلة عندما يقوم المستخدم بكتابة الحرف الأول من الموقع حيث تظهر في تلك القائمة المنسدلة جميع المواقع التي زارها المستخدم وتبدأ بهذا الحرف. كما تخزن هذه القاعدة جميع المواقع والصفحات التي قام المستخدم بوضعها في قائمة المفضلة bookmark. الشكل رقم 2 يوضح قاعدة بيانات places.sqlite داخل أحدى برامج توضيح تركيبة قواعد بيانات SQLite (يسمى SQLite Spy). [![post-51148-048308600 1291234943](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-048308600-1291234943.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-048308600-1291234943.png) الشكل 2 يوضح الجداول والحقول داخل قاعدة بيانات places.sqlite كما يتضح من الشكل رقم (2) فإن قاعدة بيانات places.sqlite تحتوي على عدد كبير من الجداول وكل من هذه الجداول مكون من العديد من الحقول والتي تحتوي على الكثير من المعلومات، فمثلاً الجدول moz\_historyvisits يحتوى على جميع المواقع التي تمت زيارتها ولكنه مرتبط بالجدول moz\_places من خلال الحقل الغريب foreign key المسمى ب place\_id. وأيضاً الجدول moz\_bookmark مرتبط بحوالي 4 جداول. هذا التداخل في القاعدة وتعقيد تركيبتها تجعل من الصعب أن يقوم المحقق باستخراج المعلومات منها ولو من خلال برنامج كبرنامج SQLite Spy حيث على المحقق أن يكتشف العلاقات بين هذه الجداول ويقوم باستخراج البيانات عن طريق كتابة أوامر SQL كثيرة. الشكل رقم 3 يوضح شكل تقريبي لتركيبة قاعدة البيانات places.sqlite وهي من إعداد فريق firefoxforensics.com : [![post-51148-007327200 1291235214](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-007327200-1291235214.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-007327200-1291235214.png) الشكل (3) يوضح شكل تقريبي للجداول والعلاقات بينها في القاعدة places.sqlite من الملاحظات على هذه القاعدة هي طريقة تخزينها للوقت والتاريخ حيث هي تخزنه بصيغة Microseconds وليس كما بقية الأنظمة والتي تستخدم seconds في تخزين الوقت (تسمى Unix-Time أو POSIX-Time وهي عدد الثواني من اليوم الأول في الشهر الأول من سنة 1970). ولتحويل من طريقة تخزين الوقت في places.sqlite إلى طريقة Unix-Time يجب القسمة على مليون وبعد ذلك يمكن استخدام الدالة datetime في SQLite والتي سوف تقوم بمهمة تحويل هذه الثواني إلى الوقت والتاريخ بشكل نصي. الشكل (4) يوضح الاستعلام الذي يمكن من خلاله الحصول على جميع المواقع التي تمت زيارتها موضحاً وقت وتاريخ الزيارة لكل موقع. [![post-51148-075152700 1291235308](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-075152700-1291235308.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-075152700-1291235308.png) الشكل (4) يوضح كيفية الحصول على معلومات زيارة المواقع بعد تطبيق بعض من الاستعلامات من خلال هذه الاستعلامات يمكن الحصول على جميع المعلومات المتعلقة بالمعلومات الموجودة في قاعدة بيانات المتصفح فايرفوكس، ولكن بما أن هذا يتطلب وقتاً لكتابة كل من الاستعلامات حتى يمكن الحصول على النتيجة إضافة لصعوبة تذكر هذه الاستعلامات، فإن كتابة برنامج متخصص في استرجاع هذه المعلومات بكل يسر وسهولة يعتبر أمر ضروري للمحققين حتى تساعدهم في إثبات ما يمكن إثابته على المشتبه به. من أشهر البرامج المتخصصة في استرجاع معلومات متصفح فايرفوكس هي Firefox 3 Extractor و FoxAnalysis ووظيفة هذين البرامج مشابه لبعضها حيث تقوم باستخراج كل الزيارات التي قام بها المستخدم Histories ومفضلة المستخدم Bookmarks وملفات الكوكيز Cookies والملفات التي قام بتحميلها المستخدم Downloads ومعلومات تسجيل الدخول (اسم المستخدم وكلمات المرور إذا قام المستخدم بحفظها) Logins. إضافة إلى تزويد المحقق بخاصية البحث في تلك المعلومات وحفظ هذه المعلومات وتصديرها على شكل ملفات أكسل أو ملفات CSV. **استخراج محادثات برنامج المحادثة Yahoo Messenger :** عند إجراء المحادثات عبر برامج المحادثة Yahoo! Messenger فقد يحفظ البرنامج هذه المحادثات حتى تستطيع الرجوع إليها فيما بعد إذا احتجتها وذلك على حسب إعداداتك للبرنامج، النسخ السابقة من برنامج المحادثة Yahoo كانت خاصية الحفظ بها مفعلة بالشكل الإفتراضي عند تنصيب البرنامج، بعد ذلك قامت الشركة بإلغاء خاصية الحفظ التلقائية وجعلها على حسب إعدادات المستخدم. المحادثات في برنامج Yahoo Messenger لا تحفظ بشكل نصي واضح clear text ولكن تحفظ في ملفات ثنائية binary files لها بنية غير موثقة وغير معروفة والغرض من ذلك جعل هذه المحادثات غير مقروءة لأي شخص يريد الإطلاع عليها. ويستطيع المستخدم الذي نتجت منه هذه الملفات استخدام برنامج ال Yahoo Messenger نفسه لكي يقوم بالحصول على النص الواضح من هذه الملفات. الشكل 5 يبن القائمة التي يمكن من خلالها الحصول على المحادثات بشكل واضح. [![post-51148-001922200 1291235461](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-001922200-1291235461.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-001922200-1291235461.png) الشكل (5) يوضح كيف يستخرج برنامج الياهو المحادثات المسجلة طريقة تشفير المحادثات تتم باستخدام أحد أبسط الأساليب على الإطلاق ( المحادثات في برنامج Yahoo Messenger تشفر باستخدام تطبيق عملية XOR المنطقية مع اسم المستخدم) وعندما سُئل فريق العمل عن ذلك ذكروا بأن الغرض الأساسي هي سهولة حفظ المعلومات وليس الغرض تشفيرها عن المستخدم. مواقع تخزين هذه الملفات قد تختلف باختلاف نسخة برنامج المحادثة ياهو وباختلاف نسخة نظام التشغيل المستخدم ، ففي نظام ويندوز XP فهي تخزن على المسار التالي: أما في نظام التشغيل ويندوز 7 فتغيرت هذه المسارات وخزنت في مكان أخر. وأسهل طريقة يمكن أن تحصل على هذه الأماكن وذلك بالاستفادة من وجود الخاصية في برنامج Yahoo Messenger والتي تمكنه من قرائه الرسائل (كما في الشكل 5 ) هي عن طريق مراقبة البرنامج عند تطبيق هذا الأمر ثم تتبع ماذا يقوم به حتى يمكن الحصول على المسار. الشكل (6) يبين كيف يمكن الحصول على المسار من خلال مراقبة البرنامج باستخدام process monitor. [![post-51148-019916500 1291235593](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-019916500-1291235593.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-019916500-1291235593.png) الشكل (6) يبين كيفية الحصول على مسار تخزين ملفات ال حادثة عن طريق مراقبة برنامج الياهو أثناء التسجيل كما يتضح من الشكل فإن المسار في نظام ويندوز 7 هو التالي: C:UsersAppDataLocalVirtualStoreProgram FilesYahoo!MessengerProfilesArchiveMessages ويتم تغيير باسم المستخدم للنظام وتغيير باسم حساب المستخدم في الياهو وتغيير باسم حساب الطرف الآخر في المحادثة، وعند فتح ذلك المجلد سوف نجد جميع المحادثات التي تمت بين هذه الطرفين في ملفات ثنائية بالامتداد .DAT ، كل من هذه الملفات يكون اسمه مكون من تاريخ بدء المحادثة واسم المستخدم والذي سيستخدم كمفتاح في عملية فك تشفير الرسالة المخزنة. [![post-51148-077243600 1291235640](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-077243600-1291235640.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-077243600-1291235640.png) الشكل (7) يبين المحادثات المسجلة في الجهاز بنية هذه الملفات تتكون من : أربعة بايتات تمثل تاريخ المحادثة ويتم تخزينها ك Unix-time تليها أربعة بايت محجوزة (أو لا يعرف الغرض منها) ثم تليها أربعة بايتات أخرى توضح هل الرسالة قادمة من الطرف الأول صاحب البريد (تكون القيمة صفر) أو أنها قادمة من الطرف الآخر (تكون القيمة واحد) ثم تليها أربعة بايتات توضح عدد بايتات الرسالة (إلى هنا جميع هذه المعلومات تخزن بترميز Little Endianes ) ثم تأتي بعد ذلك الرسالة ويكون عدد بايتاتها معلوم لأن الحقل الذي سبقها كان يحدد عدد بايتات الرسالة، وعندما تنتهي الرسالة تأتي أربعة بايتات أخرى تكون قيمتها صفر وذلك لكي تفصل ما بين كل رسالة ورسالة. الشكل (8) يبين كيف يمكن الحصول على الرسالة الأولى التي تم إرسالها من الطرف الأول للطرف الثاني. [![post-51148-039240900 1291235956](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-039240900-1291235956.png)](https://informatic-ar.com/wp-content/uploads/2013/09/post-51148-039240900-1291235956.png) الشكل (8) يبين كيف يمكن فك تشفير الرسالة للحصول على النص الأصلي الذي كتب في البرنامج هكذا يستطيع المحققين استخراج هذه الرسائل بشكل يدوي، ولكنها عملية متعبة وتستغرق كثير من الوقت لاستخراج الرسائل وفك ترميزها وتحويل الوقت وما إلى ذلك من عمليات. لذلك اللجوء إلى البرامج التي تساعد المحققين أمر مهم جداً. هناك بعض من البرامج التي تقوم بهذه المهمة منها مجاني ومنها تجاري .ويمكنك عمل برنامج مشابه لذلك و يستخرج جميع الرسائل وأوقات هذه الرسائل كما يوضح المرسل والمرسل إليه لكل من الرسائل. **خاتمة:** تناولت هذه المقالة عن أهمية علم الأدلة الجنائية والتحقيق في الجرائم المتعلقة باستخدام الانترنت كبرامج التصفح وبرامج المحادثة، وتناولت المقالة أساليب التحليل التي يتبعها المحللون حتى يستخرجوا المعلومات بشكل يدوي، وكمثال على ذلك تم تحليل برنامج التصفح فايرفوكس وتم استخراج المعلومات التي تفيد المحققين أثناء التحقيق في الجرائم الالكترونية، كما تم تحليل برنامج المحادثة ياهو وتوضيح كيف يمكن استخراج المعلومات والمحادثات بعد فك تشفير هذه المحادثات. **المراجع:** [Yahoo! messenger archive file ](http://www.0xcafefeed.com/2007/12/yahoo-messenger-archive-file-format/?ref=informatic-ar.com "رابط خارجي") [Access Data’s FTK: Internet Forensics](http://www.venkydude.com/articles/yahoo.htm?ref=informatic-ar.com "رابط خارجي") [http://hiddenillusio...gid=6&Itemid=63](http://hiddenillusion.net/new-school/index.php?option=com%5Fdocman&task=doc%5Fdownload&gid=6&Itemid=63&ref=informatic-ar.com "رابط خارجي") ### الوصول للمنطقة Programming in The Zone URL: https://informatic-ar.com/programming_zone/ Last updated: 2026-10-03T22:32:23.000Z عندما تجلس على جهازك وتبدأ في التكويد والبرمجة ، شيئاً فشيئاً سوف تنسى العالم الخارجي المحيط بك، لن تعير انتبهاك لرسائل البريد، لن تعير انتبهاك لجوالك الذي يرن فهو في الوضع الصامت فأنت لا تراه ولا تسمعه، لن تسمع من يتحدث خلفك ، لن تفتح الانترنت,, هنيئاً لك، **وصلت المنطقة (Zone).** **ال Zone تعتبر أكثر من الأوقات انتاجية للمبرمج**، تقوم بحل الكثير من المشاكل في لمح البصر، تكتب عشرات أو مئات الأسطر بسلاسه، مثلما قد يحدث أحياناً عندما تعود للمنزل قد لا تتذكر كيف عدت أو اي الطريق سلكت لكنك فعلتها! كثير من المبرمجين كتبوا أو شرحوا عن هذه المنطقة وفوائدها وسلبياتها، وكيف يمكنك ان تصل لل Zone بطريقتك .. هناك من لديه ممارسات وصوتيات معينة يبدأ بها حتى يصل وهناك من يسمع لصوت المياه او شيء ما,, أو يمكنك الوصول للZone بطرق اخرى وعليك ان تكتشف ذلك بنفسك، مثلاً قد تصل اليها بعد البدء بعمليات ترتيب الكود ال Refactoring، أو كتابه الأكواد المعروف التي تعرف كيف يمكن كتابتها،، أو قد تحتاج لتشغيل ملف صوتي ووضع السماعات حتى تبعدك عن الجو المحيط بك، وربما قد تضع السماعات لفترة بدون وجود اي ملف صوتي يعمل وهذا طبيعي.. الشيء المزعج وقت ذروة الانتاجية هو في الانقطاع Interruption ، فالخروج منها سهل جداً وفي ثواني قد تفقد تركيزك سواء كانت مكالمة هاتفية أو وقت الغداء أو من سؤال من زملائك في العمل (خاصه السؤال الذي لا فائده منه) هكذا قد تحتاج ل 10 الى 15 دقيقة اخرى حتى تعود وتركز بشكل كامل وتبدأ في الانتاجية. الانقطاع اثناء التركيز أمر مزعج في جميع التخصصات وبالذات للمبرمجين، لأن الانتاجية تتطلب تركيز عالي وحفظ هذه المعلومات في ذاكرة صغيرة لديك، وأي نوع من الازعاج Interruption قد يؤدي لمسح كل هذه المعلومات منك، وعندما تعود للعمل مرة أخرى قد تجد أنك لا تتذكر هذه التفاصيل (مثلاً اين وصلت في كود الخوارزمية، أين استخدمت هذا المتغير المحلي local variable)، وهكذا سوف تحتاج للنظر مجدداً وبالبدء ببطء حتى تعود بكامل سرعتك. هذا الوقت الضائع لاعادة التركيز (لنقل 10 دقائق) كان من الأحرى ان يقوم زميلك في العمل في البحث عن اسم الدالة في Google لايجادها، الامر الذي لا يأخذ منه سوى نصف دقيقة بدلاً من اضاعه 10 دقائق من زميلك. بالطبيعي طلب المساعده أمر حتمي فأحياناً تكون لديك خبرة ويتطلب الزميل منك مساعده، ولكن عليه بالبحث اولاً وبذل مجهود ولو قليل في البحث ثم بعد ذلك يمكن طلب المساعده في حال اشكل عليه الأمر (عملية البحث بذاتها جيدة وسوف يتعلم المبتدئ بنفسه كثيراً). هناك أراء أخرى ضد المنطقة، وأشهرها رأي روبرت مارتن (الشهير بأنكل بوب في كتابه Clean Coder) ويقول أنه احياناً عليك الهروب من هذه المنطقه خصوصاً عند بناء الهيكل الأساسي أو الديزان للكود، حيث يمكنك ان تصمم سريعاً ولكنه قد يكون خاطئ لأنك تعمل في دائرة مغلقة وقد لا ترى جميع الجوانب، لذلك يقول في تلك الحالات اهرب من المنطقة Avoid the Zone ، و قم بأخذ جولة 5 دقائق بالخارج ، أو حتى تصفح أي موقع آخر لكي تهرب من ذلك العالم,, وقد ذكر أن هذا من مزايا ال Paring أو ما يعرف ب Pair Programming ( اثناء يبرمجان في جهاز واحد شخص يستلم لوحة المفاتيح والثاني يراقب ويناقش) حيث أنه لا يوجد مجال للدخول في المنطقة لأنها حالة لا تستطيع الحديث اثنائها uncommunicative state بعكس الParing والتي بنيت في الأساس على مفهوم المحادثه والنقاش constant communication الدائم بين الزميلين. **خلاصه**: - لا تقطع زميلك من العمل بدون فائده - ركز جيداً في عملك واعرف متى تنتج وتكون في اعلى انتاجية وقم بغلق كل وسائل الاتصال ومواقع التواصل الاجتماعي (قد ذكرنا في الموضوع السابق عن فكرة [برنامج لغلق الشبكات الاجتماعية من جهازك](https://informatic-ar.com/small%5Fprojects/) ، وسوف نقوم بصنعه في القريب ان شاء الله حتى يساعد الكثير). - اثناء عمل الأجزاء المهمه في المشروع حاول ان تفكر في كل الأبعاد والا فاجلب فنجان القهوه قبل البدء. **مقالات مفيدة**: [Where do These People Get Their (Unoriginal) Ideas?](http://www.joelonsoftware.com/articles/fog0000000068.html?ref=informatic-ar.com) [Programmer Interrupted](http://blog.ninlabs.com/2013/01/programmer-interrupted/?ref=informatic-ar.com) [7 Tips for programming in The Zone](http://www.computus.org/journal/?p=982&ref=informatic-ar.com) ### أساسيات لابد لأي مبرمج معرفتها الترميز (Encoding) URL: https://informatic-ar.com/unicode_encoding/ Last updated: 2026-10-03T22:32:23.000Z كثير من المبرمجين لديهم نقص في أساسيات مهمه Basic Concepts والذي يؤثر على مدى امكانياتهم لحل المشاكل التي تواجههم بطريقة علمية، بدلاً من التجربة والنسخ واللصق الى ان تحل المشكلة بدون معرفة السبب أو كيف تم حل المشكلة. في هذه السلسلة " **أساسيات لابد لأي مبرمج معرفتها**" سوف تتناول مواضيع أساسية لأي مبرمج مثل مواضيع ال Encoding أو ال Time وال Locale والصور Images والألوان Color وأساسيات التشفير Cryptography والضغط Compression وغيرها من الأساسيات المطلوبة لأي مبرمج والتي تجعلك قادر على حل المشاكل بطريقة علمية وممنهجة. هذا أول موضوع في السلسلة وسوف يتحدث عن 4 أمور مهمه وهي: 1. ما هو ال Character Sets وأنواعها مثل ASCII و ال Unicode وما هي ال Encoding وأنواعها مثل الـ UTF-8 والـ UTF-16 والـ UTF-32 2. ما هو ال Endianness ولماذا يتم ذكره عند التعامل مع الملفات المرمزة ب Unicode أو عند ارسال أو تخزين الملفات الثنائية Binary Files 3. لماذا يجب أن تكون حريصاً عند تعاملك مع الملفات النصية التي تحتوى على ترميز Unicode وما هي العلامة BOM فيها 4. طرق أخرى لترميز البيانات في حال وددت ارسال ملفات ثنائية من وسيط لأخر Byte to String **مستوى المقالة** / مبتدئ (لا يفترض ان تعرف سوى مبادئ الحاسب Bit, Byte, Binary, Hex, Decimal ). **الفئة المستهدفه**/ سواء كنت مطور تطبيقات ويب أو موبايل أو تطبيقات سطح مكتب تتعامل مع اي نوع من الملفات فأنت بحاجه الى قرائه هذه المقالة، وتعلم ال Encoding. ## مقدمة عن ال Encoding **أول** درس تعلمته في مسيرتك الحاسوبية هي أن الحاسب لا يعرف الا اللغه الثنائية Binary، أي أن كل الصور والملفات و الأرقام و الأحرف التي تكتبها وتراه على الشاشه تخزن بشكل مجموعه من البتات Bits (أي صفر و واحد) في أماكن التخزين Storage سواء كانت في الذاكرة أم القرص. **الدرس الثاني** الذي تعلمته هو أنه يصعب التعامل مع الأرقام الثنائية وحفطها بالنسبة لنا البشر لذلك وجدت أنواع التمثيل الأخرى Representations مثلاً النظام السادس عشر Hexadecimal أو النظام الثماني Octal أو النظام العشري Decimal الذي نستخدمه في حياتنا اليومية، وهكذا يمكن أن نمثل الرقم 237 (المكتوب بالنظام العشري) بأكثر من طريقة ففي النظام الثنائي سوف يكون 10011111 أما في النظام السادس عشر سوف يكون 9F أما في ال Octal سوف يكون 159، وكل هذه الطرق تمثل نفس العدد ولكن بتمثيل مختلف ، وفي الغالب نحن نستخدم النظام السادس عشر لأنه مختصر وأسهل من الثنائي. اذاً عندما تريد تخزين رقم فسواء استخدمت أي طريقة لتمثيله فسوف يتم تخزينه بالشكل الثنائي على الحاسب (الرقم 237 يتم تخزينه 10011111) ، هذا بالنسبة للأرقام ولكي نتعامل مع الأحرف Characters فيجب أن تحولها بشكل أو أخر لأرقام يفهمها الحاسب حتى يخزنها بالنظام الثنائي ، ومن هنا وجدت أول طريقة تحويل Mapping من الأحرف الى الأرقام وهو نظام ال ASCII (نسمية **Character Set**) حيث هو مجرد اسناد رقم لكل حرف حتى يمكن التعامل معه وهكذا الأمر بالنسبة للأحرف الانجليزية وبعض رموز التحكم Control Characters سيتم اسناد رقم لكل منهم في جدول الأسكي. في بادئ الأمر كان أي حرف عباره عن 7بت وبالتالي مجمل الحروف التي نستطيع التعامل معها هي 127 (في النظام الثنائي 1111111 وهي تساوي 127)، ولهذا السبب تم تطوير ال Extended ASCII وجعله 8 بت واضافه رموز وأشكال أخرى وهكذا اصبحنا نتعامل مع 256 حرف (في النظام الثنائي 11111111). *جدول الأسكي*: [![Standard-ASCII-Table](https://informatic-ar.com/wp-content/uploads/2013/09/Standard-ASCII-Table.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Standard-ASCII-Table.jpg) **تعريف 1**: عمليات تحويل الحرف الى بتات Bits تسمى Encoding Schema واختصاراً *نطلق عليها* **Encoding** ، في الجدول أعلاه بعد عمل ASCII Encoding للحرف D الناتج هو القيمة 01000100 **تعريف 2**: عملية تحويل البتات الى حرف تسمى **Decoding** وهي العملية العكسية لل Encoding. **تعريف 3** **:** اي مجموعه من الأحرف التي يمكن أن تقوم بعملية Encoding لها نسميها بشكل عام ***Character Set***، أي أن الأحرف هي ال Character Set وعملية تحويلها الى بتات هي Encoding، واختصاراً أي جدول يحتوى على حرف ومقابله سوف تسمى ب Character Set ، اذاً ال ASCII هو Character Set و عملية التحويل فيه تسمى ASCII Encoding. هذه ال 256 كانت مناسبة في اللغه الانجليزية لكنها لم تكن كافيه لبقية العالم لترميز أحرفهم ولغاتهم، فكان الحل بأن تكون أول 127 حرف هي ثابته كما هو في ال ASCII العادي و ال 128 الباقية تكون للغه التي تريد التعامل معها وسوف **تسمى** **Code Page** **.** **هكذا سنجد لكل لغه** **Code Page** **خاصه بها**، فاذا استخدمنا ال Code Page للغه العربية (وهو الـ ISO-8859-6 وله اسم اخر في نظام الويندوز وهو Windows-1256 ) سوف نحصل على الأحرف العربية، مثال اذا كان لدينا رقم 200 واستخدمت Code Page عربي فأنني سأحصل على الحرف العربي الذي يقع في تلك الخانه ، وهكذا اذا استخدمت Code Page للغه الصينية سوف تحصل على الحرف الذي يقابل القيمة 200 في ذلك ال ( Code Page في العادة تسمى هذه بال **ISO-8859 Encoding** ) . من ال Code Page المشهورة الآخرى (أو ال ISO-8859 Encoding الآخرى ان صح التعبير) هو الـ ISO-8859-1 (أيضاً له اسماء مختلفة مثلاً Latin Alphabet أو ISO Latin ) وكان يستخدم سابقاً في الويب بسبب أنه احتوى على الأحرف الفرنسية والاسبانية [![iso-8859-1-character-set](https://informatic-ar.com/wp-content/uploads/2013/09/iso-8859-1-character-set.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/iso-8859-1-character-set.gif) **ايضاً هناك** **Code Page** **أخريات** [![codepages](https://informatic-ar.com/wp-content/uploads/2013/09/codepages.png)](https://informatic-ar.com/wp-content/uploads/2013/09/codepages.png) ظهرت مشكلة آخرى بعد ذلك وهي أنه توجد هناك لغات لا تكفيها بايت واحد (اي أن ال 128 المتبقة لا تكفيها لأن حروفها أكثر) وبها أحرف أكثر من ذلك البايت ومن هنا طور نظام ال Unicode. ## **نظام الترميز يونيكود** **Unicode** صمم هذا النظام Character Set في الأساس على أنه نظام 16 بت (بايتين 2 Byte لكل حرف وليس كالASCII بايت لكل حرف) ، وتم تصميمه بنفس مبدأ وضع رقم لكل حرف كالأسكي (لكن الأرقام هنا في هذا النظام **تسمى** **Code Point** **).** هكذا أي حرف في اي لغه موجودة تم اسناد رقم ( Code Point ) له (مجال ال Code Point هو من صفر الى أكثر من مليون 1,114,111 حرف، وفي ال Unicode عاده يتم استخدام نظام السادس عشر Hex في الكتابه مع علامه U+ في البدء، وبالتالي مجال ال Code Point هو من U+0000 الى U+10FFFF ). [![Unicode-2-Characters](https://informatic-ar.com/wp-content/uploads/2013/09/Unicode-2-Characters.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Unicode-2-Characters.png) بما أن ال Unicode في الأساس صمم لكي يكون 16 بت ( يحمل حد أقصى 65536 حرف المجال من U+0000 الى U+FFFF ) فأن اول 16 بت **تسمى** **Basic Multilingual Plane** **(اختصاراً** **BMP** **)** والبقية من U+10000 الى U+10FFFF تسمى **Supplementary Characters** **.** *وهكذا فأن اي حرف في ال* *Unicode* *هو أما يكون في* *BMP* *أو يكون* *Supplementary* *.* **نأتي لنقطة مهمه** وهي أن ال Unicode هو مجرد عباره عن أحرف لها Code Point (قيمه عادية) ولكي تستخدمه يجب أن تستخدم ال Encoding له وهو يكون في 3 أشكال UTF-8 و UTF-16 و UTF-32 كل منهم يختلف في ال Code Unit ( *ال Code Unit هي عدد البايتات، فمثلاً UTF-8 ال Code Unit لها هو 1 بايت ، بينما UTF-32 فال Code Unit هو 4 بايت*). اذاً لكي تتعامل مع أحرف ال Unicode ونقوم بتخزينها سوف نحتاج لعمل Encoding لها ويكون على أساس الحجم Code Unit (أي التحويل Encoding يكون من Code Point الى واحد أو أكثر Code Unit على حسب ال Encoding المستخدم) ولدينا 3 طرق لعمل Encoding وهي UTF-8 و UTF-16 و UTF-32. ## **ما الفرق بين هذه ال** **Encoding** ؟ في ال UTF-32 نقوم بتمثيل أي حرف Unicode (أي Code Point) الى Code Unit واحدة فقط، وبما أننا نتعامل مع وحدة 32 بت فاذا أي حرف Unicode يكون عباره عن 4 بايتات وهي بالطبع تأخذ حجماً أكثر من الEncoding الأخرى. في الUTF-16 تستخدم واحد أو أثنين Code Unit لتمثيل اي Code Point، فالأحرف ال BMP تمثل بCode Unit واحد فقط (وبالتالي تكون 2 بايت فقط) أما الأحرف ال Supplementary تمثل باثنين Code Unit وبالتالي تمثل ب 4 بايتات. في ال UTF-8 فهي تمثل بواحد الى 4 Code Unit (أي واحد الى 4 بايت) لتمثيل الCode Point، فالمجال من U+0000 الى U+007F يمثل ب 1 بايت، أما المجال من U+0080 الى U+07FF يمثل ب 2 بايت، أما المجال U+0800 الى U+FFFF يمثل ب 3 بايتات ، أما المجال U+10000 الى U+10FFFF يمثل ب 4 بايتات. **مثال**: [![unicod encoding](https://informatic-ar.com/wp-content/uploads/2013/09/unicod-encoding.png)](https://informatic-ar.com/wp-content/uploads/2013/09/unicod-encoding.png) **الصورة اعلاه احتوت على حرفين BMP** : - دائماً في ال UTF-32 سوف تكون النتيجة 4 بايت - بما أنه BMP فاذا في ال UTF-16 تكون النتيجة 2 بايت - في ال UTF-8 بما أن الحرف A يفع في المجال (من U+0000 الى U+007F ) فهو يكون 1 بايت ، ولكن الحرف الأخر يقع ضمن النطاق الذي يرمز ب 3 بايت لذلك هنا سوف يكون 3 بايت. ميزة ال UTF-8 هي أن الأحرف العادية التي كانت تمثل في ال ASCII بواحد بايت ما زالت تمثل بواحد بايت ولذلك فأنه **متوافق Compatible مع ال ASCII**. أغلب تطبيقات الويب ونظام لينوكس يعمل ب UTF-8 فهو الأشهر والأكثر استخداماً, بينما استخدامات ال UTF-16 هي في الجافا والدوت نت والويندوز.. بحكم أن الأحرف العربية هي موجودة ضمن ال BMP ففي الغالب في لغات البرمجة (مثلاً جافا ودوت نت) نستخدم ال String وهو UTF-16 فيكون الحرف الواحد هو 2 بايت ، لكن اذا قمت بجعل البرنامج يدعم اللغات الصيينية واليابانية فأنت تحتاج الى أن تتعامل مع ال Supplementary وكافة اللغات تحتوي دعماً لذلك أيضاً حيث كل حرف يمثل ب 4 بايت في ال UTF-16 لهذه الأحرف Supplementary. **خلاصه ال** **Unicode** هو عباره عن جدول Character Set وتحتاج لاستخدام Encoding لكي تتعامل مع تلك الأحرف، ويجب أن تعلم أن الحرف قد يكون حجمه مختلف بناء على ال Encoding المستخدم. ## الفرق بين الملفات النصية Text File والملفات الثنائية Binary File هناك نوعين من الملفات التي يمكن ان توجد على حاسوبك 1. الملفات النصية Text File والتي تحتوى على نصوص بأي لغه كانت 2. الملفات الثنائية Binary File والتي لا تحتوى على نصوص (واء كانت صور أو ملفات فيديو أو برامج). هذه الملفات بنوعيها تكون مخزنة على شكل بتات Bits ، **اذاً كيف تفرق بين الملف النصي والملف الثنائي**؟ **الجواب**: على حسب ما تريد أنت (على حسب تفسيرك لها)، اذا قمت بقرائه تلك البتات وقمت بعمل Decode لها على أساس انها ASCII (بالاعتماد على جدول أسكي) فسوف تحصل على الحرف المقابل لكل بايت، فاذا كان تلك البايتات ناتجه عن عملية ASCII Encoding فسوف تحصل على الtext الصحيح ، أما اذا كانت بخلاف ذلك فسوف تحصل على text خاطئ، للتجربة قم بفتح اي ملف ثنائي (صورة مثلاً) من خلال ال Notepad سوف تجد ال text بعد عملية ال decoding على اساس الأسكي. **نظرة حول مفهوم ال** **Endianness** كل المتغيرات التي توجد في برنامجك تكون مخزنة على الذاكرة، وبما أن هناك بعض أنواع المتغيرات حجمها أكثر من بايت واحد Multi-Bytes فسوف يتم تخزينه في أكثر من slot في الذاكرة بشكل متتابع Contiguous، مثلاً لنفرض أننا في بيئة 32 بت وقمت بعمل int x فلو خزن هذا المتغير في العنوان x0100 فهذا يعني أنه يبدأ من x0100 و 0x101 و 0x102 و 0x103 (لأن المتغير يأخذ 4 بايت). لنفرض أنك خزنت القيمة 110642547 في هذا المتغير (بالهيكس هي 0x01234567 ) ، فكيف يتم وضع القيم في تلك العنواين؟ هناك طريقتين في ذلك: - حفظ من البايت الأصغر Least Significant Byte ( الموجود في أقصى اليمين) من أول العنوان في الذاكرة وتسمى هذه الطريقة ب Little Endian وهي التي تستخدم في معالجات Intel والمتوافقه معها. - حفظ من البايت الأكبر Most Significant Byte ( الموجود في أقصى اليسار) من أول العنوان في الذاكرة، وتسمى ب Big Endian وهي تستخدم في معالجات التي تنتجها IBM, SUN ( هذا يعتمد على المعالج ، فلو انتجت IBM جهاز بمعالج متوافق مع انتل فسوف يكون Little Endian ). [![1006294_603380733026915_1054245893_n](https://informatic-ar.com/wp-content/uploads/2013/09/1006294_603380733026915_1054245893_n.png)](https://informatic-ar.com/wp-content/uploads/2013/09/1006294%5F603380733026915%5F1054245893%5Fn.png) لو رجعنا للمثال السابق بالقيمة 0x01234567 ( فلو كان العنواين تبدأ من اليسار لليمين ) فسوف تكون موجودة هكذا في ال Little Endian تكون القيمة هي 67452301 ، أما في ال Big Endian تكون القيمة هي 01234567. ربما يتسائل البعض من أين أتت التسمية بهذا الشكل، فهذا يعود الى أحد القصص القديمة حيث تنازعوا في كيفية فتح البيضه، هل من الجزء الكبير Big أم من الجزء الصغير في البيضة Little ، ومن هنا سميت على هذه الواقعه. الجدير بالذكر أنه لا توجد طريقة أفضل من أخرى، والمهم هو في اختيار الاسلوب والالتزام به، **متى ينبغى للمبرمج ان يهتم لهذا الموضوع؟** - في حال كنت تكتب برنامج يرسل binary data من جهاز بمعالج يختلف عن الجهاز الأخر فسوف تحتاج لتوحيد اختيار ال Endianness المستخدم - ايضاً في كتابه برامج الClient-Server فمثلاً اذا كتبت احدهم بالجافا (وهي تستخدم Big Endian ) والثاني بسي# (وهي تستخدم Little Endian ) فقد ترسل بايتات تصل بشكل معكوس وعليك تحويلها من أحد الأطراف ويجب الاتفاق على طريقة واحدة بين التطبيقين - عندما تتعامل مع الملفات النصية التي تستخدم UTF-16 أو UTF-32 كما سيتبين ذلك في الفقرة التالية ## ما هي علامة ال BOM؟ لماذا يجب أن تكون حريصاً عند تعاملك مع الملفات النصية التي تحتوى على ترميز Unicode وما هي العلامة BOM في هذه الملفات؟ [![1005423_599906113374377_350642331_n](https://informatic-ar.com/wp-content/uploads/2013/09/1005423_599906113374377_350642331_n.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/1005423%5F599906113374377%5F350642331%5Fn.jpg) عندما تتعامل مع الملفات النصية أو البرمجية (مثل ملفات XML, PHP, HTML, JAVA فهي ملفات نصية في الأخر) التي تحتوى على نصوص عربية أو أي أحرف غير لاتينية، فعليك حفظ الملف باستخدام ال Unicode حتى تخرج الأحرف صحيحة.. كما ذكرنا قبل قليل، يونكود هو تظام ترميز عياره عن جدول الذي يوضح الحرف وما يقابله من الرقم Code Point وأنه يجب إختيار ال Encoding حتى تستطيع حفظ الملف و هي ثلاثه تراميز UTF-8 و UTF-16 و UTF-32 . وبما أن ال UTF-16 يرمزوا اي حرف باثنين أو اربعه بايتات ، وفي الUTF-32 يرمز أي حرف ب 4 بايتات ، فان هذه البايتات يجب أن ترتب داخل الملف ، وهناك طريقتين لترتيب البايتات Byte Order (تسمى ال Endianness كما سبق في الفقرة اعلاه) وهي Little endian و Big endian. **هكذا أي ملف** **Unicode** **(مرمز ب** **UTF-16** **أو** **UTF-32** **)** **تكون البايتات فيه اما** **Little endian** **أو** **Big endian** **،** **السؤال كيف ستعرف هل الملف من النوع الأول أم الثاني**؟ **الجواب**: عن طريق ال BOM (اختصاراً Byte Order Marks) وهو عباره عن حرف يتم وضعه في أول الملف ولا يظهر عندما تفتح الملف بشكل عادي (فقط في ملفات ال Unicode ) طوله يتراوح من 2 بايت الى 4 بايت على حسب نوع ال Encoding، وهو يوضح لك نقطتين: - نوع ال Endianness المستخدم (هل هو Big أو Little) ؟ - نوع ال Encoding المستخدم (هل هو UTF-8 أم UTF-16 أم UTF-32)؟ ملفات UTF-8 ليست بحاجه الى توضيح ال Endianness بسبب بسيط الا وهو أن ال Code Unit لها هو بايت واحد وبالتالي لا يوجد داعي لتوضيح الEndianness والسبب أن كل البايتات تكون بنفس الترتيب. قم بالتجربة الآن، وافتح برنامج المفكرة Notepad في الويندوز ، وقم بحفظ الملف Save AS سوف تجد الخيارات التالية: [![notepad](https://informatic-ar.com/wp-content/uploads/2013/09/notepad.png)](https://informatic-ar.com/wp-content/uploads/2013/09/notepad.png) - ANSI وهي لحفظ الملف بالترميز الافتراضي في النظام (على حسب ال Locale اعدادات اللغه لديك) - Unicode وهو لحفظ الملف ب UTF-16 ( في ويندوز هذا الترميز الاكثر انتشاراً لذلك يطلق عليه هكذا باسم يونكود وهو اسم غير واضح Misleading على اية حال) - Unicode Big Endian وهو UTF-16 BE فقط - UTF-8 وهي UTF-8 with BOM ، للأسف لا تتضح كلمة ال BOM هنا، ولكن في غالب برامج ميكروسوفت فهي تضع ال BOM وبرامجها تتوقع أي ملف UTF-8 يحتوي على تلك العلامه وتتجاهلها بعد ذلك، أي هي BOM-aware software. جرب اكتب اي كود جافا في ذلك الملف وقم بحفظ الملف ب UTF-8 (والذي فيه الBOM)، عندما تشغل المترجم سوف تتفاجئ بالخطأ في السطر الأول error: illegal character: 187 مترجم الجافا للأسف لا يستطيع التعامل مع الحرف BOM الموجود ولا يستطيع تجاهله، وهناك الكثير من البرامج ايضاً ليست BOM Aware. والحل هو بتغيير ترميز ذلك الملف واستخدام UTF-8 Without BOM، للأسف برنامج Notepad في ويندوز لا يحتوى خيار كهذا ، لذلك ننصح الجميع دائماً باستخدام Notepad++ وتستطيع تغيير ال Encoding بسهوله من قائمة Encoding . [![notpadplus](https://informatic-ar.com/wp-content/uploads/2013/09/notpadplus.png)](https://informatic-ar.com/wp-content/uploads/2013/09/notpadplus.png) **من ناحية أخرى** هناك بعض البرامج خصوصاً برامج ميكروسوفت تتوقع ال BOM في اي ملف Unicode، جرب مثلاً قم بعمل ملف فيه Comma Separated Value (اي قيم مفصولة بعلامه فاصلة Comma ) واجعل في بعض الكلمات العربية ثم احفظ الملف بالامتداد csv وافتحه بالأكسل Excel سوف تجد أنه لم يستطيع اخراج الاحرف صحيحة كما هي ، و يمكنك التصحيح من خلال اضافة ال BOM باستخدام ال Notepad++ وتغيير الترميز الى UTF-8 أو من خلال UTF-8 في Notepad والذي يحتوي على BOM في الوضع الطبيعي. اذاً هناك برامج تتوقع وجود BOM في ملفات ال UTF-8 وهناك برامج لا تتوقع وجودها لذلك حينما تجد أخطاء في أول سطر أو احرف غريبة فتذكر **البووم** **BOM** **.** يمكن كتطبيق عملي ([المشروع رقم واحد في قائمة هذه المشاريع](https://informatic-ar.com/small%5Fprojects/)) كتابه برامج يقوم بقرائه اي ملف ويخرج لنا نوع ال Encoding فيه من خلال ال BOM ، ويمكنك ايضاً توفير خاصية لحذف ال BOM ايضاً من الملف.. الفكرة ببساطة، سوف تقرأ الملف كباينري bytes ، وتفحص اول بايتات اذا احتوت على : - *00 00* *FE FF* فهذا يعني أنه UTF-32, big-endian - *FF FE 00 00* فهذا يعني أنه UTF-32, little-endian - *FE FF* فهذا يعني أنه UTF-16, big-endian - *FF FE* فهذا يعني أنه UTF-16, little-endian - *EF BB BF* فهذا يعني أنه UTF-8 لكي تحذفها يمكنك عمل Skip لها من المصفوفة، أو كتابتها في في مصفوفة اخرى بدون تلك البيانات ومن ثم تكتبها في الملف مرة أخرى، هكذا سوف يخرج لنا ملف بدون BOM. اذا وجدت ملفات نصية بدون BOM فما زال بامكانك معرفة ال Encoding ولكن الامر يتطلب فحص النص الموجود داخله ومجاله حتى تعرف نوع ال Encoding، لذلك قم بالمهمه في الملفات التي تحتوى على BOM. ## **طرق أخرى لترميز البيانات في حال وددت ارسال ملفات ثنائية من وسيط لأخر** **Byte to String** أحياناً قد تريد معاملة الملفات الثنائية على انها ملفات نصية لعده أسباب، مثلاً : - تريد ان تحفظ الصور في ملف XML ؟ - تريد حفظ الأحرف العربية في ملف لا يقبل الا ASCII ؟ - تريد ارسال ملف في وسيط لا يقبل الا النصوص text-based channel ؟ **الجواب** : عن طريق ال Encoding وتحويل هذه البيانات الثنائيه Binary Data أو حتى النصية (فسوف نتعامل على اساس ال Bytes) الى صيغة يمكن ارسال أو تخزين البيانات من خلالها وهي في الغالب US-ASCII ( وهو اي حرف يمكن طباعته Printable تحت ال 127 في جدول الأسكي وهي الأحرف التي ترمز بال سبعة بتات 7-bit ) ، ومن ثم يقوم الطرف الأخر بعد الحصول عليها القيام بال Decode وارجاعها الى شكلها الأصلي الثنائي Binary. **أسهل طريقة لتحويل البايتات لأحرف هي عن تحويلها الى ال** **Hex Representations**، كما نعلم أي بايت يرمز بحرفين في ال Hex ، هكذا لو لدى صورة حجمها 100K وقمت بتحويل البايتات بها الى Hex Digits سوف أحصل على سلسله Hex Digits من 200K (الأحرف التي في السلسه تكون محصورة في ال16 حرف أو رقم من 0-9 و A-F ). كما تلاحظ طريقة الHex تضاعف حجم Capacity البيانات المرسلة ، وهو أمر يمكن السكوت عنه في حال كانت البيانات صغيرة (كما هو الحال عندما يتم عمل URL Encoding ويتم تحويل الرابط بشكل %DD فهو يستخدم نظام الHex). لكن لا يمكن القيام بذلك في حال كبرت البيانات ، مثلاً تريد ارسال ملف مرفق مع البريد Email Attachment ، فكما هو معلوم انظمة البريد في السابق كانت لا تعمل الا بنظام ال 7- bit اي لا يمكن ارسال الا الأحرف والأرقام والمرموز المطبوعه فقط Printable Character أي ال US-ASCII). هناك كثير من الأنظمة القديمة ليست 8-bit clean وما زالت تعمل ب 7bit ( نتيجة لذلك وجدت أنواع كثيرة من ال Encoding **أشهرها نظام ال** **Base64** ) نظام ال Hex يسمى أحياناً ب Base16 والسبب ان مجال الأحرف هو 16 التي استطيع الترميز بها، بمعنى كل البايتات سوف تتحول لهذه الاحرف ال 16 ، وبنفس الفكرة ال Base64 مجال الأحرف هو 64 حرف ورقم تتكون من الأرقام العشرة، والأحرف الانجليزية ال26 لل Lower-case وال 26 للUpper-case، وايضاً الاشارات + و / واخيراً الاشاره = والتي تستخدم في حالة كانت هناك Padding. [![17635_602117139819941_1284589950_n](https://informatic-ar.com/wp-content/uploads/2013/09/17635_602117139819941_1284589950_n.png)](https://informatic-ar.com/wp-content/uploads/2013/09/17635%5F602117139819941%5F1284589950%5Fn.png) الBase64 يأخذ 3 بايتات من المدخل ويخرج 4 بايتات تكون أحرف مطبوعه ASCII، وبالتالي يمكنك ارسال اي ملف ثنائي لأي وسيط لا يدعم الا ال 7-bit من خلال ال Base64. **مثال بسيط:** الصورة الثنائية احتوت على 3 بايتات هي 155 162 233 نقوم أولاً بأخذ الشكل الثنائي: 100110111010001011101001 ، بعد ذلك سنأخذ كل ستة بتات في مجموعه ، وسيخرج التالي: 100110 سوف تتحول الى 38 ، و 111010 سوف تتحول على 58 ، و 001011 سوف تتحول الى 11، و 101001 سوف تتحول الى 41\. [وبالرجوع الى جدول Base64](http://email.about.com/od/emailbehindthescenes/l/blbase64enctabl.htm?ref=informatic-ar.com) سوف تأخذ الحرف المقابل لكل رقم و سوف تحصل على الأحرف m6Lp، هكذا حصلنا على 3 بايتات والمدخل كان 4 بايتات. اذاً البيانات ايضاً تزيد حجمها عندما تستخدم Base64 ولكن معدل زيادتها اقل كثيراً من ال Hex. فاذا رجعنا لمثال الصورة 100K فهي تتحول لل Hex ب 200K ولكن في الBase64 تكون 133K. بالنسبة لل Decode فهي العملية العكسية لل Encode وستدخل الأحرف وستحصل على الbinary data كما كانت قبل التحويل. ## **خلاصة** ال Base64 هو نظام تحويل Encoding يضمن ارسال البيانات بشكل كامل Safe Transportation على اي نظام أو برتوكول لا يدعم 8-bit وهكذا ستضمن ان البيانات تصل بدون اي خراب Data Corruption. اخيراً، هناك من يخطئ ويقول بأن ال Base64 هو تشفير Encryption وهذا أبعد من الصواب، لأن التشفير يعتمد على كلمة سر أو مفتاح ولا يمكن الحصول على النص الأصلي، أما هنا بمجرد عمل ال Decoding سوف تحصل على الكائن الأصلي! وصلنا لآخر المقالة ، وأرجوا أن تكون احتوت على معلومات مفيدة للقارئ أو الباحث في علوم الحاسب، اذا كان لديك اي ملاحظة أو اضافة أو سؤال فلا تتردد بالسؤال والمشاركة، ### أفكار لبرامج صغيرة للمبرمجين URL: https://informatic-ar.com/small_projects/ Last updated: 2026-10-03T22:32:24.000Z كثير من المبرمجين بحاجه الى أن يطبقوا مشاريع يأخذوها منها الخبرة وتساهم في تعزيز فهمهم للغه التي يبرمجوا بها، وحتى نخرج من اطار الأمثلة التقليدية وبرنامج "المضروب" الشهير في الجامعات سوف نعرض أمثلة حية لبرامج ونوضح كيف تعمل بالاضافه الى الفائده التي ستخرج منها بعد تطبيقك لهذه الفكرة. المشاريع عملية جداً وهي تصلح للطلاب الجامعيين أو حتى الهواه ومن يحتاج لمشروع يصقل مهارته فيه، فمنها تستطيع التعلم بشكل أفضل ، ومنها تستطيع وضعها على سيرتك الذاتيه، خاصه اذا كنت واضعها بشكل مفتوح المصدر Open Source هكذا ستتعلم وتستفيد وتفيد غيرك بالبرامج والأكواد اذا كانت مفيدة. ## برنامج لتحميل فيديوهات اليوتيوب هناك الكثير من الدورات التعليمية على اليوتيوب وقد تريد حفظها لكي تحضرها لاحقاً، الفكرة بعمل برنامج يأخذ الرابط لقناه معينة ويقوم بتحميل كل ال Playlist مع توفر خاصية تحويل الفيديو الى صيغ اخرى مثلاً لل IPhone أو xVid حتى يكون اكثر فائده **مثال** لبرنامج مشابه: [![youtubedownload](https://informatic-ar.com/wp-content/uploads/2013/09/youtubedownload.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/youtubedownload.gif) **رابط البرنامج**: [http://download.cnet.com/YTD-Video-Downloader/3000-2071\_4-10647340.html](http://download.cnet.com/YTD-Video-Downloader/3000-2071%5F4-10647340.html?ref=informatic-ar.com) **المستوى**: متوسط ، أي شخص تعلم اساسيات الملفات وعمل واجهات المستخدم والتعامل مع الشبكة Network Programming والتعامل مع أكثر من Threads. **الفكرة** **بواسطة**: https://twitter.com/mmot33 ## برنامج لاستخراج ومسح المحفوظات من متصفحات الانترنت اغلب المتصفحات تحتوى على كمية كبيرة من البيانات التي تود ان تحذفها في حال سينتقل الجهاز لشخص أخر، سواء كنت في مكان عام، أو بالعكس تريد استخراج المحفوظات لجهاز اخيك الصغير مثلاً وتريد معرفة نوعية المواقع التي يتصفحها **مثال** لبرنامج مشابه: [![firefox](https://informatic-ar.com/wp-content/uploads/2013/09/firefox.gif)](https://informatic-ar.com/wp-content/uploads/2013/09/firefox.gif) **رابط البرنامج**: [http://www.nirsoft.net/utils/mozilla\_history\_view.html](http://www.nirsoft.net/utils/mozilla%5Fhistory%5Fview.html?ref=informatic-ar.com) **المستوى**: سهل لمتوسط ، أي شخص تعلم اساسيات الملفات وعمل واجهات المستخدم والتعامل مع قواعد البيانات SQLite **مراجع مفيدة** : [مقدمة في تحليل جرائم الانترنت Internet Forensics](https://informatic-ar.com/internet-forensics/) ## برنامج ايجاد نوع ال Encoding في الملف النصي أي ملف نصي Text File يكون لديه ترميز معين حتى يمكن للمحرر عرضه بالشكل الصحيح، فالملفات التي تستخدم ترميز ال Unicode في الغالب لديها علامه غير مرئية في أول الملف تسمى BOM توضح لك ما هيه هذه الملف ، الأن فكرة البرنامج كلها عن طريق قرائه ال BOM وهي تكون أول 2 الى 4 بايتات ومن ثم يمكنك التعرف على نوع ال Encoding المطلوب. **مثال** لبرنامج مشابه: [![File_BOM_Detector](https://informatic-ar.com/wp-content/uploads/2013/09/File_BOM_Detector.png)](https://informatic-ar.com/wp-content/uploads/2013/09/File%5FBOM%5FDetector.png) **رابط البرنامج**: [http://www.bryntyounce.com/filebomdetector.htm](http://www.bryntyounce.com/filebomdetector.htm?ref=informatic-ar.com) **الفائدة منه**: سوف تتعلم كيفية القرائه والكتابه من الملفات في لغة البرمجة ، وسوف تتعلم طرق الترميز المعروفة ومالفرق بين UTF-8 و UTF-16 والخ وسوف تتعلم ما هيه ال BOM. **المستوى**: سهل لمتوسط ، أي شخص تعلم دوال I/O والقرائه من الملفات مع عمل واجهات المستخدم GUI يستطيع عمله، اذا كنت مبرمج جديد مثلاً تستخدم لغه السي++ ولا تعرف كيف يمكن عمل الواجهات فيمكنك عمله Console Application على الشاشه السوداء. **مراجع مفيدة لكي تتعلم اساسيات ال Encoding**: [أساسيات لا بد لأي مبرمج معرفتها (ال Encoding)](https://informatic-ar.com/unicode%5Fencoding/) ## **برنامج تعديل الصور المقلوبة Image Rotation** كثير من الأحيان بعد أن تلتقط الصور من جوالك وقمت بنقلها الى جهازك سوف تجدها مقلوبة ، وتحتاج الى فتح محرر الصور في ويندوز وقتوم بقلبها يدوياً (هذا ما حصل لي بعد التقاطي صور من ايفون وجالاكسي ومشاهدتها على ويندوز 7) المشكلة تكون في حال كان لديك صور كثيرة 3000 صورة مثلاً) والمشكلة الأخرى أن مستعرض الصور في ويندوز يقوم بمسح بعض المعلومات المهمه في الصورة metadata عندما تقوم بعمل التدوير Image Rotation. **المطلوب**: برنامج يقوم بتعديل الصور Image Rotation الموجودة في المجلد المحدد بدون تقليل جودة الصورة (لأن الصور JPEG عندما يحصل لها تدوير فهذا يقلل من جودتها، جرب أن تقوم بفتح صورة وقم بتدويرها مرات كثيرة وسوف تكتشف بأن الجودة قلت). هناك طرق متقدمة في تعديل الصورة بدون الاخلال بحجمها لكننا سوف نبدأ بالأسهل وهي عن طريق استخدام مكتبة جاهزة أو Command Line نستدعيها من برنامجك. **مثال على برنامج**: [![jpeglossless3](https://informatic-ar.com/wp-content/uploads/2013/09/jpeglossless3.png)](https://informatic-ar.com/wp-content/uploads/2013/09/jpeglossless3.png) **الفائدة من المشروع**: سوف تتعلم امور كثيرة بخصوص الصورة والتعامل معها [والهيدر Exif](http://en.wikipedia.org/wiki/Exchangeable%5Fimage%5Ffile%5Fformat?ref=informatic-ar.com)، سوف تتعلم كيف تقوم بعمل برنامج يتعامل مع I/O وقرائه الصور ، سوف تتعلم كيف تستدعي Command Line من برنامجك تقوم بعملية ما (سوف نستدعي مكتبة تسمى jpegtran تقوم بالعملية) [هذه المكتبة تستخدم من قبل أكثر من 84 برنامج](http://sylvana.net/jpegcrop/losslessapps.html?ref=informatic-ar.com) وبرنامجك ان شاء الله سوف يكون من ضمنهم) النسخ التالية من المشروع هي البدء بالعملية من الصفر اذا اراد المبرمج ان يتعلم اكثر عن الصورة والعمل عليها. ايضاً اذا قمت بدعم ال Right Click مثل الصورة التي فوق سوف تتعلم بعض الامور عن ال Shell Extensions في الWindows وكيف تضع برامجك على هذه الخانه Right Click Menu **المستوى**: متوسط **المشاركة**: يمكنك البدء من الصفر لو أحببت ، لكني في السابق قمت بعمل واجهه لذلك واستخدمت الcommand line ويحتاج كثير من التحسينات واضافة Thread مع Progress bar لدلاله على ان العمل ما زال في الخلفية (المشروع هنا مفتوح المصدر [برنامج مدور](https://bitbucket.org/WajdyEssam/modawer?ref=informatic-ar.com) بسي++ مع Qt ، وهذه صورة منه): [![modawer](https://informatic-ar.com/wp-content/uploads/2013/09/modawer.png)](https://informatic-ar.com/wp-content/uploads/2013/09/modawer.png) ## برنامج حمايه الصور Image Protection الفكرة أنه اريد ان احمي صوري بحيث لا يستطيع احد عرضها الا من خلال باسورد ، عندما تقوم بتحديد الصورة وكتابه الرقم السري سوف يتم تشفير الصورة ووضعها بشكل لا يمكن عرضه ولا يمكن فتحها الا بالبرنامج مع الرقم السري. **مثال برنامج مشابه**: [![PhotoCrypt_1](https://informatic-ar.com/wp-content/uploads/2013/09/PhotoCrypt_1.png)](https://informatic-ar.com/wp-content/uploads/2013/09/PhotoCrypt%5F1.png) **الرابط**: [http://www.softpedia.com/get/Security/Encrypting/PhotoCrypt.shtml](http://www.softpedia.com/get/Security/Encrypting/PhotoCrypt.shtml?ref=informatic-ar.com) **الفائده من المشروع**: سوف تتعلم قرائه الملفات I/O ، والتشفير المتناظر Symmetric Encryption وتقوم بتشفير الصورة بالمفتاح (الرقم السري) وفك تشفيرها ايضاً. **شرح للبرنامج بالعربي**: [من موقع المحترف](http://www.th3professional.com/2013/02/photo-crypt.html?ref=informatic-ar.com) وللأسف أغلب المواقع العربية تحتوي شرح لبرامج وفي الأساس يمكن للمبرمجين العرب القبام بها بكل سهوله والاعتماد على النفس بدلاً من البرمجيات الاجنبية. **المستوى**: سهل لمتوسط ## برنامج ايقاف الشبكات الاجتماعية الكثير منا اثناء العمل قد يفتح احدى الشبكات الاجتماعية ويبدأ بالحديث والقرائه ويضيع وقته وهو لا يدري ، فيمكن عمل برنامج يقوم بايقاف كل الشبكات الاجتماعية اثناء ساعات تحددها أنت (ساعات عملك) حتى تستطيع التركيز في العمل. المشروع يمكن عمله بشكل تجاري بعد ان تبدأ فيه. **مثال**: [![antisocial_windows2](https://informatic-ar.com/wp-content/uploads/2013/09/antisocial_windows2.png)](https://informatic-ar.com/wp-content/uploads/2013/09/antisocial%5Fwindows2.png) **رابط المشروع**: [http://anti-social.cc/](http://anti-social.cc/?ref=informatic-ar.com) **الفائدة من المشروع**: سوف تتعلم كيفية الدخول والتحكم في مجرى البيانات الداخلة والخارجة من جهازك ، سوف تتعلم اساسيات ال DLL وكيف تقوم بعملها، وسوف تتعلم اساسيات في برمجة الWindows. **مستوى المشروع**: متوسط ## برنامج حذف الملفات المكررة في الغالب يحتوي جهازك على الكثير من الملفات المكررة ولكنك نسيتها، هذا البرنامج يقوم بعرض كافه الملفات المكررة لديك حتى تقوم بحذفها وبالتالى يقلل من استخدام المساحة الموجودة لديك على القرص. **مثال**: [![howtoduplicatecleanerwin](https://informatic-ar.com/wp-content/uploads/2013/09/howtoduplicatecleanerwin.png)](https://informatic-ar.com/wp-content/uploads/2013/09/howtoduplicatecleanerwin.png) **الرابط**: [http://www.digitalvolcano.co.uk/duplicatecleaner.html](http://www.digitalvolcano.co.uk/duplicatecleaner.html?ref=informatic-ar.com) **الفائده من المشروع:** تعلم التعامل مع الملفات I/O، كيف تحسب ال Hash للملفات ، وكيف تقوم بعمل Traversal على الملفات الموجودة في المجلد (دعم البحث داخل المجلدات الفرعية Nested Sub-folder) ، وكيف تقوم بعمل Thread مع Progress bar حتى يتم الانتهاء من الحذف. **المستوى**: سهل لمتوسط ## برنامج المتحكم في الكاميرا كم مرة اشتريت لابتوب جديد وقمت بالصاق شريط على الكاميرا الموجودة فيه، الآن سوف تحافظ على منظر لابتوبك وتتحكم في الويب كام كيفما تريد **مثال**: [![image001](https://informatic-ar.com/wp-content/uploads/2013/09/image001.png)](https://informatic-ar.com/wp-content/uploads/2013/09/image001.png) **الرابط**: [http://www.mywebcamlock.com/](http://www.mywebcamlock.com/?ref=informatic-ar.com) **الفائده من المشروع**: *يمكن ان يكون تجارياً وتكسب منه*، سوف تتعلم امور متقدمة في الويندوز وكتابه Drivers يعمل على مستوى الكيرنل حتى تتحكم جيداً بالجهاز (في السابق قمت بعمل شيء ممثال عن طريق ال Hooking ولكنه كان Alerting وليس تحكم فيه). **المستوى**: متقدم ## برنامج كسر الملفات المضغوطة كثيراً ما يكون لديك ملفات مضغوطة برقم سري ولكنك نسيت ذلك الرقم، فيمكن استخدام البرنامج وبالاستفاده من قدرات جهازك بمحاولة لكسر هذا الرقم السري **مثال**: [![zippasswordunlocker_mainscreen](https://informatic-ar.com/wp-content/uploads/2013/09/zippasswordunlocker_mainscreen.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/zippasswordunlocker%5Fmainscreen.jpg) **الرابط**: [http://securityxploded.com/zip-password-unlocker.php](http://securityxploded.com/zip-password-unlocker.php?ref=informatic-ar.com) **الفائده من المشروع**: سوف تتعلم كيفية الاستفاده من ال Cores في جهازك في محاولة تخمين الكلمة، سوف تتعلم عن المصطلحات المعروفة في أمن المعلومات مثل Brute Force Attach و Dictionary Attack وكيف تقوم بهذه الهجمات ، سوف تقوم بعمل خوارزمية لتوليد النصوص والارقام بعدة لغات. **المستوى**: متوسط #### وفقة حول برامج موقع Security Exploded الموقع [http://securityxploded.com](http://securityxploded.com/?ref=informatic-ar.com) فيه كمية جيدة من البرامج الخاصة بالحماية Security والتي يمكنك عملها لو أردت انظر [لقائمة البرامج لديهم](http://securityxploded.com/tools.php?ref=informatic-ar.com) وهي في ال Password Cracking و ال Network Security وايضا بعضها في حمايه الSystem، هي برامج اغلبها سهله ويمكنك القيام بها خصوصاً لو تعلمت أساسيات البرمجة في ويندوز ستجد أنك تستطيع ان شاء الله برمجه أغلب القائمة الموجودة وعمل برامج مماثله بالعربي. فيمكنك ان تستقى الأفكار والبدء بعمل مشاريعك بلغتك واستهداف الشريحة التي ترغب بها. ## برنامج وضع معلومات الجهاز على سطح المكتب اذا كنت مدير لعده انظمة وكل دقيقة تقوم بالوصول البعيد لسيرفر موجود Remote Access فقد تنسى ان السيرفر المفتوح امامك هو سيرفر قاعده البيانات والسيرفر الثاني هو للموقع ، وقد تضيع وقتك بالقيام بعمليات خاطئة، هذا البرنامج يسهل لك ويضع لك معلومات الجهاز فوق الخلفية حتى تستطيع بلمح النظر معرفة نوع هذا السيرفر. **مثال** (للمبرمج الخبير مارك، أحد أفضل مبرمجي الويندوز على الاطلاق، صاحب ادوات SysInternal): [![bb897557.BgInfo(en-us,MSDN.10)](https://informatic-ar.com/wp-content/uploads/2013/09/bb897557.BgInfoen-usMSDN.10.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/bb897557.BgInfoen-usMSDN.10.jpg) **الرابط**: [http://technet.microsoft.com/en-us/sysinternals/bb897557.aspx](http://technet.microsoft.com/en-us/sysinternals/bb897557.aspx?ref=informatic-ar.com) **الفائده من المشروع**: سوف تتعلم كيف تفتح صورة وتكتب فوقها وتضعها كخلفية على سطح المكتب، أو يمكنك عمل برنامج يعمل في الخلفية وتقوم بعمل نافذه بدون اطار شفافه وتكتب فيها المعلومات حتى تتحدث من فترة لأخرى على حسب المعلومات التي تود اخراجها، ايضاً ستتعلم كيفية الحصول على المعلومات مثلاً اسم الجهاز، عنوان الايبي، المعالج والذاكرة والخ من المعلومات. **المستوى**: متوسط ## **حذف الأكواد الخبيثة من صفحات موقعك** احياناً يصاب موقعك بكود جافا سكربت خبيث يصيب كل الملفات على السيرفر، وبالطبع عمليه ازالتها يدوياً أمر مكلف، فالفكرة هنا بعمل برنامج يقوم باستقبال كود الفايروس ويقوم بالدخول في كل الملفات في المجلد ومسح تلك الجزئية من الملف. **مثال**: [http://www.maliciouscodecleaner.com/](http://www.maliciouscodecleaner.com/?ref=informatic-ar.com) **الفائدة من المشروع**: تعلم كيفية البحث عن نص داخل ملف، وحذف جزئية معينة من الملف، والبحث Iterations في الملفات داخل مجلد معين. **المستوى**: سهل ## **برنامج حفظ كلمات المرور** هذه الأيام قد يوجد لأي مستخدم اكثر من 10 حسابات موزعة بين ايميلات ومواقع وحسابات بنكية وغيرها ، حفظ هذه المعلومات في ملف نصي أو في بريد قد يعرضها للسرقة لذلك عليك بحفظها في مكان واحد مشفرة بحيث لا تفتح الا برقم سري **مثال**: [![Add-400x284](https://informatic-ar.com/wp-content/uploads/2013/09/Add-400x284.png)](https://informatic-ar.com/wp-content/uploads/2013/09/Add-400x284.png) **الرابط**: [http://www.thewindowsclub.com/passbox-download](http://www.thewindowsclub.com/passbox-download?ref=informatic-ar.com) **الفائده من المشروع**: سوف تتعلم عمل قاعدة بيانات صغيرة مشفرة أو ملف مشفر بخوارزميات التشفير مع واجهه مستخدم جميلة. **المستوى** : سهل ## خاتمة - القائمة سوف تتحدث باستمرار - القائمة خاصه بتطبيقات سطح المكتب وسيتم عمل موضوع اخر لتطبيقات الويب والموبايل ان شاء الله - القائمة مناسبة للمتعلمين او من يودوا العمل على مشكله ما - يمكنك تطبيق اي من هذه المشاريع باللغه التي تود ، و تذكر أن حل المشكلة بتطبيق هو اهم من اختيار اللغه بدون حل لمشكلة - يمكنك الرد في هذا البوست واضافة افكار اخرى تراها مناسبة لاثراء الموضوع ### خارطة طريق لتعلم الجافا URL: https://informatic-ar.com/how_to_learn_java/ Last updated: 2026-10-03T22:32:25.000Z [![chooseingbook](https://informatic-ar.com/wp-content/uploads/2013/09/chooseingbook.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/chooseingbook.jpg) هناك كثير من الكتب في لغه الجافا متوفرة في السوق أو المواقع الالكترونية ، فكيف تختار الكتاب الجيد الذي سوف يفيدك أثناء التعلم ؟ وما هي الكتب المناسبه لك حالياً؟ هذه المقالة سوف تجيبك على تلك الأسئلة ، وسوف نقسم الموضوع الى 3 سلات Buckets (وليست مستويات) لأن الفكرة هو ان تقطف من هنا وهناك وليس الجلوس في مكان واحد خصوصاً بعد معرفة أساسيات البرمجة. هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا - [الإسلوب الصحيح لكتابه Utility Classes](https://informatic-ar.com/utility%5Fclasses/) - [هل مللت من دالة البناء Constructor ؟](https://informatic-ar.com/constructor-vs-factory/) - [استخدام ال Builder Pattern في دالة البناء](https://informatic-ar.com/builder%5Fpattern/) - [لا تستخدم Public الا وقت الحاجة](https://informatic-ar.com/inaccessible%5Fclass/) - [كيف تصمم الPackages جيداً في تطبيقات جافا](https://informatic-ar.com/packages%5Fin%5Fjava/) - [جافا دائماً Passing by Value](https://informatic-ar.com/java%5Fpassing%5Fby%5Fvalue/) - [نظرة حول الدالة Equals](https://informatic-ar.com/java-object-equals/) - خارطة طريق لتعلم الجافا (المقال الحالي) ## **السلة الأولى: اللغه والمكتبات** في هذه السله سوف تحتاج الى أن تتعلم قواعد اللغه Grammar والمكتبات في اللغه Foundations Classes وقد تحتاج الى تعلم مكتبات خارجيه اخرى Third-Party Libraries من المكتبات الجيدة. مثلاً مكتبة [Guava ](https://code.google.com/p/guava-libraries/?ref=informatic-ar.com)وهو توفر Collections و String Processing API وغيرها من قوقل ، أو مكتبة [JodaTime ](http://www.joda.org/joda-time/?ref=informatic-ar.com)للتعامل مع الوقت والتاريخ. بخصوص قواعد اللغه فسوف تجدها مشروحة بشكل جيد في اغلب كتب الجافا المتوفرة ، لذلك يمكنك قرائة اي منها كما تشاء لأنها ممتازة ، فاذا كنت جديد على البرمجة وهذه أول ايامك بها وليس لك اي ممارسات سابقة ، فهذه الكتب مناسبة: [Java How to Program](http://www.amazon.com/Program-early-objects-Edition-Deitel/dp/0132575663?ref=informatic-ar.com) [![javahowtoprogram](https://informatic-ar.com/wp-content/uploads/2013/09/javahowtoprogram.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/javahowtoprogram.jpg) الكتاب مناسب للمبتدئين ، وهو يحتوي على كميه من الامثلة والتمارين بأخر الفصول حتى تمرن نفسك اكثر في البرمجة ، يشرح الكتاب اساسيات البرمجة (المصفوفات، التكرار ، الدوال) وغيرها بالتفصيل مما يجعله مناسباً كأول كتاب في مسيرتك البرمجية بالجافا [Head First Java](http://www.amazon.com/Head-First-Java-2nd-Edition/dp/0596009208?ref=informatic-ar.com) [![headfirstjava](https://informatic-ar.com/wp-content/uploads/2013/09/headfirstjava.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/headfirstjava.jpg) ايضاً الكتاب سهل وممتع ومليئ بالصور التوضيحية، يشرح الكثير من المفاهيم بشكل سلس وهو يمكن أن يكون كتابك الأول عند تعلمك الجافا. واجهات المستخدم GUI والوصول لقواعد البيانات JDBC Database وبرمجة الشبكات Network Programming وأساسيات ال Threading تنطوي تحت السله وسوف تجدها في الكتب اعلاه ، لأنها مجرد API سوف تستخدمها بعد أن تتعلم المنطق البرمجي، لذلك عليك بفهمها وربما تكتب برامج صغيرة بها ولكن هذا ما زال تحت المبتدئ. لذلك قرائه كتاب شامل للأساسيات مثل Java How to Program مفيد جداً للمبتدئ,, اذا كانت لديك خلفية ممتازة بلغة اخرى فيمكنك اختيار Core Java 2 فهو مناسب جداً لمن لديهم خبرة في البرمجة. [Core Java Volume 1 -- Fundamentals](http://www.amazon.com/Core-Java-I--Fundamentals-9th/dp/0137081898/?ref=informatic-ar.com) [![Core Java Volume 1 -- Fundamentals](https://informatic-ar.com/wp-content/uploads/2013/09/Core-Java-Volume-1-Fundamentals.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Core-Java-Volume-1-Fundamentals.jpg) كتاب Core Java مكون من جزئين ، الجزء الاول يتناول الأساسيات بشكل عميق ويتناول معظم الأشياء الضرورية لتقوية اساس مبرمج الجافا مثلاً Generic Programming وبرمجة ال GUI والLayouts ويدخل في ال Multithreading ويتحدث بشكل جيد عن ال Exceptions وأساسيات ال OOP. لذلك ان كنت قادم من سي++ أو أي لغه اخرى وتعرف المنطق البرمجي فهذا مناسب لك لكي تتعلم الجافا بشكل جيد. [Core Java Volume 2 -- Advanced Features](http://www.amazon.com/Core-Java-II--Advanced-Features-9th/dp/013708160X/?ref=informatic-ar.com) [![Core Java Volume 2](https://informatic-ar.com/wp-content/uploads/2013/09/Core-Java-Volume-2.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Core-Java-Volume-2.jpg) الجزء الثاني يدخل في مواضيع مهمه بدئاً من الIO وبرمجة الشبكات ويمر على Advanced GUI والعمل مع XML والDatabase والتشفير Security & Cryptography وكيف تكتب تستدعي كود سي++ بالجافا JNI وغيرها. اعتبره من المراجع الرئيسية لي في العمل. هذه اهم الكتب في هذه السله، هناك كتب اخرى شهيره مثلاً كتاب بروس اكيل [Thinking in Java](http://www.amazon.com/Thinking-Java-Edition-Bruce-Eckel/dp/0131872486/?ref=informatic-ar.com) وان كنت أرى ان ال Core Java أشمل وأوسع منه ## **السله الثانية: تفاصيل اللغه بشكل أعمق** بعد أن تتعلم الأساسيات وتستطيع بناء برامج بسيطة ، عليك العودة مرة أخرى للغه وتدرس تفاصيل اللغه بشكل اعمق حتى تعرف الاستخدام الامثل لها Practical Usage (مثلاً لماذا يفضل استخدام static factory على دالة البناء ، وكيف تكتب دالة hash code بشكل جيد ، ولماذا يجب عليك أن تحاول أن تجعل اي كلاس لديك Immutable اذا استطعت الخ) كل هذه المواضيع السابقة لن تجدها في كتب الأساسيات وتحتاج لكتب مختصه بهذه التفاصيل ,, وهذا هو فاصل جيد بين شخص مبتدئ باللغه وشخص خبير باللغه، ايضاً هذه السلة سوف تحتوى على مواضيع الConcurrency وكيفية بناء برنامج يستخدم امكانيات الجهاز (جميع الCores ويستغلها بشكل جيد)، وسوف تمر على مرحلة أعمق في OOP وهي طرق التصميم Design Pattern وكيف تبنى هيكلية احترافيه للمشروع، ومروراً بكيفة كتابة كود عالى الجودة Quality Code وعمل تغيير في هيكلة الكود بحيث تضمن مقروئية وصيانته Refactoring ،، السله مليئة هذه المرة بكثير من الأمور ولكنها فاصل واضح بين مبرمج مبتدئ وشخص متوسط أو خبير في اللغه. الكتب التالية هي مفتاحك: [Java Concurrency in Practice](http://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601?ref=informatic-ar.com) [![javaconcurrenyinpractice](https://informatic-ar.com/wp-content/uploads/2013/09/javaconcurrenyinpractice.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/javaconcurrenyinpractice.jpg) اذا أردت كتابه اي برنامج فيه أكثر من Thread واحد ، فهذا هو افضل ما ستقرئه على الاطلاق، هذا الكتاب مناسب لمصممي الأنظمة أو ال Infrastructure للغير مثلاً تريد كتابه Web Server أو تعمل في مشروع يتطلب أن تدير اكثر من Request في آن واحد في شتى المجالات بدئاً من تجربة الخوارزميات ومعالجة الصور والالعاب والخ. [Effective Java](http://www.amazon.com/Effective-Java-2nd-Joshua-Bloch/dp/0321356683?ref=informatic-ar.com) [![Effective Java](https://informatic-ar.com/wp-content/uploads/2013/09/Effective-Java.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Effective-Java.jpg) كتاب جوشا (أحد مبرمجي Collections API) هو من افضل الكتب لفهم الكثير من التفاصيل حول اللغه التي يحتاج أي خبير ان يعرفها، حتى لو كنت مبرمج بلغه اخرى فهذا الكتاب سوف ينفع معك لأنه يناقش مواضيع كثيرة مثلاً Serialization أو كيف تكتب Exception وتقوم بعمل mange لها في بشكل صحيح ، والخ ، الكتب السابقة تعلمك كيف تقوم هذا الكتاب يعلمك لماذا تقوم . [Clean Code](http://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/?ref=informatic-ar.com) [![Clean Code](https://informatic-ar.com/wp-content/uploads/2013/09/Clean-Code.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Clean-Code.jpg) كتاب أنكل بوب الشهير ، يوضح لك كيف تكتب كود مقروء وواضح، اذا كنت تكتب دوال فيها اكثر من 100 سطر وكلاسات متداخله الأبعاد فأنت بحاجه الى قرائه هذا الكتاب [Head First Design Pattern](http://www.amazon.com/First-Design-Patterns-Elisabeth-Freeman/dp/0596007124?ref=informatic-ar.com) [![Head First Design Pattern](https://informatic-ar.com/wp-content/uploads/2013/09/Head-First-Design-Pattern.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Head-First-Design-Pattern.jpg) اسهل طريقة لتعلم اساسيات ال Design Pattern هو من خلال هذا الكتاب، بعد هذا الكتاب ستبدأ بالشعور بالانتقال من مرحلة Junior الى مرحلة Senior أو البداية في الArchitecture بشكل جيد [Refactoring](http://www.amazon.com/Refactoring-Improving-Design-Existing-Code/dp/0201485672?ref=informatic-ar.com) [![Refactoring](https://informatic-ar.com/wp-content/uploads/2013/09/Refactoring.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Refactoring.jpg) هل تصدق أن الكتاب عمره قارب ال15 سنه، ومازال يحتفظ بقيمته الى الآن، الكتاب يوضح لك كيف تحسن ال Design لمشروعك الحالي، ويحتوى على نصائح توضح لك متى تقوم بعملية التنظيف أو التحسين هذه ولماذا تقوم بها ، الكتاب لحسن الحظ أمثلته بلغه الجافا ولكنه يعمل على باقي اللغات (اذا لاحظت ان اغلب الكتب في هذه السله تكون من هذه النوعيه، وهي طريق جاد لكي تصبح مبرمج خبير). [Practical API Design](http://www.amazon.com/Practical-API-Design-Confessions-Framework/dp/1430243171?ref=informatic-ar.com) [![Practical API Design](https://informatic-ar.com/wp-content/uploads/2013/09/Practical-API-Design.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Practical-API-Design.jpg) لأن أي مبرمج يمكن أن نطلق عليه مصمم للدوال API Designer فإن ال API هي من أهم الأمور التي يجب الاعتناء بها في مشروعك أو شركتك حتى تستفيد منها فيما بعد ، هناك اساسيات في كتابى الAPI حتى يستخدمها المبرمجين فيما بعدك ، هذا الكتاب يتناول تفاصيل كثيرة حول بناء ال API (وهناك كتاب أخر شهير ولكن بسي++) ، مؤلف هذا الكتاب هو احد مؤسسي ال Netbeans ومكاتنه معروفه في مجتمع الجافا ## **السله الثالثه : الأدوات** هذه الأيام لا يمكن أن **تسمى نفسك مبرمجاً هذه الأيام بدون استخدام أدوات مساعدة**، هذه الأدوات تسهل عملك وتجعله اوتوماتيكي وجعل الحاسب يقوم بالأمور الممله وحفظ وقتك للأمور الأهم ،، لذلك يمكن القول أن تعلم منهجية تطوير (مثلاً Agile) بالاضافة الى الأدوات المطلوبة لأي مبرمج (أدوات Source Control ، أدوات Unit Testing ، أدوات Building ، أدوات Continuous Integration) ، الكتب التالية هي ما يجب النظر اليه اذا كنت لا تستخدم اي أدوات سوى ال IDE : [Java Power Tools](http://www.amazon.com/Java-Power-Tools-Ferguson-Smart/dp/0596527934?ref=informatic-ar.com) [![Java Power Tools](https://informatic-ar.com/wp-content/uploads/2013/09/Java-Power-Tools.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Java-Power-Tools.jpg) من أفضل الكتب في جافا التي تتحدث عن الأدوات (Documentation , Unit Testing, Source Control , Bugs Checking والخ) الكتاب قديم منذ 2008 الآن لأنه يتكلم عن أدوات ، و الأدوات تتطور مع الوقت لكنه يفيد لأخذ فكرة عن كل أداه ودورها في عملية التطوير [Ship It!](http://www.amazon.com/Practical-Guide-Successful-Software-Projects/dp/0974514047?ref=informatic-ar.com) [![shipit](https://informatic-ar.com/wp-content/uploads/2013/09/shipit.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/shipit.jpg) [Pragmatic Unit Testing in Java with JUnit](http://www.amazon.com/Pragmatic-Unit-Testing-Java-JUnit/dp/0974514012?ref=informatic-ar.com) [![Pragmatic Unit Testing in Java with JUnit](https://informatic-ar.com/wp-content/uploads/2013/09/Pragmatic-Unit-Testing-in-Java-with-JUnit.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Pragmatic-Unit-Testing-in-Java-with-JUnit.jpg) [Pragmatic Project Automation](http://www.amazon.com/Pragmatic-Project-Automation-Deploy-Monitor/dp/0974514039?ref=informatic-ar.com) [![Pragmatic Project Automation](https://informatic-ar.com/wp-content/uploads/2013/09/Pragmatic-Project-Automation.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Pragmatic-Project-Automation.jpg) الثلاثي المرح من اصدار سلسله The Pragmatic Programmers ، هي قديمة منذ 2005 ولكنها كانت وما زالت من الكتب الجيدة ، فكتاب Ship It يوضح لك منهجية بسيطة في تطوير المشاريع وما هي الأمور الذي يجب عملها في اي مشروع برمجي سواء كانت في الأدوات المستخدمه في طريقتك اثناء التطوير (عمل قائمة المهام وكيف تتفاعل مع فريقك) ، كتاب Unit Testing with JUnit هو الكتب القديمة ايضاً عن JUnit والمكتبة حصلت فيها تغييرات كثيرة بعد ذلك ولكنه ما زال يشرح الفكرة والهدف الأساسي من Unit Testing بشكل سلس ومبسط، أما كتاب Automation فهو للمبرمجين الذين لا يستخدموا IDE وهو بصراحة جيد ويبدأ بتعليمك كيف عمل المجلدات في المشروع Folder Structure واين تضع الكود وكود الاختبار وكيف تستخدم ال Building Tool وينتقل تدريجياً بك حتى تصبح عملية الBuild وTesting وال Deployment هي بضغطة زر واحدة ، الكتاب يستخدم Scripts لأنه موجود قبل ان تشتهر ال Continuous Integration ولكنه مفيد مع ذلك. [JUnit In Action](http://www.amazon.com/JUnit-Action-Second-Petar-Tahchiev/dp/1935182021?ref=informatic-ar.com) [![JUnit In Action](https://informatic-ar.com/wp-content/uploads/2013/09/JUnit-In-Action.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/JUnit-In-Action.jpg) الكتاب يعلمك كيف تقوم بعمليات ال Unit Testing وما هي فوائدها، هو اكثر حداثه من الكتاب السابق الخاص ب Junit حيث يتناول الاصدار 4 ويمكن القول بأنه اشمل مرجع عن Unit Testing سواء في تطبيقات ال SE وحتى في ال Enterprise Application. ## **السله الرابعه: هندسة البرمجيات** هذه المرحلة لن تهتم كثيراً بالSyntax أو بالأمور التقنية ، ولكن سوف تنظر للأمور من بعد أعمق، كيف تطور المشروع من البدايه الى النهايه ، وكيف تدير الأشخاص العاملين ، وكيف يمكن ان تتخذ القرارات المناسبة اثناؤ سير المشروع ، كل ذلك ينطوي تحت ادارة المشروع واي مبرمج يجب أن يلم من جانب هندسة البرمجيات وكيفية بنائها من البدايه الى النهايه والا سوف يكون متوقف عند نطقة اللغه. [Code Complete](http://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670?ref=informatic-ar.com) [![Code Complete](https://informatic-ar.com/wp-content/uploads/2013/09/Code-Complete.jpg)](http://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670?ref=informatic-ar.com) الكتاب الأول لكل مهندس برمجيات وهو يتناول قضايا كثيرة في تطوير البرمجيات بدءاً من المتطلبات وكيفة العمل مع الفريق واختيار اللغه المناسبه للمشروع والتقديرات والخ ، اذا أردت أن تكون مهندس برمجيات فعليك بهذا الكتاب. مهم أيضاً ان تتعلم كيف تحدد زمن المشروع أو المهمة التي عليك انجازها ، كثير من اسباب فشل المشاريع هي في تحديد الوقت الخاطئ وبالتالي ترصد ميزانيه خاطئة ،، هنا قرائه : [Agile Estimation And Planning](http://www.amazon.com/Agile-Estimating-Planning-Mike-Cohn/dp/0131479415/?ref=informatic-ar.com) [![Agile Estimation And Planning](https://informatic-ar.com/wp-content/uploads/2013/09/Agile-Estimation-And-Planning.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Agile-Estimation-And-Planning.jpg) الأحداث غير المتوقعه و التحديات اثناء المشروع هي من الامور التي تواجه اي مدير للمشروع أو اي مبرمج مسؤول عن العمل، هذا الكتاب يتناول فكرة الخطط التي تتغير على حسب حالة المشروع وكيف أن الخطة تكون متغيرة طوال فترة المشروع، ويتناول الكثير من القضايا الاخرى في هذا الجانب وفي جانب التقدير بالشكل الصحيح . [The Agile Samurai](http://www.amazon.com/Agile-Samurai-Software-Pragmatic-Programmers/dp/1934356581?ref=informatic-ar.com) [![The Agile Samurai](https://informatic-ar.com/wp-content/uploads/2013/09/The-Agile-Samurai.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/The-Agile-Samurai.jpg) من اسهل الكتب حول منهجية ال Agile ، كتابي المفضل وقد قمت بتخليصه على هذه المدونة وقريباً سيتم الانتهاء من العمل وجمعه على كتاب عربي، الكتاب يوضح لك كل ما تريد عن ال Agile بدئاً من اليوم الأول في مشروعك وكيف تبدأ وكيف تخطط وتنفذ مشروعك باتقان، لا غنى عنه لأي مبرمج هذه الأيام. [Clean Coder](http://www.amazon.com/Clean-Coder-Conduct-Professional-Programmers/dp/0137081073?ref=informatic-ar.com) [![Clean Coder](https://informatic-ar.com/wp-content/uploads/2013/09/Clean-Coder.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Clean-Coder.jpg) أنكل بوب مرة آخرى ولكن هذه المرة في تجاربه ونصائحه التي تعلمها بعد سنوات طويلة من العمل والتطوير ، سوف يوضع نقاط عملية في امور كثيرة مهمه للمبرمج مثلاً التقدير وكيف تتعامل مع مديرك وكيف تقول الحقيقة، وكيف تتعامل مع ضغوط العمل، وكيف تدير وقتك، صراحة الكتاب رائع جداً وهناك الكثير من النقاط المفيدة فيه. ## **السله الخامسه: نظرة حول الصندق JVM** جافا الآن هي ليست الجافا فقط ، بل هي لغه جافا والJVM ، قد لا تصدق ولكن JVM زادت شهرتها عن الجافا لأنها أصبحت منصه تعمل فوقها لغه الجافا واللغات الأخرى التي تعرف ب Polyglot مثلاً Groovy, Scala, Clojure . هذه السله سوف تنظر للأمور واسع اكثر وتزيد من حصة معرفتك في JVM. [Java Performance](http://www.amazon.com/Java-Performance-Charlie-Hunt/dp/0137142528?ref=informatic-ar.com) [![Java Performance](https://informatic-ar.com/wp-content/uploads/2013/09/Java-Performance.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Java-Performance.jpg) افضل كتاب تتعلم فيه ال JVM ماهيته ودوره ، وكيف تقوم باعداده (عمل Performance Tunning) حتى يعمل على مشروعك الكبير هو كتاب Java Performance ، فأغلب برامج جافا الكبيرة تحتاج لما يسمى Performance Engineer يقوم بتناول قضايا الJVM وال Memory وكيف يمكن اعداد وتهئية الGC المناسب لبرنامجك (مثلاً برنامجك تريده ان يعمل 24 ساعه فكيف تريده أن يعمل -سريع في العمل أم سرعه في الاستجابه أم معايير اخرى). [Covert Java](http://www.amazon.com/Covert-Java-Techniques-Decompiling-Engineering/dp/0672326388?ref=informatic-ar.com) [![Covert Java](https://informatic-ar.com/wp-content/uploads/2013/09/Covert-Java.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/Covert-Java.jpg) احياناً سوف تحتاج لتعلم بعض التفاصيل الLow Level عن الجافا مثل Byte Code وكيف يمكن حمايه برنامجك أو العكس كيف يمكن عمل Reverse لبرنامج الجافا ، وكيفية تشويش الكود وكيفية صناعه License System باستخدام ال Digital Signature (يضمن لك أن المستخدم لن يستطيع توليد مفاتيح تسجيل) هناك الكثير من الأمور الممتعه في هذا الكتاب اذا كنت من هواة ال Cracking [The Well-Grounded Java Developer](http://www.amazon.com/Well-Grounded-Java-Developer-techniques-programming/dp/1617290068?ref=informatic-ar.com) [![The Well-Grounded Java Developer](https://informatic-ar.com/wp-content/uploads/2013/09/The-Well-Grounded-Java-Developer.jpg)](https://informatic-ar.com/wp-content/uploads/2013/09/The-Well-Grounded-Java-Developer.jpg) كتاب جديد نوعاً ما في الجافا ومفيد للمطورين المتوسطين أو الخبراء، مقسم مثل هذه المقالة الى الامور الواجب ان يعلمها اي مطور جافا (وهي ما تحتويه السلات السابقة) مثل الأدوات المهمه وال API الجديدة في جافا 7 ، وال Concurrency وال JVM Tunning وال Polyglot Programming ويأخذ مثال على Scala وGroovy، صراحة الكتاب قيم لكنه متقدم ولا ينفع للمبتدئين على الاطلاق. ## **ملاحظات** - لا غني لأي مبرمج عن المشاركة في الOpen Source Projects فهي السبيل لكي ترى ثمرة ما تقرئه واكتساب خبرة حقيقية في البرمجة ، القرائه لا تكفي وحدها. - الكتب اعلاه تتناول ناحية الSoftware Engineering Skills الواجبة لأي Modern Java Developer (وكما لاحظت كثير من الكتب اعلاه تصلح لأي مبرمج بغض النظر عن اللغه) ، لكن اذا أردت التعمق في الGUI أو Network Programming أو Image Processing أو Collection Intelligence فلكل منها كتبها الخاصه، - بنفس الفكرة لم نستعرض كتب ال Java Enterprise وهذا يحتاج لموضوع اخر ولكن اليك الخبر السعيد اذا قطفت من السله الأولى والثانية والثالثة والرابعه والخامسة فال Enterprise لن تكون معقده لك (ستأخذ وقتاً في أولها حيث التقنيات كثيرة ولكن بعد ذلك سوف تستطيع كتابه كود أفضل بكثير من غيرك البرمجين لأنك تعرف جيداً ما تفعل). - حتى تكون مبرمج جيد عليك أن تضيف شيئاً في الProblem Solving Skills ولو في Algorithms And Data Structure هكذا سيزداد ثقلك، كتب Data Structure بالجافا كثيرة وجيدة في الغالب، وهذا الكتاب [Beginning Algorithms](http://www.amazon.com/Beginning-Algorithms-Simon-Harris/dp/0764596748/?ref=informatic-ar.com) رائع فهو يدمج الData Structure مع ال Unit testing ويجعلك تكتب Unit Testing مع اي Structure تقوم بكتابته (Array, Linked List, Stack) وسوف تتعلم الUnit Testing جيداً بهذه الطريقة. - نذكر أن التقسم كان على اساس سلات وليس (مبتدئ ، متوسط ، محترف) اذ ان المبتدئ يمكن (أو يجب) ان ينتقل ويأخذ من هنا وهناك، كل ما تزداد معرفتك وتضع في الToolbox الخاصه بك كمية أكبر، كلما تنتقل من مستوى مبتدئ الى متوسط وهكذا ،، فقط استمر بالقرائه والتكويد. ### كيف تخطط لمشروعك Agile Planning URL: https://informatic-ar.com/agile_planning/ Last updated: 2026-10-03T22:32:26.000Z # التخطيط في ال Agile - هل تستطيع تقدير وقت المشروع بشكل جيد ؟ - هل تريد أن تعرف حالة المشروع بشكل دائم وتعرف مدى تأثير أي حدث على المشروع مثلاً خروج أحد المطورين؟ - هل تعرف ماذا تفعل عندما يكون المشروع في نهايته وقد قارب وقت التسليم وما زال هناك الكثير من المهام ؟ - هل مشاريعك ما زالت متأخرة في التسليم ؟ - هل العميل يطلب اضافه المزيد نم المهام ولكنك لا تستطيع التصرف أمامه أو ابلاغه بتأثير ذلك في المشروع ؟ - هل لديك وثيقة متطلبات ضخمة ولكن العميل يغير ارائه واصبحت لا تعرف ماذا تفعل ؟ بعد أن تعرفت على أساسيات بداية المشروع في ال Agile، انت الآن بحاجه الى أن تخطط بطريقة ال Agile أو ما نطلق عليه ال Agile Planning ، وهذه الخطط تكون متغيرة Adaptive Planning وليست ثابته كما في بعض المنهجيات وهذا يعتمد على حسب العوامل في المشروع، وهي أقرب للواقع واذا قمت بها بشكل جيد فسوف تحصل على كل الاجابات التي تسأل عنها في المشروع، وقد يعي العميل مدى تأثير قراراته عندما يضيف مهام جديدة . [![agile4_0](https://informatic-ar.com/wp-content/uploads/2013/08/agile4_0.png)](https://informatic-ar.com/wp-content/uploads/2013/08/agile4%5F0.png) لمراجعة السلسة (كل ما تحتاجه عن ال Agile): - [ما هي ال Agile ](https://informatic-ar.com/agile%5Fin%5Fnatual/) - [فريق التطوير في ال Agile ](https://informatic-ar.com/agile%5Fteam/) - [كيف تبدأ الخطوة الأولى في المشروع ](https://informatic-ar.com/how%5Fstart%5Fproject/) - كيف تخطط في مشروع ال Agile (المقالة الحالية) في هذه المقالة سوف نتحدث عن ثلاثه أمور أساسية في تطوير المشروع الا وهي: 1. أول خطوة تقوم بها مع العميل وهو كتابه قائمة المهام والأعمال التي يريدها العميل والتي ستقوم بعملها (ويطلق عليها User Stories ) 2. الحديث عن تقدير Estimation تلك المهام الموجودة وكيف يمكن ان تقدر الوقت لها 3. سوف نتحدث عن بناء الخطة التي سوف تعمل بها وتتوقع التسليم على أساسها، وايضاً سوف نناقش بعض المشاكل التي لطالما تحدث اثناء التطوير وكيف سوف تتعامل معها . > المقالة طويلة بعض الشيء لأنها تحتوي على لب فكرة التخطيط Agile Planning فاذا كنت ليست من هواة القرائه للمقالات الطويلة ، فقم بقرائه كل نقطة من النقاط الثلاثه على حده حتى تضمن أفضل استيعاب ممكن لما جاء بها. # النقطة الأولى: متطلبات المشروع User Stories أول شيء تحصل عليه من العميل أو صاحب المشروع هي المتطلبات و الخصائص Features في ذلك المشروع والأشياء التي يريد العميل تطبيقها في النظام، بالطبع عليك ان تقوم بكتابه تلك المتطلبات حتى تقوم بتطبيقها فيما بعد، عندما تعمل بأسلوب ال Agile سوف تنبذ فكرة جمع كل المتطلبات وتفاصيلها الدقيقة وكتابه الوثائق الضخمة Heavy Documentation منذ البدء وذلك لأن الوثائق الكثيرة والكبيرة والتي يتم كتابه وجمع المتطلبات بها نادراً ما تنجح في المشاريع البرمجية، وبالتالي لن يحصل العميل على ما يريد ولا الفريق البرمجي لن بيني ما يريد وسوف يكون هناك ضياع كبير في الوقت والمجهود الذي بذل لكتابه هذه الوثائق. من المشاكل الأخرى التي سوف تقع بها عندما تكون لديك كل المتطلبات مكتوبة في وثائق كبيرة : 1) لا يمكن ان تتغير بسهوله، فالوثيقة مكتوبة وانتهت عملية اخذ المتطلبات وبالتالي لا يوجد مجال في تغييرها ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_1.png) 2) أحيانأ تكون المتطلبات أخذت بشكل خاطئ أو اعطاها العميل بشكل غير جيد وبالتالي بنى المشروع على ذلك الأساس وليس كما يريده العميل ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_2.png) 3) أو أنها تكتب بشكل غامض ويقوم الفريق بالتخمين في تلك الأشياء وبالتالي النتيجة شيء خاطئ، وهكذا اضعت الوقت والجهد ايضاً ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_3.png) هذه المشاكل ( في الاعتماد الكامل على الوثائق Documentations في أخذ المتطلبات ) لم تكن بسبب الحجم فقط وانما في كيفية ايصال المطلوب بشكل جيد Communication ، حيث قد تكون هناك اجزاء تفهم بشكل اخر بناء على ما هو مكتوب، وأحياناً ما هو مكتوب قد يكون له أكثر من تفسير، مثال على ذلك الجملة التالية سوف تجد أنه يمكن ان تفهم من كل جمله شيء اخر: ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_4.png) قد لا تظن أن هذا أمر مهم ولكن في الحقيقة الأمر كذلك، فقد حدث في السابق ان خطأ في جملة واحدة تسبب في خسارة ملايين الدولارت، بسبب جملة خاطئة حيث أدت لشيء مغاير لما يريده العميل (خطأ في كلمة واحدة أدت الى خسارة مليون دولار في أحدى الشركات الكندية The Comma That Costs 1 Million Dollars ) [المصدر ](http://www.nytimes.com/2006/10/25/business/worldbusiness/25comma.html?ref=informatic-ar.com). وهذا يقودنا الى القاعده المهمه في ال Agile: > **أفضل طريقة لأخذ المعلومات والمتطلبات هي التي تكون وجها لوجه** ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_5.png) ما نريده كبديل هو شيء نستطيع من خلاله حفظ المحادثة Conversation التي تجري اثناء الحديث حول المتطلبات وتكون بها جوهر أو زبدة ما يريده العميل، وفي نفس الوقت تكون صغيرة وواضحه بحيث عندما نتحدث حول هذا الموضوع بالامكان الاشارة للاسم فقط وسوف يعرف الجميع اننا نتحدث عن هذه النقطة. ### **مرحباً بال** **User Story** حيث هي عباره عن وصف قصير للخصائص Features التي يريدها العميل في برنامجه، وهي تكتب في بطاقات أو أوراق صغيرة Index Card (حتى لا تقوم بكتابه كل شيء) وفي نفس الوقت تحمل فكرة الخاصية التي يريدها العميل [![UserStoryIndexCard](https://informatic-ar.com/wp-content/uploads/2013/08/UserStoryIndexCard.jpg)](https://informatic-ar.com/wp-content/uploads/2013/08/UserStoryIndexCard.jpg) ربما تتسأئل هل ورقة صغيرة تكفي لوصف كل الخاصية وما يحيط بها من كل الجوانب؟ ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_6.png) في الأساس لا يجب ان تكتب كل شيء، فعملية استخراج المتطلبات Requirement Gathering لا يجب ان تقوم بها بأخذ كل التفاصيل، ولكنك ستأخذ كل الخصائص والأشياء الواضحه وتكتبها على الورق الصغير وتقوم بحفظها في ملف وسوف ترجع لها فيما بعد. على سبيل المثال قمت بجمع 10 خصائص يريدها العميل، فلا يجب الأن ان تقوم بأخذ كل صغيرة وكبيرة عن هذه الخصائص، والسبب أنه مع الوقت قد يتم الغاء الخاصية بأكملها فكل شيء يتغير مع الوقت. لذلك احفظ وقتك وطاقتك حيث ستقوم بالدخول في التفاصيل فيما بعد (الدرس القادم سوف يتحدث عن كيفية التحليل وأخذ كل الجوانب في هذه المتطلبات البسيطة)، ويمكنك القول بأن ال User Story هي عباره عن المحادثه التي جرت ، وقمت بتدوينها وسوف تعود للأشياء التي قد تعمل عليها في وقت لاحق. ### **كيف تكتب** **User Story** **جيدة** أول نقطه في كتابه User Story جيدة هي أن تحمل شيء مفيد للعميل Something of Value بمعنى أخر شيء سوف يدفع لك من أجله حتى تقوم بعمله، على سبيل المثال تقوم بكتابه مشروع لمطعم فيجب عليك كتابتها بلغه العميل حتى تكون مفهومه له ولك، المثال التالي يوضح الفرق بين قائمتين وانظر للفرق جيداً: ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_7.png) كما هو واضح أن الصورة الثانية افضل لأنها بلغه العميل وبالطريقة التي يعرفها، لذلك ال User Story يجب أن تكون بنفس لهجه الBusiness، فقم بكتابتها دائماً بشكل مبسط وبلغه العميل وابتعد عن المصطلحات التقنية التي لا يعرفها العميل. هذا لا يعني أننا لن نستخدم مثلاً Design Pattern في كتابة الكود، أو Connection Pooling في الاتصال مع قاعدة البيانات، ولكن ال User Story يجب ان تكون واضحه وبلهجه مبسطة، ويمكنك تحويل الكلام التقني الى فوائده التي يستطيع العميل فهمها، مثلاً: ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_8.png) النقطة الثانية في كتابه User Story جيدة، وهي أنها يجب أن تكون حول خاصية بكل جوانبها From End to End من شاشه المستخدم وكود ال Business وايضاً مع ال Database Layer ، يمكنك فهمها على أنها قضمه من الكيك Slices the cake ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_9.png) كما انك لا تستطيع اكل الكيك بشكل افقى فالعميل لا يريد انصاف الحلول او حل غير مكتمل، وهكذا ال User Story يجب أن تكون مثل الSlice (من أعلى الى اسفل) في كل ال Layer في النظام حتى تقدم شيء مفيد Something of Value. النقطة الثالثة وهي انه يجب ان تكون مستقلة Independent، حيث كما تعرف أن الأشياء تتغير اثناء المشروع، فربما بعض متطلبات الأمس تصبح اليوم ليست مهمه، لذلك اذا كانت كل ال User Story مرتبطة ببضعها البعض فسوف يصعب العمل واخذ القرارات فيها، ويمكن استخدام طريقة ال Slicing في حال واجهت صعوبة حيث تسهل عليك فصل الأشياء وجعلها غير مرتبطة. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_10.png) النقطة الرابعه وهي أنها ليس ثابته Negotiable وقابلة للتغيير لأي سبب سواء كان من صاحب المشروع أو بسبب تقنى ، وقد يكون هناك اكثر من طريقة للتطويرها وسيتم الاختيار بناء الميزانية ومتغيرات المشروع ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_11.png) النقطة الخامسة وهي ان تكون قابلة للاختبار Testable ، فكل من هذه الUser Story يجب أن يتم اختبارها والتأكد من أنها تعمل جيداً، ولذلك سنقوم بكتابه الاختبارات والتي تعطي الضوء الأخضر بأن هذه الخاصية تعمل وفقاً للاختبارات التي اجريت عليها. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_12.png) النقطة السادسة وهي أن تكون صغيرة وقابلة للتقدير Estimable، حيث يمكن جعل اغلب ال User Story تكون في حدود يوم الى 5 ايام عمل، وهكذا ستقوم بادخالها في مرحلة Iteration من مراحل التطوير (سيتم الحديث عن مراحل التطوير في الدرس القادم). ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_13.png) النقاط الستة السابقة تسمى INVEST وكل حرف منها اختصار ل Independent, Negotiable, Valuable, Estimable, Small and Testable، والجدول التالية بين الفرق بين ال User Story ووثائق المتطلبات Requirement Documents: ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_14.png) لنأخذ مثال عملي على شخص يملك شاطئ ويريد منك عمل موقع خاص به، بكل تأكيد سوف تقوم بالجلوس معه وتبدأ بمعرفة ما يريده هو في ذلك الموقع بدون الدخول في التفاصيل الدقيقة High Level Descriptions ، ولنقل ان المحاورة كانت: 1) اريد موقع لسكان المنطقة، حيث يمكن عرض المسابقات وأوقات حدوث الأمواج في البحر التي تحصل بها حتى يتمكن الجميع من معرفتها وأمور مشابه لذلك. 2) اريد مكاناً لعرض البضائع مثل الواح السباحة وملابس وأدوات الغطس وأشرطة الفيديو وأشياء من هذا القبيل ويجب أن يكون ذو مظهر جيد وسهل الاستخدام 3) أريد كاميرا ويب على الشاطئ تعرض صور للشاطئ محدثه باستمرار في الموقع، حتى يتمكن الزوار من مشاهدته ومعرفة الطقس قبل المجئ للشاطئ، ويجب أن ان يكون الموقع سريعاً. الآن حاول ان تستخرج 6 مهام من التفاصيل السابقة 6 User Stories ، لا تقلق اذا لم تستطيع كتابتها بشكل جيد فهذا تمرين لكي تستطيع تحويل ما تسمعه من العميل الى User Story كما يريديها العميل. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_15.png) بعد محاولتك في استخراج ال User Stories قم باختبارها هل حققت مفهوم ال INVEST ؟ أي هل هي Independent, Negotiable, Valuable, Estimable, Small and Testable ولا تقلق اذا لم تستطيع جعلها جيدة منذ البدء وركز على جعلها مفيدة للعميل وانها تمثل شيء يريده العميل. هذه أول محاولة لكتابة ال User Stories للتفاصيل الموجودة: ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_16.png) السؤال الآن هل أخر وظيفتين (الموقع يجب ان يكون سريع، و التصميم يجب ان يكون جيد) هم User Stories جيدة أم لأ ؟ قد تفكر بأن المهمه الموقع يجب أن يكون سريعاً (The website must be super-fast) غامضة قليلاً وهذا صحيح، حيث كلمة سريع جداً Super-fast لا توضح السرعة المرغوبة، وبنفس المنطلق التصميم يكون جيداً (The design should look really good) فكيف يمكن ان تعرف ان الموقع يظهر بشكل جيد أم لأ. هذه ال Stories تسمى قيود Constraints فهي ليست User Stories يجب أن نطورها في كل دورة تطوير، وهي مهمه ايضاً لأنه تحدد مالذي يريده العميل في برنامجه، ويمكن ان نضعها على Card ولكن تكون ورقة قيد Constraint Card بعد تحويلها لشيء مفهوم –اذا استطعت- بشكل أفضل حتى يتم اختباره ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_17.png) هكذا أصبحت اوضح وعرفت ماذا تعني كلمة Super-Fast وهكذا تستطيع كتابه الاختبارات لها. ويفضل ان تضع ال Constraints على Card مختلفة اللون من ال User Stories حتى تفرق بينهم، واجعل فريق التطوير يعرفوا القيود جيداً وقم باختبارها بشكل متكرر اثناء تطوير المشروع. ### **القالب العام** **Template** **لل** **User Story** اي مجموعه من الكلمات توضح مقصد العميل يمكن ان تكون في ال User Story ، ولكن ان أردت ان تستخدم نسق معين في كتابتها فيمكن ان تستخدم الطريقة التالية (تقوم بكتابه من الذي سيقوم بهذا الدور وما هو الدور الذي سيقوم به والسبب الذي يجعله يقوم بذلك): ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_18.png) اذا طبقنا الطريقة هذه في تفاصيل الموقع السابق يمكن ان نخرج بالوصف التالي: 1) كرياضي احب ركوب الامواج، اريد ان اتحقق من الطقس قبل الخروج من المنزل ، حتى لا اذهب الى هناك ولا اجد امواج قوية 2) كرياضي في ركوب الامواج، اريد ان ارى احدث الأدوات والتصاميم للملابس ، حتى ابدوا بمنظر جيد في الصيف الشيء الجيد في القالب هو انه يجب على 3 أسئلة مهمه في ال User Story وهي من Who وماذا What ولماذا Why. فيكتب بها جمل وتوضيحات اكثر وتركز على ما يريده العميل وهذا المطلوب. على كل هناك من لا يفضل الاطالة بهذه الاسئلة الثلاث وهناك من يفضل ذلك، لذلك يمكنك تجربة كل منهم واختيار ما تراه مناسباً لك. ويمكنك أن تعمل بهم الاثنين، فتكتب في الورقة من الأمام الوصف القصير ومن الخلف تقوم باتباع القالب اذا كان سيفيدك ذلك فيما بعد. ### **كيف تأخذ ال** **User Story** **من العميل ؟** قبل أن تنشىء الخطة التي ستبنى على اساسها المشروع، يجب ان تقوم باعداد كل الFeatures التي يريدها العميل في مشروعه، واحد الطرق لأخذها هي عن طريق اجتماع العميل مع فريق التطوير Story Gathering Workshop. الغرض من هذا الاجتماع هو الحديث بشكل عام Breadth واكتشاف ال Features بقدر ما تستطيع والهدف من ذلك ليس لبنائها بتفاصيلها بل لايجادها جميعهاً ومعرفة مالذي سيتم بنائه ومالذي لن يتم بنائه وجعلك على الصورة كاملة. هذه بعض من النقاط المفيدة: 1) ايجاد غرفة كبيرة بها طاولة: لأنك ستتحرك وتأخذ الأوراق وتضعها على اللوحة، وتكتب في اوراق والخ ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_19.png) 2) رسم المخطاطات والصور التوضيحية: فالصور هي وسيلة جيدة لعصر الدماغ واستخراج الأفكار وال Stories ، الصورة التالية توضح ال Personas (الأشخاص الذين سيعملوا على النظام) وهي جيدة لمعرفة المستخدمين، وتوضح ال Flowcharts وال Scenarios وال Process Flows فهي جيدة لتوضيح الأدوار في النظام وكيف يعمل النظام، وتوضح ال System map لتقسيم اجزاء النظام، وتوضح ال Concept Design وال Paper Prototypes وهي لتوضيح ما يمكن ان يقوم النظام به. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_20.png) بعد أن تقوم برسم مجموعه من الصور التوضيحية ومعرفة كيف يعمل النظام يمكن بعدها استخراج ال Stories من الصور التوضيحية. 3) كتابه الكثير من ال Stories: يمكنك الان ان تستخرج ال Stories مع العميل من تلك الرسوم التوضيحية ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_21.png) اذا كان كل البرنامج موجود فقط في صورة واحدة أو اثنين، فيمكنك اخذها وتقسيمها لأجزاء أصغر، وفي العادة من Flow Chart واحد ممكن اخذ معظم الCore Stories في المشروع. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_22.png) قد تجد بعد ان تنتهي من استخراج ال User Stories أن هناك Stories كبيرة وهي تسمى Epics (الUser Stories التي تأخذ عده اسابيع من العمل). وال Epics سوف يتم تقسيمها لعدة Stories وقت التطوير عندما تبدأ العمل عليها. وفي الأخير 10 الى 40 User Stories تعتبر كافية للعمل من 3 أشهر الى 6 أشهر، اذا كانت لديك مئات ال Stories فهذا يدل انك قمت بالدخول في ادق التفاصيل وهذا خطأ فنحن نريد النظرة العامة Breadth وليست التفاصيل الدقيقة Depth. 4) فكر بأي شيء أخر: الصور التوضيحية مفيدة ولكنك قد لا تستطيع اخذ كل شيء بها، مثلاً الاختبارات Load Testing/User Acceptance Testing ، دليل البرنامج Manuals and Documentations ، أدوات التدريب Training Materials ، وغيرها ، فيجب كتابتها سواء ك Constrains أو Stories عادية مع غيرها. 5) ترتيب القائمة: بعد أن جهزت القائمة قم باعادة النظر اليها عده مرات وقم بحذف المتكرر ان وجد، قم بالنظر عن الاشياء التي نسيت ان تكتبها، رتب المهام بشكل منطقي مع بعضها وهكذا تكون بدئت في اعداد خطة مشروعك. الآن لديك جميع المهام وأنت جاهز لمرحلة التقدير. # **النقطة الثانية : التقدير Estimation** حان الوقت للتعرف على كيفية التقدير للمشروع بطريقة ال Agile ومعرفة المهام وكم سيستغرق تطويرها، والتقدير بشكل عام هو من أصعب الأمور التي يواجهها المطورين ،، والسبب هو نسيان أن التقديرات الأوليه ما هي الا مجرد تخمين Guesses وفي الغالب تكون سيئة ايضاً ، والمشكلة بعد ذلك أنه يتم الزام المطورين عليها وجعلها كوعد للتسليم Hard Commitments لا يمكن ان يتأخر عنه. ستيف ماكونيل (صاحب كتاب Code Complete الشهير) سمى عملية التقدير بعدم اليقين cone of uncertainty ووضح أن التقدير الأولي قد يختلف بنسبة تصل الى 400% عن الوقت الحقيقي للمشروع، ولذلك يجب أن تدرك أن التقديرات الدقيقة منذ البدء امر لا يمكن أن يتم بشكل كامل ، ويجب ان تتوقف من التفكير بهذه الناحية. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_24.png) الشيء الوحيد الذي سوف تجيب عليه التقديرات الأولية هو الاجابه على السؤال: **هل يمكن القيام بهذا المشروع في هذا الوقت وبهذه المصادر Resources المتوفرة؟** اذاً نحن بحاجه الى طريقة للتقدير تسهل لنا التخطيط في تطوير المشروع، وفي نفس الوقت تذكرنا بأن هذه التقديرات الأولية هي تخمينات. في ال Agile نحن دائماً متفقين على ان التقديرات الأولية هي مجرد تخمينات ولا نثق بها، وفي نفس الوقت ندرك ان الميزانية يجب ان توضح والتوقعات حول المشروع يجب أن تكون موجودة، ولكي نحل هذا التعارض نقوم بما يفعله اي شخص وهو بناء الشيء ومن ثم حساب الوقت الذي تم اخذه في البناء واستخدام هذا الوقت في في تقدير البقية الأشياء. ولكي تقيس بشكل صحيح، فأنت تحتاج الى : 1) قائمة المهام التي يريدها العميل User Stories مرتبه في مجموعات على حسب حجمها Relative to each other 2) طريقة تسمح لنا بمعرفة التقدم في التطوير point based system لكي نرتب المهام على حسب حجمها Relative Sizing نأخذ المثال التالي لكي تفهم الفكرة ، لنفترض انك تستطيع اكل قطعه من الكعك في 10 ثواني، وطلبنا منك تقدير الوقت اللازم لأكل 7 كعكات و 14 كعكة ؟ فما هو جوابك ؟ ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_25.png) الآن لنفرض أننا طلبنا منك تقدير شيء آخر، شيء بسيط ولكنك لم تقم بذلك من قبل، فكم الوقت المطلوب في كل من هذه المهام ؟ ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_26.png) بالتأكيد ستجد أن عملية التقدير الأولى (تقدير عملية الأكل بناء على تقدير موجود مسبقاً Relative Sizing) اسهل بكثير من الثانية (تقدير شيء جديد بدون عمله مسبقاً Absolute). والفرق بين التمرينين، هو أن الأول كان التقدير عملية نسبية Relatively بينما التمرين الثاني كان عملية تقدير مطلقة Absolute. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_27.png) والبشر بطبيعتهم يستطيعوا التقدير بسهوله بشكل نسبي Relative Estimations ، لنأخذ مثال لديك صخرتين أمامك فيمكنك بسهوله ان تعرف ان الصخرة الأولى اكبر من الثانية Relative Sizing، بينما لا تستطيع ان تعرف حجم الصخرة بشكل دقيق Absolute Estimation ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_28.png) هذا المبدأ البسيط يعتبر من أسس ال Agile Estimation ، فبطريقة التقدير النسبية وحساب كم تأخذ كل منهم يمكن أن تبدأ بعمل خطة التطوير وتضع التوقعات حول تواريخ التسليم. الصورة التالين تبين قائمة المهام على اليسار وبعد أن تقوم بمعرفه أحجامها بالطريقة اعلاه وتحسب كمية العمل المنجزة بواسطة الفريق (سرعه الفريق سوف ننتاولها فيما يلي) سوف تستطيع معرفة متى ستنتهي عملية التطوير وتطلق المشروع. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_29.png) هناك ملاحظة مهمه بطريقة التقدير النسبية Relative Estimation الا وهي أن اليوم الواحد في التقدير لا يعني يوم واحد في العمل، وقد يعمل الفريق بشكل أسرع أو ابطئ من ما هو في التقدير. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_30.png) على سبيل المثال نفرض اننا قدرنا أن ال Story تأخذ 3 ايام من العمل ولكن في الحقيقة تم الانتهاء منها بعد 4 ايام. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_31.png) هكذا سوف نحتاج لتعديل كل التقديرات لكل ال Stories بهذه النسبة الجديدة (مقدار 33%) ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_32.png) لدينا مشكلة وهي ان الارقام 1.33 يوم ماذا تعني ، والمشكلة الثاني هي اعادة التقدير للجميع في حالة اكتشفنا ان الوقت الفعلي اكثر. لحل هذه المشكلة، وتقادي عملية اعادة تقدير كل ال User Stories مجدداً فسوف نقوم بعملية التقدير باستخدام النقاط Point System ### **نظام النقاط لتقدير المهام Point Based System** سوف يسهل لنا هذا النظام معرفة مدى التقدم Track Progress وايضاً التقدير النسبي بدون القلق حول التقدم الفعلي مقارنه بالتقدير. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_33.png) بهذه الطريقة التقدير يكون نسبي، ووحدة القياس ليست مهمه ، فالمهمه هي معرفة المهام وترتبيها بشكل نسبي مقارنة بالمهام الاخرى ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_34.png) للتسهيل اكثر يمكنك التفكير بأن التقدير هي كعملية معرفة حجم التيشرت (مقاس صغير Small Size، مقاس وسط Medium Size، مقاس كبير Large Size). هذه الطريقة Point Based System سهله وبسيطة وتذكرنا بأن التقديرات هو مجرد تخمين وتصور المهمه بحجمها الطبيعي، هناك بعض الفرق Teams تستخدم مفهوم اليوم المثالي Ideal Days بدلاً من النقاط وهو اليوم الذي تعمل فيه بدون توقف أو اي مقاطعات وفي الحقيقة هذا صعب في بيئات العمل فهناك دائماً توقفات وانقطاعات ولا يمكن العمل 8 ساعات بدون اي توقف الا نادراً. لذلك سوف نستخدم مفهوم النقاط Points. الآن لكي تقدر المهام الموجود لديك ، توجد طريقتين يمكن ان تستخدمها لعمل التقديرات لكل ال Stories لديك: 1) طريقة ال Triangulation 2) الاجتماع مع الفريق وتقدير المهام مع بعض (تسمى Planning Poker) ### طريقة ال Triangulation وهي ببساطة اخذ اي Stories سهله وتقديرها ومن ثم تقدير بقية ال Stories بناء على ذلك ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_35.png) لنفرض أن لديك هذه ال User Stories وأنك بحاجه لعمل Estimation لها ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_36.png) قبل ان تبدأ فنحن نريد تقسيم المهام الصغيرة والمتوسطة والكبيرة حتى نعرف كيف نضمها في دورة التطوير Iteration (في الغالب اسبوع او اثنين)، وايضاً التقسيم يجب أن يكون منطقياً بمعنى المهام المرتبطة ببضعها لأداء غرض، وبمجرد ان تبدأ وتختار ما تستطيع ان تقسمه سوف يكون هو الأساس الذي سوف تعتمد عليه في البقية. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_37.png) بعد أن تقسم أول مهام سوف تستطيع تقدير المهام الأخرى بالإعتماد على المهام التي قسمتها في أول خطوة ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_38.png) قد تحتاج لأن تقوم بمراجعه القائمة بعد التقسيم وتعيد تقسيم المهام التي لم تقدرها جيداً، ولكن لا ينبغى تعديلها بشكل دائم لأنك بعد ذلك سوف تقيس سرعة انجازك بالاعتماد عليها. واذا لم تستطيع تقدير مهمه بسبب أنك لم تقم بعمل اي شيء مشابه في السابق، فيمكنك القيام بال Spike وهي تعني القيام بعملية التجربة والبحث في ذلك الموضوع في فترة زمنية معينة حتى تستطيع تقدير تلك المهمه لكنك لن تقوم بعمل المهمه وانما محاولة أخذ فكرة عن كم ستأخذ في العمل عليها فقط. وال Spikes لا تتجاوز في الغالب عدة ايام وهي بالفعل مفيدة حيث ستحصل من خلالها على معلومات كافية تستطيع اخبار العميل بكم ستأخذ هذه المهمه وهو من سيقرر القيام بها أم لا. ### الاجتماع مع الفريق للتقدير Planning Poker هي لعبه بسيطة تقوم بها مع الفريق في الاجتماع، فكرتها هي ان تقوم باختيار مهمه ومن ثم يقول كل فرد في الفريق كم ستأخذ، ولكن سوف يقوم الفريق كلهم في آن واحد حتى لا يغش البعض ويأخذ اجابات الغير وذلك عن طريق وضع رقم التقدير المكتوب على ورقة أو بطاقة مباشرة على الطاولة، هكذا سيضع الجميع بطاقاتهم بمجرد سماع المهمه، ومن ثم تبدأ المقارنه والنقاش حول الأرقام المكتوبة. فاذا كانت الأرقام جميعها متساوية فهذا يعني ان هذا الرقم هو الصحيح، أما اذا اختلفت فيتم النقاش حولها واختيار الأفضل بناء على سؤال كل فرد في الفريق لماذا وضعت الرقم بهذا الشكل (لا يهم من الصحيح ومن الخطأ) وهكذا حتى يتم اخذ الرقم الذي سيتم الاتفاق عليه. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_39.png) ال Planning Poker مفيدة لأن الأشخاص الذين يقوموا بعمل التقدير هم من سيقوم بتلك المهام (المبرمجين)، وايضاً قد يكون ال DBA أو المصممين موجودين في الاجتماع في حال كانت لديهم مهمه في العمل ايضاً. من الملاحظات المعروفة وهي أن ال Planning Poker ليست نظام تصويت Voting System لكنها طريقة لجعل الجميع يدلوا بأرائهم ومن ثم يتم اخذ التقدير الصحيح بناء على المناقشة التي ستدار بين اعضاء الفريق. هناك أدوات تجارية تساعدك في ال Planning Poker ووتحتوي على بطاقات بأرقام 8,13,20,40 و 100 ولكنك في الغالب لا تحتاج لذلك، فقط الارقام 1 و 3 و 5 تكفي للقيام بذلك. الآن قمنا بعمل التقديرات باستخدام طريقة Point-Based System والآن أنت جاهز في بناء أول خطة لك Agile Plan. # **النقطة الثالثة: التخطيط في ال** **Agile**: كثير من الأحيان لا تكون الأمور كما هو متوقع، واذا لم تكن لديك القابلية للعمل تحت ظروف التغيير فإنك ستقع في مشاكل جمة ولن تستطيع اكمال المشروع، وهذه من مشاكل الخطط الثابته التي لا تتغير فالمشروع قد يبدأ بشكل جميل، ولديك الفريق الممتاز، وتستخدم التقنية الصحيحة ولديك خطة التطوير الكاملة، لكن بعد عده اسابيع من العمل تتفأجي بأن الفريق سوف يتغير ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_40.png) فقد يغادر ال Lead Developer من المشروع سواء لشركة أخرى أو حتى مشروع مهم أخي في نفس الشركة، وهكذا ستجد نفسك لا تنتج كما هو في الخطة (الشيء الذي كنت تتوقعه هو بعيد عن الشيء الذي يستطيع عمله الفريق). ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_41.png) من المشاكل الأخرى وهي تغيير المتطلبات فقد يكتشف العمل انه حقاً يريد عمل تلك الخاصية ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_42.png) فالموقع الذي بدا أنه بسيط اصبح صعب ومعقد وبه الكثير من التفاصيل، ومن الصعب انجازه في الوقت المتبقى والمصادر المتاحه لك، وفي الأخير سوف تحصل المشكلة الكبيرة وهي أن الوقت انتهي ومازلت لم تكمل العمل ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_43.png) الشيء الذي يحصل هو ان العميل يريد المشروع بسرعه في أسرع وقت، وسوف يبدأ المبرمجين باكمال العمل بأي شكل، المهم ان ينتهي ولو كان بالاستعجال بدون مراعاه للكود وتصميمه Rushing ولن تتم الاختبارات بشكل جيد، والفريق سوف يعمل طوال اليوم ويتم الغاء الاجازات لهذا الفريق، واخيراً عندما يتم انجاز العمل سوف يكون بجودة رديئة والنتيجه لن يستخدم أحد هذا المشروع، هكذا اصبح لدينا مشروع فاشل أخر متأخر late كلف أكثر من الميزانية المطروحة over budget. اذا حصلت لك هذه القصه أو كنت قريباً من تلك الحادثه، فلا تخف فلست أنت لوحدك، فتغير الفريق، والمتطلبات هي شيء عادي في تطوير اي مشروع حقيقي. ولكى تتعامل مع هذا الواقع يجب ان نستخدم طريقة للتخطيط تسمح لنا: 1) انتاج شيء مفيد للعميل 2) طريقة واضحه للجميع وصادقة في نفس الوقت 3) تجعلك تستطيع الوعد بالتسليم من خلال الاعتماد عليها 4) تتكيف مع التغيرات في المشروع بحيث تتغير الخطة بما يلزم الأمر ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_45.png) مرحباً في ال Agile Plan ، وهي في شكلها البسيط عباره عن **طريقة لقياس سرعه الفريق من حيث قدرتهم على تحويل ال User Stories الى برنامج يعمل بشكل جيد ، ومن ثم باستخدام هذا المقياس يمكن تخمين متى سوف تنتهي من العمل.** ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_46.png) قائمة المهام في مشروع الAgile تسمى ب Master Story List وهي تحتوي على جميع ال Features التي يريدها العميل في المشروع، والسرعه التي تستطيع انجاز ال User Story وتحويلها لشيء يعمل تسمى Team Velocity وهو ما ستستخدمه لقياس سرعه الانتاجية للفريق ووضع التوقعات Expectation حول مواعيد التسليم في المستقبل. هذه العمليات تتم خلال دورة التطوير (تسمى Iteration) وهي اسبوع أو اثنان من العمل في ال User Stories الموجودة وتحويلها لشكل قابل للعمل Production Ready Software. حتى يتم حساب وقت التسليم يتم قسمه الجهد المطلوب في المشروع على سرعه الفريق ومن ثم يتم حساب كم Iteration مطلوبة حتى تسلم المشروع، وهذه هي الخطة التي ستعمل على أساسها Agile Planning. #Iterations = Efforts / Team Velocity على سبيل المثال: #Iterations = 100 pts / 10 pts per iteration #iterations = 10 من المهم معرفة أن أول Plan لن تكون دقيقة (وهي ليست وعد بالتسليم Hard Commitment) هي مجرد تخمين حيث أنك لا تعلم سرعه الفريق منذ بداية المشروع، وحتى تقوم بالعمل في المشروع ومن ثم تقيس سرعتك فلا يمكنك معرفة أن هذا الموعد الأولي مقبول وفي النطاق أو غير منطقى اطلاقاً. **لذلك كثير من المشاريع تفشل في بداياتها أو ربما قبل البدء بسبب معاملة الخطة الأولوية Initial Plan كوعد للتسليم Hard Commitment**. حيث عندما تبدأ بالعمل قد تجد أنك تعمل بشكل أسرع مما كنت متوقع أو أنك تعمل بشكل أبطئ مما كنت متوقع (وهو الغالب). ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_47.png) أن تعمل بشكل أسرع فهذا يعني أنك وفريقك متقدمان على الخطة أو ال Schedule ، وأن تكون أبطئ وهو الأمر العادي حصوله، فهذا يعني أن هناك الكثير من المهام يجب عملها وأن الوقت غير كافي لكل تلك المهام. في ال Agile عندما تكون في موقف هكذا يقوم الفريق بعمل المهام الأقل فبدلاً من الالتزام بالخطة التي لا يمكن عملها فيقوموا بتغييرها وتقليل المهام أو نطاق المشروع (تسمى ب Reduce Scope). ### يجب أن تكون مرناً من ناحية ال Scope بهذه الطريقة تكون مشاريع ال Agile محافظه على تكاملها وسير العمل، فمثلاً تجعل العميل يزيل بعضاً من المهام Story عندما يقوم باضافة واحدة جديدة وهكذا تسمح له بتغيير بعض ارائه بدون أن يدفع اي تكاليف عالية جديدة،. ## ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_48.png) في حال لم يريد العميل أن يزيل ال Story عندما يضيف واحدة جديدة وهو مستعد على دفع اي تكاليف جديدة عليها فعليك في هذه اللحظة هو تأخير موعد التسليم Push our delivery date. لذلك لكي تتعامل في هذه الحالات لا يوجد لديك الا أن تأخر موعد التسليم أو تقلل ال Scope ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_49.png) **الذي لا يمكن أن يحدث أن يضيف العميل في القائمة ولا يتوقع حدوث مقابل لذلك سواء كان في الوقت أو ال Scope،** فهذا لا يمكن أن يحدث في ال Agile، وعندما تكون بين الأمرين (تأخير الوقت أو تقليل ال Scope ) فال Agile يختاروا الثانية وهي تقليل المهام و تقليصها. قد تتسائل ماذا اذا رفض العميل أن يكون مرنا من ناحية الScope وفي نفس الوقت يريد المزيد من العمل والمهام ؟ في هذه الحالة لديك بعض الخيارات: 1) تتجاهله وتعمل كما الخطة الأصلية، أو أن تعطيه تقدير جديد للوقت بدون أن تنظر لسرعه الفريق وتأمل أن تنتهي الأمور في الأخير وأنت لا تعرف كيف سيحصل ذلك (الانتظار لحدوث المعجزة Management by Miracle). 2) عندما لا تجد طريقة وتفشل كل خيارات، عليك بتقديم الحقائق وتقولها كما هي "على بلاطة" وتنتظر ردة الفعل من الادارة أو العميل، حيث انك لا تستطيع المداراة أكثر من ذلك وهذا الأمر لطالما يحصل عند التطوير وهو سبب كثير من المشاكل. ### كيف تبنى أول خطة Agile في مشروعك؟ لديك 5 خطوات بسيطة: #### **1) انشئ قائمة المهام Master Story List** قائمة المهام هي قائمة تحتوي على ال User Story (ال Features المطلوبة) التي يريدها العميل وتكون مرتبة بحسب الأهمية التي يريدها العميل (الFeatures الأكثر أهمية أولاً ثم التي تليها وهكذا) وتقوم وفريقك بتقدير Estimate الوقت لها وتكون هذه هي أساس خطتك لتطوير المشروع. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_50.png) في الغالب تحتوى قائمة المهام على كمية من العمل تقدر من شهر الى 6 أشهر ولا تحتاج لأن تكون أكثر من ذلك لأنك لا تعرف مالذي سيتغير في العالم بعد ذلك الوقت ولا الذي سوف يحدث للمشروع حيث قد لا تصل لتلك المرحلة من الأساس فلماذا تزعج نفسك بفترة طويلة الأمد. بعض الأحيان قد تقوم بعمل كل شيء في القائمة ولكن قد يحدث ايضاً انك لا تستطيع اكمالها والسبب انه توجد أعمال أكثر من المال والوقت المتاح للمشروع، لذلك عليك بتحديد مالذي سوف يكون الآن وما هي ال Features التي سوف تأتي فيما بعد وسوف نقوم بتقسيم المهام الى مجموعات ونقوم بالعمل على كل منها وكل اصدار Release من المشروع سوف يحتوى على تلك المهام، وهكذا سوف يكون التطوير على مراحل Releases. ال Release هي مجموعه من ال Stories متعلقة ببعضها البعض وتقوم بتطويرها لتخرج شيء له قيمة في المشروع وتسمى أحياناً ب Minimal Marketable Features واختصاراً MMF والحرف الأول M يعني أننا نريد تطوير شيء له قيمة بسرعه (في الغالب 80% من قيمة المشروع تكون في 20% من الFeatures) لذلك عليك أن تختار أقل عدد من ال Features التي تحتوى على صلب المشروع والتي تجعل أول Release لها أكبر قيمة. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_50b.png) ال M الثانية تذكرك بأن ال Release يجب أن تكون مفيدة للعميل (والا فلن يستخدمه) لذلك فال Minimal وال Marketable من الأمور المهمه في اختيار ال Stories لأول Release في مشروعك. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_51.png) بعد أن تقوم بعمل ال Releases وتحديد قائمة المهام سوف تبدأ بعملية التقدير Estimations. #### **2) تقدير المهام Estimations** وقد سبق أن تحدثنا عن الطرق المستخدمه في هذا الموضوع، وهنا نريد معرفة كم تأخذ فترة التطوير وهل هي شهر أم اثنان أم ثلاثه أم 9 أشهر ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_52.png) فبعد أن تقوم بتقدير المشروع باحدى الطريقتين ( ال Triangulation أو ال Planning Poker) عليك بترتيب المهام على حسب أولويتها أو أهميتها بالنسبة للعميل. #### **3) ترتيب المهام Prioritizing** العمل يجب أن يكون على الأمور الأكثر أهمية في المشروع أولاً، فاجعل العميل يحدد المهام الموجودة واعطائها الأهمية التي يريد من جهه منظورة (منظور العمل Business Point of View) ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_53.png) كما يحق للعميل بتحديد ماالذي سيتم بنائه ومتى فيمكن لك تقديم الاقتراحات حول ال Stories التي يجب أن تبنى من الأول والتي تراها ضرورية في العمل أو لكي تقلل اي خسائر يمكن أن تحدث Reduce Architecture Risk لذلك لا تخف من تقديم تلك المهام والتحدث عنها فخبرتك مهمه هنا. بعد أن تنتهي من الترتيب ولديك قائمة مرتبه ومقدرة فعليك أن تعرف كيف يمكن ان تقيس سرعه الفريق أو المطورين في العمل. #### **4) قياس سرعه الفريق في العمل Team Velocity** خطة ال Agile تعمل في الواقع والسبب أننا خططنا للمستقبل القريب على حسب ما نعرف من قدرتنا للتنفيذ بناء على خبرتنا السابقة، لكن بما أننا لا نستطيع معرفة سرعه الفريق في التنفيذ منذ أول المشروع فاننا نحتاج للتخمين ايضاً. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_54.png) اذا كانت كل ال Stories لديها نفس الحجم ، مقياس السرعه يكون عن طريق المعادلة: Team Velocity = Stories Completed / Iteration لكن الغالب لن يكون بهذا الشكل لأن ال Stories سوف تختلف حجمها وتقاس بالمعادلة: Team Velocity = Story Points Completed / Iteration في أول المشروع سوف تكون السرعه متقلبة لذلك لا تقلق فهذا الأمر طبيعي حيث أن أعضاء الفريق يحتاجوا للتعود على البيئة والعمل مع بعضهم البعض بأفضل شكل. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_55.png) بعد ثلاثه أو اربعه دورات Iterations سوف تثبت ال Velocity ويمكنك معرفة سرعه الفريق بشكل جيد حينها، ولا توجد قاعدة جيدة لذلك، فعليك بسؤال الفريق كم يمكن أن ينجزوا خلال ال Iteration وضع في اعتبارك تواجد العميل (اذا احتاجه الفريق اثناء العمل) وايضاً تواجد الفريق مع بعضهم في مكان واحد Co-Located Team. من المهم أن تذكر الفريق أن كلمة انتهت Done تعني أنك بالفعل أنتهيت (تحدثنا عنها في أول موضوع عن ال Agile) من كل شيء بخصوص هذه ال Story وهذا يشمل التحليل Analysis والاختبار Testing والتصميم Designing والتطوير Coding كل شيء متعلق بها. أيضاً لا يحبذ أن تكون متشدد في تقديرك الأولى ، اذا كنت تنظر بشكل عالي جداً Shoot too high فقد تجد صعوبة في اقناع العميل والتحدث معه أكثر بكثير من اذا كنت تنظر بشكل بشكل متواضع جداً Shoot too low لذلك كن متوسطاً وذكر كل العاملين Stakeholders بأنك تقدر فقط وابدء بالقياس من اليوم الأول. ايضاً عليك باستخدام ال Velocity للفريق وليس للفرد، فقولك باسم الفريق "نحن نستطيع انجاز هذا كل هذه الفترة" سوف يختلف عن "محمد يستطيع اكمال 10 واحمد يستطيع اكمال 2" فاذا كنت تود تدمير مشروعك وحصول المشاكل بين الفريق وسوء التواصل بينهم Miscommunication وعدم مشاركة المعلومات فقم بتحديد الأفراد بدلاً من العمل كفريق. وتذكر هذا يقتل روح الفريق وتشارك المعلومات وقد تحصل على منتج سيء في الأخير. الآن قدرت السرعه وأنت جاهز لوضع التوقعات حول مواعيد التسليم. #### **5) اختيار التواريخ Dates** لديك منهجيتين في التسلييم ، وهي تسليم المشروع بزمن معين Deliver by date أو تسليم المشروع بالمميزات Deliver by features set. ##### **التسليم بالوقت Deliver by date** هي أن تقول بأنك سوف تطلق المشروع وتسلمه في هذا التاريخ بغض النظر عن اي شيء يحدث، فاذا اكتشفت Stories جديدة اثناء التطوير فتقوم بازالة بعض ال Stories الأقل أهمية بدلاً من الجديدة، وقد تواجه صعوبة في بعض القرارات ولكن أنت مستعجل فلديك موعد ثابت وعلى الجميع معرفة ذلك. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_56.png) اذا كنت مرناً من ناحية الوقت والمهم هو أن تكمل ال Features فيمكنك ان تستخدم الأسلوب الثاني وهو ال Deliver by Features ##### **التسليم بالمهام Deliver by Features** وهي أن تقوم بالعمل على ال Core Features حتي تنتهي منهم كلهم ، وقد تكتشف Features جديدة اثناء التطوير لكنك سوف تهتم بال Core Features حيث أنك لا تستطيع التسليم بدونها ، وقد يكون هناك خطورة Risk حول التاريخ في هذه الطريقة وعلى العميل أن يقرر اذا كان سوف يقبل بهذه الطريقة في التسليم. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_57.png) الآن أنت انشئت خطة ال Agile وهي ببساطة **قائمة المهام Master Story List تكون مرتبة بالأهمية Prioritize ومقدرة الحجم Estimated وقدرت سرعه الفريق في التطوير Velocity واخترت تواريخ التسليم Dates.** هناك مخطط يوضح لك حالة المشروع ولا يمكن ان تكون خطة ال Agile بدون استخدامه : الا وهو ال Burn Down Chart. ## **مخطط** **Burn Down** هذا المخطط يوضح لك حالة المشروع وكيف يعمل الفريق على ال Stories المطلوبة وتوضح لك متى تتوقع التسليم النهائي. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_58.png) في محور ال Y يكون هناك العمل المطلوب انجازه (سواء بالأيام أو النقاط) وفي محور ال X يكون هناك ال Iterations ، وعن طريق معرفة العمل المتبقى points وترسمه على المخطط ، وميل المستقيم هو سرعه الفريق (كم انجز كل Iteration). من هذا المخطط يمكنك معرفة : \- كم العمل المنتهي \- كم العمل المتبقى \- سرعه الفريق \- الوقت المتوقع للتسليم كل عمود في المخطط يمثل العمل المتبقى في المشروع وسوف تكون الأعمدة متجهه للأسفل. في العالم الافتراضي تكون السرعه ثابتة وتبدأ من 15 نقطة وتنحدر من اليسار الى اليمين وتبقى كذلك خلال المشروع، لكن في الواقع يكون المخطط بهذا الشكل ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_59.png) في الغالب الأمور لا تمشي مع الخطة والسرعه تتقلب وهناك Stories جديدة تكتشف وStories يتم الغائها ، وهذا المخطط يجعل كل هذه الأحداث واضحة في مشروعك، افاذا اراد العميل اضافة Stories سوف ترى تأثير ذلك على وقت التسليم ، واذا أبطئ الفريق في العمل بسبب خروج أحد المطورين سوف ترى ذلك في سرعه الفريق وانخفضاها في المخطط. هذا المخطط يخرج الحقائق خلف الأرقام، ويمكنك اجراء المحادثات مع العميل حول الأشياء التي تحدث في المشروع، وسوف ترى تأثير القرارات التي تنفذ ، لذلك المخطط يقول الحقيقة وهو أكثر جزء واضح في خطة Agile فأنت لا تخفى شيئاً ومن خلال المراجعة الدورية على الخطط فيمكنك وقع التوقعات بصدق وجعل كل اعضاء المشروع يعرفوا متى تكون النهايه. من المخططات الأخرى المشابه هو Burn up وهو نفس المخطط السابق ولكنه معكوس ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_60.png) البعض يفضل استخدام هذا المخطط لأنه يوضح ال Scope ومتى يحصل زيادة فيه عن طريق اضافة Stories جديدة (خط افقى مستقيم وأي زيادة سوف توضح في الخط المستقيم)، فاذا كنت تحب وضوحية ال Scope في Burnup وسهوله ال Burndown فيمكنك دمجهم مع بعض ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_61.png) اختيار المخطط يرجع اليك ، فقط تأكد من أنه يسهل وضع التوقعات حول العمل المتبقى والوقت المتوقع للانتهاء ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_62.png) ## الإنتقال في منتصف المشروع الى Agile قد تكون في منتصف مشروع وتفكر بالانتقال الى ال Agile بسبب الطريقة التي تعمل بها فقد لا تكون مفيدة أو انك تريد العمل يتم بسرعه وشفافيه افضل، اذا كانت المشكلة في فهم لماذا المشروع ومن يريده وبعض الأمور الأساسية فعليك بالبدء بانشاء ال [Inspection Deck](https://informatic-ar.com/how%5Fstart%5Fproject/) وقد لا تحتاج لكل تفاصيلها ولكن على الأقل على الجميع أن يعرفوا: لماذا نحن هنا، مالذي نحاول فعله، من هو العميل، ما هي الأمور الأساسية الواجب عملها، من نستدعي في حال أردت الاستفسار. اذا كنت تريد الانتاج Ship بشكل اسرع، فقم بالغاء الخطة الحالية وانشىء واحدة جديدة تؤمن بها كما لو كنت ستنشىء خطة جديدة من الصفر (عمل قائمة المهام ثم تقديرها وترتيبها بالأهمية)، وتحدد أقل مهام ختى تخرج بأول نسخه Release من المشروع. اذا كنت تريد اظهار تقدم العمل Progress ولكنك تريد العمل وفقاً لخطتك الحالية، فقم بعمل شيء مفيد كل اسبوع أو Iteration وقم بعملها بالكامل، وبعد أن تريهم انك قادر على الانتاج وتكسب الثقة فيمكنك التهدئه قليلاً واعمل على الخطة وحدد ال Release بالاعتماد على سرعه الفريق الحالية وكمية العمل المتبقى، وهكذا استمر في ال Delivering حتى تصل لشيء يمكنك اطلاقه، فحدث الخطة كلما تتقدم ونفذ بقوة واشعر الجميع بالعجلة حتى لا يقف شيئاً في طريقك. الان أن تعلم الكثير في التخطيط بال Agile ولكن هناك اربعه تحديات سوف تواجهها اثناء العمل وكيف يمكنك التعامل معها بخطتك في ال Agile ### السيناريو الأول: العميل لديه متطلبات جديدة ## ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_64.png) عندما يريد العميل اضافه أمور جديدة فقم بسؤاله كيف يريد معالجة هذا الأمر، حيث يمكنك تأخير موعد التسليم (كما أن ستقول اريد المزيد من المال) أو يمكنك ازالة بعضاً من ال Stories غير المهمه بدلاً منها. لكن لا تأخذك العاطفة عندما تبدأ هذه المحادثه، فهذا ليس وقتك لاثبات شيء ما It’s not your call to make، فأنت ما الا وسيلة للتوصيل فكن محايداً تجاه النتيجة فمسؤليتك هي جعله يدرك مدى تأثير قراراته واعطه كافه المعلومات اللازمه التي يحتاجها لاتخاذ القرار. اذا كان العميل يريد كل شيء، يمكنك عمل قائمة Nice to have واخبره اذا وجد وقت في اخر المشروع سوف يتم العمل على تلك القائمة ولكن وضحها جيداً فهذه القائمة ليست من ضمن صلب الخطه وهي حالياً خارج الطاولة. ### السيناريو الثاني: لم تعمل بشكل سريع كما تتوقع: ## ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_65.png) اذا تفاجئت بعد 3 أو 4 Iterations بأن السرعه ليست كما كنت متوقعها فلا تقلق فهذا متوقع لذلك وضعنا التوقعات حول السرعه من الأول وأخبرنا العميل بأن الخطة الأولية هي تقدير وليس موثوقه بشكل كامل، لكن الأمر الجيد أنك تعلم الآن السرعه فيمكن التعديل على أساسها. المرونه من ناحية ال Scope هي الطريقة المفضلة للمحافظة على التوازن ، يمكنك اضافه المزيد من ال Resources (والتي سوف تبطئ في البدايه) أو تأخر موعد التسليم الى وقت آخر. المهم هو المحادثه مع العميل واعطائه الخيارات وهذا قد يجعلك في موقف صعب مع العميل ولكنك لا تستطيع اخفاء هذا الأمر، انشر الأخبار السيئة مبكراً هي من العادات في الأجايل Bad News Early. أحياناً قد تسأل هل يكفى هذا الوقت للقيام بذلك الأمر؟ وهنا سوف ستقوم بالطريقة الصادقة الشفافه وهي بناء أقل شيء يمكن واذا لم تستطيع القيام به فالخطة بالتأكيد خاطئة وتحتاج لأعادة بنائها. وهي تعمل عن طريق اخذ أهم Features للمشروع (شيء من Core Features) وقيس كم من الوقت سوف يأخذ لعمل اقل من هذه المهام، واستخدم ذلك لبقية ال Stories لكي ترى هل النسخه المبسطة Minimalists من المشروع يمكن القيام بها في ذلك الوقت والمصادر ، فاذا كان الوقت يبدوا جيداً فاكمل العمل والا فعلى الأقل انت تعلم ذلك. ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_66.png) حينها تستطيع بدء محادثه "تغيير الخطة" بكل ثقة ومصداقية لأنها بالاعتماد على الحقائق ولا مجال للعواطف هنا، في ذلك الوقت تستطيع مع العميل اتخاذ القرار الصحيح. ### السيناريو الثالث: خروج احد المطورين الرئيسين ## ![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_67.png) خسارة أحد أعضاء الفريق ليس بالأمر الهين، وسوف ترى تاثير ذلك على الفريق، حينها عليك أن تخبر العميل بأنه سوف يحصل تأثير على العمل (يمكنك تخمينه اذا أردت) وحتى تجد فرصه لقياس التأثير على سرعه الفريق (بعد 2 أو 3 دورات) تستطيع اخبار العميل بالضبط كم كان التأثير. قد يحدث ان يتم توظيف بديل له ويقول مديرك بأن المبرمج الجديد محترفاً وادائه افضل من القديم لذلك لا يجب أن يكون هناك أي تأثير في السرعه، لكن هذا يعتمد على عدة أمور فمن المحتمل أن يكون الجديد لا يناسب الوظيفة أو أنه صاحب سيرة CV ضخمه ولكنه لا يستطيع العمل كما في السيرة، لذلك كن حذراً وتوقع ذلك. ### **السيناريو الرابع: أنت تعديت الوقت المسموح Run out of time** ## **![](https://informatic-ar.com/wajdy_pics/agile/agile4/agile4_68.png)** الاجابه المحتملة بأن تكون مرناً من ال Scope واذا وصلت لنهايه المشروع فقم بعمل نصف المهام التي تريد اطلاقها، الاجابه لهذه الواقعه هو أن تجلس مع العميل وابحث عن حلول ابداعية لمساعدته (مثلاً هناك Story يمكنك عمله بشكل بسيط Spartan أو مثلاً كان العميل يريد 10 تقارير ثابته يمكنك عمل تقرير واحد Dynamic). عليك أن تساعده وتكون ناصح موثوق وذلك عن طريق اعطائه الخيارات وهذا طريق لبناء علاقه جيدة مع العميل Relationship ، لا يجب أن تكون صارماً وتخوف العميل بأنك لا تستيطع التسليم فهذا لا ينفع اي أحد فقط كن صادقاً معه لحل المشكلة. المرة القادمة ان شاء الله سوف نتحدث عن تنفيذ هذه الخطة وكيف يمكن تحوليها لشيء حقيقي يعمل وجاهز للعمل Production Ready Software. ### كيف تبدأ الخطوة الأولى في تطوير المشروع URL: https://informatic-ar.com/how_start_project/ Last updated: 2026-10-03T22:32:27.000Z # **اليوم الأول في مشروعك الجديد** في هذه المقالة سوف نتحدث عن الخطوة الأولى في بدايه أي مشروع، وهو موضوع مهم حيث كثير من المشاريع تموت من قبل أن تبدأ حتى، وذلك بسبب وقوعها في أحد الأخطاء القاتلة (وهي انهم لم يستطيعوا سؤال الأسئلة المناسبة وقت بدايه المشروع، أو انهم تغاضوا بجهل عن ذلك وبدئوا مباشرة في العمل بدون الرؤية الصحيحه للمشروع). الخطوة الأولى سوف تكون بالاجابة على مجموعة من الأسئلة نطلق عليها Inception Deck ، وهي عباره عن 10 أسئلة وسأقولها لك بكل صراحة: سوف تكون فد تسرعت اذا لم تقم بسؤالها قبل البدء بأي مشروع برمجي، لأنك ببساطة سوف تدمر كل شيء. بعدما تجاوب على هذه الأسئلة سوف ترى المشروع بشكله الحقيقى أنت وفريقك ومن ثم سوف تبدأ رحلة العمل فيه. [![agile3_1](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_1.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F1.png) لمراجعة السلسة (كل ما تحتاجه عن ال Agile): - [ما هي ال Agile ](https://informatic-ar.com/agile%5Fin%5Fnatual/) - [فريق التطوير في ال Agile ](https://informatic-ar.com/agile%5Fteam/) - كيف تبدأ الخطوة الأولى في المشروع (المقالة الحالية) - [كيف تخطط في مشروع ال Agile](https://informatic-ar.com/agile%5Fplanning/) ## **ما الذي يقتل اغلب المشاريع** احد اسباب فشل المشاريع وهي الرؤية المختلفة للمشروع من قبل اعضاء الفريق ومدراء المشروع، حيث عندما تبدأ المشروع الجديد سوف تجد كل شيء لديه نظرة مختلفة عن المشروع ولديه معاييره الخاصه التي يعتبر المشروع سوف ينجح اذا طبقت : [![agile3_2](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_2.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F2.png) **لماذا النظرة المختلفة تسبب في موت المشاريع ؟** السبب في ذلك هو ان اعضاء الفريق سيعملوا سوية ويستخدموا نفس المصطلحات ولكن سوف يتفاجئوا فور اول نسخه من التطبيق بأنه شيء مختلف تماماً عن ما يريده الشخص الأخر، لذلك افتراض توافق الفكر بين الجميع هو الذي يقتل المشروع بالتحديد The assumption of consensus where none exists is what kills most لكي تحل هذه المشكلة، عليك بأن توضح الأهداف goals والرؤية لهذا المشروع لأعضاء الفريق حتى يكونوا بالصورة، وتقوم باعطاء جميع المدراء ومن لهم القرار stakeholders المعلومات اللازمة والتي يمكن ان يقرروا هل يبدئوا أو يوقفوا هذا المشروع. [![agile3_3](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_3.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F3.png) ولكي تستطيع اعطاء مثل هذه المعلومات فيجب أن تسأل الأسئلة المناسبة في الوقت المناسب، وهذا الوقت يكون في بدايه المشروع ، حيث في أي بدايه مشروع سوف تجد هناك العديد من الأسئلة التي تدور في رأسك وتريد معرفة اجابه لها، لذلك قم بسؤال الأسئلة، وحتى الأسئلة العامة Wide-Open Questions قد تفيدك اذا كانت لديك اي استفسارات مثلاً: ما مدى خبرة الفريق؟ هل سبق ان قمت بشيء مماثل في السابق؟ هل الميزانية كافيه؟ من هو المسؤول عن هذا المشروع؟ هل يكفي وجود محليين وعشرة مطورين؟ ما هي المشاريع التي قمت بعملها مع فريق من المبتدئين Juniors وقمت باعاده مشروع قديم باستخدام Ruby on Rails بفهوم ال Agile؟ [![agile3_4](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_4.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F4.png) الأسئلة التي سنقوم بسؤالها في ال Agile تكون في صميم اي مشروع حيث لا يمكن البدء بدون الاجابه عنها وسوف نطلق عليها ال Inception Deck. [![agile3_5](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_5.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F5.png) ## ال Inception deck هي مصباحك لرؤية المشروعك بوضوح وتهدف لازالة الرؤية الضبابية ، وهي عباره عن 10 اسئلة ونقاط مهمه للمشروع تحتاج لأن تقوم بسؤالها مع افراد الفريق قبل ان تبدا المشروع،وسوف تصل بعدها لفهم جيد لجميع اعضاء الفريق shared understanding، حيث ستبدأ مع اعضاء الفريق وتقوم بعرض الأسئلة سؤال تلو الأخر وتقوم بتعبئة الاجابه لكل سؤال عن طريق مشاركة الجميع (عرض بوربوينت يكفي، يمكنك تحميل قالب العرض الجاهز للتعديل في اخر المقالة) وهكذا سيعرف الجميع ما هو هذا المشروع، وما الذي سوف يؤديه وكيف يمكن تطبيقه خلال التطوير. أعضاء الفريق في هذا الاجتماع هم من لهم علاقه بالتطوير سواء العميل Customers، المسؤولين Stakeholders اعضاء الفريق team members وهذا يشمل المحللين والمبرمجين والمختبرين وأي شخص لديه اسهام وعمل في تطوير هذا المشروع. من المهم ادخال المسؤولين في هذا الاجتماع لأن ال Inception deck مفيدة ايضاً لهم حتى يستطيعوا اخذ القرارات المصيرية في هذا المشروع. وفي العاده يمكن الانتهاء من ال Inception deck في عدة ايام الى اسبوعين ويجب مراجعتها دائماً في حال كانت هناك تغييرات جذرية في اتجاه المشروع. ولأن ال Inception deck لن تكون مجرد ورقه وتضع على الرف بل يمكن ان تضع على حائط كبير حتى تذكر جميع اعضاء الفريق في ماذا يعملوا ولماذا هم يعملوا، وبالطبع الاسئلة ال10 يمكن ان تقوم باضافه ما تشاء اليها من اسئلة تراها مهمه وتحتاج لايضاح قبل البدء لذلك لا تخاف في الاضافة او حتى التعديل لأن فكرة الAgile كلها قائمة على العمل بالشيء المناسب لك. الأسئلة المهمه Inception deck باختصار 1) **أسأل لماذا نحن هنا**؟ وهذا بالطبع تذكير سريع حول سبب الاجتماع، ومن هو العميل ولماذا نريد بناء هذا المشروع من الأساس 2) **فكرة المشروع باختصار**؟ اذا كانت لديك 30 ثانية وفقرتين من الحديث لكي تصف المشروع، فماذا ستقول 3) **تخيل شكل المشروع Product Box** اذا قمت بفتح خبر او مجلة أو اعلان في موقع عن مشروعك، ووجدت الاعلان عن منتجك فماذا سوف يقول هذا الاعلان وهل ستشتريه؟ 4) **انشئ قائمة الاشياء التي بخارج حدود مشروعك NOT List**: فمن السهل الحديث عن المشروع والجميع يعرف ما هو ولكن بالحديث عن الاشياء التي لم تقوم بعملها فهذا يجعل الفكرة اكثر وضوحاً بكثير 5) **من هم شركاء المشروع او المهتمين Neighbours للمشروع**؟ سوف يكون هناك مهتمين كثر بالمشروع، فلماذا لا يتم الاجتماع بهم والتعريف بهم. 6) **كيف سنطبق المشروع Solution** ؟ وهو مجرد رسم blueprint لهيكلة المشروع حتى تتأكد ان الجميع يفكر في نفس الاتجاه 7) **المشاكل التي قد تواجهنا**؟ بالحديث عن هذه المشاكل و كيف يمكن مواجهتها 8) **قدر المشروع** فهل هذا مشروع صغير ياخذ 3 شهور ام مثلاً 6 شهور ام 9 اشهر تقريباً 9) **الحديث عن القيود التي لا تتغير المشروع**: وهي الوقت والحدود والميزانيه والجودة وما هو الذي يتغير وما هو الشيء الثابت 10) **كم سياخذ وقت لتطوير المشروع** وما هي الميزانيه له وما هو الفريق المطلوب الخمس فقرات من ال Inception Deck هي الأسئلة التي تجيب عن لماذا Why ، بينما الخمس نقاط الأخيرة هي التي تجيب كيف سنقوم بعملها How ، سنبدأ الأن بتناول هذه الأسئلة والنقاط بالتفصيل ولنبدأ بالأسئلة التي تتحدث عن لماذا؟ [![agile3_6](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_6.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F6.png) ### **السؤال الأول لماذا نحن هنا** **Why Are We Here** [![agile3_7](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_7.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F7.png) قبل البدء بتطوير اي مشروع ناجح يجب ان يعلم افراد الفريق لماذا يبنوا هذا المشروع والفائده من ورائه، بفهم لماذا اولأ سوف يستطيع الفريق اتخاذ القرارات الصحيحه وايجاد الحلول المناسبة لأنهم سوف يفكروا عن الحل بأنفسهم، لذلك قد يحتاج ان تذهب وترى بنفسك المشكلة اذا تطلب ذلك ، مثلاً كنت تطور مشروع لمشكلة يواجهها العميل في موقع معين فيمكنك الذهاب وقضاء اليوم في مراقبة طريقة العمل وسؤال الأسئلة واخذ الملاحظات في ذلك. لذلك الفكرة من هذا السؤال هو معرفة الهدف الحقيقي من المشروع، ومن ثم تأكيد ذلك من العميل حتى تضمن صحه فهمك لمشكلته. ### **السؤال الثاني:** **وصف للمشروع بعبارات بسيطة** **Elevator Pitch** تخيل أنك تعرض مشروعك لأحد المستثمرين وأمامك 30 ثانية فقط لوصف مشروعك، وتريد خلالها وصف جوهر وفكرة المشروعك بشكل واضح بحيث يعرف المغزى من المشروع ويعرف نوعية المستخدمين المستهدفين والذي قد يشتروا هذا البرنامج، القالب التالي يمكن ان تستخدمه كمنوذج وتقوم بتعديل ما يلزم لكي يناسبك: [![agile3_8](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_8.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F8.png) بالطبع لا يمكن ان نوحد قالب واحد لكل شيء ويمكن ان تغيره بما يناسبك، ولكن نقاش النقاط من القالب سوف يفيدك في الوصف بشكل جيد: **لمن المشروع For** : وهنا سوف توضح الشريحه المستهدفه من هذا المشروع **من يحتاجه Who**: والذي يريد هذا المشروع والذي يسوف يفيده في هذا أو يحل مشكلة كذا. **المشروع The:** ابدء حياة مشروعك من الأن بوضع اسم عليه وهذا مهم لأنه وسيلة في التخاطب وسوف يسهل عليك فيما بعد . **وهو Is a:** تصنيف المشروع أو الخدمه او المهمه التي سيقدمها المشروع **الذي That**: تحدث عن ميزته والتي ستجعل العميل يود شرائه مباشره فور رؤيته **بعكس Unlike** : المنافسين وتحدث لماذا لا نستخدمهم **مشروعنا Our Product**: سوف يقوم بهذا وتلك (تحدث عن الميزة أو الفارق عنهم) والتي سوف تميزك عن المنافسين هكذا بهذه العبارات البسيطة سوف توصل فكرة مشروعك بشكل سلس ، وهذا ما يميز ال Elevator Pitch وهي أنها قصيرة، ولكن لا تظن ان كتابه شيء قصير أمر سهل لذلك اذا لم تستطيع كتابتها من أول مرة فلا تخف ويمكنك المحاولة فيها اكثر من مره مع الفريق حتى تتمكن من كتابه الوصف القصير الجيد، سوف يحتاج الأمر بعض الجهد لكن سوف يفيدك بكل تأكيد. كيف تنشى ال Elevator Pitch مع الفريق؟ يمكنك ان تقوم بطباعه القالب وتجعل كل شخص يقوم بتعبئته قبل الاجتماع، أو يمكنك ان تحفظ الوقت وتبدأ الأجتماع ومن ثم تقوم بفتح القالب في شاشه عرض وتقوم مع الفريق بتعبئة القالب والاجابه على كل فقرة منها. هكذا اصبحت لديك نبذه المشروع المختصرة، لننظر الأن لشكل المشروع الخارجي. ### **السؤال الثالث: كيف سيبدوا المشروع** **Design Product Box** [![agile3_9](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_9.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F9.png) تخيل برنامجك معروض مع بقية البرامج المنافسه، فما هو الشيء الذي سوف يجذب المشتري له، أو تخيل اعلان موقعك مع البقية فما هو الأمر الذي سيجلب الزوار لك بدلأ عن غيرك، انشاء شكل المشروع وتحديد الأمور التي تجلب المستخدم وتجعله يشتري منتجك أو الخدمة التي تقدمها من الأمور المهمه في بدء المشروع، قد تقول لا اعرف في الاعلانات وليس هذه تخصصي ولكن بالعكس فسوف تستطيع ذلك بخطوات بسيطة: اعصر عقلك مع الفريق واخرج الفوائد من المشروع Brainstorm your product benefits: فلا تخبر العميل او المستخدمين عن المزايا Features في المشروع فهم لا يهتموا بها، الشيء الذي يجعل المستخدمين يهتموا بالمشروع هو الامر الذي يجعل حياتهم اسهل من خلاله، وبعباره ابسط فوائد المشروع. مثال على ذلك، مثلاً اردت تريد بيع كاميرا رقمية بدقة 24 ميغا بكسل، فاذا جائك زبون لا يعرف شيء عن مواصفات الكاميرات ولا شيء فعندما تقول له هذه ب24 ميغا سوف يكون جوابه "وبعدين" لأنه لا يفهم ذلك، يمكنك كسب الزبون بتحويل ال features الى benefit بذكر سوف تستطيع تصوير بجودة عالية والصور سوف تكون واضحه ، وهكذا حولتها الى فوائد قد يقرر العميل شرائها اذا علم انها تفيده. [![agile3_10](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_10.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F10.png) اختر العباره الرنانة لمشروعك Create a Slogan: والذي سيحمل الكثير بمجرد ذكر الاسم، مثلاً ستجد كثير من الأوصاف التي تأتي مع الاسماء التجارية ، مثلاً: Acura—The true definition of luxury. Yours. FedEx—Peace of mind. Starbucks—Rewarding everyday moments فيمكنك اختيار (اسم البرنامج – الوصف السهل المناسب له) ، يمكنك المحاولة مع اعضاء الفريق وقم بعمل اجتماع لمدة 10 دقايق فقط time boxing لاختيار الوصف المناسب لمشروعك . هكذا تكون خرجت باسم المشروع وفوائده مع وصف بسيط له [![agile3_11](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_11.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F11.png) ### **السؤال الرابع: المهام الخارجة عن نطاق المشروع** **Create Not List** عندما تقوم بوضع التوقعات حول حجم المشروع وحدوده فمن المهم ايضاً ان تقوم بتحديد الأشياء الخارجة عن نطاق مشروعك، هكذا ستضمن ان الجميع سوف يركز على الأشياء المهمه ويتجاهل بقية الاشياء الخارجة عن نطاق المشروع. [![agile3_13](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_13.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F13.png) ويمكن ان تقوم بها مع جميع افراد الفريق وايضاً مع العميل ومحاولة عصف الذهن جيداً لكي يتم معرفة ما ستقوم به وما الذي يعتبر خارج نطاق المشروع، ويمكن عمل قائمة من 3 خانات: المهام الداخلة في نطاق العمل IN والتي سوف تعمل عليها وهي بشكل عام جداً high level مثلاً سوف يحتوي المشروع على خاصية التقارير، أو له اداء عالي وقابل للزياده Scalability، القائمة الثانية وهي للمهام الخارجة من نطاق المشروع OUT او التي سوف تكون في النسخ التالية Next Release واي مهمه لا تهتم بها ضعها هنا، القائمة الثالثة وهي للمهام التي تحتاج اخذ بعض القرارات Unresolved وهي مهمه لأنه في الواقع سوف تجد ان هناك اشياء تحتاج لبعض الدراسه او اخذ القرار بعملها او لأ بناء على المتطلبات والقيود في المشروع، وفي الأخير كل المهام التي في هذه القائمة سوف تذهب اما للمهام الداخلة في نطاق المشروع IN أو الخارجه حالياً عنه OUT. [![agile3_12](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_12.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F12.png) ### **السؤال الخامس:** **التعرف على أعضاء الفريق Neighbours** تعرف الفريق وبناء العلاقات بينهم امر مهم في العمل حيث تعتبر اداة مهمه في بناء الثقة بين الفريق وهو عامل اساسي من عوامل النجاح لذلك يمكن جمع اعضاء الفريق الذين سوف يعملوا على المشروع وحاول عصر ذهنك وجلب جميع الاشخاص المحتملين الذي سوف تكون لهم علاقه بهذا المشروع، ولا بأس بجعل الاجتماع اقل رسمية مع تناول الشاي والدونات وجعل اعضاء الفريق يحسوا بأنهم مهمين في المشروع ولهم قيمة في المشروع، هكذا ستكسب ود الفريق وهذا سيسهل الكثير وقت البدء في المشروع. [![agile3_14](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_14.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F14.png) تعرفنا الأن على نقاط لماذا Why ولنكمل الحديث حول الجزء التي يصف كيف يمكن How، وسنبدأ بوضع بعض النقاط في هذا الموضوع، حيث سنقوم بعرض الطريقة التي سنحل بها المشكلة بشكل تقنى ونتحدث عن المخاطر التي قد تواجهنا مع محاولة تقدير الوقت الزمني للمشروع، ولنبدأ بالحل التقني ### **النقطة السادسة** **:** **قم بوضع تركيبة المشروع** **Technical Architecture** وضع اليه عمل المشروع بشكل مرئي Visualization سوف يفيد جميع اعضاء الفريق حيث سيدركوا كيف سيتم بناء هذا المشروع، لذلك الحديث عن المشروع مع الفريق مهم لأنه سوف يحدد التقنيات والادوات واللغات التي سوف تستخدم في المشروع، وايضاً سوف يوضح الافتراضات حول حدود المشروع Scope بالاضافه الى انه يوضح المخاطر Risk اذا تواجدت في المشروع . [![agile3_16](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_16.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F16.png) هذه الرسم المبدئي لتركيبة المشروع مهم وحتى لو كان جميع اعضاء الفريق يعرفوا كيف سيتم بناء المشروع وتركيبته ، فهو على الأقل سيضمن لك ان الجميع يعرف نفس الفكرة والتصور وقد يصحح اي مفاهيم خاطئة تتواجد لديهم. السؤال الأن هو كيف تقوم بوضع الألية أو تركيبة المشروع؟ وهنا عليك بالاجتماع مع اعضاء الفريق التقنيين وتبدؤا بالحديث عن كيفية بناء هذا المشروع، وتبدأ بوضع الرسم التي تحدد المعماريه مع فريقك Architectural Diagram وحاول معرفة اي احداث جانبية (ماذا سيحدث اذا حصل كذا) مع فريقك حتى تصلوا لمعرفة حجم المشروع الحقيقي ومدى تعقيده، ايضاً اذا كانت هناك أدوات أو مكتبات مساعده يمكنك الحديث فيها اذا كانت ستفيد في العمل (ويمكن مثلاً الحديث عن رخص المكتبات المسموحه بها أو التي لا يسمح بها ان كنت تعمل ضمن قيود معينة)، هكذا تكون قد كونت الحل المبدئي للمشروع ولتنظر في المخاطر Risks المحتمل حدوثها في المشروع. ### **النقطة السابعة** **: مخاطر المشروع** **Project Risks** هناك العديد من الأشياء يمكن ان تكون مسببة لأي مشكلة في المشروع، مثلاً قد تكون التقديرات الزمنية Estimation ليست جيدة كفايه، أو ان يكون العميل (وهم كذلك) يغير متطلباته بشكل متكرر كثيراً وغيرها من الأشياء التي تجعلك تقوم بالعمل اكثر من الوقت والميزانية الموجودة ، وهذه الأمور هي مخاطر المشروع Risks. [![agile3_17](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_17.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F17.png) قد لا يبدوا الحديث عن المخاطر مهم في اول المشروع ولكن العكس هو الصحيح، حيث سيوضح كثير من الأشياء التي يمكن ان تسبب في فشل المشروع بحيث يتم تجنبها او حلها منذ البدء، مثلاّ اعضاء الفريق مبتعدين عن بعض، أو لا يوجد عميل، او ان المبرمجين لا يختاروا اللغات والأدوات وانما تفرض عليهم، بالاضافه الى ان الحديث في هذا المخاوف وتبادل القصص والخبرات السابقة وحتى ان لم تؤخذ على محمل الجد فانه سوف يكون متوقع الحدوث من الجميع، لذلك لديك فرصه الأن ويمكنك الحديث بكل حرية. [![agile3_18](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_18.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F18.png) بعد ان تقوم بالحديث في المخاطر المحتملة مع الفريق وعصر الافكار لاستخراج كافة المخاطر، يمكنك الان تقسم القائمة الى قسمين، القسم الأول الاشياء التي تستحق الاهتمام والثانية التي لا تستحق: [![agile3_19](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_19.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F19.png) مثلاً الحديث عن اقتصاد الدولة وانه قد يسبب ضعف في الميزانية وسوف يتم غلق المشروع وطرد الفريق هذا أمر لا ينبغى الحديث حوله او الاهتمام به، لكن مثلاً مغادرة المبرمج الرئيسي Lead Programmer في المشروع لعمل اخر قد يؤثر في المشروع لذلك يجب البدء بأخذ الاحتياطات اللازمة وضمان ان جميع معلومات المشروع Knowledge موزعه Shared بين الجميع ، وأنه لا يوجد شخص متخصص في جزء ما ولا يعرفه اي من افراد الفريق الاخرين. ### **النقطة الثامنه** **: تقدير المشروع** **Timeline** وهنا سوف نوضح المدة التقريبية للمشروع هل هو مشروع ينتهي في شهر واحد، أم ثلاثه اشهر، أم سته أشهر، بالطبع لا نستطيع ان نكون دقيقين في هذه المرحلة ولكن نحتاج لمعرفة تقريبية للوقت حتى يتم اعطائها للعميل او الراعي للمشروع Sponsor ويأخذ فكرة عن الوقت المتوقع لتسليم المشروع وحتى لو كان تقييم ليس دقيقاً. [![agile3_20](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_20.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F20.png) سيتم الحديث عن كيفية التقديرHow To Estimate في المقالة التالية ان شاء الله ، لذلك سنفترض الان انك قمت بعمل الخطوات وقدرت المشروع مع الفريق والأن سوف تعرض النتيجة هنا، ولكن للتذكير يجب ان تقدر في حدود مغلقة وليست مفتوحة Open-Ended فالمشاريع الكبيرة المفتوحة قد لا تنتهي ولا تسلم اصلاً، لذلك في الAgile يفضلوا التقديرات الصغيرة والتي يستطيع ادارتها والتعامل معها وهي بشكل عام سته اشهر وكل ما زاد عن ذلك كلما كانت هناك مخاطرة. وبالطبع التقدير يكون شامل لكل شيء سواء الاختبارات والتدريب لو تطلب المشروع وكل ما هو مطلوب لتشغيل المشروع. [![agile3_21](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_21.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F21.png) من الضروري تذكر ان هذا التقدير هو تقدير في النهايه، وقد يأخذ المشروع وقتاً اقل أو أطول من ذلك، وعلى العميل ان يدرك ذلك، فهذا ليس وعد بالتسليم Commitment ، وهذه التقديرات سوف تعرف حتها وقتما تبدأ بالمشروع وفي خطواتك الأولى وحينها تستطيع القياس بشكل دقيق جداً. ### **النقطة التاسعة** **: كن دقيقاً في معرفة ماذا ستقوم به** هناك بعض الأمور الثابته التي لا تتغير بسهوله في اي مشروع، مثلاً الميزانية Budget والوقتDates دائماً ثابتين ، وفي المقابل تجد المشروع يتوسع مع الوقت Scope Increasing والجودة الجميع يريدها اعلى شيء يمكن، لكن في العالم الحقيقي الأمر ليس بهذا الشكل فكلما تعطي معامل اهمية ستجد أن هناك تقليل في المعامل الثاني، السؤال هو ما ذلك العامل؟ [![agile3_22](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_22.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F22.png) قبل أن نجيب على ذلك، قم بالجواب على كل من هذه الأسئلة : 1) **ما هو الشيء الأكثر أهمية في المشروع** ؟ الجودة Quality الوقت Time حدود المشروع Scope الميزانية Budget 2) **عندما تصادف الكثير من المهام ولديك وقت قليل فماذا تفعل** ؟ قلل حدود Scope المشروع أو ال Features فيه اضف مزيداً من المبرمجين للمشروع غير وقت تسليم المشروع Release Date انجز بدون الاهتمام بالجودة 3) **ما هو الشيء الأكثر الماً painfu**l ؟ المشى فوق النار اكل الزجاج المكسر *اخبار الراعي Sponsor او صاحب المشروع بأنك بحاجه للمزيد من المال* بعد النظر لهذه الأسئلة وفهمها، قد تجد ان الجواب هو قد يعتمد it depends على عدة أمور ولا يوجد جواب قطعي، ومن هنا يجب الحديث حول الأمور الأربعه الاساسية في المشروع، وهي الوقت Time والميزانية Budged والجودة Quality وحدود المشروع Scope . [![agile3_23](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_23.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F23.png) **الوقت Time** عامل مهم حيث هو محدود ولا يمكن استرجاعه، لذلك يجب بذل ما تستطيع في الوقت المتاح لك، وهكذا مستخدمي ال Agile يجب ان يفضلوا المهام المحدودة time-boxing لأن تأخير الوقت والتسليم قد يشكك العميل في العمل بالاضافه الى انك قد تتأخر ولن تسلم المشروع من الأساس وهذا أسوء شيء يمكن ان بكون في المشاريع. **الميزانية Budged** متلازمة مع الوقت فيجب أن تكون ثابته ولا تتغير، فمن أسوء اللحظات هو ان تذهب وتطلب من العميل او الراعي للمشروع المزيد من المال فهي تجربه لا يود اي شخص خوضها (مع ذلك فنسبه حصولها ورادة وان قلت) ، لذلك لكي تتجاهل هذه اللحظات عامل الميزانية مثل الوقت ، ثابتين Fixed ولا يتغيرا. **الجودة Quality** عامل مهم ومن يظن أن السرعه والتسليم في الوقت المحدد اهم منها فهو مخطئ، والسبب أن العمل السريع والانجاز بغض النظر عن جودة العمل سوف يؤثر كثيراً فيما بعد في وقت لاحق في المشروع ، مثل ان تقوم بوضع يدك على النار في الشتاء فقد تدفئك لثواني معدودة ولكن بعدها سوف تبدأ بحرقك بسرعه، لذلك الجودة يجب ان تكون بنفس المستوى بدون اي تقليل وبأفضل المعايير الممكنة. **حدود المشروع Scope** فالأن لدينا الوقت Time والميزانية Budget والجودة Quality كلهم ثابتين، لكن لديك الورقة الأخيرة للتفاوض وهي حدود المشروع، فاذا كان هناك الكثير فيمكن انجاز معظم المهام منها أو التي يمكنك انجازها في الوقت المسموح، فاذا تعارض الواقع مع الخطة فعليك بتغيير الخطة، ربما قد يظن البعض بأن الخطة غير قابلة للتغير ولكن في الواقع الأمر كذلك. يمكنك الأن الحديث مع العميل ومحاولة فهم ما يهمه هو في المشروع فربما لديه معايير، باستخدام هذا الشريط Trade-off Sliders يمكنك معرفة ذلك بسهوله: [![agile3_24](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_24.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F24.png) بالطبع لا يمكن ان أن تكون كل الاجابات بنفس الدرجة أو حتى اجابتين بنفس الدرجة، فالفكرة هنا معرفة مدى اهمية العامل مقارنة بغيره، ولكن هذا لا يعني ان العامل الثاني غير مهم: [![agile3_25](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_25.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F25.png) [![agile3_26](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_26.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F26.png) قد لا تكفى هذه المعايير وتريد اضافه معاير اخرى خاصه بمشروعك وتريد من العميل وضع اهميتها، فيمكنك ذلك ايضاً، مثلاً كنت تبرمج لعبه والعميل من اهم اولياته انها تكون مرحة وجاذبة للاعب ، فيمكنك وضعها في هذه القائمة ايضاً على الشريط: [![agile3_27](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_27.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F27.png) هكذا تكون قد قمت بمعرفة ما يدور في فكر العميل، وبقى ان تعرض كيف سيطور هذا المشروع والمراحل التي فيه. ### **النقطة العاشرة** **: مراحل التطوير** **Releases** الى هذه النقطة اصبحت لديك رؤية جيدة للمشروع، وقمت بعمل التقدير الزمني له لذلك تستطيع الان القيام بتقدير التكلفة له، مع ايضاح مراحل العمل على المشروع، وايضاً معرفة الفريق الذي سوف تحتاجه في العمل معك، ولنبدأ بالفريق، في العادة هذا يعتمد على طبيعة المشروع، لذلك يمكن معرفه ما تحتاجه من الادوار في الفريق، يمكنك مراجعه المقاله السابقة (التعرف على اعضاء الفريق لتعرف ادوار الفريق)، مثلاً اذا كنت تعرض مقترح المشروع على راعي له فيمكنك تقديم عدد الأشخاص والمهارات اللازمة لكل منهم: [![agile3_28](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_28.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F28.png) بعد ذلك عليك التعرف على من له الكلمة العليا فلا يوجد ما يزعج المبرمجين وجود اكثر من شخص وكل منهم له اراء مختلفة عن الاخر ، لذلك تحديد هذا العميل او صاحب المشروع والذي يكون له المرجعيه امر مهم ايضاً: [![agile3_29](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_29.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F29.png) تحديد من يقود دفه المركب مهم وحتى لو كان الجميع يعرف من هو، لأن تحديده سوف يزيل كل الشكوك وسوف يوضح للجميع من هو صاحب القرار في المشروع سواء للفريق أو مدراء المشروع او اي شخص يدخل في اطار العمل. لنتحدث عن الميزانية الأن، قد لا يكون لك الخيار في تحديد ميزانية المشروع أو تكلفته، ولكن بعض الأحيان مثلاً تكون تعمل عمل حر فيجب عليك تقديم التكلفة لهذا المشروع، ويمكنك حساب ذلك من خلال معرفة تكلفة الساعة للفرد في الفريق ومن ثم ضربها في الوقت الكلي للتطوير وضربها في عدد الاشخاص مع نسبة للربح وهكذا يمكنك تقدير التكلفة الاجمالية للمشروع. [![agile3_30](https://informatic-ar.com/wp-content/uploads/2013/04/agile3_30.png)](https://informatic-ar.com/wp-content/uploads/2013/04/agile3%5F30.png) هكذا نكون قد غطينا ال Inception Deck بشكل موجز، وهكذا تكون عرفت المشكلة لديك ، والفريق وهذه هي نقطة البدايه في المشروع، والان الجميع يعرف لماذا سوف تقوم ببناء هذا المشروع، ومن هم اعضاء الفريق وكيف سيبدوا المشروع والمخاطر في المشروع، وما هي القيود التي لا تتغير في المشروع، والتقدير للوقت والتكلفة للمشروع. الأن انت جاهز للبدء في التخطيط للمشروع Agile Planning ، وسوف نتحدث عنها في المقالة التالية ان شاء الله وسنتحدث بها عن التقدير Estimation وكيفية انشاء قائمة المهام للمشروع، وبعض الأمور الأخرى كالحديث عن سرعه انجاز الفريق للعمل، فالى لقاء قريب. **يمكنك تحميل القالب من هنا**: [رابط قالب Agile Inception Deck](http://agilewarrior.files.wordpress.com/2011/02/blank-inception-deck1.pptx?ref=informatic-ar.com) > هذه المقالة ضمن سلسلة مقالات تلخيص كتاب The Agile Samurai ### ارسال واستقبال البيانات عبر HTTP في Qt URL: https://informatic-ar.com/qt_http_post_get/ Last updated: 2026-10-03T22:32:28.000Z هذه الفترة احتجت ان ارسل HTTP Post Request باستخدام Qt واقوم بارسال ملفات للسيرفر (عباره عن شهاده رقمية Certificate للكلاينت بالاضافه الى بعض الملفات الأخرى) ، وايضاً احتجت ان اقوم بعمل Get Request من السيرفر وارسل ID ومن خلاله سوف احصل على هذه الملفات التي قمت برفعها في JSON،، وأحببت وضع هذه التدوينه لعلها تفيد من يريد القيام بنفس المهمه, في كيوت الكلاس QNetworkAccessManager هو المسؤول عن ارسال والاستقبال الطلبات في الشبكه، وفي الغالب سوف تحتاج لQNetworkAccessManager واحد في التطبيق، ويحتوى الكلاس على signals تفيد بان العملية جاري او انها انتهت وايضاً بها دوال لأخذ الطلب الذي تريد وبعض البيانات الأخرى وترجع QNetworkReply والذي يحتوي على النتيجة الذي ارجعها السيرفر عندما يعالج الطلب. المثال البسيط الذي تجده في التوثيق Documentation: ```cpp QNetworkAccessManager *manager = new QNetworkAccessManager(this); connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *))); manager->get(QNetworkRequest(QUrl("http://qt.nokia.com"))); ``` هذا المثال يوضح ان ارسلت Get Request وعندما ترجع النتيجة سوف يتم استدعاء replyFinished وحينها يمكنك طباعه النتيجة او حتى مشاهده ال header الخاص بهذا ال response. من المهم القيام بحذف هذا الناتج QNetworkReply لذلك يمكنك استخدام الدالة delelteLater في الslot : ```cpp void MainWindow::replyFinished(QNetworkReply *reply) { if (reply) { if (reply->error() == QNetworkReply::NoError) { qDebug() << "Server replay now: " << QString::fromUtf8(reply->readAll()); } else { //get http status code int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); qDebug() << "Error: " << httpStatus; } reply->deleteLater(); } } ``` من المهم معرفه ان دوال الكلاس هي Asynchronous وهي تعني ان الطلب سوف يتم عمله في Thread أخر وعندما ترجع النتيجة سوف يتم استدعاء ال slot الذي قمت بربطه مع ال signal ، لذلك تنفيذ الكود سوف يستمر بعد استدعاء الدالة get، ايضاً الطلبات التي تقوم بعملها سوف توضع في Queue حتى يتم معالجتها ولل HTTP فسوف يتم معالجة 6 طلبات في اللحظة الواحدة من هذا ال queue. لكن في بعض الأحيان نظراً لتصميم الكلاس أو لسبب أخر هو أنك تريد الحصول على النتيجة فوراً Synchronous بمعنى عندما تستدعي الدالة فلا تريد ان ترجع الا مع النتيجة لذلك في هذه الحالة يمكنك تحريك الEvent loop حتى تتم معالجة هذا الطلب: ```cpp QString url = ""; QNetworkReply *reply = this->manager->get(QNetworkRequest(QUrl(url))); QEventLoop loop; QObject::connect(reply, SIGNAL(readyRead()), &loop, SLOT(quit())); loop.exec(); QString result(reply->readAll()); reply->deleteLater(); ``` في الاكواد السابقة كنا نقوم بعمل ربط QNetworkAccessManager مع الfinished Signal والأمر يعمل بشكل طبيعي، ولكن بعض الأحيان قد تحتاج أن تتصل على السيرفر وتحصل على النتيجة وبناء على تلك النتيجة تقوم بعمل طلب أخر، باستخدام ال Signal السابقة فسوف يتم استدعاء الدالة replyFinished مرة أخرى عندما تقوم بعمل الطلب والسبب هو انك كل الطلبات التي سوف تتم سوف يتم استدعاء هذا الslot: ```cpp connect(m_networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(replyFinished(QNetworkReply *))); ``` لذلك من الأفضل ربطها بال QNetworkReply حيث بها نفس ال Signal وبالتالي لكل طلب يمكن ان تكون هناك slot خاصه فيه ، المثال: ```cpp void MyApp::getSocialNetwork() { QNetworkRequest request; request.setUrl(QUrl("http://www.socialnetwork.foo")); m_networkManager = new QNetworkAccessManager(this); QNetworkReply *reply = m_networkManager->get(request); connect(reply, SIGNAL(finished()), this, SLOT(onRequestCompleted())); } void MyApp::onRequestCompleted() { QNetworkReply *reply = qobject_cast(sender()); QByteArray data = reply->readAll(); QString userId = parseUserIdFromData(data); QString statusUpdate("Hello, World!"); QNetworkRequest request; QString requestUrl = QString("http://www.socialnetwork.foo/%1/%2").arg(userId).arg(statusUpdate); request.setUrl(QUrl(requestUrl)); QNetworkReply *reply = m_networkManager->get(request); connect(reply, SIGNAL(finished()), this, SLOT(onStatusUpdateCompleted())); } void MyApp::onStatusUpdateCompleted() { // Do something when we have updated the status. } ``` المثال اعلاه من المقال الرائع والذي يوضح لماذا يفضل ان تستخدم QNetworkReply في ال Signals: [http://www.johanpaul.com/blog/2011/07/why-qnetworkaccessmanager-should-not-have-the-finishedqnetworkreply-signal/](http://www.johanpaul.com/blog/2011/07/why-qnetworkaccessmanager-should-not-have-the-finishedqnetworkreply-signal/?ref=informatic-ar.com) نتحدث الان عن ال HTTP POST وهناك طريقتين في Qt الأولى تعمل على نسخه 4.8 وما فوق باستخدام الكلاس QHttpMultiPart والثانية تعمل على النسخ القديمة والحديثة وذلك لأننا سنقوم ببناء ال POST Request يدوياً ونقوم بارساله ، لنفرض ان السيرفر به 3 مدخلات ويجب ان تدخل من الكلاينت: [![server](https://informatic-ar.com/wp-content/uploads/2013/04/server.png)](https://informatic-ar.com/wp-content/uploads/2013/04/server.png) المثال باستخدام QHttpMultiPart : ```cpp void MainWindow::uploadUsingQHttpPart() { QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); // Original Text QHttpPart textPart; textPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=" OriginalText "")); textPart.setBody("OriginalText"); // Signature QHttpPart sigPart; sigPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); sigPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=" Signature " filename=" a.bin "")); sigFile = new QFile("a.bin"); if (!sigFile->open(QIODevice::ReadOnly)) return; sigPart.setBodyDevice(sigFile); sigFile->setParent(multiPart); // Certificate QHttpPart cerPart; cerPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); cerPart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=" Certificate " filename=" a.cer "")); file = new QFile("a.pdf"); if (!file->open(QIODevice::ReadOnly)) return; cerPart.setBodyDevice(file); file->setParent(multiPart); multiPart->append(cerPart); multiPart->append(sigPart); multiPart->append(textPart); QUrl url("http://localhost:20607/DocInfo/Create"); QNetworkRequest request(url); QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(this); connect(networkAccessManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(sendReportToServerReply(QNetworkReply *))); QNetworkReply *reply = networkAccessManager->post(request, multiPart); multiPart->setParent(reply); } ``` اذا كنت تعمل على نسخ Qt 4.7 وما قبل فلن تستطيع استخدام هذا الكلاس وعليك القيام بالعملية كاملة ، المثال التالي يوضح كيفية بناء ال Request: ```cpp void MainWindow::uploadManualWay() { QString boundary = "---------------------------723690991551375881941828858"; // Certificate QByteArray data(QString("--" + boundary + "rn").toAscii()); data += "Content-Disposition: form-data; name=" Certificate "; filename=" a.cer "rn"; data += "Content-Type: application/octet-streamrnrn"; cerfile = new QFile("a.cer"); if (!cerfile->open(QIODevice::ReadOnly)) return; data += cerfile->readAll(); data += "rn"; // Signature QByteArray data(QString("--" + boundary + "rn").toAscii()); data += "Content-Disposition: form-data; name=" Signature "; filename=" a.bin "rn"; data += "Content-Type: application/octet-streamrnrn"; sigfile = new QFile("a.cer"); if (!sigfile->open(QIODevice::ReadOnly)) return; data += sigfile->readAll(); data += "rn"; // OriginalText data += QString("--" + boundary + "rn").toAscii(); data += "Content-Disposition: form-data; name=" OriginalText "rnrn"; data += "OriginalTextrn"; data += "rn"; data += QString("--" + boundary + "--rn").toAscii(); QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(this); QNetworkRequest request(QUrl("http://localhost:20607/DocInfo/Create")); QString header = "multipart/form-data; boundary=" + boundary; request.setHeader(QNetworkRequest::ContentTypeHeader, header.toAscii()); request.setHeader(QNetworkRequest::ContentLengthHeader, data.size()); connect(networkAccessManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(sendReportToServerReply(QNetworkReply *))); QNetworkReply *reply = networkAccessManager->post(request, data); cerfile->setParent(reply); sigfile->setParent(reply); } ``` اذا كنت تود بارسال HTTP POST بدون ان تحتوى على ملفات فيمكنك استخدام: ```cpp void MainWindow::uploadText() { QUrl parameters; parameters.addQueryItem("OrgName", "Wajdy Essam"); parameters.addQueryItem("OriginalText", "this is an example"); parameters.addQueryItem("Signature", "this is my sign"); parameters.addQueryItem("ExpiraryDate", "2/2/2012"); parameters.addQueryItem("SignedDate", "5/1/2013"); QNetworkRequest request(QUrl("http://localhost:20607/DocInfo/Create")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(this); connect(networkAccessManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(sendReportToServerReply(QNetworkReply *))); QNetworkReply *reply = networkAccessManager->post(request, parameters.encodedQuery()); } ``` طبعاً في حال ارسال بيانات نصية فال MIME Type يكون application/x-www-form-urlencoded وللبيانات الbinary يفضل ان استخدام multipart/form-data والسبب أن الأول قد يرمز البايت البايرني الواحد ب3 بايتات وهذا ما يجعله خيار غير جيد في ارسال البيانات binary. يمكنك معرفة المزيد من هنا مثلاً: [http://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data](http://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data?ref=informatic-ar.com) في الاخير يمكنك عمل Wrapper يسهل لك كل هذا ويجعل الامر اكثر تلقائية بدل من تحديد اسماء الحقول في دالة الارسال ، يمكن عمل الأتي: ```cpp #ifndef FORMDATA_H #define FORMDATA_H #include #include class Field { public: Field(QString n, QString v); QString getName() const; QString getValue() const; private: QString name; QString value; }; class FileField : public Field { public: FileField(QString name, QString value, QString mime); QString getMimeType() const; private: QString mimeType; }; class FormData { public: FormData(); void addField(QString feildName, QString fieldValue); void addFile(QString fieldName, QString filepath, QString mimType); QList getData() const; QList getFiles() const; private: QList data; QList files; }; #endif // FORMDATA_H ``` ال Implementation : ```cpp #include "formdata.h" Field::Field(QString n, QString v) { this->name = n; this->value = v; } QString Field::getName() const { return this->name; } QString Field::getValue() const { return this->value; } FileField::FileField(QString name, QString value, QString mime) : Field(name, value) { this->mimeType = mime; } QString FileField::getMimeType() const { return this->mimeType; } FormData::FormData() { } void FormData::addField(QString feildName, QString fieldValue) { Field field(feildName, fieldValue); this->data.append(field); } void FormData::addFile(QString fieldName, QString filepath, QString mime) { FileField field(fieldName, filepath, mime); this->files.append(field); } QList FormData::getData() const { return this->data; } QList FormData::getFiles() const { return this->files; } ``` ويمكن عمل Singleton Wrapper لل QNetworkAccessManager بحيث تستخدمه في أكثر من مكان: ```cpp #ifndef NETWORKMANAGER_H #define NETWORKMANAGER_H #include #include #include #include #include #include #include #include "formdata.h" class NetworkManager : public QObject { Q_OBJECT public: static NetworkManager *instance(); QString getRequest(QString url); QString postRequest(QString url, const FormData &formData); private: NetworkManager(); static NetworkManager *networkManager; QNetworkAccessManager *manager; }; #endif // NETWORKMANAGER_H ``` وأخيراً ال Implementation , وكما تلاحظ فضلت ال Synchronous Way وخصوصاً ان الامر يتم بسرعه : ```cpp #include "networkmanager.h" NetworkManager *NetworkManager::networkManager = NULL; NetworkManager *NetworkManager::instance() { if (networkManager == NULL) { networkManager = new NetworkManager; } return networkManager; } NetworkManager::NetworkManager() { this->manager = new QNetworkAccessManager(this); } QString NetworkManager::getRequest(QString url) { QNetworkReply *reply = this->manager->get(QNetworkRequest(QUrl(url))); QEventLoop loop; QObject::connect(reply, SIGNAL(readyRead()), &loop, SLOT(quit())); loop.exec(); QString result = QString(reply->readAll()); reply->deleteLater(); return result; } QString NetworkManager::postRequest(QString url, const FormData &formData) { QString boundary = "---------------------------723690991551375881941828858"; QString header = "multipart/form-data; boundary=" + boundary; QByteArray data; // add all files foreach (FileField line, formData.getFiles()) { data += QString("--" + boundary + "rn").toUtf8(); data += QString("Content-Disposition: form-data; name=" % 1 "; filename=" % 2 "rn") .arg(line.getName()) .arg(line.getValue()) .toUtf8(); data += QString("Content-Type: %1rnrn").arg(line.getMimeType()).toUtf8(); QFile *file = new QFile(line.getValue()); if (!file->open(QIODevice::ReadOnly)) return ""; data += file->readAll(); data += "rn"; file->close(); } // add all text fields foreach (Field line, formData.getData()) { data += QString("--" + boundary + "rn").toUtf8(); data += QString("Content-Disposition: form-data; name=" % 1 "rnrn") .arg(line.getName()) .toUtf8(); data += line.getValue().toUtf8(); data += "rnrn"; } data += QString("--" + boundary + "--rn").toUtf8(); QNetworkRequest request(QUrl(url.toUtf8())); request.setHeader(QNetworkRequest::ContentTypeHeader, header.toUtf8()); request.setHeader(QNetworkRequest::ContentLengthHeader, data.size()); QNetworkReply *reply = this->manager->post(request, data); QEventLoop loop; QObject::connect(reply, SIGNAL(readyRead()), &loop, SLOT(quit())); loop.exec(); QString result = QString(reply->readAll()); reply->deleteLater(); return result; } ``` استخدام الكلاس سوف يكون بهذه الطريقة: ```java FormData formData; formData.addField("OriginalText", QString::fromStdString(data)); formData.addFile("Signature", QString::fromStdString(SIGNATURE_PATH), "application/octet-stream"); formData.addFile("File", QString::fromStdString(CERTIFICATE_PATH), "application/octet-stream"); NetworkManager *manager = NetworkManager::instance(); QString serverResponse = manager -> postRequest(QString::fromStdString(SERVER_POST_URL), formData); ``` بالنسبة ل Get فهو مجرد استدعاء الدالة Get الموجودة في ال Wrapper ، وبعد الحصول على JSON Result قد تريد عمل Parse لها ، واذا كانت البيانات بهذه الفورمات : [![output](https://informatic-ar.com/wp-content/uploads/2013/04/output.png)](https://informatic-ar.com/wp-content/uploads/2013/04/output.png) فيمكنك استخدام: ```cpp // your ouput filed struct JsonResult { QString text; QString certificate; QString signature; bool status; }; /** * this parser assume the following format of data * {text: "this is message", certificate: "base64 encoding of cer", * signature: "this base64 encoding of signature" } * * if server send null on any of them then it mean not correct input and * the result is false in JsonResult type */ JsonResult parseJSon(QString input) { QScriptEngine engine; QScriptValue sc = engine.evaluate("value = " + input); QScriptValueIterator it(sc); JsonResult jsonResult; QString certificate, message, signature; while (it.hasNext()) { it.next(); QString header = it.name(); message = it.value().toString(); it.next(); certificate = it.value().toString(); it.next(); signature = it.value().toString(); jsonResult.text = message; jsonResult.certificate = certificate; jsonResult.signature = signature; if (header != "text" || message == "null" || certificate == "null" || signature == "null") { jsonResult.status = false; break; } jsonResult.status = true; break; } return jsonResult; } ``` أرجوا أن يفيد هذا ال Wrapper ، ويمكن طرح الأسئلة والملاحظات ان وجدت،، *شكراً لوصولك لهذه النقطة ..* ### قم بتحديث JSF Implementation URL: https://informatic-ar.com/majorra_bug/ Last updated: 2026-10-03T22:32:29.000Z الاسبوع الماضي واجهت مشكلة في سيرفر احدى المواقع التي أقوم بتطوير مشروع باستخدام Java EE ، المشكلة كانت غربيه حيث انها لا تظهر Not Reproducible على جهازي الشخصي ، وكانت المشكلة في الصفحة الرئيسية حيث كنا نطور فيها باستمرار وكانت تعمل بلا مشاكل، ولكن بمجرد اضافة بعضاً من ال HTML/JSF Code على الصفحة اصبحت لا تعمل، وكلما اقوم بحذفه تعمل الصفحة ، وبمجرد اضافته فلا تعمل الصفحة ويخرج هذا ال Exception: [![majorra_pr](https://informatic-ar.com/wp-content/uploads/2013/04/majorra_pr.png)](https://informatic-ar.com/wp-content/uploads/2013/04/majorra%5Fpr.png) ```java Caused by : java.lang .IllegalStateException : PWC3999 : Cannot create a session after the response has been committed at org.apache.catalina.connector.Request.doGetSession(Request.java : 2880) at org.apache .catalina.connector.Request.getSession(Request.java : 2577) at org.apache.catalina .connector.RequestFacade.getSession(RequestFacade.java : 920) at com.sun.faces.context .ExternalContextImpl.getSession(ExternalContextImpl.java : 155) at com.sun.faces.renderkit .ServerSideStateHelper.writeState(ServerSideStateHelper.java : 175) at com.sun.faces .renderkit.ResponseStateManagerImpl.writeState(ResponseStateManagerImpl.java : 122) at com .sun.faces.application.StateManagerImpl.writeState(StateManagerImpl.java : 166) at com.sun .faces.application.view.WriteBehindStateWriter .flushToWriter(WriteBehindStateWriter.java : 225) at com.sun.faces.application.view .FaceletViewHandlingStrategy.renderView(FaceletViewHandlingStrategy.java : 418) at com.sun .faces.application.view.MultiViewHandler.renderView(MultiViewHandler.java : 131) at javax .faces.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java : 288) at com.sun .faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java : 121) at com.sun .faces.lifecycle.Phase.doPhase(Phase.java : 101)... 27 more ``` طبعاً كانت تفكيري ينصب حول هذا الCode الجديد والذي هو عباره عن لنك لصفحات JSF تستخدم JSF Managed Bean وقمت باعاده كتابته اكثر من مرة ولا فائده ، بعد البحث على الانترنت وجدت أن هذه Bug موجودة في ال JSF Implementation المسمي ب Majorra ويجب التحديث للاصدارات الحديثة من بعد اصدار 2.1.8 والمشكلة بسبب أن حجم الصفحة اصبح كبير اكبر من 2K والسيرفر بشكل تلقائي يقوم بعمل flush للResponse اذا وصل لهذا الحد والمشكلة هنا أنه بسبب ان الصفحة لم تصل للنهايه وتمت كتابه الاجزاء الأولى فأن انشاء ال Session اصبح متأخراً حيث تم ارسال ال Response ولكن بدون الكوكيز (حيث انها يجب ان تكتب في ال Response Header ).. الحل هو بتحميل نسخه Majorra حديثة: [http://javaserverfaces.java.net/](http://javaserverfaces.java.net/?ref=informatic-ar.com) ومن ثم بعد تحميل ملف ال jar قم بوضعه على السيرفر glassfish سواء كان السيرفر تم تنصيبه بشكل مستقل أو كان الذي يأتي مع الNetbeans (الجدير بالذكر ان الذي يأتي مع ال Netbeans في Glassfish 3.1.2 هو نسخه قديمة ايضاً Majorra 2.1.6 ويجب تحديثها اذا كنت تعمل على هذه النسخه من ال application server). عندما تنسخها ضعها على المسار /glassfish/modules وقم بجعل الاسم الملف javax.faces.jar بدون رقم الاصدارة واستبدله مع الملف الموجود هناك: [![updatemajorra](https://informatic-ar.com/wp-content/uploads/2013/04/updatemajorra.png)](https://informatic-ar.com/wp-content/uploads/2013/04/updatemajorra.png) مصادر اخرى للخبير بلاس: [http://stackoverflow.com/questions/11877420/jsf-error-illegalstateexception-pwc3999-cannot-create-a-session-after-the-re ](http://stackoverflow.com/questions/11877420/jsf-error-illegalstateexception-pwc3999-cannot-create-a-session-after-the-re?ref=informatic-ar.com)**[http://java.net/jira/browse/JAVASERVERFACES-2277](http://java.net/jira/browse/JAVASERVERFACES-2277?ref=informatic-ar.com)* *ارجوا ان تفيد التدوينه كل من واجه أو سيواجه هذه المشكلة فقد تحفظ عليك ساعات من شد الشعر :)* ### تعرف على فريق ال Agile URL: https://informatic-ar.com/agile_team/ Last updated: 2026-10-03T22:32:29.000Z [![1](https://informatic-ar.com/wp-content/uploads/2013/03/1-150x150.png)](https://informatic-ar.com/wp-content/uploads/2013/03/1.png)فريق ال Agile ليس كباقي الفرق في منهجيات التطوير التقليدية، حيث لا يوجد اهتمام كثيراً بالألقاب ولا بالأدوار الثابته، اي شخص يستطيع القيام بأكثر من دور ، قد تظن ان هذه عشوائية بسبب عدم وجود الرسميات والمناصب بين اعضاء الفريق Hierarchy ولكن في الحقيقة فريق ال Agile بهذا الشكل يستطيع انتاج منتجات ذو جودة عالية ، في هذه المقالة سوف نتعرف على ما يميز فريق الAgile ، وما هي مواصفات فريق ال Agile الجيد، وكيف يبدوا هذا الفريق، وكيف يمكن أن تكون فريق من الاعضاء الجيدين Quality Players قبل أن تبدأ نقطة البدايه في المشروع. لأخذ نظرة عن السلسة كل ما تحتاجه عن ال Agile: - [ما هي ال Agile ](https://informatic-ar.com/agile%5Fin%5Fnatual/) - فريق التطوير في ال Agile (المقالة الحالية) - [كيف تبدأ الخطوة الأولى في المشروع](https://informatic-ar.com/how%5Fstart%5Fproject/) - [كيف تخطط في مشروع ال Agile](https://informatic-ar.com/agile%5Fplanning/) ## ماذا يميز فريق ال Agile ولماذا هو مختلف؟ اول نقطة هي في أدوار اعضاء الفريق حيث تتقلص الالقاب بشكل كبير في المشروع، والكل يعمل حتى في اشياء اخرى لا تتعلق بمسماه الوظيفي (التشبيه الصحيح بفريق ال Agile هو كأن الفرد يعمل ضمن شركة ناشئة صغيرة Mini-Startup حيث ستعمل على أي شيء مهما كان اذا كان ذلك يسبب في نجاح المشروع بغض النظر عن وظيفتك الحقيقية). بالطبع مهارات كل شخص ستكون موجودة ويستفاد منها في محلها المعروف، ولكن في فريق ال Agile لا يوجد الالتزام الكامل بدور واحد وتجاهل الاخر في حال كان هناك نقص بسبب أنه لا يخصك ولا يأتي تحت مسماك الوظيفي. وهذا ما يجعل الفريق يعمل بروح واحدة لانتاج المشروع وليس ان يعمل كل فرد على جزئيه ولا يساعد في اي شيء اخر لأنه انهى عمله ، الصورة التالية تبين خفة الادوار وتقليصها في الفريق. [![2](https://informatic-ar.com/wp-content/uploads/2013/03/2.png)](https://informatic-ar.com/wp-content/uploads/2013/03/2.png) ثانياً الشيء الذي يميز فريق ال Agile هو أن التحليل Analysis والتصميم Design والبرمجة Coding والاختبارات Testing هي عمليات مستمرة دائما continuous activities لا تتوقف ابدأً. ولا وجود للفصل التام بين هذه العمليات كما في المنهجيات التقليدية بل بالعكس سيتم العمل عليها بشكل يومي طوال فترة المشروع، الصورة التالية توضح الفريق بين ال Agile والطريقة التقليدية ولاحظ أن هذه العمليات مستمرة دائماً في ال Agile ولا تتوقف. [![3](https://informatic-ar.com/wp-content/uploads/2013/03/3.png)](https://informatic-ar.com/wp-content/uploads/2013/03/3.png) ثالثاً في فريق ال Agile لا توجد محاسبة لفرد واحد بل الفريق بالكامل مسؤول في التطوير، فاذا وجدت مشكلة Bug في التطوير فلن تسمع السؤال : "*كيف لم يتمكن أفراد QA ايجاد هذه المشكلة اذا هم السبب*"، فالجميع مسؤول والجميع يقوم بالعمل بأفضل اداء لذلك ال Quality Assurance موجودة في كل مرحلة في التطوير. [![4](https://informatic-ar.com/wp-content/uploads/2013/03/4.png)](https://informatic-ar.com/wp-content/uploads/2013/03/4.png) هكذا، تقليص المسميات و القيام بعمليات التطوير بشكل مستمر وجعل الفريق بالكامل مسؤول عن التطوير هو ما تتوقع ايجاده في أي فريق يسمى نفسه Agile وهو ما ستقوم به في حال اردت الوصول لذلك، لنتعرف الآن على ما يجعل فريق ال Agile فريق ناجح بكل المقاييس. ## مواصفات فريق ال Agile الناجح قبل البدء بمشروع ال Agile هناك بعض الأشياء يمكن ان تقاتل من أجلها من أجل نجاح المشروع: ### **اقتراب اعضاء الفريق** **Co-Location** اذا كان هناك شيء واحد سوف يزيد من انتاجية الفريق فسوف يكون هو جلوس الفريق مع بعضهم Sitting Together، فالفرق المتقاربة تعمل أفضل، يتم اجابه الأسئلة أسرع، تحل المشاكل بمجرد اكتشافها، ويتم بناء الثقة بين الفريق سريعاً، حقيقة من الصعب منافسه فريق ال Agile الصغير الذين يعملون مع بعضهم البعض. بالطبع اذا تعسر ان يكون الفريق متقارب ووجب ان يكون متباعد Distributed فيمكن القيام ببعض الأمور لتقليص الفجوة مثلاً ان تقوم بحجز مبلغ من الميزانيه في اول المشروع لغرض جلب جميع اعضاء الفريق مع بعضهما البعض وحتى اذا كانت لعدة ايام، فهذا الوقت الذي سيتم قضاؤه للتعرف وتبادل المزح وتناول الطعام مع بعضهما سوف يفيد على المدى الطويل في بناء فريق عالي الأداء، لذلك حاول بأقصى ما تستطيع جلب الجميع على طاولة واحدة منذ البدايه. بعد ذلك يمكن استخدام طرق الاتصال المتوفرة (سكايب Skype ، اجتماعات الفيديو Video Conferencing ، الشبكات الاجتماعية .. الخ) وذلك لجعل الفريق البعيد يبدوا كالقريب وحتى هم في دول ومناطق مختلفة. ### **عميل متواجد** **Engaged Customers** العميل المتواجد هو الذي نبني له المشروع و الذي نريه عرض البرنامج Demo وهو الذي يجيب على الأسئلة ويضع ملاحظاته Feedback خلال فترة تطوير المشروع، وهو يعتبر في ال Agile من ضمن أعضاء الفريق المهمين حيث كيف يمكن أن تبنى مشروع للعميل غير المتواجد خلال فترة التطوير؟، لذلك المنهجيات التي تعتبر ضمن ال Agile مثل Extreme Programming أو Scrum تحاول بأقصى ما تستطيع ان تدخل العميل في التطوير اليومي (وتطلق على On-Site Customer أو ب Product Owner) وهو حقاً دور مهم وسوف نتعرف على طبيعة عمل هذا العميل بعد قليل. قد تتسائل "ماذا اذا لم يتوفر العميل المتواجد في التطوير" ربما فقد حضر العميل منذ بدايات المشروع واختفى، وربما يظن العميل انه لم يعد بحاجه لهذا المشروع أو حتى الأسوء انك لن تستطيع القيام به وتُنجح المشروع. وبغض النظر عن المشكلة فاذا كنت تريد من العميل ان يكون اكثر يقيناً باستمرار العمل Build Customer Credibility فأخبر العميل بمجرد ان تراه بأنك خلال فترة الاسبوعين القادمين سوف تقوم بحل المشاكل الفلانية في المشروع، وقم بها وعد اليه بعد اسبوعين واريه كيف قمت بالعمل المشكلة والحل الذي قمت به، وهكذا اعد هذه العملية وقم بالعمل على مشكلة أخرى وقم بحلها. ربما تحتاج للقيام بذلك 3 أو 4 مرات وربما اكثر حنى تبدأ بالحصول على الانتباه Attention وفي الأخير سوف تحصل عليه وسيلتفت اليك بمنظور اخر وسوف يراك على حقيقتك: فريق أو مبرمج صادق يمكن الاعتماد عليه في انهاء المشروع. ربما تكون اسباب عدم توفر العميل اخرى مثلاً لم تستطيع وضع التصورات الجيدة منذ بدايه المشروع وبدء العميل بالشك في تقديراتك، أو كان مشغول بالفعل، ولكن في النهايه حتى تحصل على انتباهه وحضوره فقم ببناء الثقه بالتدريج مجدداً رويداً رويداً وسوف تفوز بذلك حتماً. ### **الفريق المنظم من تلقاء نفسه** **Self-Organizing** أعضاء فريق ال Agile يفضلوا أن يعطوا المشكلة أو الهدف الرئيسي ومن ثم جعل كل فرد في الفريق يفكر كيف يمكن ان يصل لذلك الهدف، وللقيام بذلك يحتاج الفريق ان يعرف طريقة تنظيم نفسه حتى يستطيع الوصول لذلك الهدف. تنظيم الفريق لنفسه Self-Organizing يعني ان تضع أنانيتك Ego خارج الغرفه (مع احتفاظك على مهاراتك وابداعاتك) وتفكر كفريق في كيف يمكن الوصول لأفضل حل واخراج المشروع Deliver في افضل وجه. مثال على ذلك يمكن ان يلعب الدور الشخص المناسب بناء على رؤية الفريق: "بالتأكيد محمد يستطيع كتابه الكود، ولكن هو لديه نظره خاصه في التصميم لذلك هو سوف يساعد في بعض عمل التصميمات الأولية على الورق Mock-up" "نعم عادل هو من أفضل المختبرين Testers لكن ملاحظاته دقيقة عندما يتحدث مع العميل ويضع تقديرات جيدة " هذا لا يعني ان المطورين Developers يجب أن يكونوا محترفين في التصميم أو المختبرين Testing سوف يعملوا على ادارة المشروع، ولكن هو اتجاه حول جعل العمل يختار الشخص المناسب وليس ان تختار الشخص للعمل Let the role fit the person instead of making the person fit the role ، السؤال هنا يمكن ان تجعل الفريق منظماً بهذا الشكل : - دع الفريق بالكامل يضع خطة التطوير plan ويصل للتحديد المدة الزمنية وتقديرها Estimation وجعلهم يدركوا دورهم الحيوي في المشروع. - تقليل المسميات والألقاب في اثناء التطوير والاهتمام أكثر بالانتاج المستمر الصحيح والذي تم اختباره continuous production of working, tested software. - أن تدع الفريق يعمل وكأنهم خبراء في رحلتهم وليس ان يجلسوا في انتظار الأوامر، فكلما اعطيتهم الثقة فكلما انتج الفريق بشكل افضل. > افضل بنية برمجية Architectures واستخلاص للمتطلبات Requirements وتصميم للمشروع سوف تحصل عليه من الفريق المنظم Self-Organizing Teams الآن بعدما اعطيتهم الثقة تبقت نقطة متعلقه بها وهي ### **المحاسبة والصلاحيات للفريق** **Accountable and Empowered** فريق ال Agile الجيد يعلم انه مسؤول عن النتائج التي يخرجها، وأن العميل يعتمد عليهم في تطوير العمل لذلك هم لا يتهربوا من المسؤلية (طريقة التسليم المتكرر للعميل من الطرق الجيدة لزيادة مسؤولية الفريق وجعلهم يدركوا ان هناك من يعتمد على ذلك العمل)، وفي المقابل يحتاج الفريق الصلاحيات اللازمة واختيار الخيارات المتنوعه اثناء التطوير فهذا يعطيه حريه أفضل وايضاً يؤدي لزيادة الأحساس بالمسؤولية، وكما ذكرنا اذا وجدت ان الفريق لديه مشكلة في موضوع المسؤولية فقم بأسهل حل وهو عمل عرض تجريبي Demo مع العميل، هكذا ستضعهم امام عميل حقيقي وتجربة للبرنامج وهذا سيزيد احساس المسؤولية بشكل كبير، والأمر يزيد ويتضح في حال كان العرض سيئ والبرنامج لا يعمل جيداً فهذا سيزيد من اهتمام الفريق نحو جودة العمل ومحاولة ايصاله بشكل افضل. ### **الفريق متعدد المهارات** **Cross Functional** الفريق متعدد المهارات هو الذي يستطيع تطبيق اي خاصية يطلبها العميل من البدايه الى النهايه، وهذا يعني ان الفريق لديه المهارات والخبرات اللازمة في جوانب المشروع، لذلك عندما توظف اعضاء الفريق فاحرص على جلب المبرمج الذي يستطيع العمل بأكثر من لغه وتقنية مختلفة Generalists (سواء كان Front-End أو Back-End) واحرص على جلب المختبر الذي يستطيع الاختبار طوال فترة المشروع وهكذا بالنسبة للمحللين وبقية اعضاء الفريق، هناك حالات تتطلب خبرة في اشياء معينة (مثلاً عمل ضبط للقاعدة البيانات للحصول على اداء افضل Database Tuning) ولكن في اغلب الأحوال يعمل الفريق مع بعضهم البعض وربما يستطيع حلها بدون الاستعانه بالخبرات. بعد أن تحدثنا عن مواصفات الفريق الناجح، السؤال هو كيف يمكن أن يعمل هذا الفريق مع بعضهم البعض، والجواب عن طريق لعب الدور بشكل جيد، سواء كان العضو يعمل في اكثر من دور اذا لم يكن هناك فريق مكتمل (مثلاً ان تعمل بنفسك أو مع شخص أخر، حينها عليك ان تلعب اكثر من دور) أما اذا كان فريقك مكتمل وان الأدوار مكتملة ولا تحتاج لشخص ان يلعب اكثر من دور فحينها هناك مهمه رئيسية لكل دور في الفريق وهو ما سنتناوله فيما يلي حتى يتم القيام به شكل صحيح. ## الأدوار Roles في فريق ال Agile عندما نتحدث عن المشاريع فكل ما يهم ان هناك مشروع سوف نقوم به نحن فريق التطوير Development Team و نريد بنائه للعميل Customer [![5](https://informatic-ar.com/wp-content/uploads/2013/03/5.png)](https://informatic-ar.com/wp-content/uploads/2013/03/5.png) فريق التطوير لا يضم فقط المطورين Programmers بل يضم المختبرين Testers والمحللين Analysts وكل ما يدخل في تطوير المشروع، فكلهم ضمن المشروع ولا تهتم ال Agile بمن يلعب ذلك الدور بل جل الاهتمام هو القيام بالدور بالشكل الصحيح، ولنلقى نظرة على أدوار فريق ال Agile ولنبدأ بأحد أهم الأدوار وهو العميل Customer ### **العميل** **Agile Customer** العميل هو الذي نأخذ منه المتطلبات وهو الذي يريدهذا المشروع عندما ننتهي منه، وهو على علم بتفاصيل العمل Subject-Matter ويهتم بكيف سيكون شكل البرنامج وكيف سيعمل، وهو الذي يجيب على الأسئلة ويطرح الملاحظات للفريق، كما انه يقوم بتحديد الأولويات في التطوير فقد يريد هذه الخاصية الآن بشكل عاجل بينما الأخرى فيما بعد (عملية تحديد الأولويات تحتاج ان تكون مع فريق التطوير وذلك لأن بعض الخصائص قد لا يمكن تنفيذها الا بعد تنفيذ خصائص أخرى، ولذلك تحديد الأوليات تحتاج للعميل والفريق بسبب الأمور الفنية في المشروع). للقيام بكل ذلك تحتاج أن يتوفر العميل بشكل دائم On-Site Customer والا فاذا لم يتوفر (كما هو الحال في الغالب) فقم بقدر الامكان بمحاولة جلبه بشكل مستمر واجعله يفهم طبيعة دوره في المشروع وان قراراته Decisions مهمه جداً في سير المشروع ونجاحه بشكل عام. الصورة التالية تبين دور العميل في المشروع: [![6](https://informatic-ar.com/wp-content/uploads/2013/03/6.png)](https://informatic-ar.com/wp-content/uploads/2013/03/6.png) لنلقى الأن نظره على فريق التطوير: [![7](https://informatic-ar.com/wp-content/uploads/2013/03/7.png)](https://informatic-ar.com/wp-content/uploads/2013/03/7.png) هذه المجموعه هي التي ستقوم بتحويل ما يطلبه العميل من ال features الى برنامج جاهز للعمل Production-Ready ، وهو يشمل اي شخص يعمل في المشروع، ولنبدأ بالمحلل. ### **المحلل** **Analyst** عندما يبدأ العمل على ال Feature فسوف يبدأ المحلل بالعمل ويقوم بمعرفة التفاصيل لكل شيء متعلق بهذه الخاصية، وسيعمل كمحقق مع العميل لكي يستخرج كل المعلومات المطلوبة للمشروع، كما أن له أدوار أخرى في المشروع حيث يساعد في كتابه ال user stories، وهو الذي يقوم بالتحليل الشامل لكل Feature يراد تطويرها، ايضاً يمكن أن يساعد في اعداد شكل البرنامج على الورق Mock-Ups والPrototypes (سوف نتحدث عن هذا الدور بشكل موسع وكيف يمكن استخراج المعلومات وكتابه ال user stories في المقالات التالية ان شاء الله). [![8](https://informatic-ar.com/wp-content/uploads/2013/03/8.png)](https://informatic-ar.com/wp-content/uploads/2013/03/8.png) ### **المبرمج** **Programmer** هو من يقوم بتحويل الخاصية Feature الى شيء يعمل عن طريق البرمجة، مبرمج ال Agile يهتم كثيراً باختبار الأكواد حتى يحصل على كود ذو جودة عالية وجاهز للعمل وذلك عن طريق كتابه الاختبارات Unit Testing وأحياناً يستخدم تلك الاختبارات كموجه له في تصميم هيكلة المشروع باستخدام طريقة كتابه الأختبار قبل كتابه الكود Test Driven Development، ايضاً يقوم المبرمج بعمليات التحسينات Refactoring الدائمة للمشروع من ناحية الهيكلة Architecture ، ويستخدم بعض الأدوات للتأكد من جاهزية الكود للعمل في اي لحظة باستخدام أدوات ال Continuous Integrations، وبالطبع سوف يعمل مع العميل ايضاً مع غيره من اعضاء الفريق حتى يتأكد من ما تم بنائه وانهم في الطريق الصحيح. (سوف نتحدث عن هذا الدور بشكل موسع والأدوات المستخدمه في المقالات التالية ان شاء الله). [![9](https://informatic-ar.com/wp-content/uploads/2013/03/9.png)](https://informatic-ar.com/wp-content/uploads/2013/03/9.png) ### **المختبر** **Tester** المختبر هو من يتأكد من ان البرنامج يعمل بشكل صحيح، لذلك هو يعمل منذ البدء مع الفريق ويتأكد من انه كل user story تعمل بشكل صحيح، ولأن كل Feature تحتاج للاختبار فسوف يتواجد المختبر في كل المراحل اثناء التطوير، حيث ستجد المختبر يعمل مع العميل لكي يستخرج المتطلبات التي يحتاجها في اختبار المشروع، وسيعمل المختبر مع المطورين ويساعدهم في كتابة الاختبارات ويقوم بايجاد الاخطاء عن طريق تطبيق انواع الاختبارات المختلفة (اختار الوظائف Functional Testing والاداء Performance and Scalability وغيرها). (سوف نتحدث عن هذا الدور في أحد المقالات التالية ان شاء الله). [![10](https://informatic-ar.com/wp-content/uploads/2013/03/10.png)](https://informatic-ar.com/wp-content/uploads/2013/03/10.png) ### **مدير المشروع** **Project Manager** مدير المشروع يعلم انه لكي ينجح في عمله فيجب ان ينجح فريقه في العمل، لذلك يقوم مدير المشروع بكل ما يلزم حتى يبعد كل ما يقف بين الفريق ونجاح المشروع، ولديه كثير من المهام سنتعرف عليها فيما بعد (التخطيط في المشروع، اعادة وتعديل الخطة بما بلزم على حسب سير المشروع) ويقوم بعمل التقارير وسير الفريق للادارة او ال stakeholders ومن مهامه المهمه هو عزل الفريق من الضغوط الخارجية وبناء العلاقات الطيبة بين افراد الفريق. المدير الجيد هو من لا يخبر الفريق بما يجب ان يفعلوه لأن الفريق ليس بحاجه لذلك، فهو يساعد في بناء البيئة الجيدة والتي سيعمل فيها الفريق حتى لو غاب المدير لمدة اسبوع. (سوف نتحدث عن هذا الدور بشكل موسع وكيف يمكن كتابه الخطة وأدارة المشروع في المقالات التالية ان شاء الله). [![11](https://informatic-ar.com/wp-content/uploads/2013/03/11.png)](https://informatic-ar.com/wp-content/uploads/2013/03/11.png) ### **المصمم** **UX Designer** وهو من يقوم بانشاء واجهات الاستخدام المفيدة للعميل، في العاده المصمم يكون لديه حس في التصميم وحماس في سهوله الواجهات Usability وايضاً يجتمع مع الفريق ليعرف كيف يمكن ان يخرج افضل واجهه ممكن للمشروع، ويكون تصميمه بالتوازي مع تطوير المبرمجين بحيث لا يسبق الفريق بخطوت كثيرة ولا أن يقوم بتصميم كل شيء منذ البدء. اذا كان المصمم يعرف عمله جيداً فسوف تحصل على واجهات احترافيه سهله الاستخدام user experience. [![12](https://informatic-ar.com/wp-content/uploads/2013/03/12.png)](https://informatic-ar.com/wp-content/uploads/2013/03/12.png) ### **اعضاء الفريق الأخرين** اعضاء الفريق الأخرين لهم أدوارهم المهمه مثلاً مسؤول قواعد البيانات Database Administrators (أو DBA) ومسؤول النظام System Administrators ، والكتاب التقنيين Technical Writers والمدربين Trainers ومسؤول الِشبكات، فهكلم ضمن فريق التطوير مثلهم مثل البقية. في منهجية ال Scrum هناك دور يسمى Scrum Master وهو يمكن تشبيه بالمدرب Coach الذي يساعد الفريق الذي يعمل على ال Agile لأول مرة، فهو يوضح لهم مفاهيم ال Agile وفلسفتها ويضمن ان الفريق يعملوا على العادات الجيدة ولا يعودوا للعادات القديمة اثناء التطوير. والمدرب يكون فائدته اكبر للفرق الجديدة اكثر منه للفرق التي لديها خبرة في هذه المنهجية. الأمر المهم والذي يجب أن يعلمه جميع افراد الفريق أن الشخص يمكن ان يلعب اكثر من دور في اثناء تطوير المشروع، مثلاً ليعلم المحلل ان المطور سوف يتحدث مع العميل ، وليعلم المختبر بأن المطور سوف يقوم بكتابه الاختبارات، ومثلاً اذا لم يتوفر مصمم للمشروع فهذا لا يعني انه لا يتواجد اي Usability بها ولكن سيلعب الدور احد افراد الفريق. ## اختيار وتوظيف فريق الأجايل Forming Agile Team اخيراً، بقى الحديث عن كيفية اختيار هذا الفريق وتوظيفه Forming Agile Team، والأفضل هو أن تبحث عن من يستطيع العمل بأكثر من أداة أو تقنية Generalists فهم يناسبوا طبيعة هذه المنهجية ولأنهم قد يعملوا بأكثر من شيء مثلاً كتابه الأكواد ومن ثم اجراء تحليل للمتطلبات وربما القيام ببعض الاختبارات في يوم أخر, ايضاً تأكد من ان اعضاء الفريق مرتاحين في العمل مع المتطلبات غير المكتملة بشكل 100% والتي تتطلب ان تكتشفها اثناء التطوير والتي قد تغير في الخطة الموضوعه أو طريقة تنفيذ المشروع حتى يتعامل مع الاحداث الجديدة، ايضاً ابحث عن من لديه روح الفريق والعمل الجماعي القابل للانسجام والتعلم والتطوير مع الاخرين في الفريق. وصلنا لنهايه هذه المقالة ، واصبحت تعرف مفاهيم ال Agile بشكل عام وتكونت لديك خلفية عن الفريق وكيفية تكوينه والمميزات التي تكون في الفريق الجيدة، الأن انت جاهز لبدايه مشروعك الأول Kick start وهذا هو عنوان المقالة التالية ان شاء الله ([كيف تبدأ مشروعك Agile Project Inception](https://informatic-ar.com/how%5Fstart%5Fproject/)). ### ما هي منهجية ال Agile ؟ URL: https://informatic-ar.com/agile_in_natual/ Last updated: 2026-10-03T22:32:30.000Z > حيث لا مكان للاختباء - تلخيص بتصرف من الكتاب The Agile Samurai لتنسى أنك مطور (لدقائق معدودة) وتخيل أنك صاحب مشروع كبير ولديك المال المناسب لهذا المشروع وقد وظفت عدة مبرمجين ليقوموا بالتطوير في هذا المشروع، السؤال المهم الأن: ما هو الشيء الذي يجعلك تثق في أن الفريق يعمل ويسير في الإتجاه الصحيح؟ مجموعه من الملفات الورقية والتقارير ؟ أم عرض Power Point به صور متحركة (هذا يحدث كثيراً للأسف، هناك مبرمج بعد ثلاثه أشهر من العمل جاء بعرض Power Point! ) أم الأفضل أن تشاهد نسخه م[![agile-edge-case-ninja](https://informatic-ar.com/wp-content/uploads/2013/03/agile-edge-case-ninja-150x150.png)](https://informatic-ar.com/wp-content/uploads/2013/03/agile-edge-case-ninja.png)صغرة من البرنامج تعمل أمامك، وخلال كل اسبوع أو اثنان ترى Feature جديدة في البرنامج ؟ لننسى التخيل ولنعد للواقع، وشاهد كيف اتضح لك أن الشيء الملموس ولو كان مصغراً أفضل كثيراً من التقارير والأوراق والوعود الزائفه، بهذا المنظور سوف تعمل جيداً والأشياء الجيدة سوف تحدث. كيف تقوم بذلك، وذلك باتباع طريقة تطوير تسمى Agile، ال Agile هي ممارسات معينة في تطوير البرمجيات تكون غالباً بشكل متكرر وعلى وتيرة واحدة الى حين تطوير المشروع بالكامل Iterative Development، وكما هي طبيعة البرمجيات فلا توجد اي منهجية أو سيلة تضمن نجاح المشروع هكذا بدون أي عوامل أخرى ، وحنى لو استخدمت الAgile فقد تفشل في مشروعك اذا قمت به بشكل خاطئ،لكن باتباع الAgile بالشكل الصحيح فسوف تزيد نسبة النجاح بشكل أعلى من غيرها لأن الأمور سوف تتضح جداً منذ بدايات المشروع. قد تكون صادفت هذه الكلمة كثيراً في مسيرتك البرمجية، وسنتعرف في هذه السلسله من الدروس عن طريقة التطوير هذه وكيف يمكن أن تطور مشروعك بها، وحينها قد تستطيع تسليم المشروع في الوقت والميزانيه المطروحة بسهوله on time, on budget. كل ما تحتاجه عن ال Agile: - ما هي ال Agile (المقالة الحالية) - [فريق التطوير في ال Agile](https://informatic-ar.com/agile%5Fteam/) - [كيف تبدأ الخطوة الأولى في المشروع](https://informatic-ar.com/how%5Fstart%5Fproject/) - [كيف تخطط في مشروع ال Agile](https://informatic-ar.com/agile%5Fplanning/) ## مقدمة عن ال Agile تغطي ال Agile اهم ثلاثه مراحل في التطوير والتي عاده ما تكون فشل المشروع هو القيام بهذه المراحل بشكل خاطئ: 1) **كيف تبدأ مشروعك الجديد Kick Start** سواء كان مشروعك تقوم به لوحدك أو تعمل ضمن فريق كامل، نقطة البدايه مهمه جداً حيث قد يبدأ المشروع وكل شخص قد يكون لديه تخيل عن شكل المشروع مختلف عن الأخر، وفي الأخير بعدما ينتهي الوقت سوف تصل لمرحلة لا تحمد عقباها (مثلاً ان يعتقد مدير المشروع بأن المشروع سوف يتجه بهذا الاتجاه، ويعتقد مدير الفريق بأن المشروع سوف يكون بهذا الشكل، والمبرمج له رأيه ايضاً)، لذلك نقطة البدايه مهمه لازالة كل التشويش والصور غير المكتملة لجميع افراد الفريق (من المطورين والمختبرين والعميل والمحللين ومدير المشروع والى المصممين),, حتى اذا كنت تطور بمفردك فتحديد طريقة البدايه بالطريقة الصحيحه سوف يفيدك في هذا المشروع ، اضافة لتحضيرك لكي تعمل على احدى فرق ال Agile ولن تجد متاعب حينها. 2) **كيف تجمع المتطلبات Requirements** وتقوم بحساب الوقت المناسب لها Estimation وتقوم بوضع الخطة plan التي ستعتمد عليها في التطوير ، هذه المرحلة من اهم المراحل فكل المشاريع تحتاج خطة واضحه المعالم، بالاضافة الى أن كثير من المبرمجين يواجهوا صعوبات في تقدير الوقت المطلوب في انجاز العمل، في هذه المرحلة ستم توضيح كيف يمكن التقدير بصورة صحيحة واعداد الخطة الصادقة والواضحة الأهداف والمعالم. 3) **كيف تبدأ مرحلة التنفيذ Execution** وهنا كيف ستقوم بتحويل الخطة الى منتج حقيقي ذو جودة في الكود Quality Code، وهنا ستمر على كافه مراحل التطوير (التحليل ، التصميم، البرمجة، الاختبار) ولكن لن تكون بشكل تقليدي لأن تطبيقها سوف يكون على حسب خطتك بالاضافة الى الاعتماد على الاساليب الصحيحة في بناء البرمجيات (مثلاً القيام بعمليات تحسين الكود بشكل متكرر Refactoring، استخدام الأدوات الصحيحة Unit Testing والخ). ## قواعد أساسية Manifesto في منهجية ال Agile قبل أن نبدا بالخوض في غمار ال Agile فهناك بعض قواعد لهذه المنهجية يجب ان يدركها اي شخص يعمل ضمنها، سوف نذكرها بشكل موجز حتى تستعد للرحلة القادمة، لا تقلق اذا كان هناك غموض فهذا طبيعي مع أول درس في اي مجال، فقط استمر بمتابعة البقية وستجد الأمر سهل الادراك: 0) **التسليم المتكرر كل فترة Deliver Something of Value Every Week**، لا يوجد افضل من تسليم ميزة Feature تعمل جيداً من التطبيق كل فترة (اسبوع او اثنان)، هكذا يطمئن العميل ان مشروعه يسير في المسار الصحيح وان ماله لن يذهب سدى، لذلك تعتبر هذه من قواعد ال Agile المهمة . ![تسليم شيء ذا قيمة كل فترة](https://informatic-ar.com/wajdy_pics/agile/agile1.png) 1) **تقسيم المشكلة الكبيرة الى أجزاء صغيرة**: كل أسبوع لن تستطيع تسليم شيء ضخم لأنه أسبوع واحد فقط، لذلك عليك بتقسيم المهام الى أبسط أشكال ممكنة حتى تستطيع العمل على كل يكفي خلال الاسبوع، مثلاً لو أردت بناء موقع للعميل، فسوف تقوم بتقسيم العمل على اجزاء صغيرة (الصفحة الرئيسية، الصفحات الأخرى، لوحة التحكم، ارسال البريد، الخ). ![](https://informatic-ar.com/wajdy_pics/agile/agile2.png) 2) **فقط اهتم بالأشياء المهمة وانسى البقية**: فالCustomer أو المدير لن يهتم بالتوثيقات Documentation والأوراق اذا لم يكن هناك شيئاً يعمل، فالأوراق والخطط والتوثيقات هي لدعم البرنامج (اقصد الذي يعمل Working Software) هي مهمه ولكن اذا لم يكن لديك برنامج يعمل فما فائدتها؟. عندما تركز على هذا النقطة (ميزة Feature تعمل كل أسبوع) سوف تكون أفضل في أداء هذه الميزة Feature حيث التركيز لها وتنسى البقية الأن، فأنت تبنى بالتدريج. 3) **الاختبار ثم الاختبار الدائم**: بما أنك تطور بالتدريج ميزة تلو الأخرى فالاختبار سوف يكون تدريجي ايضاً منذ أول اسبوع، مثله مثل التطوير. 4) **استمع جيداً وخذ الأراء Feedback**: كل اسبوع أو انثان عليك بمعرفة الاراء من العميل، هكذا تعلم أنك في المسار الصحيح، وتتحرك بقلب مطمئن الى الأمام (من مشاكل الطرق التقليدية في التطوير وبالأخص Waterfall هي انك بعد عناء التطوير والمدة الكبيرة مثلاً 8 أشهر ومن ثم تأتي لمرحلة التسليم وتبدأ عملية مرحلة التذمر من العميل وأنه لا يريد هذا ولا يريد هذا وان هذا يريده بشكل اخر، الAgile سوف يقلص هذا لأقل درجة ممكنة حيث العميل -الذي يريد البرنامج- يعرف شكل البرنامج ويعرف الخصائص حيث كان يشاهده طيلة فترة التطوير وكل التغييرات سوف تحدث اثناء التطوير). 5) **لذلك اقبل التغيير متى ما كان**: المتطلبات تتغير دائماً (هذه احدى القواعد في تطوير البرمجيات، يجب عليك ان تدركها جيداً)، , وأيضاً طبيعة العمل تتغير فقد يغادر ذو الخبرة من الفريق Senior Programmer أو أن يذهب في اجازه والخ، اذا كنت تتبع الخطط بشكل أعمى فسوف تصل في نهايه المشروع الى شيء لا يريده أحد (ولن تستطيع تحمل الركلات واللكمات) ، فعندما يتعارض الواقع مع الخطة، فغير الخطة وليس الواقع when reality messes with your plan, you change your plan not reality. ![](https://informatic-ar.com/wajdy_pics/agile/agile3.png) 6) **كن مسوؤلاً become accountable**: التسليم الأسبوعي يعطيك الأحساس بالمسؤولية أكثر فأنت ترى العميل كل اسبوع وتريه التقدم وفي أين دفعت النقود، لذلك هناك من يرفض طريقة العمل هذه، ربما لأنها تتسم بالوضوح ومشاهدة العمل الحقيقي فلا مكان للاختباء أو الهرب فكل شيء مكشوف في اخر الاسبوع (هل انت عملت أم لم تعمل).. لكن في الحقيقة اذا كنت شخص متحمس وتحب العمل ف Agile توفر لك بيئة ممتعة حقاً . ![](https://informatic-ar.com/wajdy_pics/agile/agile4.png) ولا تنزعج من فكرة كل اسبوع تسليم، فهذا يختلف من فريق لأخر ، هناك من يسلم كل اسبوعين وهناك من يسلم كل ثلاثه اسابيع، الفكرة هنا ان تتعود على تسليم شيء يعمل كل فترة لأخذ الأراء والملاحظات وان تكون مستعد للتغيرات اذا حدثت. (من حسب تجربتي في ال Agile فهي تجدي نفعاً مع المشاريع الموجهه للعميل أو يمكن أن نقول Enterprise Application أو Web Application حيث لا وجود للبحث العملي في فترة التطوير، لكن بالنسبة للعمل البحثي الذي يتطلب الابحاث والتجارب فبعض ممارسات ال Agile سوف يجدي ولكن البعض الاخر لن يفيد كثيراً ). 7) من أعلى أولويات ال Agile هو **ارضاء العميل من خلال التسليم المبكر والمستمر early and continuous delivery** لأشياء تعمل في النظام، لذلك اذا كنت تستطيع اطلاق الصفحة الرئيسية من الموقع بعد اسبوع فافعل ذلك وارى العميل انك تعمل وهذه النتيجة . 8) **طريقة التطوير Agile ليست عشوائية بل هناك مرحلة تخطيط لأي مرحلة Plan** ، والتخطيط في مشروع Agile لا يختلف عن التخطيط لرحلة طويلة، وبدلاً من استخدام لفظ قائمة المهام To Do List هنا يستخدموا لفظ Master Story List و User Stories (عذراً لن نترجم المصطلحات ولكن سنشرحهم جيداً فيما بعد). ![](https://informatic-ar.com/wajdy_pics/agile/agile5.png) في ال Agile يمكن أن نشبه ال Master story list بال to-do list كما ذكرنا، حيث هي تحتوي على كل الFeatures بشكل مبسط جداً High Level (يطلق على اي Feature بال user story) والتي طلبها العميل في المشروع، ويقوم العميل بترتيبها حسب الأهمية التي يراها Prioritized، وتقوم انت المبرمج وفريقك بتخميين Estimate الوقت المناسب لهذه الuser stories وهكذا تعتبر هذه أول نواه للخطة Plan في مشروعك. لو لاحظت في الصورة السابقة أن هناك مراحل تسمى Iterations وهي الفترة الزمنية (اسبوع أو اثنين) والتي تقوم فيها بتطوير اي من ال user story التي يريدها العميل (سوف تأخذ الأكثر أهمية بناء على تقييم العميل) وتقوم بتحويل هذا الطلب الى شيء يعمل في المشروع.. اثناء بدء المشروع سوف تعلم انت وفريقك سرعتكم في التطوير (تسمى Team Velocity بمعنى مقدار العمل الذي انجز خلال الIteration الواحد)، وهكذا بتتبع ال Velocity واستخدامه كمؤشر يمكنك ان تتوقع كم ستنجز في الفترة المقبلة والتالي تستطيع اعطاء العميل اكثر خطة واقعية وصادقه honest plan وهكذا تبتعد عن مشاكل القول بلا فعل over-committing. احياناً قد يطلب منك عمل اكثر من ما تستطيع، وفي هذه الحالة عليك بفعل ما تستطيع وهو فعل الأقل، وكلما كنت مرناً في مزايا المشروع Flexible on Scope كلما تكون مماشياً مع الخطة ومع الوعيد التي اعطيتها Commitments. وكما ذكرنا عندما يختلف الواقع Reality مع الخطة Plan ، فقط عليك بتغيير الخطة (وهذه من أهم ما يميز ال Agile وهو **الخطط المتغيرة Adaptive Planning**). فأنت تعمل مع العميل منذ أول يوم وهو يعرف كل خطوة تقوم بها والمشاكل الحالية وسوف يقوم بأخذ القرارات المتعلقة بسير المشروع، والتواريخ والميزات الموجودة فيه. هكذا في الAgile لا يوجد شيء يسمى العمل على خطة مهما كان سواء كانت مناسبة أم غير مناسبه ومن ثم انتظار المعجزة كي تحدث ويخرج المشروع,, ![](https://informatic-ar.com/wajdy_pics/agile/agile6.png) 9) **الصدق في الحديث وان الميزة عندما تنتهي يعني انها انتهت من كل شيءDone Means Done**، فأكثر ما يزعج العميل أو المدير ان تقول له انتهيت ثم تأتي بعد فترة وتقول هناك شيء لم ينتهي بعد (لا نريد اي مفاجئات خصوصاً في الوقت الأخير) ، لذلك هذه القاعدة من اساسيات ال Agile فعندما يقال ان الميزة انتهت فهذا يعني انها انتهت فعلياً (التحليل، التصميم، البرمجة، الاختبار، اي اختبارات اخرى), [![](https://informatic-ar.com/wajdy_pics/agile/agile7.png)](https://informatic-ar.com/wajdy%5Fpics/agile/agile7.png) 10) **لا تنسى القواعد الأساسية الثلاثه في تطوير البرمجيات Three Simple Truths** (لا يمكنك كامل جمع المتطلبات منذ البدء، المتطلبات سوف تتغير دائماً، الوقت دائماً ضيق مقارنه بالمطلبات الأساسية والموراد ايضاً قليلة) ![](https://informatic-ar.com/wajdy_pics/agile/agile8.png) النظرية الأولى (لا يمكنك جمع كامل المتطلبات منذ البدء) تعني انك لن تخف في البدء في المشروع بدون معرفة تفاصيل كل شيء بشكل دقيق، فقط سوف المتطلبات الأساسية والبقية سوف يتم اكتشافها وقت التطوير,, النظرية الثانية ( المتطلبات تتغير دائماً) فهذا يعني ايضاً انك تعي ان التغيير قادم لا محالة، وان كودك يجب ان يكون مهيئ لذلك، وايضاً الخطة المطروحة سوف تتغير مع الوقت وتتماشي مع هذه الاضافات أو التغييرات. النظرية الثالثه (الوقت والمبلغ لا يكفيان لكل العمل) فهذا يعني انك لن تتوقف من العمل بمجرد ان قائمة المهام كثيرة اكثر من الوقت والمبلغ المدفوع، هذا الأمر يحصل دائماً في كل المشاريع وحينها عليك بترتيب المهام بحسب الأعلى أهمية ومن ثم تقوم بالعمل عليها واترك البقية بعدما تنهي المهم. بمجرد قبولك لهذه القواعد فان كثير من الضغوط والقلق سوف تزال اثناء العمل على المشروع، وسوف يكون تركيزك اكثر على العمل وليس على لماذا هذا التغيير ولا يمكن ذلك والخ. 11) **أخيراً الAgile هي ممارسات عامه في التطوير there is no one ultimate flavor of agile** وهناك الكثير من المنهجيات تندرج تحتها مثلاً Scrum أو XP ![](https://informatic-ar.com/wajdy_pics/agile/agile9.png) فمثلاً هناك ال Scrum والتي تستخدم في ادارة مشاريع ال Agile (يمكن القول بأنها قواعد معينة في سير مشروع ال Agile) ، وهناك ال XP والتي هي ايضاً ممارسات معينة تطبق على مشروع ال Agile وغيرها من المنهجيات التي جميعها تطبق تحت مفهوم ال Agile، كل هذه المنهجيات تأتي ضمن فكرة ال Agile وتستخدم ممارستها ولكنها تضيف بعض الأمور خاصه بها، بالطبع يمكنك بعد دراسه الAgile وتطبيقها ايجاد منهجيتك الخاصه في التطوير فلا يوجد مانع لذلك المقالة القادمة ان شاء الله سوف تتحدث عن [الفريق Agile Team](https://informatic-ar.com/agile%5Fteam/) وكيف يكون هذا الفريق وأدوارهم وبعض الأمور التي يجب ان يعلمها كل افراد الفريق قبل البدء في المشروع, الى لقاء قريب,, ### الإسلوب الصحيح لكتابه Utility Classes URL: https://informatic-ar.com/utility_classes/ Last updated: 2026-10-03T22:32:31.000Z [![](https://informatic-ar.com/wp-content/uploads/2012/10/utilities-150x150.jpg "utilities")](https://informatic-ar.com/wp-content/uploads/2012/10/utilities.jpg) هناك الكثير من الدوال أو الثوابت نقوم بكتابتها ووضعها في كلاس ومن ثم نجد أن هناك كلاسات اخرى بحاجه لها، احد الحلول الخاطئة تماماً وهو نسخ ولصق تلك الدوال في اي كلاس كان في حاجه له، بالطبع الحل الصحيح وهو نقل هذه الدوال أو الثوابت الى كلاس عام يسهل للجميع الوصول اليه، تسمى هذه الكلاسات التي تحتوي على الدوال **بالإسم Utility Classes** بينما التي تحتوي على متغيرات فقط **تسمى Constant Classes**. لذلك كثير من الأحيان قد تحتاج لعمل هذا الكلاس وفيه مجموعه من الدوال والمتغيرات الstatic فقط ( كما توجد في الباكج java.lang.Math و java.util.Arrays ) ولن تحتاح لعمل كائن من الكلاس، فقط أن تريد ضم مجموعه من الدوال المتعلقه بشيء ما في كلاس معين. مثلاً دوال تتعامل مع عرض التاريخ أو مع المجلدات. هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا - الإسلوب الصحيح لكتابه Utility Classes (المقال الحالي) - [هل مللت من دالة البناء Constructor ؟](https://informatic-ar.com/constructor-vs-factory/) - [استخدام ال Builder Pattern في دالة البناء](https://informatic-ar.com/builder%5Fpattern/) - [لا تستخدم Public الا وقت الحاجة](https://informatic-ar.com/inaccessible%5Fclass/) - [كيف تصمم الPackages جيداً في تطبيقات جافا](https://informatic-ar.com/packages%5Fin%5Fjava/) - [جافا دائماً Passing by Value](https://informatic-ar.com/java%5Fpassing%5Fby%5Fvalue/) - [نظرة حول الدالة Equals](https://informatic-ar.com/java-object-equals/) - [خارطة طريق لتعلم الجافا](https://informatic-ar.com/how%5Fto%5Flearn%5Fjava/) ## عمل دالة بناء خاصة Private Constructor اذاً نريد أن نمنع الكلاينت من عمل كائن من هذا الكلاس، كيف السبيل لذلك؟ كما نعلم في أساسيات ال OOP في حال قمنا بعمل الكلاس ولم نقم بتزويده بداله بناء Constructor فيمكن أن يقوم المستخدم بعمل كائن منه وذلك بسبب أن المترجم سوف يزود الكلاس بدالة بناء افتراضية default constructor اذا لم يجد أي دالة بناء، أحد الحلول لمنع انشاء الكائن من الكلاس هي **بجعل الكلاس abstract وبالتالي لا يستطيع أحد القيام بعمل كائن منه** ولكن هذا لا يحبذ لأن الكلاس قد يتم الوراثه منه ويتم عمل الكائن بسهوله اضافه وهي الاهم أن المستخدم يخطئ ويظن ان الكلاس مصمم للوراثه منه**.** الطريقة الافضل لمنع الكلاينت من انشاء كائن منه عن طريق تزويد الكلاس بدالة بناء private constructor**،** المثال التالي يبين ذلك: ```java // Noninstantiable utility class public final class UtilityClass { // Suppress default constructor for noninstantiability private UtilityClass() { throw new AssertionError(); } ... // Remainder omitted } ``` لاحظ أننا قمنا بعمل throw لexception فقط حتى تضمن أنه لا يمكن عمل كائن من الكلاس سواء من داخله وخارجه وأنه سيحدث خطأ يوقف سير البرنامج اذا قام أحد بطريقة ما بانشاء الكائن ،،هكذا أصبح لدينا **كلاس Utility لا يمكن انشاء كائن منه** **وفي نفس الوقت غير قابل للوراثه** والسبب أن دالة البناء private والكلاس الابن يحتاج لهذه الدالة لكي يستطيع انشاء الكائن، لذلك لا يمكن الوراثه من ذلك الكلاس. كأسلوب Convention في التسمية يفضل ان تتبع اسلوب واحد في تسمية ال Utility ، مثلاً أن تكتب DateHelper (ينتهي ب Helper) بالنسبة لي فأستخدم اللاحقه Util فهي أسهل في الفهم، مثلاً الكود التالي من أحدى ال FileUtil لأحد المشاريع -تم ازالة كثير من الدوال ووضع ال import العامة التي تنتهي بعلامه \*- : ```java package informatic - ar.com.util; /** * * @author wajdyessam */ import static informatic - ar.com.util.PreconditionsChecker.checkNull; import static informatic - ar.com.util.PreconditionsChecker.checkNotEmptyString; import java.io.*; import java.util.*; /* * Noninstantiable utility class */ public final class FileUtil { /* * Suppress default constructor for noninstantiability */ private FileUtil() { throw new AssertionError(); } /** * Generic Method to write any serializable object to file * @param object type, must be implement Serializable interface * @param object the object to be written to the file * @param file the file path * @throws IOException if there are errors in the written process */ public static void writeObject(T object, File file) throws IOException { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); out.writeObject(object); out.close(); } /** * Generic Method to read any serializable object from file * @param the type of object, must be implement serializable interface * @param file the file path * @return the object to be written * @throws IOException * @throws ClassNotFoundException */ public static T readObject(File file) throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); T object = (T)in.readObject(); in.close(); return object; } /** * create new folder if folderPath is not exists * @param folderPath */ public static void createFolder(String folderPath) { folderPath = checkNull("filepath can't be null ", folderPath); folderPath = checkNotEmptyString("file path must be not empty", folderPath); createFolder(new File(folderPath)); } /** * create new folder if folder is not exists * @param folder is path to folder */ public static void createFolder(File folder) { folder = checkNull("folder must be not null", folder); if (!folder.exists()) folder.mkdir(); } /** * execute command line utility and read the output stream from it * @param path is the path for command line utility * @return the result of output stream as list of string * @throws IOException if the utility is not found * @throws NullPointerException if the path contain null data or empty string */ public static List readProgramOutputStream(String path) throws IOException { path = checkNull("path can't be null", path); path = checkNotEmptyString("path must have a value", path); Process process = Runtime.getRuntime().exec(path); BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = null; List result = new ArrayList(); while ((line = input.readLine()) != null) { result.add(line); } input.close(); return (result); } public static boolean removeDirectory(String path) { return removeDirectory(new File(path)); } public static boolean removeDirectory(File dirPath) { if (dirPath.isDirectory()) { File[] files = dirPath.listFiles(); for (File file : files) { if (file.isDirectory()) removeDirectory(file); else { boolean status = file.delete(); if (!status) return false; } } } return dirPath.delete(); } } ``` ## الثوابت Constant Interface هذا ما يتعلق بكتابه الUtility Class بالنسبة للدوال ،، نأتي الأن لموضوع الثوابت ، وفي الحقيقة نجد أن هناك تصميم غير جيد منتشر منذ القدم لدى بعض مبرمجي الجافا وهو تعريف الثوابت في interface ومن ثم جعل الكلاسات التي تريد هذه الثوابت بعمل implement لهذا ال interface (**هذا يسمى ب Constant Interface**) ولا تحبذ هذه الطريقة بسبب الثوابت يفترض أن تكون جزءاً من عمل الكلاس Implementation ولكن بهذا الشكل سوف تكون جزءاً من الأشياء المتاحة في الكلاس Exported API، تخيل مثلاً انك في النسخه القادمة تريد ازالة ثابت منها فلن تستطيع حتى تحافظ على ال Compatibility في النسخ السابقة ، أيضاً لو كان هناك ابناء لهذا الكلاس فجيمعهم سوف يحمل هذه الثوابت معه حتى ولو لم يحتاجها. لذلك الحل الأفضل للثوابت هي وضعها في Utility Class بدالة بناء private ، وبالنسبة لي افضل أن ينتهي اسمها ب Constants للدلالة على الثوابت بداخلها، على سبيل المثال: ```java package informatic - ar.com.constants; /** * Containing variable names related to the current JRE * implementation also hold some look and feel names * * also hold the application name and version * * @author wajdyessam */ public final class SystemConstant { // application name public final static String APPLICATION_NAME = "My Application Name: "; // application version public final static String APPLICATION_VERSION = "2"; // application and version public final static String APPLICATION_NAME_VERSION = APPLICATION_NAME + APPLICATION_VERSION; // auther name public final static String AUTHER = "WAJDY"; // current user name public final static String USER_NAME = System.getProperty("user.name"); // user home directory public final static String USER_HOME = System.getProperty("user.home"); // user working directory public final static String USER_DIRECTORY = System.getProperty("user.dir"); // operating system version public final static String OS_NAME = System.getProperty("os.name"); // operating system version public final static String OS_VERSION = System.getProperty("os.version"); // java run time version public final static String JRE_VERSION = System.getProperty("java.version"); // java home path public final static String JRE_HOME = System.getProperty("java.home"); /** look and feel constant */ public final static String DUSK_LOOK_AND_FEEL = "org.jvnet.substance.skin.SubstanceDustLookAndFeel"; public final static String RAVEN_LOOK_AND_FEEL = "org.jvnet.substance.skin.SubstanceRavenGraphiteLookAndFeel"; public final static String BUSINESS_LOOK_AND_FEEL = "org.jvnet.substance.skin.SubstanceBusinessLookAndFeel"; public final static String WINDOWS_LOOK_AND_FEEL = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; } ``` ## **خلاصة** تأكد من وضع private constructor لكل من ال Utility Class و ال Constant Class حتى تضمن عدم وراثتها وانشاء كائن منها، ولا تستخدم interface لحمل الثوابت فالClass أفضل منها. ### قل لا ل Constructor بمعاملات كثيرة URL: https://informatic-ar.com/builder_pattern/ Last updated: 2026-10-03T22:32:31.000Z **ما هو عدد دوال البناء اذا أردت عمل overloading لأكثر من 4 معاملات بترتيب مختلف؟** هذه المرة الثانية نشن هجوماً على دالة البناء constructor، في مقالنا الأول ([هل مللت من دالة البناء](https://informatic-ar.com/constructor-vs-factory/) ) فضلنا دالة static factory method على ال constructor بسبب مقروئيتها وامكانية دعمها للCaching لو أردت في وقت لاحق. عندما تريد انشاء كائن من كلاس به متغيرات ضرورية وقت الانشاء وبها اختياريه فسوف نقول أيضاً: **لا لل constructors ولا أيضاً لل static factory methods**. **للتوضيح**: كما نعلم أنه لا يمكن انشاء كائن بدون استدعاء دالة البناء بشكل او أخر، فعندما نقول لا لدالة البناء فنحن نقصد لا بجعلها متاحة للكلاينت public constructor ولكن نريد اخفائها private constructor عن الكلاينت، في مقالة هل مللت من دالة البناء قمنا بجعل دالة البناء private وقمنا باستدعائها من دالة public static في نفس الكلاس، بينما هنا في موضوعنا هذه المقالة سوف نخفى دالة البناء private constructor ونقوم بانشاء الكائن من خلال inner class في نفس الكلاس، لماذا؟ اكمل القرائة الآن. هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا - [الإسلوب الصحيح لكتابه Utility Classes](https://informatic-ar.com/utility%5Fclasses/) - [هل مللت من دالة البناء Constructor ؟](https://informatic-ar.com/constructor-vs-factory/) - استخدام ال Builder Pattern في دالة البناء (المقال الحالي) - [لا تستخدم Public الا وقت الحاجة](https://informatic-ar.com/inaccessible%5Fclass/) - [كيف تصمم الPackages جيداً في تطبيقات جافا](https://informatic-ar.com/packages%5Fin%5Fjava/) - [جافا دائماً Passing by Value](https://informatic-ar.com/java%5Fpassing%5Fby%5Fvalue/) - [نظرة حول الدالة Equals](https://informatic-ar.com/java-object-equals/) - [خارطة طريق لتعلم الجافا](https://informatic-ar.com/how%5Fto%5Flearn%5Fjava/) ## **Telescoping Constructor** دوال البناء العادية constructor ودوال الstatic factory قد لا يستطيعوا تقديم الحل الأفضل في حالة كان لديك العديد من المعاملات المرسلة لدالة البناء وخصوصاً لو كانت هناك معاملات اختيارية ،، على سبيل المثال لو أردنا عمل كلاس يمثل بيتزا يقوم العميل بتخصيص طلبها على حسب ما يريد ، ففي البدايه عليه باختيار الحجم ثم يحدد ما يريده من الخيارات المتاحة هي جبنة وهوت دوق وببروني ،، بطريقة دوال البناء العادية سوف يخرج لدينا كلاس بهذا الشكل: ```java // 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 وذلك عن طريق انشاء الكائن فارغ أولاً، ثم وضع القيم التي يريدها المبرمج، المثال التالي يوضح ذلك: ```java // 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) . المثال: ```java // 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; } } ``` انظر لكيفية تكوين الكائن: ```java Pizza pizza = new Pizza.Builder(10).cheese(true).peppernoi(true).build(); ``` وهكذا حققنا المقروئية والسهوله ويتم تحقيق الأمان والتحقق عن طريق فحص المتغيرات في دالة البناء Pizza ومن ثم عمل throw لException اذا وجد هناك خطأ وفي داخل داله الbuild يتم عمل catch اذا وجد هذا الexception. مثال أخر لتوضيح الفرق لبناء كائن بطريقة telescoping و builder pattern من كلاس يحتوي على ثلاث متغيرات : ```java // 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](http://wiki.netbeans.org/NetBeans%5F72%5FNewAndNoteworthy?ref=informatic-ar.com#Replace%5FConstructor%5FWith%5FBuilder)، ولو ألقيت نظرة على مكتبات الجافا الحديثة مثلاً Guava ستجد الكثير جداً من ال Builder Pattern والتي تفضل على دوال البناء العادية.. لذلك اذا كان لديك مثل هذه الكومة وحتى ان لم يكن لديك overloading : ```java 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. ### لا تستخدم Public الا وقت الحاجة URL: https://informatic-ar.com/inaccessible_class/ Last updated: 2026-10-03T22:32:32.000Z > قلل درجة الوصول للClass أو الMember بأقصى ما تستطيع! [![](https://informatic-ar.com/wp-content/uploads/2012/10/privacy_icon2-150x150.jpg "privacy_icon2")](https://informatic-ar.com/wp-content/uploads/2012/10/privacy%5Ficon2.jpg) هناك نوعين من الكلاسات يمكن كتابتها في جافا، النوع الأول وهو الكلاس العادي الذي تكتبه عندما تنشى أي ملف جافا -ليس بداخل اي كلاس أخر- ونسميه عادة Top Level Class، النوع الأخر وهي الكلاسات التي تكون بداخل كلاس أخر وهي Nested Class. هذه المقاله تحث على تقليل ال Public والذي هو أحد Access Level المتاحة في لغه الجافا، واذا نظرنا بشكل أوسع لل Access Level في اللغه واستخداماتها في الكلاسات (وحتى الInterface) التي من النوع Top Level فسوف نجد أننا أمام خياران فقط (**public class** أو **package-private class**) ولن تستطيع ان تقوم بعمل private class أو حتى protected لل Top Level. هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا - [الإسلوب الصحيح لكتابه Utility Classes](https://informatic-ar.com/utility%5Fclasses/) - [هل مللت من دالة البناء Constructor ؟](https://informatic-ar.com/constructor-vs-factory/) - [استخدام ال Builder Pattern في دالة البناء](https://informatic-ar.com/builder%5Fpattern/) - لا تستخدم Public الا وقت الحاجة (المقال الحالي) - [كيف تصمم الPackages جيداً في تطبيقات جافا](https://informatic-ar.com/packages%5Fin%5Fjava/) - [جافا دائماً Passing by Value](https://informatic-ar.com/java%5Fpassing%5Fby%5Fvalue/) - [نظرة حول الدالة Equals](https://informatic-ar.com/java-object-equals/) - [خارطة طريق لتعلم الجافا](https://informatic-ar.com/how%5Fto%5Flearn%5Fjava/) ## لماذا لا يجب أن تستخدم Public الأمر الشائع لدى كثير من المبرمجين هو **جعل الكلاسات الTop Level دائماً public، وهذا لا يفضل ذلك الا عندما تحتاج لهذا**، فلو قمت بعمله package-private سوف يكون جزء من الImplementation وليس من الExported API وهكذا تستطيع تعديله وتغييره أو حتى مسحه كاملاً في النسخ التالية subsequent release بدون التأثير على الأكواد الClient، ولكن اذا جعلته Public سوف يبقى معك للأبد ولن تستطيع حذفه لأنه سوف يؤثر على الClients. تخيل أنك تعمل في مشروع كبير ومعك أكثر من مبرمج، اذا كنت تضع الكلاسات في باكج واحد وكلهم بأعلى Level (الpublic) فماذا لو أردت تغيير أو حذف شيء ما، سوف تقوم بذلك وتنتظر الى ان يصدر المترجم رسائل الخطأ لكل كلاس يعتمد عليه ذلك الخطأ، وقد تكون أحياناً لا تعلم درجة الاعتمادية الى درجة أنك لا تقوم بتغييره خوفاً من المجهول,, كل ذلك بسبب هذا المستوى العالى من السماحية... في المقابل لو قمت [بتصميم الكلاسات بطريقة الباكجات التي تنظر لل Features](https://informatic-ar.com/packages%5Fin%5Fjava/) ومن ثم في داخل كل باكج تقوم باعطاء اقل مستوى تستطيع هكذا عندما تقوم بتغيير او حذف الFeature فبمجرد مشاهدتك Level أقل من ال public فسوف تعلم أنك في أمان وتستطيع العمل داخل الباكج.. هذه ميزة رائعه وتسهل عليك التطوير في المشاريع الكبيرة,, لذلك منذ الآن اعتبر package-private صديقك الحميم. أحياناً بعد أن تقوم بعمل الكلاس ال Top Level بدرجة وصول package-private قد تجد أنه لا يستخدمه الا كلاس واحد موجود في ذلك الpackage، ويمكنك في هذه الحالة ان تجعله private nested class لأن ذلك يقلل الوصول له حتى من الكلاسات الموجودة في نفس الpackage، ولكن المهم (وهو لب هذه المقاله) هو أن تقلل ايضاً الوصول للpublic class وذلك من خلال جعلها Package private. لذلك اذا وجدت هذا الكلاس: ```java class Point { public double x; public double y; } ``` فقبل أن تهجم على صاحبه بأنه لا يراعي مفاهيم ال Encapsulation وأنه يجب توفير Setters و Getters بهذا الشكل: ```java class Point { private double x; private double y; public Point(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public double getY() { return y; } public void setX(double x) { this.x = x; } public void setY(double y) { this.y = y; } } ``` هذا التصميم فعلاً صحيح في حاله كان الكلاس public ولكن في حالة كان package private او حتى nested class فلا يوجد ضير من ذلك لأنك الوحيد الذي سوف تستخدمه والكلاس أدى غرضه بدون مشاكل. **بالنسبة الكلاسات من النوع الأخر Nested Class** (بالاضافة الى أي شيء members مثل member variables أو member methods) هناك أربعه احتمالات للAccess Level متاحة لهم، وهي بدءاً من الأقل: 1. **private** وهنا سوف يكون ال member متاح فقط للTop Level الذي يحتوى على هذا الmember. 2. **Package-Private** وهنا يكون ال member متاح لأي كلاس موجود في الPackage الذي يتواجد فيه الكلاس (أحياناً تسمى هذه Default Access وهي عندما لا تستخدم أي Access Modifier عند تعريفك للكلاس أو ال Member). 3. **Protected** وهنا يكون ال member متاح الوصول له من الSubclasses وأيضاً من الكلاسات التي توجد في نفس ال package. 4. **Public** وهنا يكون ال member متاح للجميع ، سواء الTop Level Class الذي يحتويه، أو الكلاسات في نفس الPackage ، أو الكلاسات التي في Packages أخرى. بشكل عام يمكنك وضع جميع ال member بالنوع private، واذا احتاج كلاس اخر في نفس ال Package أن يصل ل Member في هذه الكلاس، فاستخدم الPackage private عن طريق مسح كلمة private من تعريف الmember ، لكن اذا كررت هذه العملية كثيراً فعليك بمراجعة تصميم للكلاسات وحاول استخراج تصميم افضل. على العموم هكذا باستخدام ال private أو package-private انت الأن لديك members تعتبر **جزءاً من عمل الكلاس Class Implementations وليس جزءاً من الExported API.** بالنسبة للكلاسات ال public فعندما تغيير الوصول من package-private ل protected فأنت قد رفعت درجة الوصول بشكل كبير، لأن الprotected members يشابه ال public members ويعتبر جزءاً من ال Exported API وسوف يبقى معك للأبد. **لذلك استخدام ال protected يجب أن يكون محدود**. بعض الأحيان لن تستطيع تقليل درجة الوصول لل members methods وهي في حالة الوراثه، حيث انك لا تستطيع استخدام Access Modifier أقل من ما هو موجود في الsuperclass (بالطبع هذا لضمان أنك تستطيع استخدام ال Subclass في أي مكان تستطيع فيه استخدام ال Superclass) واذا قمت بالتقليل سوف تحصل على رسالة خطأ من المترجم، مثال أخر وهو عندما تقوم بعمل implements لأي Interface فلن تستطيع جعل تلك الدوال التي قمت بعمل implement لها سوى public (وذلك لأن أي member في الinterface هو بشكل ضمني public). ايضاً ال member fields يجب أن لا تكون public ، فاذا كان لديك متغير ليس ثابت non-final أو مؤشر ثابت لكائن متغير final reference to mutable object فعندما تقوم بعمل هذا الmember ب public فأنت تسمح بالجميع الوصول له وتغيير قيمته وبالتالي قد لا تستطيع عمل اي فحص على القيمه invariants checking ، ايضاً مثل هذه الكلاسات class with public mutable fields ليست أمنه Not Thread Safe. بنفس الأمر ينطبق الكلام مع ال static fields لكن يمكنك عمل الثوابت من خلال public static final fields (بالطبع يكون ثابت ولا يتغير ، وياتباع المقاييس يجب تسميته بأحرف كبيرة Capital Letter وتفصل بين الكلمات بunderscores). ومن المهم أن تكون هذه ال fields اما primitive values أو reference to immutable objects. أما ال final field ويحتوي على reference to mutable Object له كل المساوىء كما هو الحال مع ال non-final field. بالرغم من أن ال reference لا يمكن تغييره لكن الكائن يمكن أن يتغيير بشكل غير متوقع. ايضاً لاحظ ان المصفوفات التي تكون أكبر من 0 هي دائماً Mutable لذلك من الخطأ ان يحتوي الكلاس على public static final array field ، أو ان يحتوي على Accessors يقوم بارجاع هذه ال field لأن الكلاينت يمكنه ان يغير قيمة هذه المصفوفة. مثال: ```java public static final Things[] value = {}; ``` وهناك حلين لهذه المشكلة، الأول أن تجعل المصفوفة private وتقوم بعمل دالة public ترجع نسخه Immutable من هذه المصفوفة: ```java private static final Thing[] PRIVATE_VALUES = {...}; public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES)); ``` الحل الثاني هو بجعل الصمفوفة private وارجاع مصفوفة جديدة بتلك القيم: ```java private static final Thing[] PRIVATE_VALUES = {...}; public static final Thing[] values() { return PRIVATE_VALUES.clone(); } ``` قد تقول ما الأفضل من هذين الحلين ؟ لكن هذا يعتمد على طريقة استخدام الكلاينت للreturn type وكيف تود أنت مستخدم الكلاس ان يستخدمها الكلاينت. وخلاصه لهذه المقالة يجب أن تقلل ال Accessibility بقدر المستطاع، فقلل أولاً الPublic API بقدر المستطاع (سواء كانت كلاسات أو متغيرات) وابعدهم عن حصولهم على لقب Exported API ( باستنثاء الثوابت public static final fields) وتأكد حينها أن الثابت public static final fields عباره عن مؤشر لكائن Immutable. هكذا باستخدام الباكج بال Feature مع أقل سماحية لل Classes بقى لدينا السؤال الأخير للوصول للكلاس الخارق: **To Mutate or Not Mutate** ؟ نراكم ان شاء الله في المقاله المقبلة عن Immutable VS Mutable ، وهو أحد أسس ال Functional Language ### كيف تصمم الPackages جيداً في تطبيقات جافا URL: https://informatic-ar.com/packages_in_java/ Last updated: 2026-10-03T22:32:32.000Z [![](https://informatic-ar.com/wp-content/uploads/2012/09/packages1-150x150.png "packages1")](https://informatic-ar.com/wp-content/uploads/2012/09/packages1.png) كثير من المبرمجين لا يستخدموا ال packages بشكل جيد، فتارة تجد package باسم لا يدل على محتواها، وتاره تجد package واحد بها كل الكلاسات في البرنامج، وتارة ترى أن المبرمج يستخدم ال Default Package (أي لم يقم بعمل اي package من الأساس) وهذا خطأ كبير في التطبيقات الكبيرة. فالمعامل الأول والمهم للحكم ان تطبيقك مصمم جيداً هو درجة اخفاء كل package التفاصيل عن الأخرى، أي أن الPackage الجيد يخفي كل تفاصيل الImplementations ويقدم API سهله للPackage (أو Module) الأخرى. هذا المعامل معروف بالاسم Information Hiding أو Encapsulation وهو من أهم أساسيات تطوير البرامج Software Design. هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا - [الإسلوب الصحيح لكتابه Utility Classes](https://informatic-ar.com/utility%5Fclasses/) - [هل مللت من دالة البناء Constructor ؟](https://informatic-ar.com/constructor-vs-factory/) - [استخدام ال Builder Pattern في دالة البناء](https://informatic-ar.com/builder%5Fpattern/) - [لا تستخدم Public الا وقت الحاجة](https://informatic-ar.com/inaccessible%5Fclass/) - كيف تصمم الPackages جيداً في تطبيقات جافا (المقال الحالي) - [جافا دائماً Passing by Value](https://informatic-ar.com/java%5Fpassing%5Fby%5Fvalue/) - [نظرة حول الدالة Equals](https://informatic-ar.com/java-object-equals/) - [خارطة طريق لتعلم الجافا](https://informatic-ar.com/how%5Fto%5Flearn%5Fjava/) ## مفهوم ال Information Hiding في تصميم البرمجيات الInformation Hiding مهم لعدة أسباب حيث أنه يسمح لك بالعمل على Module واحد بينما يسمح للمطورين معك على Modules أخرى، وهكذا تستطيع التطوير Developed وحتى الاختبارات والتحسينات testing/optimizations واستخدامه وتعديل الImplementation بدون اخلال شيء (break) في الModule الأخرى. هذا يساعد على سرعه التطوير لأنه يمكن أن يعمل أكثر من مطور في نفس الوقت In Parallel، أيضاً في عمليات الDebugging سوف تكون أسهل لأنك تركز على Module واحد وبنفس الأمر في عمليات اختبار وتعديل الأداء Performance Tuning. من المزايا أيضاً للInformation Hiding هو اعادة الاستخدام Reuse حيث أن تلك الModule قد لا تكون مرتبطة ببضعها بشكل قوي وهكذا قد يساعدك على اعادة استخدامها في أماكن أخرى بخلاف الموضع الأول. أيضاً هذا يقلل قليلاً من مخاطر بناء نظام ضخم Decrease Risk لأنك تبنى على دفعات (Modules) قد تنجح فيما بعد وان فشل ذلك النظام الكبير. جافا تعطينا عدة أدوات تساعدنا في Information Hiding (من خلال ال Access Control) والتي تستطيع استخدامها في الClasses و ال Interface و الMembers سواء كانت متغيرات أو دوال في الكلاس. الاستخدام الشائع الذي تعلمه كثير من المبرمجين (private لكل المتغيرات) هو قد ينجح في حالات وفي حالات قد لا يكون له نفع، والذي يحكم هذا الأمر هو **مكان وجود الكلاس وما هى درجة الوصول له** وهذا مهم ، لذلك هذه المقاله مهمه حيث أنك تضع اللبنة الأولى في تصميم Module مناسبة للكلاسات حتى تقوم بعد ذلك بتطبيق الInformation Hiding بالشكل الصحيح. السؤال المطروح الآن هو كيف اقسم ال Package في التطبيق؟ وفي الغالب للتطبيقات التي تقوم بعملها توجد طريقتين لهذا الأمر. ## **التقسيم بواسطة الخصائص أو المزايا** **Package By Feature** وفي هذا النوع سوف تكون كل package تمثل ميزة Feature في النظام، وكل الكلاسات التي تتعلق بهذه الميزه سوف تضعها فيها، هكذا سيكون الPackage يحتوي على شيء متعلق بهذه الميزة High Cohesion وتكون قد طبقت مفهوم الModular بشكل جيد والأهم من ذلك هو ان هذا التصميم يقلل التعامل مع الPackages الأخرى Minimal Coupling، فأي Item متعلق سوف يكون هنا ولن تجده موزع في مجلد التطبيق، وهكذا اذا اردت حذف هذه الميزة مثلاً من تطبيقك فكل ما عليك هو حذف ذلك المجلد Package (وحل المشاكل التي تنتج من الAPI المستدعاة من ذلك الPackage) ولكن في الأخير العملية سهله للغاية ومريحة للمبرمجين. في هذا النوع Package By Feature سوف تكون أسماء الPackge تدل على محتواها أو المشكلة التي تحلها وان كانت بمصطلحات المشروع Problem Domain، على سبيل التطبيق تطبيق يتعامل مع المخدرات قد يحتوي على هذه الPackges: - com.app.doctor - com.app.drug - com.app.patient - com.app.presription - com.app.report - com.app.security - com.app.webmaster - com.app.util في داخل كل من هذه الPackges ستجد كل شيء متعلق بها، مثلاً لو نظرنا لل com.app.doctor ستجد اكواد الModel وال GUI والاتصال بالقاعدة وكل شيء متعلق بها: - DoctorAction.java - an action or controller object - Doctor.java - a Model Object - DoctorDAO.java - Data Access Object - database items - SQL statements - user interface items - swing, JSP etc من المزايا لهذه الطريقة وهي ان عملت مع مبرمج مبتدئ يمكنك ان تعطيه Package يعمل عليه ، هكذا تستطيع العمل وانت مطمئن أنه لا يعيث في الأرض فساداً طالما هو محصور هنا، حقيقة ميزة جيدة للفرق البرمجية. ## **التقسيم بواسطة الطبقات** **Package By Layer** هذه الطريقة تعتمد على مفهوم الطبيقات الثلاثه في تقسيم الpackage ، وبالتالي ستجد package خاص للdatabase وpackage خاص لل GUI وهكذا، على سبيل المثال: - com.app.action - com.app.model - com.app.dao - com.app.util لاحظ أن أي ميزة Feature في البرنامج تجدها موزعه Spread Out في كل هذه المجلدات، فكل مجلد يحتوي كلاسات لا علاقه لها بها ، فقط بسبب انها تؤدي نفس الوظيفة، هذه التصميم يقلل من ال Cohesion وقلل أيضاً من ال Modularity وجعل التعامل بين هذه الPackages قوي High Coupling ، وكنتيجة لذلك اذا أردت أن تعدل على Feature ما فأنت تحتاج للبحث في كل هذه المجلدات لكي تعدل تلك الميزه ، وأيضاَ اذا أردت حذفها فالأمر متعب حقاً. لذلك عندما تصمم ال Package في التطبيق، اختر الطريقة الأولى By Feature وفضله على الثانيه لأنها تقدم لك Higher Modularity (وبمزايها High Cohesion و Low Coupling) ، وتقدم لك طريقة سهله لأدارة الكود وتصفحه Easier Code Navigation ، وايضاً تقدم لك نظرة عامه جيدة على المشروع High Level Abstraction. أنظر الأن وحاول ايجاد الفرق بين التصميمين وأوجد أيهما أفضل ؟ ( الLayer على اليمين , الFeature على اليسار) ![](https://informatic-ar.com/wajdy_pics/difference_package.png "difference_packages") الأن بعد مرور وقت في التطوير وكبر حجم المشروع سوف تجد طريقة ال Layer بهذا الشكل: ![](https://informatic-ar.com/wajdy_pics/package_by_layer.png "package_by_layer") بينما الطريقة الأولى By Feature سوف تكون بهذا الشكل المتناسق: ![](https://informatic-ar.com/wajdy_pics/package_by_feature.png "package_by_feature") كما تلاحظ الطريقة الثانية By Layer ليست جيدة وحقيقة لم أرى لها أي ميزه عندما تستخدم بمفردها الا التعقيد والملفات الكثيرة... ما أجمل الكود لو تصفحته وعرفت درجة الفصل بين الModules به.. أرجوا أن تتذكر ذلك وقت تصميمك لمشروع القادم أو عند عمل Refactoring لمشروعك الحالي. بعض الأحيان قد تكون لديك أكثر من Feature تستخدم كلاس ما ففي تلك الحالة عليك بنقلها في مكان عام مناسب لتلك المزايا حتى تقوم بخدمتهم وقد تجد أنها أصبحت ك Layer أو Service (ان صح التعبير) وهذا شيء صحيح ويمكن وضعها ايضاً في مكتبة jar اذا أردت ذلك. هذا اول الغيث، وأنت الآن في طريق تصميمك للبرنامج الرائع Well Designed Modules لكن ينقصك شيء واحد وهو أن "[**لا تستخدم public أبداً الا وقت الحاجة**](https://informatic-ar.com/inaccessible%5Fclass/)" وهذا هو عنوان المقاله القادمة ان شاء الله. ### ماذا أضاف الProject Coin في جافا 7 ؟ URL: https://informatic-ar.com/project_coin_java7/ Last updated: 2026-10-04T04:36:48.000Z ## **ماذا تعرف عن ال Project Coin** [المشروع Coin](http://openjdk.java.net/projects/coin/?ref=informatic-ar.com) هو مشروع مفتوح المصدر بدء منذ 2009 وهو جزء أساسي من لغه جافا 7 و النسخه القادمة 8، والفكرة من وراء هذا المشروع هو تقديم التغييرات الصغيرة في اللغه حتى يتم اعتمادها، قدم لهذا المشروع أكثر من 70 مقترح وتمت الموافقه على 6 منها وهي تعتبر من التغييرات الموجودة في النسخه 7. قبل أن نطرح هذه التغييرات من المهم معرفة الجهد المبذول لأي خاصية تقدم جديدة في الجافا، حقيقة خلال تطوير جافا 7 قدمت الكثير من الاراء حول بعض المزايا الجديدة في اللغه، ولكن المشكلة أن بعض الميزات تحتاج لعمل وجهد أكبر من ذي بعض الميزات ، وبشكل عام المخطط التالي يوضع مقدار الجهد والوقت المطلوب للقيام بالعمل، ولو لاحظنا تقديم مكتبة او العمل عليها اسهل بكثير من تقديم ميزة ضمن منصه الجافا. ![](https://informatic-ar.com/wajdy_pics/efforts.png "java_effort") وبشكل عام يجب اختيار الطريق الأسهل الذي يؤدي للحل، فمثلاً اذا كان يمكن عمل التغييرات الجديدة في مكتبة فهذا ما يفضل عمله، لكن بالطبع ليست جميع المزايا يمكن تطبيقها كمكتبة لأنها قد تحتاج ان تطبق داخلياً على مستوى اللغه او المنصه. التصنيف اعلاه يمكن أن تضع التغييرات الجديدة فيه ، مثلاً احد التغييرات الجديدة (التي سنتناولها بعد قليل) وهو السماح بال Underscore Number سيتم تصنيفه على انه Syntactic Sugar ، ومثلاً التغيير الجديد TWR يمكن تصنيفه تحت ال Small new language feature، لو نظرنا في السابق في جافا 5 عندما قدمت ال Annotations يمكن تصنيفها ضمن ال Class file format change، في جافا 7 هناك ايضاً ال InvokeDynamic وهو يقع ضمن ال New JVM Feature. (للمعلومية كلمة Syntactic Sugar تعني ان الSyntax مكرر أي انها موجودة في اللغه ولكن الان وجد بشكل جديد اسهل بدلاً من الSyntax الأول، وفي الغالب ال Syntactic Sugar يتم حذفها اثناء عمل الترجمة بواسطة المترجم ويقوم بارجاعها الى شكلها القديم أو الSyntax الأول، وكما تلاحظ أن تقديم Syntactic Sugar في اللغه سهل لأنه يتطلب فقط التعديل في المترجم). مشروع ال Coin في الغالب يحتوي على ال Syntactic Sugar الى الوصول للتغيرات الصغيرة باللغه Small new language features. حان الوقت لالقاء نظره على تلكم التغيرات في جافا 7. ## **التغييرات في ال Project Coin** كما ذكرنا قدم هذا المشروع 6 ميزات جديدة في جافا 7، حيث سمح باستخدام ال String في جملة switch ، وقدم طريقة جديدة لكتابه الأرقام في الكود Number Literal، وحسن طريقة التعامل مع الException ، ايضاً الآن بامكانك غلق المصادر Resource بدون الحاجة لاستدعاء close من خلال الميزة الجديدة try-with-resource، والميزة الجديدة في تعريف Generic وهي الDiamond syntax، وأخيراً قام بحل بعض التحذيرات التي يصدرها المترجم عندما تتعامل مع Varargs (المعاملات التي تستقبل عدد غير محدد من القيم) .. سوف نتناول الآن هذه التغييرات بشيء من التفصيل، مع تقديم السبب الذي ادى لظهور هذه المتغيرات (سواء بكتابة ذلك أو من خلال الأمثلة المطروحة ) ، ودعنا نبدأ بطريقة استخدام النصوص داخل جملة ال switch. ### **التغيير الأول String in Switch** كما نعلم ان جملة الswitch تسهل لنا كتابة الشروط المتعددة بدون استخدام if-else-if else القبيحة المنظر ، مثل الكود التالي نريد معرفة اليوم الحالي : ```java public void printDay(int dayOfWeek) { switch (dayOfWeek) { case 0: System.out.println("Saturday"); break; case 1: System.out.println("Sunday"); break; case 2: System.out.println("Monday"); break; case 3: System.out.println("Tuesday"); break; case 4: System.out.println("Wednesday"); break; case 5: System.out.println("Thursday"); break; case 6: System.out.println("Friday"); break; default: System.err.println("Error!"); break; } } ``` في السابق ايام جافا 6 وما قبلها هذه القيمة المستخدمه داخل جملة الswitch يجب أن تكون من الأنواع (byte, int, short, char) أو Enum. الان في جافا 7 تستطيع استخدام الString اضافة للأنواع القديمة وبالتالي يمكن أن يكون شكل جملة الSwitch الآن: ```java public void printDay(String dayOfWeek) { switch (dayOfWeek) { case "Saturday": System.out.println("Study Java"); break; case "Sunday": System.out.println("Read Books"); break; case "Monday": System.out.println("Swimming"); break; case "Tuesday": System.out.println("Play Football"); break; case "Wednesday": System.out.println("Write Blog"); break; case "Thursday": System.out.println("Coding"); break; case "Friday": System.out.println("Sleeping"); break; default: System.out.println("Error: '" + dayOfWeek + "' is not a day of the week"); break; } } ``` كما تلاحظ لم يتغير اي شيء في تركيبه الجملة Switch ، وهكذا اغلب تغييرات الCoin Project فقد تقدم تغيير بسيط لكن هذا يسهل لك البرمجة في جافا.. ### **التغيير الثاني وهو تحسين كتابة ال Numeric Literals** هناك الكثير من الاقتراحات التي قدمت لهذا المشروع ، ولكن ما تم اختياره هو اثنان فقط، الأول يسمح لك بكتابة الأرقام من خلال كتابتها بالترميز الثنائي Binary Literals، والثاني يسمح لك باستخدام الشرطة السفلية Underscore للفصل بين الأرقام وذلك لتسهل قرائتها Improve Readability. هذه ليست تغييرات قد تفيدك في برمجتك اليومية، ولكن قد تفيد المبرمجين الذين يعملوا في مكتبات التشفير أو بروتوكلات الشبكات أو حتى يمكن يعمل على مستوى البت والعمليات التي فيه ، بمعنى هي مفيدة لل Low Level Programmers، ولنستعرضها الآن: في السابق كنا لكي نكتب الأرقام الثنائيه نحتاج ان نستدعي الدالة parseInt ونمرر لها الترميز المطلوب: ```java int x = Integer.parseInt("1100110", 2); ``` هذا الأمر مزعج حقيقة، لأنك تحتاج ان تتذكر هذه الدالة وتتذكر المعاملات بها، ويجب أن لا تخطئ في كتابة الرقم الثنائي والا ستحصل على RuntimeException، والأهم من ذلك كله هو الأداء Performance لأنك تقوم باستدعاء دالة كل مرة تود كتابه رقم الثنائي، وهذا يعني ان عملية كتابة رقم ثنائي لن تكون مثل بقية الثوابت (يتعرف عليها المترجم Compile-Time Constant) ولكنها سوف تعامل مثل بقية العمليات وقت التشغيل runtime expression (هذا يعني أيضاً أنك لن تستطيع استخدامها داخل جملة ال Switch ).. الأن في جافا، فقط ادخل 0b قبل كتابة الرقم ، بهذا الشكل: ```java int x = 0b1100110; ``` هذا التغيير البسيط حل لنا المشاكل السابقة، واذا كنت تتعامل مع البايتات بشكل كثير في برنامجك فسوف تسعد بهذه الاضافه بكل تأكيد .. وهناك الاضافه الأخرى وهي مفيدة لأي مبرمج يكتب ارقام طويلة وهي استخدام الUnderscore. فكما تعلم أن قرائه الأرقام الطويلة امر قد يعرضك للأخطاء لذلك في حياتنا العادية نجد ان الأرقام الطويلة في البنوك يتم فصلها بفواصل (مثلاً رصيدك في البنك هو 10,000,000 ريال، ورقم هاتف هو 454-554-54 وهكذا الفصل يسهل القرائه). ولسوء الحظ لا يمكن ان نستخدم الفاصله ولا ال - في الفصل لأنها رموز لها اكثر من معنى في البرمجة، لذلك تم استعارة استخدام الفاصلة من لغه روبي وبالتالي يمكنك الأن كتابه رقمك بهذا الشكل: ```java int x = 10000000; int newX = 10_000_000; // its more readable // we can use also on other types long anotherLong = 2_147_483_648L; int bitPattern = 0b0001_1100__0011_0111__0010_1011__1010_0011 ``` ### **التغيير الثالث وهو تحسين التعامل مع ال Exceptions** هنا لدينا تحسينين ( ال Multicatch وال final rethrow) ، ولننظر لمثال يوضح كيف ستساعدنا التحسين الأول، المثال يقوم بقرائه من ملف ويقوم بعمل Parse ويقرأ الConfiguration ، وفيه العديد من الExceptions التي تم معالجتها: ```java public Configuration getConfig(String fileName) { Configuration cfg = null; try { String fileText = getFile(fileName); cfg = verifyConfig(parseConfig(fileText)); } catch (FileNotFoundException fnfx) { System.err.println("Config file '" + fileName + "' is missing"); } catch (IOException e) { System.err.println("Error while processing file '" + fileName + "'"); } catch (ConfigurationException e) { System.err.println("Config file '" + fileName + "' is not consistent"); } catch (ParseException e) { System.err.println("Config file '" + fileName + "' is malformed"); } return cfg; } ``` هذه الExceptions قد تحدث لأسباب عديدة (الملف غير موجود، الملف تم حذفه اثناء قرائته، الملف له format غير صحيحه، الملف يحتوى معلومات خاطئة) . ولو نظرنا لنوعيه هذه الأسباب يمكن أن نقول المشكلة أما أن الملف غير موجود أو أن فورمات غير صحيحة. وهنا جافا 7 قالت أنه من الأفضل اختصار هذه الExceptions لهذه المسببين الرئيسين ، وبالتالي اختصرنا المشكلة ل "الملف غير موجود" أو "الملف غير صالح" : ```java public Configuration getConfig(String fileName) { Configuration cfg = null; try { String fileText = getFile(fileName); cfg = verifyConfig(parseConfig(fileText)); } catch (FileNotFoundException | ParseException | ConfigurationException e) { System.err.println("Config file '" + fileName + "' is missing or malformed"); } catch (IOException iox) { System.err.println("Error while processing file '" + fileName + "'"); } return cfg; } ``` المتغير e الموجود في جملة الcatch لن تعرف نوعه وقت ترجمة التطبيق، ونوعه سيتم معرفته وقت حدوث الException المعين، هكذا الاختصار سهل البرنامج وايضاً سهل على المبرمج التعامل مع الExceptions (بدلاً من تكرار الكود لكل من الExceptions عندما يكونوا متفرقين). بالنسبة للتحسين الثاني (ال Final Rethrow) فبعض الأحيان تجد أنك عندما تقوم بمعالجة الException في جملة الCatch فانك تريد ايضاً بعمل throw لهذا الException للأعلى، وبالتالي قد تقوم بعمل مثل هذا الكود: ```java try { doSomethingWhichMightThrowIOException(); doSomethingElseWhichMightThrowSQLException(); } catch (Exception e) { ... throw e; } ``` المشكلة أنك عندما تقوم بعمل throw في الأعلى سوف تقوم بعمله ولكن للنوع Exception (وليس للنوع الحقيقي الذي حدث وهو اما IOException أو SQLException) ، الآن في جافا 7 من خلال كلمة final تضعها للنوع في جملة ال Exception فعندما يحدث ذلك الException وتقوم بعمل throw فالنوع سوف يكون النوع الحقيقى وليس الأب لهم Exception: ```java try { doSomethingWhichMightThrowIOException(); doSomethingElseWhichMightThrowSQLException(); } catch (final Exception e) { ... throw e; } ``` بالاضافه لهذه التحسينات في الException Handling نجد أن جافا 7 ايضاً قد قدمت تحسينات في ال Resource Management كما سوف نوضحها فيما يلي. ### **التغيير الرابع Try-with-Resources (وأختصاراً TWR)** فكرة هذه التغيير سهله الشرح حيث انك ستحدد التعامل مع المصادر (ولنقل ملف) محددة بScope معين (بلوك في الكود) وبمجرد انتهاء مجال هذا الScope (الخروج خارج البلوك) سيتم اغلاق المصدر مباشره بشكل تلقائي. فكرة التغيير كانت سهله ولكنها مهمه حقاً لأننا كثيراً ما ننسى اغلاق ال Resources أو حتى نغلقها بشكل خاطئ (حتى انه هناك بعض الأكواد الموجودة في ال JDK وجد بها أخطاء قدمت عندما اقترح هذا التغيير). لنأخذ مثالاً على القرائه من رابط URL والكتابه في ملف (في جافا 6 وما قبلها) ، يمكن أن نقوم ب: ```java InputStream is = null; try { is = url.openStream(); OutputStream out = new FileOutputStream(file); try { byte[] buf = new byte[4096]; int len; while ((len = is.read(buf)) >= 0) out.write(buf, 0, len); } catch (IOException iox) { } finally { try { out.close(); } catch (IOException closeOutx) { } } } catch (FileNotFoundException fnfx) { } catch (IOException openx) { } finally { try { if (is != null) is.close(); } catch (IOException closeInx) { } } ``` هل لاحظ جمل ال close ، المشكلة هي انك عندما تتعامل مع ال External Resources فهناك امور عدة يمكن ان يحصل بها أخطاء (مثلاً الInputStream قد لا يستطيع فتح الرابط أو القرائه منه أو حتى اغلاقه جيداً، الFile قد لا يفتح أو انك لا تستطيع الكتابه فيه أو حتى اغلاقه جيداً) أو حتى مجموعه من الأخطاء بأكثر من سبب وهو الأكثر صعوبة للحل,, لذلك قدم هذا التغيير، فهل أقل عرضه للأخطاء Less error-prone ، نشاهد المثال بعد تحويله لجافا 7: ```java try (OutputStream out = new FileOutputStream(file); InputStream is = url.openStream()) { byte[] buf = new byte[4096]; int len; while ((len = is.read(buf)) > 0) { out.write(buf, 0, len); } } ``` اعتقد أن هذا التغيير مفيد وحتى للمقروئية فالكود أصبح أسهل وأقل في عدد الأسطر، لمبرمجي السي# هذا الأمر موجود لديهم باستخدام using، وكما نلاحظ أعلاه أننا لا نقوم بكتابة الclose بل سيتم ذلك بشكل تلقائي,, عليك أن تكون حريصاً عندما تستخدم هذه الميزة لأنه قد لا يتم اغلاق الresource في حالة واحدة، وهي عندما تقوم بعمل chaning للstreams ويحصل خطأ اثناء ذلك، مثلاً الكود التالي لن يغلق الFileInputStream اذا حدث خطأ في انشاء ال ObjectInputStream (في حال كان الملف موجود someFile.bin ولكنه غير صالح لكي يكون ObjectInput ): ```java try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("someFile.bin"))) { ... } ``` لذلك لا تقوم بعمل التداخل في انشاء الكائنات ، وقم بعمل كل كائن على حده ، وهذه هي الطريقة الصحيحه والتي تضمن الاغلاق الصحيح : ```java try (FileInputStream fin = new FileInputStream("someFile.bin"); ObjectInputStream in = new ObjectInputStream(fin)) { ... } ``` داخلياً كل الكلاسات التي تقوم بهذه الميزة (الاغلاق تلقائياً) قامت بعمل implements لل AutoCloseable وهو interface يقوم اي كلاس باعادة الدوال بداخله عندما يريد تطبيق هذه الميزه TWR، الكثير من الكلاسات في جافا 7 تقوم بعمل implements لل AutoCloseable . وعلى كل النصيحة العامة للمبرمجين هي استخدام ال TWR بقدر الامكان متى توفرت في الكلاس وهكذا ستنهي كثير من المشاكل المتعلقة بعمليات الاغلاق. أذا أردت المزيد عن هذه المقترحات يمكنك زيارة الMailing List ومشاهدة النقاشات حولها، مثلاً المقترح TWR قدم هنا: [http://mail.openjdk.java.net/pipermail/coin-dev/2009-February/000011.html](http://mail.openjdk.java.net/pipermail/coin-dev/2009-February/000011.html?ref=informatic-ar.com) صاحب المقترح هو الخبير جوثا بلوك (صاحب كتاب Effective Java الغني عن التعريف). ### **التغيير الخامس Diamond Syntax**: هذا التغيير يريحك من كتابة الأنواع الكثيرة عندما تتعامل مع الGeneric، مثلاً لديك الكثير من المستخدمين الذين تعرفهم بواسطة id (رقم صحيح) وكل منهم لديه معلومات خاصه به تكون بشكل lookup (اسم وقيمه) ، في جافا 6 وما قبلها (5 فقط لأن 4 لم تكن موجودة هذه الميزه) ستقوم ب: ```java Map> usersLists = new HashMap>(); ``` بالتأكيد متعبه في القرائة والكتابه ، الآن في جافا 7 استخدم الصيغة المختصرة : ```java Map> usersLists = new HashMap<>(); ``` وسيقوم المترجم من تلقاء نفسه بمعرفة النوع من خلال التصريح اللى في الجهه اليمني. وسميت هذه الميزة ب (Diamond Syntax) والسبب انها بعد الاختصار تشبه المجوهره. أخر مزايا هذا المشروع هو حذف بعض التحذيرات التي تصدر من المترجم عندما تستخدم ال Varargs ، لنتحدث عنها الآن.. ### ا**لتغيير السادس Simplified Varargs:** اذا كنت تعلم ان جافا منذ الاصدار دعم الدوال التي تستقبل عدد غير معروف من المعاملات (تسمى Variable Arguments) المثال التالي يقوم بطباعه كل النصوص التي ترسل ويمكن ارسال اي عدد من النصوص: ```csharp import java.util.*; import java.io.*; public class Demo { public static void main(String[] args) { printAll("Wajdy", "Essam", "is", "Coder"); } public static void printAll(String... args) { for (String arg: args) { System.out.println(arg); } } } ``` داخلياً هذه القيم يتم تعبئتها في مصفوفة وبالتالي يمكنك عمل iteration عليها، المشكلة التي تواجهنا في السابق هو صدور التحذيرات من المترجم اذا كان نوع هذه المعاملات هو Non-Reifiable لأنه سوف يؤدي الى عمليات غير آمنه، الأنواع التي تسمى Reifiable هي الأنوع التي لا تفقد اي معلومات بعد عملية ال Type-Errasure وقت الترجمة ، وهذه الأنواع هي كما يلي (البقية تعتبر Non-Reifiable) : اذا كان النوع ليس Generic مثلاً String ، اذا كان النوع يستخدم الUnbounded Wildcard مثلاً List، واذا كان النوع هو raw type مثل java.util.List ، والأنواع الPrimitve تعتبر ايضاً Reifiable ، وايضاً المصفوفات التي تكون من أنواع Reifiable تكون هي كذلك مثلاً مصفوفة من String. مثال ، لو شغلت المثال التالي ومررت المعامل -Xlint:unchecked وقت الترجمة ستجد التحذير : ```csharp import java.util.*; import java.io.*; public class Demo { public static void main(String[] args) { printAll(new ArrayList(), new LinkedList()); } public static void printAll(List... args) { for (List arg: args) { System.out.println(arg); } } } ``` رسالة التحذير: > Demo.java:6: warning: \[unchecked\] unchecked generic array creation of type java. > util.List\[\] for varargs parameter > printAll(new ArrayList(), new LinkedList()); > ^ > 1 warning لكن اذا كنت متأكد من ما تفعل فيمكنك في جافا 7 استخدام @SafeVarargs حتى توقف هذه التحذيرات: ```csharp import java.util.*; import java.io.*; public class Demo { public static void main(String[] args) { printAll(new ArrayList(), new LinkedList()); } @SafeVarargs public static void printAll(List... args) { for (List arg: args) { System.out.println(arg); } } } ``` الى هنا نكون قد استعرضنا جميع الاضافات التي حصلت في جافا 7 في المشروع Coin ، ونصيحتنا هي ان تبدأ باستخدام هذه الميزات من الآن ، وبالنسبه لي بدأت بالتعود على الException بالطريقة الجديدة و ال TWR و ال Diamond Syntax. ### هل أنت جاهز للانتقال لجافا 7 ؟ URL: https://informatic-ar.com/whats_new_java7/ Last updated: 2026-10-03T22:32:34.000Z > جافا هي ليست فقط لغه جافا [![](https://informatic-ar.com/wp-content/uploads/2012/09/doppine.jpg "doppine")](https://informatic-ar.com/wp-content/uploads/2012/09/doppine.jpg) مر عام على جافا 7 (منذ July 2011)، جافا 7 كانت أول اصدار رسمي للجافا منذ 5 سنوات ماضية، أول نسخه من جافا مفتوحة المصدر تحت رخصه open source software (اختصاراً OSS) ، أول اصدار منذ تولي شراء اوراكل لصن في 2010، أوراكل عملت بجد واستطاعت ان توضح لل Java Community أنها قادرة على التطوير في جافا، وبحسب تقديرات أوراكل هناك حوالي 9 مليون مطور يستخدم جافا، تقديرات Tiobe مازالت تقول ان جافا اللغه الأكثر شعبيه، وهناك أكثر من 2 بليون جهاز يستخدم جافا، ومنصه جافا توجد على حوالي 97% من أجهزة سطح المكتب. هذا الإصدار احتوى على كثير من التحسينات والاضافات سواء كان للغه الجافا Java Language أو لدعم اللغات الأخرى التي تعمل فوق منصه الجافا Dynamic Languages وحتى في نظام الملفات والعمليات عليها File System. لكن بالمقابل ايضاً أجلت كثير من الاضافات التي كانت يفترض أن تكون في هذه النسخه 7 للنسخه القادمة 8 لغايه في نفس أوراكل، وهكذا يمكن أن نقول أن النسخه 7 ماهي الا الكلمة الأولى لأوراكل، والكلمة الثانيه بها الميزات الأخرى وأهمها الميزة المنتظرة في جافا وهي Lambda Expression (أو Closures) و دعم ال Modular Programming في اللغه وفي المنصه. ومع ذلك، هناك كثير من الاضافات في النسخه 7 قدمت تسهيلات جيدة للمبرمجين وان لم تكن بالكثيرة ولكنها تعتبر حجر أساس للتغيرات القادمة الجذريه في جافا 8 (حسب خطة اوراكل) والتي قد تغير طريقك كتابتك عن ذي قبل. جافا 5 كانت من النسخ ذات التغييرات الجذريه حيث قدمت كثير جداً من الخصائص التي غيرت طرق الكتابة عن ما كان في 4 (قدمت الGeneric و ال Annotations وغيرها). حالياً نسخه 7 احتوت تغييرات بسيطة ولكنها اسس التغييرات في 8 (لذلك القول بأن نسخه 7 هي evolutionary وليست كما نسخه 8 revolutionary). الخبر الجيد بالنسبه للمبرمجين الذين لم ينتقلوا بعد لنسخه 7 انه قد تأخر اطلاق النسخه 8 الرسمية (يمكنك تحميل نسخ قيد التطوير الآن) الى بعد 3 أشهر من الأن (بدايه عام 2013)، وها هي فرصه جيدة أن نسلط بعض الضوء على هذا الإصدار ونهيئ المبرمجين للنسخه القادمة 8، وهذا من الأمور المهمة لمطوري الجافا (وللمطورين بشكل عام) هو التعرف على المزايا الجديدة في اللغه واستخدمها خصوصاً التي تفيدك في عملك بشكل افضل ولكي تكون معلوماتك up-to date في اللغه. [![](https://informatic-ar.com/wp-content/uploads/2012/09/java_evalution.jpg "java_evalution")](https://informatic-ar.com/wp-content/uploads/2012/09/java%5Fevalution.jpg) حالياً يمكننا أن نقول أن جافا تتكون من شقين أساسين لغه البرمجة جافا Java Language (اللغه التي تعرفها وتبرمج بها فهي لغه كائنية OOP و static typed) ومنصة جافا Java Platform (وهي الJVM الذي يقوم بتنفيذ البايت كود)، التفرقه بينهم مهمه، وحتى الSpecification في جافا 7 Java Language Specification (اختصاراً JLS) مفصول تماماً عن الSpecification للمنصه VMSpec، وهذا يعني أنك قد تكون مهتم بالمنصه وتقوم بتحويل لغه ما لغه البايت كود التي تفهمها هذه المنصه، وهكذا ستعمل هذه اللغه فوق منصه الجافا. وهذا ما حصل الآن العديد من اللغات تعمل فوق منصه الجافا (بالاضافة الى لغه الجافا) وتستفيد من المزايا التي تقدمها هذه المنصه الرائعه ومن أشهر اللغات: Groovy, Scala, Clojure. ## التغييرات الجديدة الجدول التالي يبين بشكل عام التغييرات التي حصلت في جافا 7 وهي من [موقع OpenJDK الرسمي](http://openjdk.java.net/projects/jdk7/features?ref=informatic-ar.com) | [vm](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#vm) | [JSR 292: Support for dynamically-typed languages (InvokeDynamic)](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f353) | | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | | [Strict class-file checking](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#fa535991) | | [lang](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#lang) | [JSR 334: Small language enhancements (Project Coin)](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f618) | | [core](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#core) | [Upgrade class-loader architecture](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f352) | | | [Method to close a URLClassLoader](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f584) | | | [Concurrency and collections updates (jsr166y)](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f515) | | [i18n](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#i18n) | [Unicode 6.0](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f497) | | | [Locale enhancement](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#fa535895) | | | [Separate user locale and user-interface locale](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#fa538265) | | [ionet](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#ionet) | [JSR 203: More new I/O APIs for the Java platform (NIO.2)](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f250) | | | [NIO.2 filesystem provider for zip/jar archives](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#fa537814) | | | [SCTP (Stream Control Transmission Protocol)](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f405) | | | [SDP (Sockets Direct Protocol)](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f639) | | | [Use the Windows Vista IPv6 stack](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#fa535996) | | | [TLS 1.2](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#fa534339) | | [sec](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#sec) | [Elliptic-curve cryptography (ECC)](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f73) | | [jdbc](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#jdbc) | [JDBC 4.1](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#fa539110) | | [client](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#client) | [XRender pipeline for Java 2D](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f653) | | | [Create new platform APIs for 6u10 graphics features](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f650) | | | [Nimbus look-and-feel for Swing](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f244) | | | [Swing JLayer component](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f652) | | | [Gervill sound synthesizer](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#fa99999) | | [web](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#web) | [Update the XML stack](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#f568) | | [mgmt](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#mgmt) | [Enhanced MBeans](http://openjdk.java.net/projects/jdk7/features/?ref=informatic-ar.com#fa530068) | من أهم التغييرات التي نراها مهمه للمبرمج والتي سنوضحها في مقالات متفرقه في هذه المدونة: ### **مشروع الCoin (التحسينات الصغيرة في اللغه) وكانت بالرقم JSR 334** وهو نتيجة نداء مفتوح للمطورين بتقديم اي مقترحات مفيدة يمكن ضمها في جافا النسخه 7 (حدث ذلك في 2009) وقد قدم حوالي 70 مقترح وفي الأخير تم اختيار 6 من هذه المقترحات وتم تطبيقها في النسخه 7 ، هذه الاضافات الجديدة سهلت البرمجة في جافا أكثر من ذي قبل . [أنقر هنا لمعرفة المزيد عن الاضافات في جافا 7 في ال Project Coin](https://informatic-ar.com/project%5Fcoin%5Fjava7) ### **تقديم API جديدة للعمل مع الملفات File System** وسميت بالاسم New I/O واختصاراً NIO.2 وهي حملت في طياتها الكثير لمبرمجين الجافا وأهمها الاستغناء عن الكلاس File واستبداله ب ال Path ، ومن الامور الجديدة هو وجود كلاسات خاصه للIO تعمل بشكل متوزاي مع تطبيقك Asynchronous وهذا يعني انك لا تحتاج لكتابه ثريد والدخول في تفاصيله للعمليات العادية في الملفات فهذا موجود على طبق من ذهب ، ايضاً سهلت عمليات زيارة المجلدات والملفات Traversing ، ولهذه الAPI وجب أن نفرد لها مقاله خاصه نتحدث عنها بالتفصيل. ### **دعم البرمجة المتوازية** **Parallel Programming** وهي تقريباً من أهم الاضافات في النسخه 7 والتي تسمح لك بتقسيم المشكلة الى أجزاء صغيرة لكي تعمل في ان واحد في الكورز لديك في المعالج (من خلال ال Fork/Join Framework). شرح هذه الكلاسات تحتاج لمقاله أخرى لتفصيلها جيداً خصوصاً لمن لديهم معرفة بال Concurrent Programming. ### **ضم ملم النفايات الجديد G1 وضغط المؤشرات ال64 بت في الذاكرة** هذا ال GC صمم لكي يستبدل جميع الGC الموجودين في جافا حيث جمع بين مزاياهم ، ولكي نتحدث عنه يجب ان نتحدث عن الGC بشكل عام ولذلك سوف نفرد لها مقاله موسعه بهذا الأمر ان شاء الله. ### **دعم اللغات الأخرى** **Dynamic Languages** وهذه من الاضافات المهمة في جافا 7 والتي سمحت للDynamic Language العمل فوق منصه جافا وذلك بالاستفادة من قدرات InvokeDynamic وهذا جعل منصه الجافا هدف للغات الDynamic للاستفادة من الميزات التي تقدمها. هذه المقالة سوف تحدث باستمرار حتى نغطي كافه مزايا النسخه 7 (المسماه بالدولفين dolphin، هل عرفت لماذا صورة الدولفين)، وكذلك سنفرد مقاله مخصصه للمزايا المطروحة في 8 , **أمل أن تسترعي هذه النسخه انتباهك وتبدأ بشد الرحال اليها، ولكن لا تخطط للبقاء طويلاً فجافا 8 على الأبواب**. ### هل مللت من دالة البناء Constructor ؟ URL: https://informatic-ar.com/constructor-vs-factory/ Last updated: 2026-10-03T22:32:34.000Z [![](https://informatic-ar.com/wajdy_pics/constructor.jpg "constructor")](https://informatic-ar.com/wajdy%5Fpics/constructor.jpg) كونك مبرمج وتعرف أساسيات البرمجة الكائنية OOP فقد يبدوا العنوان غريب حقاً، فكيف اقوم بانشاء كائن بدون ان أكتب أو استخدم دالة البناء Constructor ؟ وهنا سوف نوضح كيف يمكن الاستعاضة عن ال Constructor بال Static Factory وما هي الفروقات بينهم هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا - [الإسلوب الصحيح لكتابه Utility Classes](https://informatic-ar.com/utility%5Fclasses/) - هل مللت من دالة البناء Constructor ؟ (المقال الحالي) - [استخدام ال Builder Pattern في دالة البناء](https://informatic-ar.com/builder%5Fpattern/) - [لا تستخدم Public الا وقت الحاجة](https://informatic-ar.com/inaccessible%5Fclass/) - [كيف تصمم الPackages جيداً في تطبيقات جافا](https://informatic-ar.com/packages%5Fin%5Fjava/) - [جافا دائماً Passing by Value](https://informatic-ar.com/java%5Fpassing%5Fby%5Fvalue/) - [نظرة حول الدالة Equals](https://informatic-ar.com/java-object-equals/) - [خارطة طريق لتعلم الجافا](https://informatic-ar.com/how%5Fto%5Flearn%5Fjava/) ## دالة البناء Public Constructor لنبدأ بالطريقة العادية (او التقليدية الآن) لإنشاء كائن من كلاس معين وهو من خلال استدعاء دالة بناء public constructor داخل هذا الكلاس، مثلاً في السطر التالي قمنا باستدعاء دالة البناء في الطالب وقمنا بانشاء كائن منه: ```java public class Item1 { public static void main(String[] args) { Student student = new Student("Ahmed"); System.out.println("student name: " + student.getName()); } } class Student { public Student(String name) { this.name = name; } public String getName() { return this.name; } private String name; } ``` الى هنا هذا الأمر هو ما قمنا به خلال سنوات ، ولكن لما لا نلقى نظرة على طريقة أخرى يمكن أن تضيفها بجانب معلوماتك ؟ هذه الطريقة أخرى تمكننا من انشاء كائن من الطالب (كما هو الحال مع دالة البناء) وذلك بعمل دالة static ترجع نسخه من الطالب (أي داخل هذه الدالة ال static سيتم استدعاء دالة البناء -هي موجودة موجودة ولكن قمنا باضافة طبقة اخرى فوقها-) ، بالتالي بعد ادخال هذه الدالة للكلاس يصبح لدينا الكود بالشكل: ```java public class Item1 { public static void main(String[] args) { Student student1 = new Student("Ahmed"); Student student2 = Student.createStudent("Ali"); System.out.println("student name: " + student1.getName()); System.out.println("student name: " + student2.getName()); } } class Student { public Student(String name) { this.name = name; } public static Student createStudent(String name) { return new Student(name); } public String getName() { return this.name; } private String name; } ``` ## Static Factory لاحظ الأن ان لدينا دالة static تقوم باستدعاء دالة البناء وهكذا انشى الطالب ايضاً، حتى نقوم بتكملة هذه الطريقة اجعل دالة البناء private وهكذا لا يستطيع اي client استدعائها ، وعلى الclient هنا هو استدعاء دالة الstatic factory التي قمت بعملها حتى ينشىء الكائن : ```java public class Item1 { public static void main(String[] args) { Student student2 = Student.createStudent("Ali"); System.out.println("student name: " + student2.getName()); } } class Student { private Student(String name) { this.name = name; } public static Student createStudent(String name) { return new Student(name); } public String getName() { return this.name; } private String name; } ``` سنجد في لغه الجافا الكثير من الكلاسات تستخدم هذا الأسلوب في انشاء الكائنات، مثل الWrapper Class (الكلاس Boolean أو Integer وغيرها من الكلاسات التي توفر خاصية الAuto-Boxing) ، مثلاً في داخل الكلاس Boolean سوف نجد أنه يتم انشائه بالشكل الأتي: ```java public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; } ``` بالتالي يمكن لأي كلاس استخدام هذا الطريقة تسمى static factory ( لاحظ هي غير متعلقه بال Design Pattern المعروف بالاسم Factory Design Pattern) بدلاً من دالة البناء العادية ، أو يمكن تزويد الكلاس بالطريقتين، كما في مثال الطالب الأول أعلاه. أمثلة أخرى موجودة في الجافا: - LogManager.[getLogManager](http://docs.oracle.com/javase/6/docs/api/java/util/logging/LogManager.html?ref=informatic-ar.com#getLogManager%28%29) - Pattern.[compile](http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html?ref=informatic-ar.com#compile%28java.lang.String%29) - Collections.[unmodifiableCollection](http://docs.oracle.com/javase/6/docs/api/java/util/Collections.html?ref=informatic-ar.com#unmodifiableCollection%28java.util.Collection%29) , Collections.[synchronizeCollection](http://docs.oracle.com/javase/6/docs/api/java/util/Collections.html?ref=informatic-ar.com#synchronizedCollection%28java.util.Collection%29) , and so on - Calendar.[getInstance](http://docs.oracle.com/javase/6/docs/api/java/util/Calendar.html?ref=informatic-ar.com#getInstance%28%29) ## ما الفرق بين Constructor وال Static Factory السؤال الذي قد يخطر ببالك الآن هو لماذا؟ لماذا نقوم بعمل دالة تستدعي دالة البناء ولا نقوم مباشره باستدعاء دالة البناء ؟ (أي لماذا تختار Static Factory ولماذا لم تختار ال Constructor ) ؟ لنلقى نظره أولاً على مزايا وعيوب كل من هذه الطرق في انشاء الكائنات وبالتالي سيجعلك تقرر لماذا اختار هذه عن تلك.. أحد مزايا هذه الطريقة ال Static Factory بعكس طريقة دالة البناء في أن هذه الداول لديها دوال واضحه الإسم ، **وبالتالي الكود أوضح وأسهل للفهم Readability** ، على سبيل المثال الكلاس BigInteger كان يحتوى على دالة بناء تستقبل عددين صحيحين وكائن من النوع Random وتقوم دالة البناء بارجاع كائن من هذا الكلاس يحتمل أن يكون عدد أولي: ```java BigInteger(int, int, Random) ``` في حالة تم استخدام طريقة الstatic factory فمن الممكن أن تأخذ الدالة شكل أوضح كثيراً كالتالي: ```java BigInteger.probablePrime(int, int, Random) ``` وهذا ما تم في نسخة الجافا 1.4 حيث أن دالة البناء الأولى لم تكن واضحه جيداً. بعض الحالات قد تحتاج لدالة بناء بنفس تصريح دالة البناء الأولى لديك في الكلاس، ومعلوم طبعاً أنه لا يمكن أن تتواجد دالتين في الكلاس بنفس الSignature ، الحل الذي يقوم به بعض المبرمجين هو تغيير ترتيب القيم في دالة البناء الأخرى وبالتالي يتم السماح لها بالتواجد، ولكن هذا الحل يصعب استخدام الكلاس فمستخدم الكلاس عليه أن يعرف أن دالة البناء التي تستقبل الترتيب الفلاني سوف تقوم بالوظيفة التالية ودالة البناء التي تستقبل الترتيب الأخر تقوم بالوظيفة الأخرى. أما لو نظرنا للحل الأخر –static factory method- سوف نجد أنه يكل سهول يمكن تحويل دوال البناء هذه الى دوال بأسماء أوضح كثيراً. مثال لدى كلاس يقوم بالحصول على معلومات من خلال كائن ومسار لمعلومات (ولدى طريقتين استطيع تقديم هذا المسار) ، باستخدام دالة البناء قد نصل الى : ```java public class GetterFoo { public GetterFoo(ImData imData, String longPath); // you can't public GetterFoo(ImData imData, String shortPath); } ``` كما تلاحظ أعلاه أنك لن تستطيع تطبيق الدالة الثانية بسبب تشابه الSignature في الدالتين وهذا خطأ في الترجمة ، لذلك الحل هو تغيير الترتيب: ```java public class GetterFoo { public GetterFoo(ImData imData, String longPath); public GetterFoo(String shortPath, ImData imData); } ``` بالطبع الحل يعقد الكلاينت الذي يستخدم هذا الكود ، لكن باستخدام ال Static Factory Method ، تصبح لدينا هذه القطعة الرائعه (بغض النظر عن اسماء الكلاس والمعاملات فهي مثال فقط): ```java public class GetterFoo { public GetterFoo getUsingLongPath(ImData imData, String path); public GetterFoo getUsingShortPath(ImData imData, String path); } ``` مثال أخر من كود لي في السابق، كنت اريد تحميل الرسائل من البريد والذي أحفظ معلوماته في EmailSetting ، الآن الكلاس المسؤول عن التحميل هو EmailDownloder ، واريد احياناً ان احمل البريد مع المرفقات واحياناً بدون ، هذا هو الكلاس الذي يستخدم طريقة دالة البناء: ```java public class EmailDownloader { public EmailDownloader(EmailSetting setting, boolean withAttachment) { } } ``` عندما أريد استخدام هذا الكلاس من الخارج: ```java // in client, what is true mean ??? EmailDownloader emailDownloder = new EmailDownloader(setting, true); ``` لكن لو استخدمنا طريقة ال static factory : ```java public class EmailDownloader { public static EmailDownloader downloadEmail(EmailSetting setting) { } public static EmailDownloader downloadEmailWithAttachment(EmailSetting setting) { } } ``` والكلاينت: ```java // in Client EmailDownloader emailDownloader = EmailDownloader.downloadEmailWithAttachment(setting); ``` هكذا الكود أكثر مقروئية فقط بالتغيير من دالة البناء الى ال static factory (اضافه الى ان تمرير boolean الى الدالة سواء كانت عادية أو دالة بناء **خصوصاً** لو كانت معامل واحد bool فقط هو من العادات التي تقلل من مقروؤية الكود ، سنتحدث عن ذلك في موضوع أخر ان شاء الله). الميزة الأخرى لاستخدام الstatic factory بعكس دوال البناء في **أنها يمكن أن لا تقوم بعمل كائن جديد في كل مرة يتم استدعائها**، وهذا يسمح للكلاسات الImmutable (الكلاسات التي لا تتغير فور بناء الكائن منها مثل الكلاس String) لاعادة استخدام كائنات جاهزة مسبقاً، أو حتى حفظ Cache الكائن عند انشائه مره واحد فقط ومن ثم تمرير هذه النسخة عند كل طلب لهذا الدالة. لو نظرنا للدالة Boolean.valueOf أعلاه لوجدناها تستخدم هذا المفهوم ،حيث هي لا تقوم بعمل كائن أبداً بل تستخدم كائن منشيء مسبقاُ في الكلاس. وهذا الأمر قد يفرق في أداء البرنامج Performance، لنفرض أن لديك كائن يستغرق وقتأ ومصادر في النظام عند انشائه وأنت تريد انشائه كثيراً فبهذه الطريقة تضمن الكائن لديك وانه تم انشائه مره واحدة فقط. في السابق كنت اقوم بقرائه بريد ال Outlook واستخرج منه بعض المعلومات من ملف PST/OST المخزن على الجهاز، كنت اريد ان اقوم بعمل استعلامات كثيرة جداً على بعض المعلومات من هذه الرسائل، لذلك ال Caching هو الحل لأن قرائه البريد قد تستغرق 5 الى 15 ثواني (في الغالب مع الأحجام تصل الى 500 ميغا) .. لذلك تم استخدام هذه الطريقة كما يلي: ```java public class OutlookEmailReader { // member variables here public static List getInstance(PSTFile pst, String p) { if (map == null || !path.equals(p)) { caching(); } return map; } } ``` هكذا داخل هذه الدالة ال Static Factory Method تستطيع التحكم Control أنت المبرمج في هل تنشيء الكائن أم تعيد استخدامه ، وعادة تسمى الكلاسات التي لديها هذه القابلية بالاسم Instance Controlled. وفي الحقيقة هناك عدة فوائد من كتابة **Instance Controlled Class** أولاً أنها قد تضمن أن كائنك هو كائن وحيد فقط Singleton (لا يتم انشاء الا كائن واحد فقط من الكلاس) أو أن الكلاس غير قابل لعمل كائن منه اطلاقاً Non-Insatiable (لا تستطيع عمل كائن ابداً من هذا الكلاس مثلاً الكلاس Math فقط انت تستخدم ما بداخله وغيرها من ال Utilities Classes). الميزة الثالثه في الstatic factory method وهي بعكس دوال البناء يمكن أن **ترجع كائن أبن لهذا الكلاس**، وهذا يعطيك حرية في اختيار العائد من الكلاس فمثلاً يمكن اعادة كائن بدون أن يكون كلاس هذا الكائن من النوع public هذا يعني اخفاء الimplementation وتسمى الطريقة بInterface-based framework (سوف نتحدث عنها في مقاله أخرى ان شاء الله). الميزة الرابعه لاستخدام الstatic factory هو في **توضيح وتسهيل انشاء كائن من نوع معين من Generic Class**، فمثلاً لانشاء Map مكونه من String و List نقوم ببنائها بالشكل: ```java Map> m = new HashMap>(); ``` لكن لو قمت بوضع Static Factory Method في الكلاس يمكن أن يكون الاستدعاء بهذا الشكل: ```java Map> m = HashMap.newInstance(); ``` وهذا يرجع لأن المترجم سوف يتعرف على نوع القيم من تعريف المتغير مباشره. (هذه الميزة كان يتوقع ان تضاف الى جافا 7 ولكن تم تطبيقها وبشكل أخر). وحالياً يمكنك الاستفادة منها في الكلاسات الGeneric التي تقوم بعملها. في جافا 7 استخدم ال **diamond Syntax for Generic Declarations** فبدلاً من أن تقوم بعمل: ```java List blah = new ArrayList(); Map blah = new LinkedHashMap(); ``` يمكنك الأن ان تستخدم أقواس فارغه Diamond Syntax وسيتم التعرف على النوع من خلال التعريف: ```java List blah = new ArrayList<>(); Map blah = new LinkedHashMap<>(); ``` **لجافا 7** وقفة مفصلة في المقالات التالية ان شاء الله. من أحد الأمثلة الشائعه في ال Static Factory هو الكلاس Complex : ```java public class ComplexNumber { /** * Static factory method returns an object of this class. */ public static ComplexNumber valueOf(float aReal, float aImaginary) { return new ComplexNumber(aReal, aImaginary); } /** * Caller cannot see this private constructor. * * The only way to build a ComplexNumber is by calling the static * factory method. */ private ComplexNumber(float aReal, float aImaginary) { fReal = aReal; fImaginary = aImaginary; } private float fReal; private float fImaginary; //..elided } ``` مثل هذا الكلاس **يفضل جعله Immutable ايضاً** (حتى تحصل على التركيبة الرائعه Static Factory Method مع Immutable Class ، وسوف نتحدث عنها ونوضح فوائد ذلك بمقاله اخرى ان شاء الله). الى هنا قد بينا محاسن ال Static Factory ولنتحدث قليلاً عن المساوئ فيها و هي في **أنك لن تستطيع وراثه الكلاس اذا لم يحتوي على دالة بناء من التوع public أو protected** ، هناك بعض الأقوال بأن ذلك يشجع المبرمج على اسخدام الCompostion بدلاً من الInheritance ولكني بالتجربة وجدت أنه أحيانا عليك في حالات عمل وراثه من كائن (حالات is-a صحيحة ) وفي هذه الحالة عليك بعمل دالة بناء protected على الأقل حتى تضمن أن تعمل دوال الstatic factory لديك. أيضاً من الأشياء التي قد تضعفها هي **عدم وجود معيار ثابت في تسمية دوال الstatic factory** ، حيث قد لا يوضح هل هذه دالة عادية أم دالة بناء أم دالة static factory، على كل هناك أسماء شائعه لهذه الدوال في لغه الجافا مثل: valueOf, of, getInstance, newInstance, getType, newType ## **خلاصة** دوال البناء والstatic factory method لهم استخداماتهم وعليك معرفة ميزات كل منهم، **وعادة يفضل استخدام الstatic factory للأسباب أعلاه، لذلك قبل أن تزود الكلاس بpublic constructor أنظر أولاً لمدى جدوى استخدام الstatic factories**. بالنسبة لي حوالي 60% من الكلاسات التي اكتبها استخدمها بها Static Factory ، فهي لها مقروئية عالية ،، **السؤال الآن للقارى** ، هل استخدمتها/ستستخدمها أم مازلت ترى أن دالة البناء العادية افضل منها؟ ### نظرة حول الدالة Equals URL: https://informatic-ar.com/java-object-equals/ Last updated: 2026-10-03T22:32:35.000Z > عندما تكون دوال الـ equals ليست جميعها equals ! من المعلوم أن أي كلاس تقوم بكتابه ويرث Extends كلاس اخر فهذا الكلاس الإبن subclass سوف يرث من الكلاس ال superclass ، وبما أن أي كلاس في جافا يرث الكلاس Object فان الكلاسات التي تقوم بكتابتها سوف ترث ما يمكن وراثته من الObject. يحتوي الكلاس Object على العديد من الدوال القابلة لاعادة تعريفها non final methods في الكلاسات الابناء subclasses، ولكن هناك أمور عليك مراعاتها Contract والا فلن تحصل على التصرف الصحيح وقت استدعاء هذه الدوال. هذه المقالة جزء من سلسلة كيف تحترف لغة الجافا وهي موجهة لأي مطور OOP ويستخدم لغة الجافا - [الإسلوب الصحيح لكتابه Utility Classes](https://informatic-ar.com/utility%5Fclasses/) - [هل مللت من دالة البناء Constructor ؟](https://informatic-ar.com/constructor-vs-factory/) - [استخدام ال Builder Pattern في دالة البناء](https://informatic-ar.com/builder%5Fpattern/) - [لا تستخدم Public الا وقت الحاجة](https://informatic-ar.com/inaccessible%5Fclass/) - [كيف تصمم الPackages جيداً في تطبيقات جافا](https://informatic-ar.com/packages%5Fin%5Fjava/) - [جافا دائماً Passing by Value](https://informatic-ar.com/java%5Fpassing%5Fby%5Fvalue/) - نظرة حول الدالة Equals (المقال الحالي) - [خارطة طريق لتعلم الجافا](https://informatic-ar.com/how%5Fto%5Flearn%5Fjava/) ## ما هي الدالة Equal أحد الدوال المثيرة للاهتمام هي الدالة equals الموجودة في الكلاس Object ، حيث تختبر هل الكائن يعتبر مساوى Equals لكائن أخر، والى وقت قريب كانت ساحات الحوار والمقالات تنتاول هذا الموضوع من وجهه نظر المبرمجين، وحتى ان هناك دراسه قديمة في 2007 وجدت ان أغلب تعاريف ال equals المنتشرة ليست صحيحة (الورقة العلمية [Declarative Object Identity Using Relation Types](http://researcher.ibm.com/files/us-ftip/ecoop2007.pdf?ref=informatic-ar.com) ) في الوضع الطبيعي عندما تقوم بعمل اي كلاس بدون اي وراثه صريحة (سوف ترث تلقائياً من Object ) وتكون الدالة equals هي نفسها ما توجد في Object والتي تحدد هل المؤشران يشيران لنفس الكائن Identity test وهذا منطقى لأنه اذا تساوى المؤشران Identity فهذا يعني انهم لديهم نفس القيمة state. الكود التالي يبين الكود الموجود في الدالة equals في الكلاس Object: ![](https://informatic-ar.com/wajdy_pics/ObjectEquals.png "objectEqual") هناك حالات قليله يجدي معها ال Identity test مثلاً عندما أختبر هل الكائن PrintStream مساوي مع أخر (Identity)، لكن في غالب الحالات تحتاج ان تطبق المساواة على قيمة ذلك الكائن state based equality. لذلك في هذه الحالات عليك ان تقوم باعادة التعريف. **كتوحيد للمصطلحات بهذه المقاله**: عندما نقول Identity test (فنحن نقصد المساواة عبر المؤشر وهو ما يوجد في Object.equals) وعندما نقول logical equivalence أو حتى state based equals (فنحن نقصد مساواة القيم في الكائن وهو ما يتطلب اعادة التعريف). وقد تبدوا عملية اعادة تعريف ال equals سهله وبديهيه ولكن في الحقيقة يمكنك ان تخطئ بها بعدة طرق وسوف تحصل على نتائج غريبه اثناء عمل برنامجك. لذلك قبل البدء بعملية اعادة التعريف اسأل هل حقاً احتاج لأعيد تعريف هذه الدالة equals ؟ فعندما لا تعرفها سوف تحصل على equals تقوم ب Identity test، وهذا ينبغي فعله في حال: 1\. كل كائن في الكلاس يمثل مهمه Entity/Activity وليس قيمة مثلاً الكلاس Thread أو PrintStream فالدالة equals الموجودة في الObject سوف تؤدي المطلوب لهذا النوع من الكلاسات. 2\. لا تحتاج أن يتواجد مفهوم المساواة بالقيمة logical equivalence (أو ما يعرف ب stated based equals) مثلاً الكلاس Random لا يقوم باعادة تعريف هذه الدالة لأنه لا يحتاجها في الغالب ويكتفى ب object.equals التي ورثها. 3\. اذا كان الأب superclass لدية equals وكانت ملائمة لجميع الابناء ، على سبيل المثال Set ترث من AbstractSet و List ترث من AbstractList و Map ترث من AbstractMap وكل من هذه الأبناء لا تقوم باعادة تعريف الدالة equals والسبب ان الأباء لديهم تعريف ملائم لهم. 4\. الكلاسات التي تستخدم instance control مثل الSingleton ، وال Enum حيث أن ال Object.equals لهذه الكلاسات هو بمثابه ال state based equality. ( الكلاسات التي تسمى instance control فهي الكلاسات التي تستخدم static factory method لانشاء الكائن بدلاً من دالة البناء constructor لأن بها تحكم اكثر - [اقرأ هنا للمزيد](http://www.sudancs.com/viewtopic.php?f=22&t=1084&ref=informatic-ar.com#p5215) \- ) حسناً، تعرفنا على الحالات التي لا يجب بها تعريف الدالة equals ولكن **متى يجب أن تقوم بتعريفها**؟ عندما تريد تطبيق Logical Equality في الكلاس ( وليس Identity) ويكون الأب لا توجد به دالة equals مناسبة ويكون كلاسك هو Value Class (الكلاسات التي تسمى value classes هي التي تمثل قيمة مثل Date و Integer أو طالب أو مدير أو اي كلاس له قيمة) وتريد مساواة هذا الكائن مع أخر ففي الغالب انت تريد مساواة القيمة Logical Equality وليس Identity . أيضاً عملية اعادة تعريف الدالة equals تجب عندما تستخدم Collections تتطلب ان تكون لديك equals . نأخذ مثال كلاس يمثل الموظف Employee ونريد أن تختبر هل الموظفين متساويين اذا كان لهم نفس الاسم والراتب وتاريخ التوظيف (بعض الأحيان خصوصاً في تطبيقات قواعد البيانات سوف تكون مساواة الموظف عن طريق ال Id -سوف تنتاول كيف يمكنك كتابه equals لهذا النوع في منتصف المقال- ولكن هذا المثال يشرح تطبيق فكرة الابتدائية من ال state based equals). ```java // Demonstrate Employee Equals import java.util.Date; import java.util.Calendar; public final class EmployeeEqualDemo { public static void main(String... args) { Calendar calendar = Calendar.getInstance(); Date date = calendar.getTime(); // change time to old date calendar.set(2010, 9, 25); Employee p1 = new Employee("Ahmed", 2000, date); Employee p2 = new Employee("Ali", 2000, date); Employee p3 = new Employee("Ahmed", 2000, calendar.getTime()); Employee p4 = new Employee("Ahmed", 2000, date); System.out.println("p1 == p2 ? " + p1.equals(p2)); // false System.out.println("p1 == p3 ? " + p1.equals(p3)); // false System.out.println("p1 == p4 ? " + p1.equals(p4)); // true } } class Employee { public Employee(String name, double salary, Date hireDate) { this.name = name; this.salary = salary; this.hireDate = hireDate; } // other getters mehtods private String name; private double salary; private Date hireDate; } ``` لاحظ أن الكلاس Employee لا يحتوي على اعادة تعريف override للدالة equals وبالتالي كل الاختبارات خاطئة (الموجودة في الدالة main) لأن لكل كائن موقع خاص في الذاكرة. لتصحيح هذا السلوك الخاطئ يجب تعريف الدالة equals داخل الكلاس Employee وليكن تعريفها كما يلي: دالة equals في الكلاس Employee عن طريق استخدام getClass ```java @Override public boolean equals(Object otherObject) { if (otherObject == this) return true; if (otherObject == null) return false; if (otherObject.getClass() != this.getClass()) return false; Employee other = (Employee)otherObject; return other.name.equals(this.name) && other.salary == this.salary && other.hireDate.equals(this.hireDate); } ``` هكذا عملت الدالة كما هو متوقع (وخرجت النتيجة الصحيحة)، تفاصيل هذه التعويذه ان بدت لك كذلك سوف تنتاولها فيما يلي ، ولكن لنلقي نظرة أخرى لدالة equals أخرى (استبدلها بالدالة اعلاه) : ```java @Override public boolean equals(Object otherObject) { if (otherObject == this) return true; // No need for this test, because instanceof will return false if otherObject is null if (otherObject == null) return false; if (!(otherObject instanceof Employee)) return false; Employee other = (Employee)otherObject; return other.name.equals(this.name) && other.salary == this.salary && other.hireDate.equals(this.hireDate); } ``` قم بتشغيل المثال وستجد النتائج الصحيحه والى هنا الأمور تبدوا لا غبار عليها، سواء استخدمت طريقة getClass (والتي تتحقق من ان الكائن من نفس نوع الكائن الثاني وترجع Boolean على ذلك التحقق) او طريقة instanceof (والتي ترجع Boolean في حالة كائن الكائن المرسل احد ابناء الكائن الأخر ) فالنتيجة صحيحة. ولكن لنقوم ببعض الاضافات الاخرى والتي توضح الفرق الكبير بين هذه الطريقتين (وهي لب هذا المقال) ولنقل ان لدينا كائن Manager يرث الموظف ولديه متغيرات state خاصه به ( بمعنى Subclass add new value to its super class). ```java // Demonstrate Manager Equals import java.util.Date; import java.util.Calendar; public final class ManagerEqualDemo { public static void main(String... args) { Calendar calendar = Calendar.getInstance(); Date date = calendar.getTime(); // change time to old date calendar.set(2010, 9, 25); Manager m1 = new Manager("Ahmed", 2000, date, 10); Manager m2 = new Manager("Ali", 2000, date, 10); Manager m3 = new Manager("Ahmed", 2000, calendar.getTime(), 10); Manager m4 = new Manager("Ahmed", 2000, date, 10); Manager m5 = new Manager("Ahmed", 2000, date, 20); System.out.println("m1 == m2 ? " + m1.equals(m2)); // false System.out.println("m1 == m3 ? " + m1.equals(m3)); // false System.out.println("m1 == m4 ? " + m1.equals(m4)); // true System.out.println("m1 == m5 ? " + m1.equals(m5)); // false } } class Manager extends Employee { public Manager(String name, double salary, Date hireDate, int bonus) { super(name, salary, hireDate); this.bonus = bonus; } // getter methods private int bonus; } class Employee { public Employee(String name, double salary, Date hireDate) { this.name = name; this.salary = salary; this.hireDate = hireDate; } // other getters mehtods @Override public boolean equals(Object otherObject) { if (otherObject == this) return true; if (otherObject == null) return false; if (otherObject.getClass() != this.getClass()) return false; Employee other = (Employee)otherObject; return other.name.equals(this.name) && other.salary == this.salary && other.hireDate.equals(this.hireDate); } private String name; private double salary; private Date hireDate; } ``` كما هو واضح من نتيجة الكود اعلاه ان مساواة المدراء خاطئة (m1 و m5) بسبب أننا لم نقوم بتعريف الدالة equals في الكلاس Manager فسوف يتم تساوي اي مديرين في حالة تساوت بياناتهم التي ورثت من الموظف بدون النظر للstate الجديدة في المدير ، وبكل تأكيد هذا تصرف غير صحيح . (بنفس الأمر لو غيرنا طريقة equals من getClass في Employee الى instanceof سوف نحصل على نفس النتيجة والسسب اننا نحتاج لتعريف الدالة داخل Manager لكي تتحقق من تلك ال state الجديدة في المدير). لنقوم بكتابه الدالة equals داخل Manager : ```java @Override public boolean equals(Object otherObject) { if (!super.equals(otherObject)) return false; Manager other = (Manager)otherObject; return other.bonus == this.bonus; } ``` الآن لو شغلت الmain السابقة سوف تحصل على المخرج الصحيح بسبب وجود الدالة equals في ال manager والتي تنظر لتلك ال state الجديدة في الكلاس وتتحق منها. الى هذه النقطة كنا نقوم بمساواه كائن مع كائن اخر من نفس النوع وكانت الأمور جيدة عندما نستخدم getClass أو نستخدم instanceof، **ولكن ماذا لو أردت أن تساوي بين المدير والموظف** ؟ اضف هذه للدالة main: ```java Manager m6 = new Manager("Wajdy", 100, date, 10); Employee p1 = new Employee("Wajdy", 100, date); // false when using getClass, false when using instanceof System.out.println("m6 == p1 ? " + m6.equals(p1)); // false when using getClass, true when using instanceof System.out.println("p1 == m6 ? " + p1.equals(m6)); ``` لو قمت بالتجربة في الكود الأخير وقمت بمساوة موظف مع مدير (وتستخدم getClass) سوف يفشل الاختبار وذلك لأنك في تعريف الدالة equals اشترطت لا يمكن ان يكون للكائنات من الكلاسات المختلفة ان يتساووا (وذلك من خلال الاختبار getClass والذي يعني انه لا يمكن ابدأ مساواة الكائنات المختلفة). لكن في حال استخدمت instanceof بدلاً من getClass فسوف تتحقق المقارنه عندما نساوي الموظف p1 مع المدير m6 (وهو صحيح حيث لهم نفس البيانات وManger instanceof Employee) ولكن سوف تحصل على false عندما تقارن المدير m6 مع الموظف p1 حيث أن التعبير Employee instanceof Manger غير صحيح وبالتالي حصلت على false. طريقة getClass صارمة ولا تسمح لك بمقارنه الكائنات المختلفة، وبالمقابل طريقة instanceof بدت واعدة بعض الشيء حيث سمحت بمقارنة الكائنات المختلفة (Mixing type) ولكن عندما تعكس عملية المقارنه كما في الأعلى فلن تحصل على المساواة، وهذا هو تجاوز Violate لأحد قوانين Contract الموجودة لأي كلاس يريد تعريف equals (تم تجاوز قانون Symmetric ، سنتحدث عنه بعد قليل). القوانين الموجودة في equals يجب اتباعها بشكل حرفي والتأكد من ذلك، لماذا ؟ حتى لا تحصل على نتيجة خاطئة تسبب لك خلل في البرنامج، تخيل نفسك تدخل كائن في Collection ومن ثم تبحث عن هذا الكائن فلا تجده؟ ```java Set hashSet = new java.util.HashSet(); hashSet.add(elem1); hashSet.contains(elem2); // returns false! ``` لنقل C هو طلب Order وان هذه سلة الطلبات Shopping Cart، عندما تريد استخراج معلومات الطلب الذي وضعته على السله فلن تجده، هذا التصرف كفيل بجعل برنامجك غير قابل للاستخدام والسبب هو تجاوز قانون ال equals!. لنلقى نظرة على معيار لغه الجافا في تعريف الدالة equals (الموجودة في Java Language Specification): ![](https://informatic-ar.com/wajdy_pics/equals.png "equals") [انقر هنا لمشاهدتها بشكل واضح](https://informatic-ar.com/wajdy%5Fpics/equals.png "here") قد تبدوا هذه الشروط صعبة ولكنها ليست كذلك والأمر المهم هنا أنه يجب الالتزام بها والا قد تحصل على اداء غريب اثناء عمل البرنامج (كما حدث في مثال الShopping Cart اعلاه). لنتحدث عن كل من هذه القوانين على حده: **القانون الأول Reflexive**: وهو ينص على أن يساوي الكائن نفسه، وفي الغالب اي كائن يساوي نفسه ويصعب تجاوز violate هذا القانون الا لو كنت متعمداً لهذا، لكن اذا قمت بذلك واضفت الكائن لأحد ال Collections فقد تحصل على اجابة غير موجود false عندما تسأل هل هذا الكائن موجود contains في ال collections. **القانون الثاني Symmetry**: وهنا ينص أنه على الكائنين الذين يرغبوا بالتساووا أن يتفقوا في ذلك وان تكون المساواة من الطرفين وليس من طرف واحد، وهو بعكس الأول يحتاج للحذر والتأكد من عدم مخالفته عند تطبيق الequals . المثال السابق عندما تساوى p1 مع m6 ولكن لم يتساوى m6 مع p1 هو تجاوز واضح لهذا القانون، كود تلك الدالة equals في ال manager باستخدام instanceof وهو مثال على تجاوز قانون symmetric ```java @Override public boolean equals(Object otherObject) { if (!super.equals(otherObject)) return false; if (!(otherObject instanceof Manager)) return false; Manager other = (Manager)otherObject; return other.bonus == this.bonus; } ``` يمكنك جعل الدالة Manager.equals تتجاهل الbonus عندما تعمل ب Mixed type ، كما يلي: مثال equals مع الblind وهو broken transitivity ```java @Override public boolean equals(Object otherObject) { if (!super.equals(otherObject)) return false; // if its employee then do manger blind comparsion if (!(otherObject instanceof Manager)) return otherObject.equals(this); Manager other = (Manager)otherObject; return other.bonus == this.bonus; } ``` لاحظ أن النتيجة (بعد تشغيل المثال) اصبحت صحيحه وانه لا يوجد تجاوز لل Symmetric ولكن للأسف هذا الحل ايضاً تجاوز القانون الثالث Transitivity. **القانون الثالث Transitivity**: وهو ينص على انه في حال تساوى الكائن A مع كائن أخرB و كان B يساوي C فإن A يجب أن يساوى C ايضاً عليك بفهم هذا القانون لأنه يسهل كسره، فمثلاً تريد اضافه قيمة جديدة في الابن اضافة لما هو موجود في الأب. نعود للمثال السابق، حيث تم حل مشكلة ال Symmetric ولكن في حال قمت بعمل مدير اخرm7 يختلف عن المدير m6 وكل منهم يساوي p1 فإن هذا يعتبر تجاوز لهذا القانون، حيث انه m7 يساوي p1 و p1 يساوي m6 وبالتالي حسب القانون يجب أن يساوى m7 ال m6 (وان كان منطقياً الكائن m7 لا يساوي m6، لكن هكذا تجاوز للقانون ونحن نعلم خطورة تجاوز هذه القوانين في البرامج). *هكذا اذا استخدمنا instanceof في الابناء الذين لديهم state جديده سوف نحصل على symmetric problem ، واذا حاولت غض النظر عن هذه الstate عندما أكتشف ان النوع يرجع للكلاس الأب فسوف احصل على Transitivity problem .* **ما هو الحل ؟** ان تمنع ال Mixing type وتجعل ال equals للكائنات من نفس النوع من خلال getClass ، أو ان تستخدم instanceof وسوف تكسر القاعدة بمجرد وجود أبناء يعيدوا تعريف equals ( وهي مشكلة معروفة في اللغات OOP تسمى Equivalence Relation وأنه لا يمكنك اضافه كلاس ابن له state جديدة (باستخدام instanceof) بدون تجاوز قوانين ال equals ). وكقاعدة استخدم دائماً getClass **الا في الحالتين** : 1) أن يكون تعريف الequals الموجود في الكلاس الأب يعمل على جميع الأبناء (مثلاً لو كان لكل موظف رقم id ، بالتالي الدالة equals فقط تحتاج ان تقارن هذا ال id فقط، وهكذا بما أن جميع الابناء يرثوا هذا ال id اذا فلا حاجة لدالة equals لدى الأبناء) في هذه الحالة يمكن استخدام instanceof ويفضل جعل الدالة final حتى لا يتم عمل override لها. 2) ان يكون الكلاس الابن يرث الاب ولا يضيف اي state جديدة ، مثلاً لديك كلاس Point (به نقطتين x و y) وقمت بعمل كلاس CounterPoint يرث النقطة ولا يحتوى على اي قيمة جديدة ولكن به عداد static Counter لعدد الكائنات من هذا الكلاس. لنفرض اننا ارسلنا كائن ال CounterPoint الى دالة contains في List of Points فاذا كان الequals تستخدم getClass فسوف ترجع false بغض النظر عن القيم المتساوية ولكن اذا استخدمت instance of فسوف تعمل الدالة. هناك طريقة اخرى تمكنك من استخدام instanceof في الكلاسات بدون حصول تجاوز للقوانين وذلك عن طريق استخدام Compositions بدلاً من الوراثه Inheritance والمثال التالي يوضح في الكلاس Manager حيث يحتوي على ال Employee ويقوم بعمل انشاء له داخلياً وبنفس الأمر في المقارنه، عيب هذه الطريقة انه يستوجب عليك عمل دوال الget في الManager لكل دالة موجودة في ال Employee تريد الوصول لها من خلال الManager ، ولكن بهذه الطريقة قمت بعملية equals باستخدام instanceof بدون اي تجاوز للقوانين: ```java class Manager { public Manager(String name, double salary, Date hireDate, int bonus) { this.employee = new Employee(name, salary, hireDate); this.bonus = bonus; } // getter methods @Override public boolean equals(Object otherObject) { if (!(otherObject instanceof Manager)) return false; Manager other = (Manager)otherObject; return other.bonus == this.bonus && this.employee.equals(other.employee); } private int bonus; private Employee employee; } ``` الحل الأخير والأكثر مرونه وهو يسمح للكلاس الابن بان لا يتساوي مع الأب في حال كان هناك state في الكلاس (وهكذا يمنع ال symmetric problem ويمنع ال transitivity problem) وفي نفس الوقت يعطي الابن قابلية التساوي مع الأب في حال لا يوجد اي state فقط كل ما علينا تزويد الكلاس الذي نريده أن لا يتساوي مع الأب (بالدالة تضع هذا الشرط وليكن اسمها canEqual). المثال التالي يوضح كيفية استخدام الدالة canEqual في المثال Employee/Manager . ```java // Demonstrate can Equals import java.util.Date; import java.util.Calendar; public final class CanEqualDemo { public static void main(String... args) { Calendar calendar = Calendar.getInstance(); Date date = calendar.getTime(); // change time to old date calendar.set(2010, 9, 25); Manager m1 = new Manager("Ahmed", 2000, date, 10); Manager m2 = new Manager("Ali", 2000, date, 10); Manager m3 = new Manager("Ahmed", 2000, calendar.getTime(), 10); Manager m4 = new Manager("Ahmed", 2000, date, 10); Manager m5 = new Manager("Ahmed", 2000, date, 20); System.out.println("m1 == m2 ? " + m1.equals(m2)); // false System.out.println("m1 == m3 ? " + m1.equals(m3)); // false System.out.println("m1 == m4 ? " + m1.equals(m4)); // true System.out.println("m1 == m5 ? " + m1.equals(m5)); // false Manager m6 = new Manager("Wajdy", 100, date, 10); Employee p1 = new Employee("Wajdy", 100, date); // false when using getClass, false when using instanceof System.out.println("m6 == p1 ? " + m6.equals(p1)); // false when using getClass, true when using instanceof System.out.println("p1 == m6 ? " + p1.equals(m6)); // transitivity Manager m7 = new Manager("Wajdy", 100, date, 20); // false when using getClass, true when using instanceof System.out.println("p1 == m7 ? " + p1.equals(m7)); // false when using getClass, false when using instanceof System.out.println("m6 == m7 ? " + m6.equals(m7)); Employee newEmployee = new Employee("Wajdy", 100, date) { // no state but other method for example // and we need to be comparable with Employee }; // false when using getClass , true when using canEqual System.out.println("p1 == newEmployee ? " + p1.equals(newEmployee)); } } class Manager extends Employee { public Manager(String name, double salary, Date hireDate, int bonus) { super(name, salary, hireDate); this.bonus = bonus; } // getter methods @Override public boolean equals(Object otherObject) { if (!super.equals(otherObject)) return false; if (!(otherObject instanceof Manager)) return false; Manager other = (Manager)otherObject; return other.bonus == this.bonus && other.canEquals(this); } @Override public boolean canEquals(Object otherObject) { return (otherObject instanceof Manager); } private int bonus; } class Employee { public Employee(String name, double salary, Date hireDate) { this.name = name; this.salary = salary; this.hireDate = hireDate; } // other getters mehtods @Override public boolean equals(Object otherObject) { if (otherObject == this) return true; if (!(otherObject instanceof Employee)) return false; Employee other = (Employee)otherObject; return other.name.equals(this.name) && other.salary == this.salary && other.hireDate.equals(this.hireDate) && other.canEquals(this); } public boolean canEquals(Object otherObject) { return (otherObject instanceof Employee); } private String name; private double salary; private Date hireDate; } ``` لاحظ ان الدالة تعمل بالضبط باسلوب getClass (تمنع مساواة الكائنات عندما تتبع لأنواع مختلفة، ولكنها تسمح ايضاُ بمساواة الكائنات عندما تريد ذلك) فقط كل ما عليك هو تعريف الدالة canEquals في الكلاس الذي تريد فيه المنع. لو نظرنا لمكتبة جافا القياسية Standard Java Library تحتوى على حوالي 150 تعريف للدالة equals بعضها يستخدم getClass وبعضها يستخدم instance of وبعضها بدون فعل شيء وبعضها فيه تجاوز لقوانين الequals! أحد الأمثلة لذلك في مكتبة جافا هو java.sql.Timestamp والذي يرث java.util.Date ويضيف متغير nanoseconds، لكن ال equals بها تخالف مبدأ ال Symmetry وسوف تسبب مشكلة اذا وضعتهم في Collection واحد أو حتى استخدمتهم بشكل مختلط Mixing them ، وتوثيق الكلاس يوضح ذلك. بالرغم انك سوف تكون في مأمن اذا لم تخلط بينهم Mixing لكن لا يوجد ما يمنع ذلك، هذا السلوك الخاطئ للكلاس Timestamp لا يجب ان يتكرر في كلاساتك. ![](https://informatic-ar.com/wajdy_pics/Timestamp.png "timestamp") لاحظ أنك تستطيع اضافة قيمة جديدة للكلاس الابن في حال كان الأب abstract class ولن تكسر القوانين وهذا جيد في البرامج التي تحتاج لهيكل من الكلاسات Hierarchal (مثلاً لديك Shape بدون اي متغير والابن Circle (لديه radius) والابن Rectangle (لديه width, length) فلن تحصل على المشاكل بسبب انك لن تستطيع عمل كائن من superclass. **القانون الرابع Consistency**: وهو ينص على انه في حال تساوى الكائنين فبنبغي ان يكونا كذلك حتى يتغيروا أو يتغير احد منهم. **القانون Non-Nullity:** كل الكائنات يجب ان لا تساوى null والا سوف تحصل على NullPointerException عندما تستخدم null object لذلك يجب فحص اذا كان الكائن المرسل otherObject == null فارجع false. اذا كنت تستخدم instance of فلا حاجه لك بهذا الاختبار لأنه تقوم بذلك وترجع false مباشره اذا كان الكائن هو null. الى هنا وصلنا لنهايه المقال وتعرفنا أن هناك طريقتين لفحص نوع الكلاس في الدالة equals، فإما من خلال استخدام instanceof وهكذا سيسمح كلاسك ب mixed type comparison بين الsuperclass و الsubclass أو من خلال استخدام getClass وهكذا سيتم اعتبار ان الكائنات من الكلاسات المختلفة غير متساوية> ## كيف تصمم دالة Equal المناسبة؟ اذا كنت تقوم بعمل كلاس جديد فكر في النقاط التالية: هل الكلاس هو Entity Type ؟ فاذا كان الكلاس من هذا النوع فهو لا يحتاج لدالة equals والدالة equals التي يرثها من Object كافية وتقوم بالفعل الصحيح. هل الكلاس هو Final Value Class ؟ في هذه الحالة يجب عليك باعادة تعريف الدالة equals حتى يؤدي الفعل المطلوب (ويمكن اختيار اي طريقة getClass أو instanceof حيث أنك تعرف لا يوجد subclass هنا) ، وهذه أحد فوائد الfinal class. هل الكلاس كلاس قابل للوراثه Not-Final Value Class ؟ في هذه الحالة يجب اعادة تعريف equals (فاذا اخترت instanceof فيجب على جميع الابناء ان لا يقوموا باعادة تعريف equals مجدداً والا سيقدموا مشكلة transitivity، واذا اخترت getClass فلن يوجد كلاس ابن يمكن مقارنته مع الكلاس الأب). اذا وجدت كلاس جاهز وتريد كتابه subclass له ، فيجب دراسه الequals في الكلاس الأب لأنها تؤثر على طريقة عمل الأبن بكل تأكيد : هل الأب يسمح بمقارنه الكائنات المختلفة Mixed-type comparison permitted ؟ اذا وجدت الأب يقوم باستخدام instanceof ولا يجب عليك ان تقوم باعادة equals. (خطأ من الأب هو السماح لك بدون قيد –وضع final على الدالة equals-) هل الأب لا يسمح بمقارنه الكائنات المختلفة Same-type comparison only؟ اذا وجدت الأب يستخدم getClass فهذا يعني ان الsuperclass object وال subclass object لا يتساووا، (مرة اخرى هذا يعني انك لو وضعت الأب في Set من الsubclass فسوف تحصل على false عندما تبحث عن الأب في هذه القائمة). ## **نقاط أخرى متعلقة بالدالة Equals ؟** عندما تتحق من القيم في الكائن: استخدم علامه المساواة == للحقول العادية primitive data type استخدم equals للكائنات، و Arrays.equals للمصفوفات استخدم Dobule.compareDouble و Float.compareFloat لل Double و Float. اذا كنت تختبر احد الأبناء فلا تنسى ان تختبر ما يوجد في الأب super.equals . اذا احتوى الكائن على قيم قد تكون null وتريد ادخالها في الدالة equals ، فاستخدم ال Conditional Operator لذلك: ```java field == null ? other.field == null : field.equals(other.field) ``` **من الأخطاء الشائعه:** هو تمرير كائن Employee للدالة equals بدلاً من Object وهذا يعتبر overloading وليس overriding ، لذلك يفضل دائماً استخدام @Overriding عندما تقوم بعمل override لأي دالة من الsuperclass ، وسوف يخرج المترجم رسالة خطأ اذا لم تتساوى المعاملات ولم تقم بعملية ال Overriding بشكل صحيح. هناك نقاط متعلقه بهذه المقاله مثلاً الhashcode ولماذا الكائنات المتساوية يجب ان يكون لها نفس hashcode وكيف نحصل على ذلك؟ ماهي الcompareTo وهل هي تستخدم equals لتأدية عملها ؟ **ولهذا مقال أخر ان شاء الله**. المصادر: [How to Write an Equality Method in Java](http://www.artima.com/lejava/articles/equality.html?ref=informatic-ar.com) [Secrets of equals](http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals.html?ref=informatic-ar.com) [instanceof versus getClass in equals Methods](http://www.artima.com/intv/bloch17.html?ref=informatic-ar.com) [Effective Java Second Edition ](http://www.amazon.com/Effective-Java-2nd-Joshua-Bloch/dp/0321356683?ref=informatic-ar.com) [Core Java, Volume I--Fundamentals ](http://www.amazon.com/Core-Java-I--Fundamentals-8th-Horstmann/dp/0132354764/?ref=informatic-ar.com)