سؤال

إذا فهمت بشكل صحيح ، فإن الآلية النموذجية لحقن التبعية هي حقن إما من خلال مُنشئ فئة أو من خلال خاصية عامة (عضو) من الفصل.

هذا يعرض التبعية التي يتم حقنها وينتهك مبدأ OOP للتغليف.

هل أنا محق في تحديد هذا المقايضة؟ كيف تتعامل مع هذه القضية؟

يرجى أيضًا رؤية إجابتي على سؤالي أدناه.

هل كانت مفيدة؟

المحلول

هناك طريقة أخرى للنظر في هذه المسألة قد تجدها مثيرة للاهتمام.

عندما نستخدم حقن IOC/التبعية ، فإننا لا نستخدم مفاهيم OOP. من المسلم به أننا نستخدم لغة OO كـ "المضيف" ، لكن الأفكار وراء IOC تأتي من هندسة البرمجيات الموجهة للمكون ، وليس OO.

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

من خلال تطبيق تقنيات مماثلة في برامج OO الخاصة بنا عبر IOC ، فإننا نهدف إلى جعل البرامج أسهل في تكوينها وصيانتها. يعد نشر التبعيات (كمعلمات مُنشئ أو أي شيء آخر) جزءًا رئيسيًا من هذا. لا ينطبق التغليف حقًا ، كما هو الحال في العالم الموجهة للمكون/الخدمة ، لا يوجد "نوع تنفيذ" للتسرب من.

لسوء الحظ ، فإن لغاتنا لا تفصل حاليًا من المفاهيم ذات الحبيبات الدقيقة الموجهة نحو الكائنات من المفاهيم الموجهة نحو الحبيبات الخشنة ، لذلك هذا تمييز يجب عليك الاحتفاظ به في عقلك فقط :)

نصائح أخرى

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

كلاسيكيًا ، يتم التعامل مع هذه الحالة الأخيرة كما تقول ، من خلال حجج المنشئ أو أساليب Setter. ومع ذلك ، هذا ليس صحيحًا بالضرورة - أعرف أن أحدث إصدارات من إطار الربيع DI في Java ، على سبيل المثال ، تتيح لك التعليق على الحقول الخاصة (على سبيل المثال مع @Autowired) وسيتم ضبط التبعية عبر التفكير دون الحاجة إلى فضح التبعية من خلال أي من الفئات من الأساليب/المنشئات العامة. قد يكون هذا هو نوع الحل الذي كنت تبحث عنه.

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

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

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

هذا يعرض التبعية التي يتم حقنها وينتهك مبدأ OOP للتغليف.

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

إذن ، ما الذي ينتهك التغليف؟

ميراث يفعل.

"لأن الميراث يعرض فئة فرعية لتفاصيل تنفيذ والديه ، غالبًا ما يقال إن" الميراث يكسر التغليف ". (عصابة من أربعة 1995: 19)

الجانب برمجة يفعل. على سبيل المثال ، تقوم بتسجيل رد الاتصال OnMethodCall () ويمنحك فرصة رائعة لحقن التعليمات البرمجية لتقييم الطريقة العادية ، مع إضافة آثار جانبية غريبة وما إلى ذلك.

إعلان صديق في C ++ يفعل.

تمديد الطبقة في روبي يفعل. ما عليك سوى إعادة تعريف طريقة سلسلة في مكان ما بعد تعريف فئة السلسلة بالكامل.

حسنًا ، الكثير من الأشياء يفعل.

التغليف مبدأ جيد وهام. ولكن ليس الوحيد.

switch (principle)
{
      case encapsulation:
           if (there_is_a_reason)
      break!
}

نعم ، ينتهك DI التغليف (المعروف أيضًا باسم "إخفاء المعلومات").

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

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

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

الآن ، أفترض أنك تقارن مع الحالة التي ينشئ فيها الكائن الذي تم إنشاؤه كائناته المغطاة ، على الأرجح في مُنشئه. إن فهمي لـ DP هو أننا نريد أن نأخذ هذه المسؤولية بعيدًا عن الكائن وإعطائها لشخص آخر. تحقيقًا لهذه الغاية ، فإن "شخص آخر" ، وهو حاوية موانئ دبي في هذه الحالة ، لديها معرفة حميمة "تنتهك" التغليف ؛ والفائدة هي أنها تسحب تلك المعرفة من الكائن ، iteself. شخص ما يجب أن يحصل عليه. بقية التطبيق الخاص بك لا.

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

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

هذا مشابه للإجابة المتقدمة ، لكنني أريد أن أفكر بصوت عالٍ - ربما يرى الآخرون الأمور بهذه الطريقة أيضًا.

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

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

