معلمات غير مؤكدة كطريقة عالمية لإنشاء بنية التطبيق في C ++ و Java كحد أدنى. السعر

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

إخلاء مسؤولية : في هذا المقال ، جمعت أفكاري حول الهندسة المعمارية المثالية. بعض الأفكار ليست أفكاري (لكنني لا أتذكر أفكارها) ، وبعض الأفكار شائعة ومعروفة للجميع - هذا ليس مهمًا ، لأنني لا أقدم أفكاري حول بنية جيدة ، ولكن رمزًا محددًا يسمح بمقاربة هذه البنية بأقل سعر.

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

إخلاء المسئولية N3 : لقد كتبت تطبيقات كبيرة من البداية ، لكنني لم أكتب تطبيقات المؤسسات الخاصة بالخوادم والعميل. كل شيء مختلف هناك ، وربما تبدو تجربتي غريبة بالنسبة للمتخصصين في هذا المجال. والمقال ليس عن ذلك ، لا يتم النظر في نفس مشاكل قابلية التوسع هنا على الإطلاق.
إخلاء المسئولية N4 ( التحديث. استنادًا إلى التعليقات): اقترح بعض المعلقين أن أقوم بإعادة اختراع فاولر وتقديم أنماط تصميم معروفة منذ فترة طويلة. هذا بالتأكيد ليس هو الحال. أقترح أداة صغيرة جدًا للمعلمات تتيح لك تنفيذ هذه الأنماط بحد أدنى من الخربشة. بما في ذلك Fowler's Dependency Injection and Service Locator ، ولكن ليس فقط - باستخدام فئة TypedSet ، يمكنك أيضًا تنفيذ مجموعة من الاستراتيجيات اقتصاديًا. في هذه الحالة ، تم الوصول إلى فاولر من خلال خطوط باهظة الثمن - أداتي ذات التكلفة الصفرية ، التكلفة الصفرية (إذا كانت صارمة تمامًا ، ثم قم بتسجيل الدخول (N) بدلاً من 2M * log (N) ، حيث M هي طول سلسلة المعلمة الخاصة بـ Service Locator. بعد ظهور constexpr typeid في c ++ 20 ، يجب أن يصبح السعر صفرًا تمامًا). لذلك ، أطلب منك عدم توسيع معنى المقال لتصميم الأنماط. هنا ستجد فقط طريقة للتنفيذ الرخيص لهذه الأنماط.

ستكون الأمثلة في لغة C ++ ، لكن كل ما سبق قابل للتطبيق في Java. ربما ، بمرور الوقت ، سأقدم تعليمات برمجية صالحة لجافا إذا كان طلب ذلك سيكون في تعليقات منك.

الجزء 1. العمارة كروية في فراغ


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

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

وأنا لا أقصد TDD. مرض نموذجي للعديد من المبرمجين هو العبادة الدينية للتقنيات التي تقرأ في مكان ما دون فهم حدود فعاليتها. يعتبر TDD جيدًا عندما يعمل العديد من المبرمجين على الكود ، وعندما يكون هناك قسم للاختبار ، ولدى السلطات فهم لماذا هناك حاجة إلى ممارسات الترميز الجيدة وهي على استعداد لدفع ليس فقط لبعض الكود الذي يحل المشكلة ، ولكن أيضًا لموثوقيتها. إذا كان رؤساءك غير مستعدين للدفع ، فستضطر إلى العمل بشكل اقتصادي أكثر. ومع ذلك ، لا يزال يتعين عليك اختبار الكود - ما لم يكن لديك ، بطبيعة الحال ، شعور بالحفاظ على النفس.

