[c#] التصميم - أين يجب تسجيل الأشياء عند استخدام وندسور



Answers

على الرغم من أن إجابة مارك تعتبر رائعة بالنسبة لسيناريوهات الويب ، إلا أن الخلل الأساسي في تطبيقه على جميع البنى (أي العميل الغني - أي: WPF ، WinForms ، iOS ، إلخ) هو افتراض أن جميع المكونات اللازمة لعملية ما يمكن / يجب أن يتم إنشاؤها ذات مرة.

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

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

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

لماذا من السوء أن كود المستخدم الخاص بي لديه حق الوصول إلى حاوية DI؟

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

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

السيناريو أ:

// not sure whether I'm responsible for creating the thing or not
DependencyContainer.GimmeA<Thing>()

السيناريو ب:

// responsibility is clear that this component is responsible for creation

Factory.CreateMeA<Thing>()
// or simply
new Thing()

السيناريو C:

// responsibility is clear that this component is not responsible for creation, but rather only consumption

ServiceLocator.GetMeTheExisting<Thing>()
// or simply
ServiceLocator.Thing

كما ترى ، فإن شركة DI تجعل من غير الواضح من المسؤول عن إدارة عمر المكون الفرعي.

ملاحظة: من الناحية الفنية فإن العديد من أطر عمل DI لديها بعض الطرق لإنشاء المكونات بشكل كسل (انظر: كيف لا تقوم بحقن التبعية - الحاوية الساكنة أو المفردة ) التي هي أفضل بكثير من تمرير الحاوية ، ولكنك لا تزال تدفع تكاليف تحوِّل شفرتك لتمرير مهام الإنشاء في كل مكان ، وتفتقر إلى الدعم من المستوى الأول لتمرير معلمات مُنشئ صالحة أثناء الإنشاء ، وفي نهاية اليوم لا تزال تستخدم آلية غير مباشرة دون داعٍ في الأماكن التي تكون الفائدة الوحيدة فيها هي تحقيق قابلية الاختبار ، والتي يمكن تحقيقها بطرق أفضل وأسهل (انظر أدناه).

ماذا يعني كل هذا؟

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

توصيلي لـ RICH-CLIENT APPS هو استخدام الحد الأدنى من الآلية التي تلبي متطلبات كل مكون في متناول اليد. 80 ٪ من الوقت يجب أن يكون هذا فوريًا. يمكن استخدام محددات الخدمة لإيواء مكونات طبقة الأعمال الرئيسية الخاصة بك (مثل: خدمات التطبيقات التي عادة ما تكون مفردة في الطبيعة) ، وبالطبع المصانع وحتى نمط Singleton يكون لها مكانها أيضًا. ليس هناك ما تقوله أنه لا يمكنك استخدام إطار عمل DI مخفي خلف محدد موقع الخدمة الخاص بك لإنشاء تبعيات طبقة العمل الخاصة بك وكل شيء يعتمد عليه في دفعة واحدة - إذا كان ذلك يجعل حياتك أسهل في تلك الطبقة ، وهذه الطبقة غير موجودة. ر تحميل تحميل كسول الطبقات الغنية التي عرض العميل القيام به . فقط تأكد من حماية رمز المستخدم الخاص بك من الوصول إلى تلك الحاوية بحيث يمكنك منع الفوضى التي تمر حاوية DI حولها يمكن إنشاء.

ماذا عن الاختبار؟

يمكن تحقيق قابلية الاختبار بدون إطار عمل DI. أوصي باستخدام إطار اعتراض مثل UnitBox (مجاني) أو TypeMock (الثمن). تمنحك هذه الأطر الأدوات التي تحتاجها للالتفاف على المشكلة في متناول اليد (كيف يمكنك الاستهزاء بالمكالمات الاستدعائية والمكالمات الثابتة في C #) ولا تتطلب منك تغيير العمارة بأكملها للالتفاف عليها (والتي للأسف حيث يوجد الاتجاه ذهب في. NET / جافا العالم). من الحكمة إيجاد حل للمشكلة في متناول اليد واستخدام آليات وأنماط اللغة الطبيعية المثلى للمكون الأساسي ثم محاولة وضع كل ربط مربع في فتحة DI المستديرة. بمجرد البدء في استخدام هذه الآليات الأبسط والأكثر تحديدًا ، ستلاحظ أن هناك حاجة ضئيلة جدًا إلى DI في مصدر التعليمات البرمجية الخاص بك إن وجدت.

ملاحظة: لأبنية MVVM

في معماريات MVVM الأساسية ، تأخذ نماذج المشاهدة على عاتق المراقبين بشكل فعال ، لذلك يجب أن تأخذ في الاعتبار جميع الأغراض عبارة "المتحكم" أعلاه لتطبيق "نموذج العرض". MVVM الأساسي يعمل بشكل جيد للتطبيقات الصغيرة ولكن مع تعقيد التطبيق ينمو قد ترغب في استخدام نهج MVCVM. تصبح نماذج العرض غالبًا DTS غبية لتسهيل ربط البيانات إلى العرض بينما يتم تفاعل التفاعل مع طبقة الأعمال وبين مجموعات من نماذج العرض التي تمثل الشاشات / الشاشات الفرعية في مكونات تحكم وحدة تحكم / تحكم صريحة. في أي من العمارة توجد مسؤولية وحدات التحكم وتظهر نفس الخصائص التي تمت مناقشتها أعلاه.

Question

سيكون لدي المكونات التالية في طلبي

  • الدخول الى البيانات
  • DataAccess.Test
  • اعمال
  • Business.Test
  • الوضعية

كنت آمل أن استخدم "وندسور كاسل" كجامعة "آي أو سي" لضم الطبقات معاً ، لكنني غير متأكد من تصميم اللصق.

سؤالي هو من يجب أن يكون مسؤولاً عن تسجيل الأشياء في وندسور؟ لدي فكرتين؛

  1. يمكن لكل طبقة تسجيل الأشياء الخاصة بها. لاختبار BL ، يمكن لمقاعد الاختبار تسجيل فصول وهمية لـ DAL.
  2. يمكن لكل طبقة تسجيل كائن تبعياتها ، على سبيل المثال تسجل طبقة الأعمال مكونات طبقة الوصول إلى البيانات. لاختبار BL ، يجب أن يقوم bench test بتفريغ كائن DAL "الحقيقي" وتسجيل الكائنات الوهمية.
  3. يسجل التطبيق (أو التطبيق التجريبي) جميع كائنات التبعيات.

هل يمكن أن يساعدني شخص ما في بعض الأفكار والإيجابيات / السلبيات مع المسارات المختلفة؟ روابط إلى مشاريع سبيل المثال باستخدام قلعة وندسور بهذه الطريقة ستكون مفيدة للغاية.




Links