ها ، يا له من تمويه رشيق من محدد الخدمة تحت DI. قد يبدو وكأنه DI! :-)
هذا هو أول تعليق على منشوري السابق ، Dependency Injection و JavaScript و ES6 Modules . بفضل زميل symbix 'u لهذا التعليق ، المعارف التقليدية. كان هو الذي أصبح سبب الغمر في التفاصيل الدقيقة للفرق بين واحد والآخر. تحت قص جوابي على السؤال في العنوان.

(KDPV لا معنى له وتهدف في المقام الأول إلى التعريف البصري لهذا المنشور ، من بين أمور أخرى)
بادئ ذي بدء ، سنتعامل مع التعاريف ( بعض أمثلة الأكواد البرمجية التي سأقدمها في PHP ، وبعضها في JS ، لأن هاتين اللغتين موجودتان حاليًا في حقائبي النشيطة ، وبعضها في أي لغة أخرى ، لأنني سرقت هذه الأمثلة من الإنترنت ).
حقن التبعية
DI باختصار - بدلاً من وحدة الطلب / الاستيراد ، يمكنك حقن التبعية من خلال معلمة المنشئ (أو أداة ضبط الخاصية). وهذا هو ، وراء هذه الكلمة الكبيرة هي "التبعيات فئة تمرير بسيطة من خلال المعلمات منشئ".
هذا التعليق من قبل زميل risedphantom بدقة تماما ينقل جوهر هذه الظاهرة. لتسهيل فهم المطور للشفرة ، يتم توضيح جميع التبعيات بشكل صريح - في شكل معلمات مُنشئ (عادة ، ولكن ليس دائمًا):
class MainClass { public function __construct($dep1, $dep2, $dep3) {} }
هذا كل شيء. DI - هو عن ذلك. يتم توفير التبعيات التي نحتاجها من قبل شخص هناك. وأين يأخذهم "شخص ما هناك" - شركة DI لا تبالي.
عند تحليل الكود ، من المهم أن نفهم ما هو "داخلي" بالضبط له (وما يمكننا تغييره بأمان) ، وما يأتي / يتجاوز مسؤولية هذه الكود. هذا هو ما يثير DI. لماذا يتم تمرير التبعيات في الغالب من خلال المنشئ ، وليس في المستوطنين؟ لأنه أكثر ملاءمة للمطور - يرى على الفور جميع تبعيات الكود الذي تم تحليله في مكان واحد. لقد اعتدنا على التفكير في أن DI شيء على مستوى الفصل ، ولكن معلمات الوظيفة / الطريقة هي DI أيضًا:
function f(dep1, dep2, dep3) {}
المنشئ هو مجرد طريقة خاصة بين الآخرين.
محدد موقع الخدمة
خدمة محدد هو معروف المضادة للنمط. ماذا يفعل؟ يوفر الوصول إلى كائن واحد لكائنات أخرى. في ما يلي واجهة نموذجية لموقع محدد:
interface ServiceLocator { public function addInstance(string $class, Service $service); public function addClass(string $class, array $params); public function has(string $interface): bool; public function get(string $class): Service; }
محدد الموقع هو حاوية يمكنك من خلالها وضع كائنات جاهزة (أو تعيين القواعد الخاصة بإنشائها) ثم الوصول إلى هذه الكائنات. بالنسبة إلى محدد الموقع ، على العموم ، لا يهم كيف تظهر الكائنات في ذلك - يتم إنشاؤها بشكل صريح من الخارج وتوضع في الحاوية أو يتم إنشاؤها بواسطة الحاوية نفسها وفقًا للقواعد المحددة.
محدد موقع الخدمة مسؤول عن تخزين الكائنات وتوفير الوصول إليها. هذا كل شيء.
حاوية حقن التبعية
ما هي حاوية DI؟ وفقًا للرواية المستقلة لمحتويات " ممارسات وممارسات وأنماط حقن التبعية ": "مكتبة البرامج التي توفر وظائف DI وتسمح بأتمتة العديد من المهام المتعلقة بتكوين الكائنات ، والاعتراض ، وإدارة مدى الحياة. تُعرف حاويات DI أيضًا باسم Inversion of حاويات التحكم (IoC). (§ 3.2.2) "
أي أن حاوية DI مسؤولة بشكل أساسي عن إنشاء كائنات ، ولكن في الثانية فقط - عن تخزينها. قد لا تقوم حاوية DI بتخزين أي كائنات تم إنشاؤها على الإطلاق ، إذا كان التطبيق نفسه لا يحتاج إلى كائنات مشتركة في التطبيق بأكمله (مثل التكوين أو المسجل).
هنا ، على سبيل المثال ، هي واجهة حاوية Symfony DI:
interface ContainerInterface extends PsrContainerInterface { public function set(string $id, ?object $service); public function get($id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE); public function has($id); public function initialized(string $id); public function getParameter(string $name); public function hasParameter(string $name); public function setParameter(string $name, $value); }
إذا لزم الأمر ، يمكنك إقناع نفسك بسهولة شديدة أن واجهة الحاوية DI تشبه إلى حد بعيد واجهة خدمات تحديد المواقع - وهي نفس الشيء get
عليها set
/ add
.
لماذا هو محدد الخدمة سيئة؟
لكن لا شيء. ليس القالب نفسه سيئًا ، ولكن الطريقة المستخدمة في بعض الأحيان. على وجه الخصوص ، هناك رأي مفاده أن " محدد موقع الخدمة ينتهك التغليف بلغات مطبوعة بشكل ثابت ، لأن هذا النموذج لا يعبر بوضوح عن الشروط المسبقة ." ومثال انتهاك:
public class OrderProcessor : IOrderProcessor { public void Process(Order order) { var validator = Locator.Resolve<IOrderValidator>(); if (validator.Validate(order)) { var shipper = Locator.Resolve<IOrderShipper>(); shipper.Ship(order); } } }
مثل ، هذا سيء للغاية ، لكنه جيد - مثل هذا:
public class OrderProcessor : IOrderProcessor { public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) public void Process(Order order) }
مرحبًا ، لقد IOrderValidator
بين قوسين من لحظة إنشاء الكائنات نفسها مع IOrderShipper
و IOrderShipper
! من الممكن في هذا التطبيق في مكان آخر وجود شيء مثل هذا الرمز:
var validator = Locator.Resolve<IOrderValidator>(); var shipper = Locator.Resolve<IOrderShipper>(); var processor = new OrderProcessor(validator, shipper); processor.Process(order);
تكوين الجذر
الحديث عن Dependency Injection ، لا يسعنا إلا أن نتوصل إلى مفهوم مثل Composition Root (يشار إليه فيما يلي باسم "نقطة التجميع"). هذا هو نفس " الشخص " الذي يتم تفويض مسؤولياته لإنشاء التبعيات وتنفيذها.
عند نقطة التجميع ، يتم تحديد جميع التبعيات بشكل صريح ، ويتم إنشاء الكائنات المقابلة وتنفيذها في انتظارها. هنا الاختلافات بين "محدد موقع الخدمات" والحاوية DI الحد الأدنى. كل من هذا وآخر يسمح بإنشاء كائنات جديدة ، لتخزين الكائنات التي تم إنشاؤها ، لاستخراج الكائنات المخزنة. وأود أن أؤكد حتى أنه في هذا المكان لا توجد اختلافات جوهرية بينهما.
الفرق الرئيسي
وأين الاختلافات بين الحاوية DI وحاوية محدد الخدمات هي الأكثر وضوحًا؟ في أي مكان آخر ، ولا سيما مع وصول ثابت إلى الحاوية:
$obj = Container::getInstance()->get($objId);
ها هو ذا. في هذا النموذج ، في أي مكان (ما لم تكن نقطة تجميع ، ولكن استخدام الوصول الثابت يكون مشكوكًا فيه للغاية) ، تصبح أي حاوية معادية لنمط تسمى محدد الخدمة.
أجاب أحد زملاء VolCh بإيجاز وبإيجاز على سؤالي :
وكيف تعتقد أن DI الصحيح يختلف عن "محدد موقع الخدمة" المتنكر كـ DI؟
مثل هذا:
وصول الحاويات
في الواقع ، هذا المنشور بأكمله هو مجرد نشر أكثر تفصيلا لإجابته هنا.
الاستخدام القانوني للحاوية
وبالتالي ، ما إذا كانت الحاوية عبارة عن حاوية DI أو حاوية خاصة بـ "محدد موقع الخدمات" تعتمد إلى حد كبير على مكان وكيفية استخدامها. كما قلت أعلاه ، عند نقطة التجميع ، يختفي الفرق بين أنواع الحاويات. ولكن ماذا نعبر إذا كانت الحاوية نفسها تبعية لأي فئة؟
class Foo { public function __construct(Container $container){} }
مرة أخرى ، كل هذا يتوقف على كيفية استخدامنا للحاوية. فيما يلي مثالان ، أحدهما يتطابق تمامًا مع الحاوية الخاصة بـ "محدد موقع الخدمات" وهو نمط مضاد ، في حين أن الآخر لديه الحق في الحياة في ظل ظروف معينة.
class Foo { private $dep; public function __construct(Container $container) { $this->dep = $container->get('depId'); } }
class Foo { private $container; public function __construct(Container $container) { $this->container = $container; } public function initModules($modules) { foreach ($modules as $initClassId) { $init= $this->container->get($initClassId); $init->exec(); } } }
ملخص
في الواقع ، كل شيء أكثر تعقيدًا إلى حد ما ، يجب أن يكون لحاوية DI وظائف أكثر من محدد الخدمات ، ولكن جوهر هذا المنشور هو أنه حتى حاوية DI الأكثر تطوراً يمكن استخدامها بسهولة كمحدد للخدمات ، ولكن لاستخدام محدد الخدمات كحاوية DI - تحتاج إلى محاولة.
PS
لا يسعني إلا تقديم روابط إلى مدونة Sergei Teplyakov الخاصة بالبرمجة - تمت تغطية هذه المشكلات جيدًا هناك.