المبدأ الثاني هو نمطية. بتعبير أدق ، نمطية معزولة للغاية دون استخدام المكتبات / القرص الثابت غير المرتبطة بالوحدة نفسها. الآن عند تصميم بنيات الخوادم ، من المألوف تقسيم المتراصة إلى خدمات ميكروية. سوف أخبرك بسر فظيع - كل وحدة في المتراصة يجب أن تكون مثل خدمة مجهرية. بمعنى أنه يجب أن تبرز بسهولة من الشفرة العامة مع الحد الأدنى من الرؤوس المتصلة في بيئة الاختبار. لم يكن الأمر واضحًا بعد ، لكنني سأشرح مع مثال: هل سبق لك أن حاولت تخصيص share_ptr من دفعة؟ إذا تمكنت في نفس الوقت من سحب ليس فقط الداعم بالكامل ، ولكن نصف مواده الخام فقط ، فهذا يعني أنك قتلت ثلاثة إلى خمسة أيام لتوقف الإدمانات غير الضرورية !!! في الوقت نفسه ، يمكنك سحب حقيقة أن Shared_ptr بالتأكيد ليس لديها ما تفعله !!!

إنها أسوأ من الخطأ - إنها جريمة معمارية.

من خلال بنية جيدة ، يجب أن تكون قادرًا على هدم Shared_ptr ، دون أي ألم أو استبدال كل شيء غير مرتبط بـ Shared_ptr بإصدارات تجريبية. على سبيل المثال ، نسخة تجريبية من المخصص. أو ننسى دفعة. دعنا نقول أنك تكتب محلل xml / html. تحتاج إلى العمل باستخدام سلاسل والعمل مع ملفات للمحلل اللغوي. وإذا كنا نتحدث عن بنية مثالية لا ترتبط باحتياجات شركة إنتاج / برامج معينة ، فبالنسبة إلى محلل لهندسة معمارية مثالية ، لا يحق لنا استخدام عمليات البحث std :: istream و std :: file_system و std :: string و hardcode مع سلاسل في المحلل اللغوي. يجب أن نقدم واجهة دفق ، واجهة لعمليات الملفات (ربما مقسمة إلى واجهات فرعية ، ولكن لا يزال يتعين الوصول إلى واجهات فرعية من خلال واجهة وحدة عمليات الملفات) ، واجهة للعمل مع السلاسل ، واجهة التخصيص ، وواجهة مثالية للخط نفسه. نتيجة لذلك ، يمكننا استبدال كل ما لا يرتبط بالتحليل بفراغات اختبار ، أو إدراج نسخة تجريبية من أداة التخصيص / العمل مع الملفات / البحث في سلسلة مع فحوصات إضافية. وسيزداد تعدد استخدامات الحل - غدًا ، تحت واجهة البث لن يكون هناك ملف ، ولكن موقعًا ما في مكان ما على الإنترنت ، ولن يلاحظه أحد. يمكنك استبدال المكتبة القياسية بـ Qt ، ثم التبديل إلى Visual c ++ ، ثم البدء في استخدام أشياء Linux فقط - وستكون التعديلات ضئيلة. باعتبارك مفسدًا ، سأقول أنه من خلال هذا النهج ، ينشأ سؤال السعر في نمو كامل - لتغطية كل شيء مع واجهات ، بما في ذلك عناصر المكتبة القياسية ، باهظ الثمن ، لكن هذا ليس هدفًا ، لكنه حل.

بشكل عام ، يعتبر مبدأ الوحدة النمطية الجذرية كما هو معلن في هذه المقالة نقطة حساسة في C ++ ورمز زائد نموذجي عمومًا. إذا قمت بإنشاء ملفات تعريف وواجهات منفصلة بشكل منفصل عن التطبيقات ، فلا يزال بإمكانك إنشاء استقلالية / عزل لملفات cpp عن بعضها البعض ، ثم ، نسبيًا ، وليس 100٪ ، يتم عادةً نسج الرؤوس في متراصة صلبة ، والتي لا يمكن إزالتها دون أي لحوم. وعلى الرغم من أن هذا له تأثير رهيب على وقت التجميع ، إلا أنه. علاوة على ذلك ، حتى لو تم تحقيق استقلال العناوين ، فإن هذا يعني تلقائيًا عدم القدرة على تجميع الطبقات. في الواقع ، فإن الطريقة الوحيدة لتحقيق استقلال كل من ملفات .cpp والرؤوس في c ++ هي التصريح عن الفئات المستخدمة مسبقًا (دون تعريفها) ، ثم استخدام المؤشرات الخاصة بها فقط. بمجرد استخدام الفئة نفسها بدلاً من مؤشر الفئة في ملف الرأس (أي ، تجميعها) ، ستقوم بإنشاء مجموعة من جميع .cpp-shniks التي تتضمن هذا العنوان ، وأن .cpp-shnik يحتوي على تعريف الفئة. لا يزال هناك fastpimpl ، لكنه مضمون فقط لإنشاء تبعيات على مستوى حزب الشعب الكمبودي.

