انعكاس التبعية. نظرة نقدية على مبدأ انعكاس التبعية. العمارة التقليدية الطبقات

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

1). لا ينبغي أن تعتمد الوحدات في المستويات الأعلى على الوحدات في المستويات الأدنى. يجب أن تعتمد الوحدات في كلا المستويين على التجريدات.

2). لا ينبغي أن تعتمد التجريدات على التفاصيل. التفاصيل يجب أن تعتمد على التجريدات.

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

فئة عامة Bard (جيتار خاص؛ Bard عام (جيتار جيتار) (this.guitar = جيتار؛) تشغيل باطل عام () (guitar.play();))

بارد الطبقة العامة (

جيتار خاص؛

الشاعر العام (جيتار الجيتار)

هذا. جيتار = جيتار؛

اللعب الفراغي العام ()

غيتار يلعب()؛

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

ملف Instrument.java:

أداة الواجهة العامة (تشغيل الفراغ () ؛)

أداة الواجهة العامة (

اللعب باطل () ؛

ملف Guitar.java:

فئة الجيتار تنفذ الآلة ( @Override public void play() ( System.out.println("اعزف الجيتار!"); ) )

فئة الجيتار ينفذ الصك (

@تجاوز

اللعب الفراغي العام ()

نظام. خارج . println("اعزف الجيتار!");

ملف عود جافا:

الطبقة العامة العود تنفذ الأداة ( @Override public void play() ( System.out.println("Play Lute!"); ) )

العود من الدرجة العامة ينفذ الصك (

@تجاوز

اللعب الفراغي العام ()

نظام. خارج . println("العب العود!");

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

فئة عامة Bard ( أداة خاصة؛ Bard () عامة () تشغيل باطل عام () ( Instrument.play ()؛) مجموعة باطلة عامة (أداة الصك) ( this.instrument = Instrument؛ ) )

بارد الطبقة العامة (

أداة خاصة ;

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

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

مقالات أخرى من هذه السلسلة:

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

أدناه، تقوم الفئة A، داخل طريقة تسمى someMethod()، بإنشاء كائن من الفئة B بشكل صريح وتستدعي طريقتها someMethodOfB()

الفئة العامة A ( باطلة someMethod() ( B b = new B(); b.someMethodOfB(); ) )

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

الفئة العامة B ( باطلة someMethodOfB() ( System.out.println("Hello World"); ) )

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

ما هو الخطأ في التبعيات المباشرة؟ تعتبر التبعيات المباشرة سيئة لأن الفئة التي تنشئ فئة أخرى بشكل مستقل داخل نفسها تكون مرتبطة "بإحكام" بهذه الفئة. أولئك. إذا كان مكتوبًا صراحةً أن B = new B(); ، فستعمل الفئة A دائمًا مع الفئة B وليس مع فئة أخرى. أو إذا كانت System.out.println("..."); ثم سيتم إخراج الفصل دائمًا إلى System.out وليس في أي مكان آخر.

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

ويمكن عرض العلاقة المباشرة بيانيا على النحو التالي:

أولئك. عندما تقوم بإنشاء فئة A في التعليمات البرمجية الخاصة بك: A a = new A(); في الواقع، لم يتم إنشاء فئة A واحدة فقط، ولكن تم إنشاء تسلسل هرمي كامل للفئات التابعة، مثال على ذلك في الصورة أدناه. هذا التسلسل الهرمي "جامد": بدون تغيير الكود المصدري للفئات الفردية، لا يمكنك استبدال أي من الفئات في التسلسل الهرمي. ولذلك، فإن الفئة (أ) في مثل هذا التنفيذ غير قابلة للتكيف بشكل جيد مع البيئة المتغيرة. على الأرجح، لا يمكن استخدامه في أي كود آخر غير الكود المحدد الذي كتبته من أجله.

لفصل الفئة A عن تبعيات محددة، استخدم حقن التبعية. ما هو حقن التبعية؟ بدلاً من إنشاء الفئة المطلوبة بشكل صريح في التعليمات البرمجية، يتم تمرير التبعيات إلى الفئة A من خلال المنشئ:

فئة عامة A (خاصة نهائية B b; عامة A(B b) ( this.b = b; ) عامة باطلة someMethod() ( b.someMethodOfB(); ) )

الذي - التي. تتلقى الفئة A الآن تبعيتها من خلال المُنشئ. الآن، لإنشاء الفئة أ، ستحتاج أولاً إلى إنشاء الفئة التابعة لها. في في هذه الحالةهذا هو ب:

ب ب = جديد ب()؛ أ أ = جديد أ(ب)؛ a.someMethod();

إذا تم تكرار نفس الإجراء لجميع الفصول، أي. قم بتمرير مثيل للفئة D إلى مُنشئ الفئة B، وتبعياتها E وF إلى مُنشئ الفئة D، وما إلى ذلك، ثم ستحصل على رمز يتم فيه إنشاء جميع التبعيات بترتيب عكسي:

G g = جديد G(); ح ح = جديد ح(); و و = جديد (ز، ح)؛ E e = جديد E(); د د = جديد د (ه، و)؛ ب ب = جديد ب(د)؛ أ أ = جديد أ(ب)؛ a.someMethod();

يمكن إظهار ذلك بيانياً مثل هذا:

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

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

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

2 إجابات

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

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

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

يحررفقط للتوضيح:

"في مكان ما أرى أيضًا أنه يسمى انعكاس التبعية"

الانقلاب:عكس إدارة التبعية من تطبيق إلى حاوية (مثل Spring).

حقن التبعية:

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

ماذا عن انقلاب التحكم (IoC)؟

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

إن انقلاب التحكم كدليل تصميم يخدم الأغراض التالية:

  • هناك فصل بين تنفيذ مهمة محددة وتنفيذها.
  • يمكن لكل وحدة التركيز على ما هو المقصود القيام به.
  • لا تضع الوحدات أي افتراضات حول ما تفعله الأنظمة الأخرى، ولكنها تعتمد على العقود المبرمة معها.
  • استبدال الوحدات لا يؤثر على الوحدات الأخرى.

للحصول على معلومات إضافيةينظر.

14 إجابة

في الأساس تقول:

  • لا ينبغي أن تعتمد التجريدات أبدًا على التفاصيل. التفاصيل يجب أن تعتمد على التجريدات.

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

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

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

تعد كتب تطوير البرمجيات الرشيقة والمبادئ والأنماط والممارسات والمبادئ والأنماط والممارسات الرشيقة في C# أفضل الموارد لفهم الأهداف والدوافع الأصلية بشكل كامل وراء مبدأ انعكاس التبعية. تعتبر مقالة "مبدأ عكس التبعية" مصدرًا جيدًا أيضًا، ولكن نظرًا لكونها نسخة مختصرة من المسودة التي انتهت في النهاية إلى الكتب المذكورة سابقًا، فإنها تترك وراءها بعض المناقشات المهمة حول مفهوم ملكية الحزمة والواجهات الأساسية لهذا المبدأ يختلف عن النصيحة الأكثر عمومية بشأن "برنامج الواجهة، وليس التنفيذ" الموجودة في كتاب Design Patterns (Gamma, et al.).

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

يتم تحقيق ذلك من خلال تصميم المكونات التي يتم التعبير عن تبعياتها الخارجية كواجهة يجب على مستهلك المكون توفير التنفيذ لها. بمعنى آخر، تعبر واجهات معينة عما يحتاجه المكون، وليس كيفية استخدامك للمكون (على سبيل المثال، "INeedSomething" بدلاً من "IDoSomething").

ما لا يعالجه مبدأ انعكاس التبعية هو الممارسة البسيطة المتمثلة في تجريد التبعيات باستخدام الواجهات (مثل MyService -> ). على الرغم من أن هذا يفصل المكون عن تفاصيل التنفيذ المحددة للتبعية، إلا أنه لا يعكس العلاقة بين المستهلك والتبعية (على سبيل المثال، ⇐ Logger.

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

ضمن هذا الهدف العام لإعادة الاستخدام، يمكننا التمييز بين نوعين فرعيين من إعادة الاستخدام:

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

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

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

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

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

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

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

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

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

ينص مبدأ انعكاس التبعية على ما يلي:

  • لا ينبغي أن تعتمد الوحدات عالية المستوى على الوحدات ذات المستوى المنخفض. وكلاهما يجب أن يعتمد على التجريدات.

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

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

العمارة التقليدية الطبقات

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

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

سيكون لدينا مكتبة أو حزمة لطبقة الوصول إلى البيانات.

// DataAccessLayer.dll فئة عامة ProductDAO ()

// BusinessLogicLayer.dll باستخدام DataAccessLayer؛ ProductBO من الفئة العامة (ProductDAO الخاص ProductDAO؛)

بنية الطبقات مع انعكاس التبعية

يشير انعكاس التبعية إلى ما يلي:

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

لا ينبغي أن تعتمد التجريدات على التفاصيل. التفاصيل يجب أن تعتمد على التجريدات.

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

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

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

تخيل أننا قمنا بتعديل الكود الخاص بنا مثل هذا:

سيكون لدينا مكتبة أو حزمة لطبقة الوصول إلى البيانات التي تحدد التجريد.

// الواجهة العامة DataAccessLayer.dll IProductDAO الفئة العامة ProductDAO: IProductDAO()

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

// BusinessLogicLayer.dll باستخدام DataAccessLayer؛ ProductBO من الفئة العامة (منتج IProductDAO الخاص DAO؛)

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

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

قم أولاً بتعريف ما هي طبقة المجال ويتم تعريف تجريد اتصالاتها من خلال المثابرة.

// واجهة Domain.dll العامة IProductRepository; باستخدام DataAccessLayer؛ ProductBO من الفئة العامة (IProductRepository الخاص، ProductRepository؛)

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

// Persistence.dll فئة عامة ProductDAO: IProductRepository()

تعميق المبدأ

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

لكن لماذا نقلب التبعية؟ ما هو الهدف الرئيسي وراء أمثلة محددة؟

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

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

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

بنيان

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

العمارة النظيفة

بالنسبة لي، مبدأ انعكاس التبعية الموصوف في المقال الرسمي

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

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

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

طريقة أوضح لتوضيح مبدأ انعكاس التبعية:

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

على سبيل المثال، بدلاً من تنفيذ فئة المنطق الخاصة بك كما يفعل الأشخاص عادةً:

تبعية الفئة (...) class Logic ( Private Dependency dep; int doSomething() ( // منطق الأعمال باستخدام dep هنا ) )

يجب عليك أن تفعل شيئا مثل:

واجهة فئة التبعية ( ... ) البيانات ( ... ) فئة DataFromDependency تنفذ البيانات ( التبعية الخاصة dep؛ ... ) فئة المنطق ( int doSomething(بيانات البيانات) ( // حساب شيء ما باستخدام البيانات ) )

يجب أن تكون البيانات وDataFromDependency موجودة في نفس الوحدة النمطية مثل Logic، وليس مع التبعية.

لماذا هذا؟

إجابات جيدة و أمثلة جيدةسبق أن قدمها الآخرون هنا.

الهدف من انعكاس التبعية هو جعل البرامج قابلة لإعادة الاستخدام.

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

يتم تحقيق ذلك عادةً عن طريق عكس حاوية IoC مثل Spring في Java. في هذا النموذج، يتم تعيين خصائص الكائنات من خلال تكوين XML، بدلاً من خروج الكائنات والعثور على تبعيتها.

تخيل هذا الكود الزائف..

الفئة العامة MyClass ( الخدمة العامة myService = ServiceLocator.service؛ )

يعتمد MyClass بشكل مباشر على كل من فئة الخدمة وفئة ServiceLocator. وهذا مطلوب لكليهما إذا كنت تريد استخدامه في تطبيق آخر. والآن تخيل هذا...

الطبقة العامة MyClass ( IService العامة myService؛ )

يستخدم MyClass الآن واجهة واحدة، وهي واجهة IService. سوف نسمح لحاوية IoC بتعيين قيمة هذا المتغير بالفعل.

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

هذا التنفيذ في جافا

FactoryClass بطريقة المصنع. مولد الغذاء

فئة عامة FoodGenerator ( Food food; public Food getFood(String name)( if(name.equals("fish"))( food = new Fish(); )else if(name.equals("chicken"))( food = new Chicken(); else food = null;

شرح الطبقة/الواجهة

فئة مجردة عامة الغذاء ( // لن يتجاوز أي من الفئات الفرعية هذه الطريقة لضمان الجودة ... جودة الفراغ العام ()) ( Stringfresh = "هذا جديد" + getName()؛ String اللذيذ = "هذا هو لذيذ " + getName(); System.out.println(fresh); System.out.println(tasty); ) سلسلة مجردة عامة getName(); )

الدجاج يبيع المواد الغذائية (فئة الخرسانة)

دجاج الطبقة العامة يوسع الطعام ( /* يجب أن تكون جميع أنواع الطعام طازجة ولذيذة، لذلك * لن تتغلب على طريقة الطبقة الفائقة "property()"*/ public String getName())( return "Chicken"; ))

بيع الأسماك المواد الغذائية (فئة محددة)

أسماك الطبقة العامة تمدد الطعام ( /* يجب أن تكون جميع أنواع الطعام طازجة ولذيذة، لذلك * لن تتغلب على طريقة الطبقة الفائقة "property()"*/ public String getName())( return "Fish"; ))

أخيراً

الفندق

فندق من فئة عامة ( public static void main(String args)( // استخدام فئة المصنع.... FoodGenerator foodGenerator = new FoodGenerator(); // طريقة مصنع لإنشاء مثيل للأطعمة... Food food = foodGenerator.getFood( "دجاج"); جودة الغذاء();

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

قد تلاحظ أيضًا أن فئة السمك والدجاج تطبق فئة الطعام ولا ترتبط مباشرة بالفندق. أولئك. يعتمد الدجاج والأسماك أيضًا على فئة الطعام.

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

وهذا ما يسمى انعكاس التبعية.

ينص مبدأ انعكاس التبعية (DIP) على ذلك

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

2) لا ينبغي أن تعتمد التجريدات أبدًا على التفاصيل. التفاصيل يجب أن تعتمد على التجريدات.

الواجهة العامة ICustomer (سلسلة GetCustomerNameById(int id);) العميل من الفئة العامة: ICustomer ( //ctor public Customer()() public string GetCustomerNameById(int id) (إرجاع "اسم العميل الوهمي";")) الفئة العامة CustomerFactory ( public static ICustomer GetCustomerData() ( return new Customer(); ) ) public class CustomerBLL ( ICustomer _customer; public CustomerBLL() ( _customer = CustomerFactory.GetCustomerData(); ) السلسلة العامة GetCustomerNameById(int id) ( return _customer.GetCustomerNameById(id); ) ) برنامج الفئة العامة ( static void Main() ( CustomerBLL customerBLL = new CustomerBLL(); int customerId = 25; string customerName = customerBLL.GetCustomerNameById(customerId); Console.WriteLine(customerName); Console.ReadKey(); ) )

ملحوظة. يجب أن يعتمد الفصل على تجريدات مثل الواجهة أو الفئات المجردة، بدلاً من الاعتماد على تفاصيل محددة (تنفيذ الواجهة).

يشارك

آخر تحديث: 11/03/2016

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

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

لا ينبغي أن تعتمد التجريدات على التفاصيل. التفاصيل يجب أن تعتمد على التجريدات.

لفهم المبدأ، خذ المثال التالي:

كتاب الفصل (نص سلسلة عامة (get; set;) طابعة ConsolePrinter العامة (get; set;) طباعة باطلة عامة () ( Printer.Print(Text); ) ) فئة ConsolePrinter (طباعة باطلة عامة (نص سلسلة) ( Console.WriteLine (نص)؛

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

الآن دعونا نحاول جعل فصولنا تتماشى مع مبدأ انعكاس التبعية، وفصل التجريد عن التنفيذ منخفض المستوى:

واجهة IPrinter (طباعة باطلة (نص سلسلة)؛) فئة كتاب (نص سلسلة عامة (احصل على؛ مجموعة؛) طابعة IPrinter عامة (احصل على؛ مجموعة؛) كتاب عام (طابعة IPrinter) (this.Printer = طابعة؛) طباعة باطلة عامة ( ) ( Printer.Print(Text); ) ) فئة ConsolePrinter: IPrinter ( طباعة باطلة عامة (نص سلسلة) ( Console.WriteLine ("طباعة إلى وحدة التحكم"); ) ) فئة HtmlPrinter: IPrinter ( طباعة باطلة عامة (نص سلسلة) ( Console.WriteLine("الطباعة بتنسيق html");

تم الآن فصل فكرة طباعة الكتاب عن التطبيقات الملموسة. ونتيجة لذلك، تعتمد كل من فئة Book وفئة ConsolePrinter على تجريد IPrinter. بالإضافة إلى ذلك، يمكننا الآن أيضًا إنشاء تطبيقات إضافية منخفضة المستوى لتجريد IPrinter وتطبيقها ديناميكيًا في البرنامج:

كتاب الكتاب = كتاب جديد(new ConsolePrinter()); كتاب.طباعة(); book.Printer = new HtmlPrinter(); كتاب.طباعة();