مقدمة: لماذا تعتبر مبادئ SOLID مهمة لمطور C++؟
\n\nSOLID هو اختصار لخمسة مبادئ أساسية للبرمجة كائنية التوجه، صاغها روبرت مارتن (العم بوب). بالنسبة لمطور C++، هذه المبادئ ليست مجرد نظرية مجردة، بل أداة عملية تساعد في إنشاء تطبيقات مرنة وقابلة للصيانة والاختبار. في عالم C++، حيث إدارة الذاكرة والأداء أمران حاسمان، فإن اتباع SOLID يتجنب "كود السباغيتي" ويبسط إعادة الهيكلة.
\n\nفي هذه المقالة، سنشرح كل مبدأ من مبادئ SOLID بأمثلة محددة بلغة C++. سترى كيف يؤدي انتهاك المبادئ إلى مشاكل، وكيفية إصلاحها. سنستخدم لغة C++ الحديثة (C++11/14/17/20) لتكون الأمثلة محدثة.
\n\n1. مبدأ المسؤولية الواحدة (SRP)
\n\nجوهر المبدأ
\nيجب أن يكون للفصل سبب واحد فقط للتغيير. بمعنى آخر، كل فصل مسؤول عن وظيفة واحدة أو جانب واحد من النظام.
\n\nانتهاك SRP في C++
\nلننظر إلى فصل ينتهك SRP: فهو يدير بيانات التقرير وتنسيقه في نفس الوقت.
\n\nclass Report {\npublic:\n void generateReport(const std::vector<Data>& data) {\n // منطق معالجة البيانات\n m_data = data;\n }\n \n void saveToFile(const std::string& filename) {\n // منطق الحفظ في ملف\n std::ofstream file(filename);\n for (const auto& item : m_data) {\n file << item.toString() << "\\n";\n }\n }\n \n void printToConsole() {\n // منطق الطباعة على الشاشة\n for (const auto& item : m_data) {\n std::cout << item.toString() << std::endl;\n }\n }\n \nprivate:\n std::vector<Data> m_data;\n};\n\nالمشكلة: إذا تغير تنسيق الإخراج، يجب تعديل فصل Report. إذا تغيرت طريقة تخزين البيانات - أيضاً. هذا ينتهك SRP.
\n\nالإصلاح: فصل المسؤوليات
\n\n// مسؤول فقط عن بيانات التقرير\nclass ReportData {\npublic:\n void addData(const Data& item) {\n m_data.push_back(item);\n }\n \n const std::vector<Data>& getData() const {\n return m_data;\n }\n \nprivate:\n std::vector<Data> m_data;\n};\n\n// مسؤول فقط عن الحفظ\nclass FileSaver {\npublic:\n void save(const std::string& filename, const ReportData& report) {\n std::ofstream file(filename);\n for (const auto& item : report.getData()) {\n file << item.toString() << "\\n";\n }\n }\n};\n\n// مسؤول فقط عن الطباعة على الشاشة\nclass ConsolePrinter {\npublic:\n void print(const ReportData& report) {\n for (const auto& item : report.getData()) {\n std::cout << item.toString() << std::endl;\n }\n }\n};\n\nالآن كل فصل لديه سبب واحد للتغيير، مما يجعل الكود أكثر مرونة وأسهل للاختبار.
\n\n2. مبدأ مفتوح-مغلق (OCP)
\n\nجوهر المبدأ
\nيجب أن تكون الكيانات البرمجية (الفصول، الوحدات، الدوال) مفتوحة للتمديد ولكن مغلقة للتعديل. هذا يعني أنه يجب أن تكون قادراً على إضافة وظائف جديدة دون تغيير الكود الموجود.
\n\nانتهاك OCP في C++
\nتخيل نظام حساب الخصومات، حيث يتم استخدام if-else لكل نوع عميل.
\n\nclass DiscountCalculator {\npublic:\n double calculateDiscount(const std::string& customerType, double amount) {\n if (customerType == "regular") {\n return amount * 0.1; // 10%\n } else if (customerType == "vip") {\n return amount * 0.2; // 20%\n } else if (customerType == "premium") {\n return amount * 0.3; // 30%\n }\n