ليس منليث واحد. النهج المعياري في الوحدة

صورة


ستنظر هذه المقالة في نهج معياري لتصميم وتنفيذ المزيد للعبة على محرك الوحدة. تم وصف الإيجابيات والسلبيات والمشكلات الرئيسية التي كان عليك مواجهتها.



يعني مصطلح "نهج معياري" مؤسسة برمجية تستخدم تجميعات نهائية مستقلة وقابلة للتوصيل داخليًا يمكن تطويرها بالتوازي وتغييرها سريعًا وتحقيق سلوك برمجي مختلف اعتمادًا على التكوين.



هيكل الوحدة



من المهم أولاً تحديد ماهية الوحدة ، وما هي هيكلها ، وأجزاء النظام المسؤولة عن ماذا وكيف يجب استخدامها.



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



تجميع البنية التحتية


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

هيكل التجميع يشبه ما يلي. طريقة:



صورة


  • الكيانات - الكيانات المستخدمة داخل الوحدة.
  • الرسائل - نماذج الطلب / الإشارة. يمكنك أن تقرأ عنها لاحقًا.
  • العقود مكان لتخزين الواجهات.


من المهم أن تتذكر أنه يوصى بتقليل استخدام الروابط بين تجميعات البنية التحتية.



بناء مع الميزات


التنفيذ المحدد للميزة. يمكن أن يستخدم داخل نفسه أيًا من الأنماط المعمارية ، ولكن مع التعديل يجب أن يكون النظام معياريًا.

يمكن أن تبدو العمارة الداخلية كما يلي:



صورة


  • الكيانات - الكيانات المستخدمة داخل الوحدة.
  • المُركِّبون - فصول تسجيل عقود DI.
  • الخدمات هي طبقة الأعمال.
  • المديرون - مهمة المدير هي سحب البيانات الضرورية من الخدمات وإنشاء ViewEntity وإرجاع ViewManager.
  • ViewManagers - يتلقى ViewEntity من المدير ، وينشئ طرق العرض المطلوبة ، ويعيد توجيه البيانات المطلوبة.
  • عرض - يعرض البيانات التي تم تمريرها من ViewManager.


تنفيذ نهج معياري



لتنفيذ هذا النهج ، قد تكون هناك حاجة إلى آليتين على الأقل. نحن بحاجة إلى نهج لتقسيم الكود إلى تجميعات وإطار عمل DI. يستخدم هذا المثال آليات ملفات تعريفات التجميع وآليات Zenject.



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



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



إشارات


الإشارة - آلية لإخطار النظام ببعض التغييرات. وأسهل طريقة لتفكيكها هي في الممارسة العملية.



لنفترض أن لدينا وحدتين. Foo و Foo2. يجب أن تستجيب وحدة Foo2 لبعض التغييرات في وحدة Foo. للتخلص من اعتماد الوحدات ، يتم تنفيذ إشارتين. إشارة واحدة داخل وحدة Foo ، والتي ستعلم النظام بتغيير الحالة ، والإشارة الثانية داخل وحدة Foo2. سوف تتفاعل وحدة Foo2 مع هذه الإشارة. سيكون توجيه إشارة OnFooSignal في OnFoo2Signal في وحدة التوجيه.

سيبدو من الناحية التخطيطية كما يلي:



صورة




استفسارات


تسمح الاستعلامات بحل مشاكل اتصال استقبال / إرسال البيانات بواسطة وحدة واحدة من وحدة أخرى (أخرى).



ضع في اعتبارك مثالًا مشابهًا تم تقديمه أعلاه للإشارات.

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



سيبدو هكذا بشكل تخطيطي:



صورة


التواصل بين الوحدات



لتقليل الروابط بين الوحدات ذات الميزات (بما في ذلك الروابط البنية التحتية والبنية التحتية) ، تقرر كتابة غلاف فوق الإشارات التي يوفرها إطار عمل Zenject وإنشاء وحدة تتمثل مهمتها في توجيه إشارات وبيانات خرائط مختلفة.



ملاحظة: في الواقع ، تحتوي هذه الوحدة على روابط لجميع تجميعات البنية التحتية غير الجيدة. لكن يمكن حل هذه المشكلة من خلال IoC.



مثال على تفاعل الوحدة