مثال نظري:

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

قل أن هذا التنفيذ الخاص لـ FOO قد يحتاج أيضًا إلى ملف iwidget للقيام بعملها. حقن مُنشئ هذه التبعية سيجعلنا ننشئ مُنشئًا مثل FOO (حجم int ، عنصر واجهة مستخدم IWIDGET)

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

المعلمة الحجم ليست تبعية - إنها بسيطة قيمة تهيئة للربع. IOC هي dandy لتبعيات خارجية (مثل القطعة) ولكن ليس لتهيئة الحالة الداخلية.

والأسوأ من ذلك ، ماذا لو كانت القطعة ضرورية فقط ل 2 من الطرق الأربعة في هذه الفئة ؛ قد أتكبد النفقات العامة لاستئصال العنمل على الرغم من أنه قد لا يتم استخدامه!

كيف تساوم/التوفيق بين هذا؟

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

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

كيف يمكنني تحقيق التهيئة المضمونة في عالم DI هذا ، عندما تكون التهيئة أكثر من تبعيات خارجية فقط؟

كما أشار جيف ستيرنال في تعليق على السؤال ، فإن الإجابة تعتمد تمامًا على كيفية تعريفك التغليف.

يبدو أن هناك معسكرين رئيسيين لما يعنيه التغليف:

  1. كل ما يتعلق بالكائن هو طريقة على كائن. لذلك ، أ File قد يكون للكائن طرق ل Save, Print, Display, ModifyText, ، إلخ.
  2. الكائن هو عالمه الصغير ، ولا يعتمد على السلوك الخارجي.

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

لذلك ، فإن حقن التبعية سوف يكسر التغليف إذا كنت تستخدم التعريف الأول. لكن بصراحة لا أعرف ما إذا كنت أحب التعريف الأول - من الواضح أنه لا يتوسع (إذا حدث ذلك ، فستكون MS Word فئة واحدة كبيرة).

من ناحية أخرى ، حقن التبعية تقريبًا إلزامي إذا كنت تستخدم التعريف الثاني للتغليف.

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

ولكن إذا كنت تقوم ببناء سيارة تريد نوعًا معينًا من كائن المحرك ، فعليك التبعية الخارجية. يمكنك إما تثبيت هذا المحرك - على سبيل المثال GmoverheadCamEngine () - داخليًا داخل مُنشئ كائن السيارة ، مع الحفاظ على التغليف ولكنه إنشاء اقتران أكثر غدرًا إلى فئة خرسانية GmoverCamEngine ، أو يمكنك ضخه ، مما يسمح لكائن سيارتك بالعمل من الناحية اللذانية (وأكثر قوة) على سبيل المثال ، على سبيل المثال ، واجهة iengine دون التبعية الملموسة. سواء كنت تستخدم حاوية IOC أو DI بسيطة لتحقيق هذه ليست هي النقطة - النقطة هي أن لديك سيارة يمكنها استخدام العديد من المحركات دون أن تقترن بأي منها ، مما يجعل قاعدة الشفرة أكثر مرونة و أقل عرضة للآثار الجانبية.

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

أنا أؤمن بالبساطة. لا يؤدي تطبيق حقن IOC/reparecy في فئات المجال إلى إجراء أي تحسن باستثناء جعل الكود أكثر صعوبة في الإدارة من خلال وجود ملفات XML خارجية تصف العلاقة. العديد من التقنيات مثل EJB 1.0/2.0 و STRUTS 1.1 تعكس مرة أخرى عن طريق تقليل الأشياء التي وضعها في XML ومحاولة وضعها في رمز ككود وما إلى ذلك ، لذا فإن تطبيق IOC لجميع الفئات التي تقوم بتطويرها ستجعل الكود غير الوسيط.

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

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

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

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

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

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

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

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

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

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

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

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

هذا أنا أقود كومبي إلى الشاطئ.

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

يمكنني قيادة سيارتي الجديدة إلى الشاطئ أيضًا. التغليف لم يكسر.

إذا تغيرت تفاصيل التنفيذ ، فإن حاوية DI (أو المصنع) تحتاج إلى تغيير. لم تكن تحاول أبدًا إخفاء تفاصيل التنفيذ من المصنع في المقام الأول.

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

يجب أن يتم تسليم قضية سبب انتهاك DI للتغليف عندما يتم تسليم المكون الذي تعمل عليه إلى طرف "خارجي" (فكر في كتابة مكتبة للعميل).