لذلك ، بالنسبة لبنية جيدة ، فإن عزل الوحدات أمر مهم - القدرة على سحب وحدة واحدة مع العنوان الأول الذي يربط بين وحدات الماكرو وأنواع المكتبة الرئيسية ، مع عنوان ثانٍ للإعلانات والعديد من الادراج التي تربط مجموعة من الواجهات. وفقط ما يتعلق بهذه الوظيفة ، ويجب تخزين كل شيء آخر في الوحدات النمطية الأخرى ويمكن الوصول إليها فقط من خلال واجهات.

نذكر الميزات الرئيسية للهندسة المعمارية الجيدة ، بما في ذلك النقاط المشار إليها أعلاه ، نقطة تلو الأخرى.

دعونا نحدد مصطلح "الوحدة النمطية". الوحدة النمطية هي مجموع الوظائف المرتبطة منطقيا. على سبيل المثال ، يمكنك العمل مع التدفقات أو عمل الملفات ، أو محلل html.

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

  1. نمطية. حتمية "الوحدة النمطية كما microservice".
  2. تخصيص 20 ٪ من الكود ينفذ 80 ٪ من الوقت في مكتبة منفصلة - جوهر البرنامج
  3. اختبار كل وظيفة من كل وحدة
  4. الواجهة ، إنها نقص القرص الثابت. يمكنك فقط الاتصال بالقرص الثابت المرتبط مباشرة بوظيفة الوحدة ، ويجب إجراء مكالمات المكتبة المباشرة الأخرى إلى وحدة نمطية منفصلة والوصول إليها من خلال الواجهة.
  5. عزل كامل للوحدة بواسطة واجهات من البيئة الخارجية. الحظر المفروض على تطبيقات "التسمير" التي لا تتعلق بوظيفة الفصل. وبشكل أكثر جذرية ، عزل المكتبات (بما في ذلك المكتبات العادية) مع واجهات / محولات / ديكورات
  6. لا يتم استخدام تجميع الفصل الدراسي أو إنشاء متغير فصل أو fastpimpl إلا عندما يكون ذلك ضروريًا للأداء.

بالطبع ، سنعرف كيف نحقق كل هذا بسرعة مقابل سعر أقل ، ولكن أود أن ألفت الانتباه إلى مشكلة أخرى ، والتي سيكون حلها مكافأة لنا - نقل المعلمات التي تعتمد على النظام الأساسي. على سبيل المثال ، إذا كنت بحاجة إلى إنشاء تعليمات برمجية تعمل بشكل متساوٍ على Android و Windows ، فسيكون من المنطقي تخصيص خوارزميات تعتمد على النظام الأساسي في وحدات منفصلة. في هذه الحالة ، على الأرجح ، قد يتطلب تطبيق android إشارة إلى بيئة Java (jni) ، JNIEnv * ، وربما كائنين من كائنات Java. قد يتطلب التنفيذ على Windows مجلد عمل للبرنامج (والذي يمكن طلبه على نظام أندرويد من النظام ، مع وجود JNIEnv *). الحيلة هي أن نفس JNIEnv * غير موجود في سياق Windows ، لذلك حتى اتحاد مدون أو بديل c ++ لـ std :: variant مستحيل. يمكنك ، بالطبع ، استخدام void * vector أو std :: any vector كمعلمة ، ولكن بصراحة ، هذا عكاز غير عادي. غير نمطية - لأنها ترفض الميزة الرئيسية لـ c ++ ، الكتابة القوية. وهذا أكثر خطورة من السارس.

