QSerializer ميت ، يعيش QSerializer

لقد مرت عدة أشهر منذ أن تحدثت هنا عن مشروع مكتبة Qt الخاص بي لتسلسل البيانات من عرض كائن إلى JSON / XML والعكس صحيح.



وبغض النظر عن مدى فخرتي بالعمارة المبنية ، يجب أن أعترف - لقد تبين أن التنفيذ ، بصراحة ، مثير للجدل.



كل هذا أدى إلى معالجة واسعة النطاق ، ستتم مناقشة نتائجها في هذه المقالة. لمزيد من التفاصيل - تحت الخفض!







مات QSerializer



كان لـ QSerializer عيوب ، والتي غالبًا ما أصبح حلها عيبًا أكبر ، وهنا عدد قليل منها:



  • باهظ الثمن (التسلسل ، الاحتفاظ بحراس الممتلكات في الكومة ، والتحكم في عمر الحراس ، وما إلى ذلك)
  • العمل فقط مع الفئات المستندة إلى QObject
  • يجب أن تستند الكائنات "المعقدة" المتداخلة ومجموعاتها أيضًا إلى QObject
  • عدم القدرة على استكمال المجموعات أثناء إلغاء التسلسل
  • فقط من الناحية النظرية التعشيش اللانهائي
  • عدم القدرة على العمل مع أنواع مهمة من الكائنات "المعقدة" ، بسبب حظر النسخ من QObject
  • الحاجة إلى التسجيل الإلزامي للأنواع في نظام Qt meta-object
  • مشاكل "المكتبة" الشائعة مثل الارتباط وقابلية النقل بين الأنظمة الأساسية


من بين أشياء أخرى ، أردت أن أكون قادرًا على إجراء تسلسل لأي كائن "هنا والآن" ، عندما كان علي استخدام ربط ضخم للطرق في مساحة الاسم QSerializer.



تحيا QSerializer!



لم يكن QSerializer كاملاً. كان من الضروري التوصل إلى حل لا يعتمد فيه المستخدم على QObject ، سيكون من الممكن العمل مع أنواع القيمة وبتكلفة أقل.



في تعليق على المقال السابق ، المستخدمميكرولالاحظت أنه يمكنك التفكير في استخدام Q_GADGET .



مزايا Q_GADGET :



  • لا قيود على النسخ
  • له نسخة ثابتة من QMetaObject للوصول إلى الخصائص


بالاعتماد على Q_GADGET ، اضطررت إلى إعادة النظر في النهج المتبع في كيفية إنشاء JSON و XML استنادًا إلى حقول الفئة المعلنة. ظهرت مشكلة "التكلفة الباهظة" في المقام الأول بسبب:



  • حجم فئة التخزين الكبير (40 بايت على الأقل)
  • تخصيص كومة للكيانات الوصي الجديد لكل ملكية والتحكم في مدة البقاء (TTL) الخاصة بهم


لتقليل التكلفة ، قمت بصياغة المتطلبات التالية:

التواجد في كل كائن قابل للتسلسل لطرق الأسلاك للتسلسل / إلغاء التسلسل لجميع خصائص الفئة ووجود طرق لقراءة القيم وكتابتها لكل خاصية باستخدام التنسيق المخصص لهذه الخاصية

وحدات الماكرو



إن التغلب على الكتابة القوية لـ C ++ ، والتي تعقد عملية التسلسل التلقائي ، ليس بالأمر السهل ، وقد أظهرت التجربة السابقة ذلك. من ناحية أخرى ، يمكن أن تكون وحدات الماكرو مساعدة كبيرة في حل مثل هذه المشكلة (تقريبًا نظام كيو تي meta-object بأكمله مبني على وحدات الماكرو) ، لأنه باستخدام وحدات الماكرو ، يمكنك إنشاء التعليمات البرمجية للأساليب والخصائص.



نعم ، غالبًا ما تكون وحدات الماكرو شريرة في أنقى صورها - يكاد يكون من المستحيل تصحيحها. يمكنني مقارنة كتابة ماكرو لتوليد رمز بوضع حذاء من الكريستال على كعب رئيسك في العمل ، لكن الصعوبة لا تعني المستحيل!



استطرادا غنائي حول وحدات الماكرو

— , , «» (). .



يوفر QSerializer حاليًا طريقتين للإعلان عن فئة على أنها قابلة للتسلسل: ترث من فئة QSerializer أو استخدام ماكرو إنشاء كود QS_CLASS .



بادئ ذي بدء ، تحتاج إلى تعريف ماكرو Q_GADGET في جسم الفئة ، وهذا يتيح الوصول إلى staticMetaObject ، وسوف يخزن الخصائص التي تم إنشاؤها بواسطة وحدات الماكرو.



