نمط Pimpl
جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 78 من 104.
إن Pimpl idiom (Pointer to Implementation) هي تقنية تُخفي تفاصيل implementation الخاصة بـ class من خلال نقلها إلى class منفصلة مُعلَن عنها مسبقًا. وهذا يقلّل من تبعيات الترجمة ويحافظ على إخفاء الأعضاء الخاصة بشكل حقيقي عن ملف header.
الفكرة الأساسية بسيطة: بدلًا من تعريف الأعضاء الخاصة مباشرةً في class الخاص بك، تعرّف مؤشرًا إلى class من implementation، ويُعرَّف هذا class في source file فقط:
// Widget.h
#include <memory>
class Widget {
public:
Widget();
~Widget();
void doSomething();
private:
class Impl; // إعلان أمامي
std::unique_ptr<Impl> pImpl;
};// Widget.cpp
#include "Widget.h"
#include <iostream>
class Widget::Impl {
public:
int data = 42;
void process() { std::cout << "Processing: " << data << "\n"; }
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
void Widget::doSomething() { pImpl->process(); }تتمثل الفوائد الرئيسية في جدار حماية الترجمة والتوافق الثنائي. عند تغيير فئة Impl، يحتاج الملف المصدري فقط إلى إعادة الترجمة - وليس كل ملف يتضمن الترويسة. وهذا يسرّع أوقات البناء بشكل كبير في المشاريع الكبيرة.
لاحظ أن destructor يجب أن يكون defined في source file (حتى إذا كان defaulted)، لأن unique_ptr يحتاج إلى النوع complete لـ Impl لحذفه. وهذا خطأ شائع عند استخدام Pimpl لأول مرة.
التحدي
سهللنبنِ معالج رسائل آمنًا باستخدام اصطلاح Pimpl لإخفاء تفاصيل تنفيذ encryption من ملف header. يوضح هذا كيف ينشئ Pimpl جدارًا ناريًا للترجمة: لن يرى أي شخص يضمّن ملف header الخاص بك آليات العمل الداخلية لمعالجة رسالتك.
ستنظّم الكود الخاص بك عبر ثلاثة file:
SecureMessage.h: عرّف الواجهة العامة لـ classSecureMessageالخاص بك.يجب أن يحتوي class الخاص بك على class
Implمُعلن عنه مسبقًا وstd::unique_ptrيشير إليه. يجب أن تتضمن الواجهة العامة ما يلي:- منشئ يأخذ
const std::string&للرسالة الأصلية - مدمّر (يجب أن يكون declared هنا، وdefined في ملف .cpp)
- طريقة
setKey(int key)لتعيين مفتاح encryption - طريقة
getEncrypted()تُرجع الرسالة encrypted بصيغةstd::string - طريقة
getOriginal()تُرجع الرسالة الأصلية
يجب أن يعرض header الواجهة العامة فقط: لا ينبغي أن تظهر هنا أي تفاصيل implementation حول كيفية عمل encryption.
- منشئ يأخذ
SecureMessage.cpp: عرّف classImplالمتداخل ونفّذ جميع الطرق.يجب أن يخزّن class
Implالرسالة الأصلية، وencryption key (default إلى 0)، وأن يتولى منطق encryption الفعلي. استخدم Caesar cipher بسيطًا من أجل encryption: حرّك كل حرف بمقدار قيمة key. على سبيل المثال، مع key يساوي 3، يصبح 'a' هو 'd'، ويلتف 'z' ليصبح 'c'.يجب أن يؤثر encryption على الأحرف الصغيرة فقط (a-z)، مع ترك جميع الأحرف الأخرى دون تغيير. تذكّر أن تعرّف المدمّر هنا (حتى إذا كان defaulted)، لأن
unique_ptrيحتاج إلى النوعImplالكامل.main.cpp: اقرأ مدخلين:- سلسلة رسالة (قد contain مسافات)
- مفتاح encryption (عدد صحيح)
أنشئ كائنًا من
SecureMessage، واضبط key، ثم اعرض النتائج:- اطبع
Original:متبوعًا بالرسالة الأصلية - اطبع
Encrypted:متبوعًا بالرسالة encrypted
على سبيل المثال، مع المدخلين hello world و3:
Original: hello world
Encrypted: khoor zruogمع المدخلين xyz abc و5:
Original: xyz abc
Encrypted: cde fghلاحظ كيف لا يكشف header file أي شيء عن implementation الخاص بـ Caesar cipher: هذه هي قوة Pimpl. إذا غيّرت لاحقًا إلى خوارزمية encryption مختلفة، فلن يحتاج إلى recompilation سوى SecureMessage.cpp، وليس أي file يضمّن header.
جرّب بنفسك
#include <iostream>
#include <string>
#include "SecureMessage.h"
int main() {
// اقرأ الرسالة (قد تحتوي على مسافات)
std::string message;
std::getline(std::cin, message);
// اقرأ مفتاح التشفير
int key;
std::cin >> key;
// TODO: أنشئ كائن SecureMessage بالرسالة
// TODO: عيّن مفتاح التشفير
// TODO: اطبع "Original: " متبوعًا بالرسالة الأصلية
// TODO: اطبع "Encrypted: " متبوعًا بالرسالة المشفّرة
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامةتدرّب بنفسك: مترجم C++ عبر الإنترنت