لنفترض أن هناك وحدتين. تسجيل الدخول الوحدة النمطية و RewardModule. يجب أن تمنح RewardModule مكافأة للمستخدم بعد تسجيل الدخول إلى حساب FB.



namespace RewardModule.src.Infrastructure.Messaging.Signals
{
    public class OnLoginSignal : SignalBase
    {
        public bool IsFirstLogin { get; set; }
    }
}


namespace RewardModule.src.Infrastructure.Messaging.RequestResponse.Produce
{
    public class GainRewardRequest : EventBusRequest<ProduceResponse>
    {
        public bool IsFirstLogin { get; set; }
    }
}


namespace MessagingModule.src.Feature.Proxy
{
    public class LoginModuleProxy
    {
        [Inject]
        private IEventBus eventBus;
        
        public override async void Subscribe()
        {            
            eventBus.Subscribe<OnLoginSignal>((loginSignal) =>
            {
                var request = new GainRewardRequest()
                {
                    IsFirstLogin = loginSignal.IsFirstLogin;
                }

                var result = await eventBus.FireRequestAsync<GainRewardRequest, GainRewardResponse>(request);
                var analyticsEvent = new OnAnalyticsShouldBeTracked()
                {
                   AnalyticsPayload = new Dictionary<string, string>
                    {
                      {
                        "IsFirstLogin", "false"
                      },
                    },
                  };
                eventBus.Fire<OnAnalyticsShouldBeTrackedSignal>(analyticsEvent);
            });


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



استبدال التطبيقات



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



يتم تحقيق ذلك عن طريق التحقق من إشارات توفر الوحدة النمطية أثناء ربط الوحدات في Zenject (في الواقع ، في حاوية) ، وبناءً على ذلك ، تكون الوحدة إما مرتبطة بحاوية أو لا. من أجل تحقيق تغيير في السلوك أثناء جلسة لعبة (دعنا نقول أنك بحاجة إلى تغيير الآليات أثناء جلسة اللعبة. هناك وحدة Solitaire ووحدة Klondike. وبالنسبة لـ 50 بالمائة من المستخدمين يجب أن تعمل وحدة kerchief) ، تم تطوير آلية والتي عند التبديل من مشهد إلى آخر تنظيف حاوية وحدة معينة وربط تبعيات جديدة.



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



يوجد أدناه الرمز الكاذب للفئة الأساسية ، والتي يُطلب من أحفادها تسجيل شيء ما في الحاوية.



    public abstract class GlobalInstallerBase<TGlobalInstaller, TModuleInstaller> : MonoInstaller<TGlobalInstaller>
        where TGlobalInstaller : MonoInstaller<TGlobalInstaller>
        where TModuleInstaller : Installer
    {
        protected abstract string SubContainerName { get; }
        
        protected abstract bool IsFeatureEnabled { get; }
        
        public override void InstallBindings()
        {
            if (!IsFeatureEnabled)
            {
                return;
            }
            
            var subcontainer = Container.CreateSubContainer();
            subcontainer.Install<TModuleInstaller>();
            
            Container.Bind<DiContainer>()
                .WithId(SubContainerName)
                .FromInstance(subcontainer)
                .AsCached();
        }
        
        protected virtual void SubContainerCleaner(DiContainer subContainer)
        {
            subContainer.UnbindAll();
        }

        protected virtual DiContainer SubContainerInstanceGetter(InjectContext containerContext)
        {
            return containerContext.Container.ResolveId<DiContainer>(SubContainerName);
        }
    }


مثال على وحدة بدائية



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



لنفترض أنك بحاجة إلى تنفيذ وحدة تقيد حركة الكاميرا بحيث لا يتمكن المستخدم من نقلها إلى ما وراء "حدود" الشاشة.



ستحتوي الوحدة على مجموعة بنية أساسية مع إشارة تُعلم أن الكاميرا حاولت الخروج من الشاشة إلى النظام.



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



صورة


  • BorderConfig هو كيان يصف حدود الشاشة.
  • BorderViewEntity هو كيان يتم تمريره إلى ViewManager و View.
  • BoundingBoxManager - يحصل على BorderConfig من الخادم ، ويخلق BorderViewEntity.
  • BoundingBoxViewManager — MonoBehaviour'a. , .
  • BoundingBoxView — , «» .




  • . , , .
  • .
  • EventHell, , .
  • — , . , , — .
  • .
  • .
  • - , . , MVC, — ECS.
  • , .
  • , .



All Articles