سيسمح لك الوراثة من QSerializer بنقل كائنات متعددة قابلة للتسلسل إلى نوع واحد وتسلسلها بكميات كبيرة.



تحتوي فئة QSerializer على 4 طرق explorer التي تتيح لك تحليل خصائص كائن وطريقة افتراضية واحدة للحصول على مثيل QMetaObject:



QJsonValue toJson() const
void fromJson(const QJsonValue &)
QDomNode toXml() const
void fromXml(const QDomNode &)
virtual const QMetaObject * metaObject() const


لا يحتوي Q_GADGET على كل ربط كائن التعريف الذي يوفره Q_OBJECT .



داخل QSerializer ، سيمثل مثيل staticMetaObject فئة QSerializer ، ولكن لا يشتق منها بأي شكل من الأشكال ، لذلك عند إنشاء فئة قائمة على QSerializer ، يجب تجاوز طريقة metaObject. يمكنك إضافة ماكرو QS_SERIALIZER إلى نص الفصل وستتجاوز طريقة metaObject نيابة عنك.



أيضًا ، باستخدام staticMetaObject بدلاً من تخزين مثيل QMetaObject في كل كائن يوفر 40 بايت من حجم الفصل ، حسنًا ، بشكل عام ، الجمال!



إذا كنت لا تريد أن ترث لسبب ما ، فيمكنك تحديد ماكرو QS_CLASS في جسم الفئة المتسلسلة، سيولد جميع الطرق المطلوبة بدلاً من التوريث من QSerializer.



إعلان المجالات



بشكل منفصل ، هناك 4 أنواع من البيانات القابلة للتسلسل في JSON و XML ، والتي بدونها لن يكتمل التسلسل لهذه التنسيقات. يوضح الجدول أنواع البيانات ووحدات الماكرو المقابلة كطريقة لوصف:

نوع البيانات وصف دقيق
حقل حقل عادي من النوع البدائي (أرقام مختلفة ، سلاسل ، أعلام) QS_FIELD
مجموعة مجموعة من القيم لأنواع البيانات البدائية QS_COLLECTION
شيء البنية المعقدة للحقول أو الهياكل المعقدة الأخرى QS_OBJECT
مجموعة من الأشياء مجموعة من هياكل البيانات المعقدة من نفس النوع QS_COLLECTION_OBJECTS


سنفترض أن الكود الذي ينشئ وحدات الماكرو هذه يسمى وصفًا ، وتسمى وحدات الماكرو التي تنشئها بالوصف.



يوجد مبدأ واحد فقط لتوليد الوصف - لحقل معين ، قم بإنشاء خاصية JSON و XML وحدد طرق كتابة / قراءة القيم.



دعنا نحلل إنشاء وصف JSON باستخدام مثال حقل نوع البيانات البدائي:



/* Create JSON property and methods for primitive type field*/
#define QS_JSON_FIELD(type, name)                                                           
    Q_PROPERTY(QJsonValue name READ get_json_##name WRITE set_json_##name)                  
    private:                                                                                
        QJsonValue get_json_##name() const {                                                
            QJsonValue val = QJsonValue::fromVariant(QVariant(name));                       
            return val;                                                                     
        }                                                                                   
        void set_json_##name(const QJsonValue & varname){                                   
            name = varname.toVariant().value<type>();                                       
        }   
...
int digit;
QS_JSON_FIELD(int, digit)  


بالنسبة لحقل الرقم الدولي ، سيتم إنشاء رقم خاصية مع نوع QJsonValue وسيتم تحديد طرق الكتابة والقراءة الخاصة - get_json_digit و set_json_digit - ، وستصبح موصلات لتسلسل / إلغاء تسلسل حقل الرقم باستخدام JSON.



كيف يحدث هذا؟
name digit, ('##') digit — .



type int. , type int . QVariant int .



وإليك إنشاء وصف JSON لهيكل معقد:



/* Generate JSON-property and methods for some custom class */
/* Custom type must be provide methods fromJson and toJson */
#define QS_JSON_OBJECT(type, name)
    Q_PROPERTY(QJsonValue name READ get_json_##name WRITE set_json_##name)
    private:
    QJsonValue get_json_##name() const {
        QJsonObject val = name.toJson();
        return QJsonValue(val);
    }
    void set_json_##name(const QJsonValue & varname) {
        if(!varname.isObject())
        return;
        name.fromJson(varname);
    } 
...
SomeClass object;
QS_JSON_OBJECT(SomeClass, object)


الكائنات المعقدة هي مجموعة من الخصائص المتداخلة التي ستعمل كخاصية واحدة "كبيرة" لفئة خارجية ، لأن هذه الكائنات سيكون لها أيضًا طرق سلكية. كل ما عليك القيام به من أجل ذلك هو استدعاء طريقة الدليل المناسبة في طرق القراءة والكتابة للهياكل المعقدة.



خلق الطبقة