عندما يتطلب المكون حقن المكافآت الفرعية عن طريق المُنشئ (أو العقارات العامة) ، فلا يوجد ضمان ل

"منع المستخدمين من تعيين البيانات الداخلية للمكون إلى حالة غير صالحة أو غير متسقة".

في الوقت نفسه لا يمكن القول ذلك

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

كلا الاقتباسات من ويكيبيديا.

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

عموما أنا كل شيء من أجل دي. في هذا مثال (متطرف) بالذات ، فإنه يصيبني بأنه خطير.

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

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

ملاحظة. بتوفير حقن التبعية أنت لا بالضرورة فترة راحة التغليف. مثال:

obj.inject_dependency(  factory.get_instance_of_unknown_class(x)  );

لا يعرف رمز العميل تفاصيل التنفيذ.

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

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

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

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

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

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

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

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

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

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

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

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

يجادل محامي DI بأن الكائن يجب ألا يستخدم المشغل الجديد أبدًا. لا ينتهك نهج "purist" فقط التغليف ولكن أيضًا مبدأ استبدال Liskov لمن يقوم بإنشاء الكائن.

أوافق على أنه تم نقله إلى أقصى الحدود ، يمكن أن ينتهك التغليف. عادة ما يعرض DI التبعيات التي لم تكن مغلفة حقًا. إليك مثال مبسط مستعار من Miško Hevery's المفردون كذابون مرضيون:

تبدأ باختبار بطاقة الائتمان وكتابة اختبار وحدة بسيط.

@Test
public void creditCard_Charge()
{
    CreditCard c = new CreditCard("1234 5678 9012 3456", 5, 2008);
    c.charge(100);
}

في الشهر المقبل تحصل على فاتورة بقيمة 100 دولار. لماذا حصلت على رسوم؟ أثر اختبار الوحدة على قاعدة بيانات الإنتاج. داخليًا ، مكالمات الائتمان Database.getInstance(). إعادة إنشاء بطاقة الائتمان بحيث تستغرق ملف DatabaseInterface في مُنشئها تكشف حقيقة أن هناك تبعية. لكنني أزعم أن التبعية لم يتم تغليفها أبدًا لأن فئة Creditcard تسبب آثارًا جانبية مرئية خارجيًا. إذا كنت ترغب في اختبار بطاقة الائتمان دون إعادة إنشاء ، يمكنك بالتأكيد ملاحظة التبعية.

@Before
public void setUp()
{
    Database.setInstance(new MockDatabase());
}

@After
public void tearDown()
{
    Database.resetInstance();
}

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

أعتقد أنها مسألة نطاق. عندما تحدد التغليف (لا تسمح لمعرفة كيف) ، يجب عليك تحديد ما هي الوظيفة المغلفة.

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

  2. وظيفة تكوين: إذا كنت ترغب في توفير وظيفة جاهزة للاستخدام ، فأنت لا تقدم فئة Quicksort ، ولكن مثيل لفئة Quicksort التي تم تكوينها بمقارنة. في هذه الحالة ، يجب إخفاء الرمز المسؤول عن إنشاء وتكوينه من رمز المستخدم. وهذا هو التغليف.

عندما تكون فصول البرمجة ، فهي تنفيذ مسؤوليات واحدة في الفصول ، فأنت تستخدم الخيار 1.

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

هذا هو تنفيذ المثيل المكون:

<bean id="clientSorter" class="QuickSort">
   <property name="comparator">
      <bean class="ClientComparator"/>
   </property>
</bean>

هذه هي الطريقة التي يستخدم بها رمز العميل الآخر:

<bean id="clientService" class"...">
   <property name="sorter" ref="clientSorter"/>
</bean>

يتم تغليفه لأنه إذا قمت بتغيير التنفيذ (يمكنك التغيير clientSorter تعريف الفول) لا يكسر استخدام العميل. ربما ، أثناء استخدام ملفات XML مع كل مكتوبة معًا ، ترى جميع التفاصيل. لكن صدقوني ، رمز العميل (ClientService) لا أعرف لا شيء عن سفره.

ربما تجدر الإشارة إلى ذلك Encapsulation يعتمد منظور إلى حد ما.

public class A { 
    private B b;

    public A() {
        this.b = new B();
    }
}


public class A { 
    private B b;

    public A(B b) {
        this.b = b;
    }
}

من منظور شخص يعمل على A الفصل ، في المثال الثاني A يعرف الكثير عن طبيعة this.b

بينما بدون دي

new A()

ضد

new A(new B())

الشخص الذي يبحث في هذا الرمز يعرف المزيد عن طبيعة A في المثال الثاني.

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top