علاوة على ذلك ، سنقوم بتحليل كيفية حل هذه المشكلة بطريقة محددة بدقة.

الجزء 2. الرصاص السحري وسعرها


لذلك ، لنفترض أن لدينا عددًا كبيرًا من التعليمات البرمجية التي يجب كتابتها من البداية ، وستكون النتيجة مشروعًا كبيرًا للغاية.

كيف يمكن تجميعها وفقًا للمبادئ التي حددناها؟

الطريقة الكلاسيكية ، المعتمدة من قبل جميع الأدلة ، هي تقسيم كل شيء إلى واجهات واستراتيجيات. بمساعدة الواجهات والاستراتيجيات ، إذا كان هناك الكثير منها ، يمكن عزل أي مشكلة فرعية في مشروعنا إلى حد أن مبدأ "الوحدة النمطية كما microservice" سيبدأ العمل عليه. لكن تجربتي الشخصية هي أنه إذا قسمت المشروع إلى 20 إلى 30 جزءًا ، والتي سيتم عزلها إلى مستوى "الوحدة النمطية كما microservice" ، فسوف تنجح. لكن الميزة الرئيسية للهندسة المعمارية الجيدة هي القدرة على اختبار أي فئة خارج سياق المشروع. وإذا قمت بعزل كل فصل بالفعل ، فهناك بالفعل أكثر من 500 وحدة ، وفي تجربتي ، فإن هذا يزيد من وقت التطوير بمقدار 3-5 مرات ، مما يعني أنه في "ظروف القتال" لن تقوم بذلك وسوف تتنازل عن السعر والجودة.

قد يشك شخص ما ، وسيكون في حقه. دعونا نجعل تقدير تقريبي. دع الطبقة الوسطى تضم 3-5 أعضاء و 20 وظيفة و 3 مُنشئين. بالإضافة إلى 6-10 مجموعة من اللاعبين والمستوطنين (mutators) للوصول إلى أعضائنا. المجموع حوالي 40 وحدة في الفصل. في مشروع نموذجي ، يحتاج كل فصل "مركزي" إلى الوصول إلى خمس وظائف في المتوسط ​​، وليس مركزًا إلى 3. على سبيل المثال ، يحتاج العديد من الفئات إلى مخصّص ، ونظام ملفات ، والعمل مع السلاسل ، والعمل مع التدفقات ، والوصول إلى قواعد البيانات.

ستتطلب كل استراتيجية / واجهة عضوًا واحدًا من النوع std::shared_ptr<CreateStreamStrategy> m_create_stream; . اثنين من الطفرات ، بالإضافة إلى التهيئة في كل من الصانعين الثلاثة. بالإضافة إلى مكان ما في التهيئة myclass->SetCreateStreamStrategy( my_create_stream_strategy ) ، ستحتاج إلى استدعاء شيء مثل myclass->SetCreateStreamStrategy( my_create_stream_strategy ) عدة مرات ، ليصبح المجموع 8 وحدات لكل واجهة / استراتيجية ، ولدينا حوالي خمس وحدات منها ، سيكون هناك 40 وحدة. وهذا هو ، جعلنا فئة المصدر مرتين مرهقة. وفقدان البساطة سيؤثر حتما على قابلية القراءة ، وفي مكان آخر في عملية التصحيح ، ومرات ونصف ، على الرغم من حقيقة أنه لا يبدو أن شيئا قد تغير بشكل أساسي.

لذا فإن السؤال هو. كيف تفعل الشيء نفسه ، ولكن بأقل سعر؟ أول ما يتبادر إلى الذهن هو التوحيد الثابت في القوالب ، بأسلوب ألكساندريسكو ومكتبة لوكي.