وبالتالي ، لدينا بنية أساسية بسيطة إلى حد ما لإنشاء فئة قابلة للتسلسل.



لذلك ، على سبيل المثال ، يمكنك جعل فئة قابلة للتسلسل عن طريق التوريث من QSerializer:



class SerializableClass : public QSerializer {
Q_GADGET
QS_SERIALIZER
QS_FIELD(int, digit)
QS_COLLECTION(QList, QString, strings)
};


أو هكذا ، باستخدام ماكرو QS_CLASS :



class SerializableClass {
Q_GADGET
QS_CLASS
QS_FIELD(int, digit)
QS_COLLECTION(QList, QString, strings)
};


مثال على التسلسل JSON
:



class CustomType : public QSerializer {
Q_GADGET
QS_SERIALIZER
QS_FIELD(int, someInteger)
QS_FIELD(QString, someString)
};

class SerializableClass : public QSerializer {
Q_GADGET
QS_SERIALIZER
QS_FIELD(int, digit)
QS_COLLECTION(QList, QString, strings)
QS_OBJECT(CustomType, someObject)
QS_COLLECTION_OBJECTS(QVector, CustomType, objects)
};


, :



SerializableClass serializable;
serializable.someObject.someString = "ObjectString";
serializable.someObject.someInteger = 99999;
for(int i = 0; i < 3; i++) {
    serializable.digit = i;
    serializable.strings.append(QString("list of strings with index %1").arg(i));
    serializable.objects.append(serializable.someObject);
}
QJsonObject json = serializable.toJson();


JSON:



{
    "digit": 2,
    "objects": [
        {
            "someInteger": 99999,
            "someString": "ObjectString"
        },
        {
            "someInteger": 99999,
            "someString": "ObjectString"
        },
        {
            "someInteger": 99999,
            "someString": "ObjectString"
        }
    ],
    "someObject": {
        "someInteger": 99999,
        "someString": "ObjectString"
    },
    "strings": [
        "list of strings with index 0",
        "list of strings with index 1",
        "list of strings with index 2"
    ]
}


— , XML , toJson toXml.



example.



محددات



الحقول الفردية



يجب أن توفر الأنواع الأولية أو المعرفة من قبل المستخدم مُنشئًا افتراضيًا.



المجموعات



يجب أن يكون صنف المجموعة مقولبًا وأن يوفر طرقًا واضحة ، بحجم ، وإلحاق. يمكنك استخدام مجموعاتك الخاصة ، وفقًا للشروط. مجموعات Qt التي تفي بهذه الشروط: QVector و QStack و QList و QQueue.



إصدارات Qt



الإصدار الأدنى Qt 5.5.0

الحد الأدنى للإصدار الذي تم اختباره Qt 5.9.0

الحد الأقصى للإصدار الذي تم اختباره Qt 5.15.0

ملاحظة: يمكنك المشاركة في اختبار واختبار QSerializer على الإصدارات السابقة من Qt



النتيجة



عند إعادة صياغة QSerializer ، لم أضع نفسي مطلقًا في مهمة تقليله بشكل كبير. ومع ذلك ، انخفض حجمه من 9 ملفات إلى 1 ، مما قلل أيضًا من تعقيده. الآن لم يعد QSerializer مكتبة في شكلنا المعتاد ، فهو الآن مجرد ملف رأس ، وهو ما يكفي لتضمينه في المشروع والحصول على جميع الوظائف للتسلسل / إلغاء التسلسل المريح. بدأ التطوير مرة أخرى في شهر مارس ، حيث تم اختراع بنية ذكية وتضخم المشروع مع التبعيات والعكازات ، وأعيد كتابتها من 0 عدة مرات. وكل ذلك من أجل أن يتحول في النهاية إلى ملف صغير.



أسأل نفسي: "هل كان يستحق الجهد المبذول فيه؟" ، أجبت: "نعم ، كان كذلك. لقد جربتها بالفعل في مشاريعي القتالية والنتيجة أسعدتني.



روابط

جيثب: رابط

أحدث إصدار: v1.1

المقال السابق: QSerializer: حل بسيط لتسلسل JSON / XML



قائمة المستقبل



  • انخفاض كبير في التكلفة (يمكن القيام به بتكلفة أقل)
  • الاكتناز
  • العمل مع الأنواع المهمة
  • الوصف الأساسي للبيانات القابلة للتسلسل
  • دعم أي مجموعة نموذجية توفر طرقًا واضحة ، بحجم ، وإلحاق. حتى الخاصة بهم
  • مجموعات قابلة للتغيير بالكامل عند إلغاء التسلسل
  • دعم لجميع الأنواع البدائية الشعبية
  • دعم أي نوع مخصص موصوف باستخدام QSerializer
  • لا حاجة لتسجيل الأنواع المخصصة



All Articles