أن تكون "جديدًا" أو لا تكون ...

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




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

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

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

أولاً ، دعنا نضع بعض القواعد الأساسية. يمكن لفئة Injectable الاستعلام عن Injectable أخرى في مُنشئها. (أحيانًا أسمي Injectable ككائن خدمة ، ولكن هذا المصطلح مثقل.) يميل الحقن إلى امتلاك واجهات ، لأن هناك فرصة بأن علينا استبدالها بتنفيذ مناسب للاختبار. ومع ذلك ، لا يمكن أبدًا لـ Injectable الاستعلام عن غير قابل للحقن (Newable) في مُنشئه. هذا لأن إطار حقن التبعية لا يعرف كيفية إنشاء Newable. فيما يلي بعض الأمثلة عن الفصول التي أتوقعها من إطار حقن التبعية الخاص بي: CreditCardProcessor و MusicPlayer و MailSender و OfflineQueue. وبالمثل ، يمكن طلب Newable من قبل Newable في المنشئ الخاص بهم ، ولكن لا يمكن حقنه (أحيانًا أسمي Newable كعنصر قيمة ، ولكن مرة أخرى ،هذا المصطلح مثقل). بعض الأمثلة على Newable: Email ، MailMessage ، User ، CreditCard ، Song. إذا اتبعت هذه الفروق ، فسيكون من السهل اختبار التعليمات البرمجية والعمل بها. إذا خالفت هذه القواعد ، فسيكون من الصعب اختبار التعليمات البرمجية الخاصة بك.

دعونا نلقي نظرة على مثال MusicPlayer و Song

class Song {
  Song(String name, byte[] content);
}
class MusicPlayer {
  @Injectable
  MusicPlayer(AudioDevice device);
  play(Song song);
}

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

الآن دعونا نرى ما يحدث إذا كسر MusicPlayer القاعدة وطلب Newable في منشئه.

class Song {
  String name;
  byte[] content;
  Song(String name, byte[] content);
}
class MusicPlayer {
  AudioDevice device;
  Song song;
  @Injectable
  MusicPlayer(AudioDevice device, Song song);
  play();
}

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

الآن دعونا نرى ما سيحدث إذا خرق Song القاعدة و طلب Injectable في مُنشئه.

class MusicPlayer {
  AudioDevice device;
  @Injectable
  MusicPlayer(AudioDevice device);
}
class Song {
  String name;
  byte[] content;
  MusicPlayer palyer;
  Song(String name, byte[] content, MusicPlayer player);
  play();
}
class SongReader {
  MusicPlayer player
  @Injectable
  SongReader(MusicPlayer player) {
    this.player = player;
  }
  Song read(File file) {
    return new Song(file.getName(),
                    readBytes(file),
                    player);
  }
}

للوهلة الأولى ، كل شيء على ما يرام. ولكن فكر في كيفية إنشاء الأغاني. من المفترض ، يتم تخزين الأغاني على القرص ، لذلك نحن بحاجة إلى SongReader. سيتعين على SongReader طلب MusicPlayer بحيث أنه عند الاتصال الجديد بـ Song ، يمكنه إرضاء تبعيات Song على MusicPlayer. هل لاحظت أي خطأ هنا؟ ما هو الخوف الذي يحتاج SongReader لمعرفته حول MusicPlayer؟ هذا انتهاك لقانون ديميتر. لا يجب أن يعرف SongReader عن MusicPlayer. على الأقل لأن SongReader لا يستدعي طرقًا باستخدام MusicPlayer. إنه يعرف فقط عن MusicPlayer لأن Song قد انتهكت فصل Newable / Injectable. يدفع SongReader للخطأ في Song. نظرًا لأن المكان الذي يتم فيه الخطأ وحيث تتجلى العواقب ليس هو نفس الشيء ، فإن هذا الخطأ دقيق للغاية ويصعب تشخيصه. وهذا يعني أيضًا أن العديد من الأشخاص من المحتمل أن يرتكبوا هذا الخطأ.

من حيث الاختبار ، هذا ألم حقيقي. افترض أن لديك SongWriter وتريد التأكد من أنها تسلسل Song إلى القرص بشكل صحيح. تحتاج إلى إنشاء MockMusicPlayer حتى تتمكن من تمريرها إلى Song بحيث يمكنك تمريرها إلى SongWritter. لماذا نأتي حتى عبر MusicPlayer هنا؟ دعونا نلقي نظرة عليه في الاتجاه الآخر. الأغنية هي ما قد ترغب في إجراء تسلسل له ، والطريقة الأسهل للقيام بذلك هي استخدام تسلسل Java. وبالتالي ، فإننا لا نقوم بتسلسل Song فقط ، ولكن أيضًا MusicPlayer و AudioDevice. لا يجب إجراء تسلسل لـ MusicPlayer أو AudioDevice. كما ترى ، فإن التغييرات الصغيرة تسهل إلى حد كبير إمكانية الاختبار.

كما ترى ، يصبح العمل مع التعليمات البرمجية أسهل إذا فصلنا هذين النوعين من الكائنات. إذا قمت بخلطها ، فسيكون من الصعب اختبار التعليمات البرمجية الخاصة بك. Newable هي الكائنات الموجودة في نهاية الرسم البياني للكائن لتطبيقك. قد تعتمد Newable على Newable الأخرى ، على سبيل المثال ، كيف قد يعتمد CreditCard على العنوان ، والذي قد يعتمد على City - هذه الأشياء هي أوراق من الرسم البياني للتطبيق. نظرًا لأنها أوراق ولا تتواصل مع أي خدمات خارجية (الخدمات الخارجية قابلة للحقن) ، فهي لا تحتاج إلى القيام بذرة. لا شيء يبدو مثل String بعد الآن String نفسه. لماذا يجب أن أصنع كعبًا للمستخدم ، إذا كان يمكنني فقط الاتصال بمستخدم جديد ، فلماذا أصنع بذرة لأي مما يلي: البريد الإلكتروني ، MailMessage ، المستخدم ، CreditCard ، Song؟ فقط اتصل جديد وانهيها.

الآن دعونا ننتبه إلى شيء خفي للغاية. من الطبيعي أن يعرف Newable عن الحقن. ما هو غير طبيعي هو أن Newable لديه إشارة إلى Injectable كحقل. بمعنى آخر ، يمكن أن تعرف Song عن MusicPlayer. على سبيل المثال ، من الطبيعي أن يتم تمرير MusicPlayer القابل للحقن عبر المكدس إلى Newable Song. لأن المرور عبر المكدس مستقل عن إطار حقن التبعية. كما في هذا المثال:

class Song {
  Song(String name, byte[] content);
  boolean isPlayable(MusicPlayer player);
}

تحدث المشكلة عندما تحتوي Song على حقل ارتباط إلى MusicPlayer. يتم تعيين حقول الارتباط من خلال المنشئ ، مما سيؤدي إلى انتهاك قانون ديميتر للمتصل ، وصعوبات في اختباراتنا.

تعرف على المزيد حول الدورات التدريبية



All Articles