نحن نكتب فصلا في الأسلوب

 template < struct Traits > class MyClass { public: void DoMainTaskFunction() { ... MyStream stream = Traits::streamwork::Open( stream_name ); ... } }; 

هذا القرار له كل المزايا المعمارية التي حددناها في الجزء الأول. ولكن هناك أيضا الكثير من العيوب.

أنا شخصياً أحب أن أراعي ، ولكني أشعر بالأسف لنفسي ، أعترف: لا يحبذ القوالب في الكود العادي سوى السحرة القوالب. كتلة كبيرة من المبرمجين مع كلمة "قالب" عبوس قليلا. علاوة على ذلك ، في الصناعة ، الغالبية العظمى من الإيجابيات ليست في الواقع إيجابيات ، لكن تم إعادة تدريبها قليلاً في syshniks من c ++ الذين ليس لديهم معرفة عميقة بالإيجابيات ، لكنهم يندرجون تحت كلمة "قالب" ويتظاهرون بالموت.

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

في نفس الوقت ، إذا أردنا ، لأغراض زيادة قابلية القراءة ، إزالة نص الوظيفة بدقة خارج الفصل ، فسنحصل على الكثير من الخربشة مع أسماء القوالب ومعلمات القالب. وفي حالة وجود خطأ في التحويل البرمجي ، نحصل على أرفف طويلة قابلة للقراءة من الأسباب ومناطق المشاكل مع مجموعة من القوالب المتداخلة المعقدة.

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

الجزء الثالث. الحل المقترح والرمز المشفر لهذا الحل


إذن هناك !!! تلبية فئة القالب TypedSet. يربط مؤشر ذكي واحد من هذا النوع بنوع واحد. علاوة على ذلك ، بالنسبة للنوع المحدد ، قد يكون له كائن ، لكنه قد لا يكون كذلك. لا أحب الاسم - لذلك سأكون ممتنًا إذا أخبرني في التعليقات بخيار أكثر نجاحًا.

نوع واحد - كائن واحد. لكن عدد الأنواع لا يقتصر! لذلك ، يمكنك تمرير مثل فئة معلمة.

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

سنقوم بثلاث وظائف أساسية: إنشاء ، واحصل على ، وتمتلك. وفقا لذلك ، إنشاء واستلام والتحقق من وجود عنصر.

 /// @brief    .      ,    ///           /// class TypedSet { public: template <class TypedElement> void Create( const std::shared_ptr<TypedElement> & value ); template <class TypedElement> std::shared_ptr<TypedElement> Get() const; template <class TypedElement> bool Has() const; size_t GetSize() const { return storage_.size(); } protected: typedef std::map< size_t, std::shared_ptr<void> > Storage; Storage const & storage() const { return storage_; } Storage & get_storage() { return storage_; } private: Storage storage_; }; template <class TypedElement> void TypedSet::Create( const std::shared_ptr<TypedElement> & value ) { size_t hash = typeid(TypedElement).hash_code(); if ( storage().count( hash ) > 0 ) { LogError( "Access Violation" ); return; } std::shared_ptr<void> to_add ( value ); get_storage().insert( std::pair( typeid(TypedElement).hash_code(), to_add ) ); } template <class TypedElement> bool TypedSet::Has() const { size_t hash = typeid(TypedElement).hash_code(); return storage().count( hash ) > 0; } template <class TypedElement> std::shared_ptr<TypedElement> TypedSet::Get() const { size_t hash = typeid(TypedElement).hash_code(); if ( storage().count( hash ) > 0 ) { std::shared_ptr<void> ret( storage().at(hash) ); return std::static_pointer_cast<TypedElement>( ret ); } else { LogError( "Access Violation" ); return std::shared_ptr<TypedElement> (); } } 

بالمناسبة ، رأيت حلاً بديلاً من الزملاء الذين يكتبون في كيو تي. هناك ، تم الوصول إلى الواجهة المرغوبة من خلال لوحة فردية ، والتي "تم تعيينها" للواجهة المرغوبة ، معبأة في Varaint ، عبر خط نصي (!!!) ، وبعد اختيار هذا الخيار ، يمكن استخدام النتيجة.

 GlobalConfigurator()["FileSystem"].Get().As<FileSystem>() 

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

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

هنا سيكون لدينا المزيد من الوظائف الأساسية: إنشاء ، الحصول ، CreateParamsSet و GetParamsSet. لم يتم وضعه ، لأنه ضروري من الناحية المعمارية: إذا كانت التعليمات البرمجية تشير إلى وظيفة العمل مع نظام الملفات ، ولم يوفرها رمز الاتصال ، يمكنك فقط رمي استثناء أو تأكيد ، أو جعل برنامج sebukka يستدعي الوظيفة abort ().

 class StrategiesSet { public: template <class Strategy> void Create( const std::shared_ptr<Strategy> & value ); template <class Strategy> std::shared_ptr<Strategy> Get(); template <class Strategy> void CreateParamsSet(); template <class Strategy> std::shared_ptr<TypedSet> GetParamsSet(); template <class Strategy, class ParamType> void CreateParam( const std::shared_ptr<ParamType> & value ); template <class Strategy, class ParamType> std::shared_ptr<ParamType> GetParam(); protected: TypedSet const & strategies() const { return strategies_; } TypedSet & get_strategies() { return strategies_; } TypedSet const & params() const { return params_; } TypedSet & get_params() { return params_; } template <class Type> struct ParamHolder { ParamHolder( ) : param_ptr( std::make_shared<TypedSet>() ) {} std::shared_ptr<TypedSet> param_ptr; }; private: TypedSet strategies_; TypedSet params_; }; template <class Strategy> void StrategiesSet::Create( const std::shared_ptr<Strategy> & value ) { get_strategies().Create<Strategy>( value ); } template <class Strategy> std::shared_ptr<Strategy> StrategiesSet::Get() { return get_strategies().Get<Strategy>(); } template <class Strategy> void StrategiesSet::CreateParamsSet( ) { typedef ParamHolder<Strategy> Holder; std::shared_ptr< Holder > ptr = std::make_shared< Holder >( ); ptr->param_ptr = std::make_shared< TypedSet >(); get_params().Create< Holder >( ptr ); } template <class Strategy> std::shared_ptr<TypedSet> StrategiesSet::GetParamsSet() { typedef ParamHolder<Strategy> Holder; if ( get_params().Has< Holder >() ) { return get_params().Get< Holder >()->param_ptr; } else { LogError("StrategiesSet::GetParamsSet : get unexisting!!!"); return std::shared_ptr<TypedSet>(); } } template <class Strategy, class ParamType> void StrategiesSet::CreateParam( const std::shared_ptr<ParamType> & value ) { typedef ParamHolder<Strategy> Holder; if ( !params().Has<Holder>() ) CreateParamsSet<Strategy>(); if ( params().Has<Holder>() ) { std::shared_ptr<TypedSet> params_set = GetParamsSet<Strategy>(); params_set->Create<ParamType>( value ); } else { LogError( "Param creating error: Access Violation" ); } } template <class Strategy, class ParamType> std::shared_ptr<ParamType> StrategiesSet::GetParam() { typedef ParamHolder<Strategy> Holder; if ( params().Has<Holder>() ) { return GetParamsSet<Strategy>()->template Get<ParamType>(); //   template          .    . } else { LogError( "Access Violation" ); return std::shared_ptr<ParamType> (); } } 

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

حسنا ، وحالة استخدام صغيرة (حتى الآن) مبسطة بشكل مفرط. آمل أن تشير إلي في التعليقات ما تريد أن تراه كمثال بسيط ، وسأجعل المقال ترقية صغيرة. وكما تقول حكمة البرمجة الشائعة ، "حرر في أسرع وقت ممكن وحسّن استخدام الملاحظات بعد الإصدار".

 class Interface1 { public: virtual void Fun() { printf("\niface1\n");} virtual ~Interface1() {} }; class Interface2 { public: virtual void Fun() { printf("\niface2\n");} virtual ~Interface2() {} }; class Interface3 { public: virtual void Fun() { printf("\niface3\n");} virtual ~Interface3() {} }; class Implementation1 : public Interface1 { public: virtual void Fun() override { printf("\nimpl1\n");} }; class Implementation2 : public Interface2 { public: virtual void Fun() override { printf("\nimpl2\n");} }; class PrintParams { public: virtual ~PrintParams() {} virtual std::string GetOs() = 0; }; class PrintParamsUbuntu : public PrintParams { public: virtual std::string GetOs() override { return "Ubuntu"; } }; class PrintParamsWindows : public PrintParams { public: virtual std::string GetOs() override { return "Windows"; } }; class PrintStrategy { public: virtual ~PrintStrategy() {} virtual void operator() ( const TypedSet& params, const std::string & str ) = 0; }; class PrintWithOsStrategy : public PrintStrategy { public: virtual void operator()( const TypedSet& params, const std::string & str ) override { auto os = params.Get< PrintParams >()->GetOs(); printf(" Printing: %s (OS=%s)", str.c_str(), os.c_str() ); } }; void TestTypedSet() { using namespace std; TypedSet a; a.Create<Interface1>( make_shared<Implementation1>() ); a.Create<Interface2>( make_shared<Implementation2>() ); a.Get<Interface1>()->Fun(); a.Get<Interface2>()->Fun(); Log("Double creation:"); a.Create<Interface1>( make_shared<Implementation1>() ); Log("Get unexisting:"); a.Get<Interface3>(); } void TestStrategiesSet() { using namespace std; StrategiesSet printing; printing.Create< PrintStrategy >( make_shared<PrintWithOsStrategy>() ); printing.CreateParam< PrintStrategy, PrintParams >( make_shared<PrintParamsWindows>() ); auto print_strategy_ptr = printing.Get< PrintStrategy >(); auto & print_strategy = *print_strategy_ptr; auto & print_params = *printing.GetParamsSet< PrintStrategy >(); print_strategy( print_params, "Done!" ); } int main() { TestTypedSet(); TestStrategiesSet(); return 0; } 

ملخص


وبالتالي ، قمنا بحل مشكلة مهمة: تركنا في الفصل فقط الواجهة المرتبطة مباشرة بوظيفة الفئة. تم "دفع" الباقي إلى StrategiesSet ، مع تجنب كل من تشوش الصف بالعناصر غير الضرورية ، و "تسمير" بعض خوارزميات الوظيفة المطلوبة للخوارزميات. سيسمح لنا ذلك ليس فقط بكتابة تعليمات برمجية معزولة للغاية ، مع عدم وجود أي تبعيات على التطبيقات والمكتبات ، ولكن أيضًا لتوفير قدر كبير من الوقت.

يمكن العثور على رمز فئات الأدوات والمثال هنا.

محدث. من 11/13/2019
في الواقع ، الكود الموضح هنا هو مجرد مثال مبسط للقراءة. والحقيقة هي أن typeid (). يتم تطبيق Hash_code في المجمعين الحديث ببطء وغير فعال. استخدامه يقتل الكثير من المعنى. علاوة على ذلك ، كما اقترح 0xd34df00d المحترم ، فإن المعيار لا يضمن القدرة على التمييز بين الأنواع من خلال hashcode (في الممارسة العملية ، هذا النهج يعمل مع ذلك). لكن المثال جيد القراءة. قمت بإعادة كتابة TypedSet بدون typeid (). Hash_code () ، علاوة على ذلك ، تم استبدال الخريطة بالصفيف (ولكن مع القدرة على التبديل بسرعة من الخريطة إلى الصفيف والعكس بالعكس عن طريق تغيير رقم واحد في # إذا). اتضح أن الأمر أكثر صعوبة ، ولكنه أكثر إثارة للاهتمام للاستخدام العملي.
في كوليرو
 namespace metatype { struct Counter { size_t GetAndIncrease() { return counter_++; } private: size_t static inline counter_ = 1; }; template <typename Type> struct HashGetterBody { HashGetterBody() : hash_( counter_.GetAndIncrease() ) { } size_t GetHash() { return hash_; } private: Counter counter_; size_t hash_; }; template <typename Type> struct HashGetter { size_t GetHash() {return hasher_.GetHash(); } private: static inline HashGetterBody<Type> hasher_; }; } // namespace metatype template <typename Type> size_t GetTypeHash() { return metatype::HashGetter<Type>().GetHash(); } namespace details { #if 1 //   ,        () class TypedSetStorage { public: static inline const constexpr size_t kMaxTypes = 100; typedef std::array< std::shared_ptr<void>, kMaxTypes > Storage; void Set( size_t hash_index, const std::shared_ptr<void> & value ) { ++size_; assert( hash_index < kMaxTypes ); // too many types data_[hash_index] = value; } std::shared_ptr<void> & Get( size_t hash_index ) { assert( hash_index < kMaxTypes ); return data_[hash_index]; } const std::shared_ptr<void> & Get( size_t hash_index ) const { if ( hash_index >= kMaxTypes ) return empty_ptr_; return data_[hash_index]; } bool Has( size_t hash_index ) const { if ( hash_index >= kMaxTypes ) return 0; return (bool)data_[hash_index]; } size_t GetSize() const { return size_; } private: Storage data_; size_t size_ = 0; static const inline std::shared_ptr<void> empty_ptr_; }; #else //    ,        (std::map) class TypedSetStorage { public: typedef std::map< size_t, std::shared_ptr<void> > Storage; void Set( size_t hash_index, const std::shared_ptr<void> & value ) { data_[hash_index] = value; } std::shared_ptr<void> & Get( size_t hash_index ) { return data_[hash_index]; } const std::shared_ptr<void> & Get( size_t hash_index ) const { return data_.at(hash_index); } bool Has( size_t hash_index ) const { return data_.count(hash_index) > 0; } size_t GetSize() const { return data_.size(); } private: Storage data_; }; #endif } // namespace details /// @brief    .      ,    ///           /// class TypedSet { public: template <class TypedElement> void Create( const std::shared_ptr<TypedElement> & value ); template <class TypedElement> std::shared_ptr<TypedElement> Get() const; template <class TypedElement> bool Has() const; size_t GetSize() const { return storage_.GetSize(); } protected: typedef details::TypedSetStorage Storage; Storage const & storage() const { return storage_; } Storage & get_storage() { return storage_; } private: Storage storage_; }; template <class TypedElement> void TypedSet::Create( const std::shared_ptr<TypedElement> & value ) { size_t hash = GetTypeHash<TypedElement>(); if ( storage().Has( hash ) ) { LogError( "Access Violation" ); return; } std::shared_ptr<void> to_add ( value ); get_storage().Set( hash, to_add ); } template <class TypedElement> bool TypedSet::Has() const { size_t hash = GetTypeHash<TypedElement>(); return storage().Has( hash ); } template <class TypedElement> std::shared_ptr<TypedElement> TypedSet::Get() const { size_t hash = GetTypeHash<TypedElement>(); if ( storage().Has( hash ) ) { std::shared_ptr<void> ret( storage().Get( hash ) ); return std::static_pointer_cast<TypedElement>( ret ); } else { LogError( "Access Violation" ); return std::shared_ptr<TypedElement> (); } } 

يتم تنفيذ الوصول هنا في الوقت الخطي ، ويتم حساب تجزئة النوع قبل إطلاق main () ، والخسائر هي فقط لعمليات التحقق من الصحة ، والتي يمكن التخلص منها إذا رغبت في ذلك.

Source: https://habr.com/ru/post/ar475268/


All Articles