سؤال

ما هو هذا "التنفيذ حول" المصطلح (أو ما شابه) الذي سمعت عنه؟ لماذا يمكنني استخدامه ، ولماذا لا أريد استخدامه؟

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

المحلول

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

public interface InputStreamAction
{
    void useStream(InputStream stream) throws IOException;
}

// Somewhere else    

public void executeWithFile(String filename, InputStreamAction action)
    throws IOException
{
    InputStream stream = new FileInputStream(filename);
    try {
        action.useStream(stream);
    } finally {
        stream.close();
    }
}

// Calling it
executeWithFile("filename.txt", new InputStreamAction()
{
    public void useStream(InputStream stream) throws IOException
    {
        // Code to use the stream goes here
    }
});

// Calling it with Java 8 Lambda Expression:
executeWithFile("filename.txt", s -> System.out.println(s.read()));

// Or with Java 8 Method reference:
executeWithFile("filename.txt", ClassName::methodName);

لا تحتاج رمز الاتصال إلى القلق بشأن الجانب المفتوح/التنظيف - سيتم الاعتناء به executeWithFile.

كان هذا مؤلمًا بصراحة في Java لأن عمليات الإغلاق كانت محفوظة للغاية ، بدءًا من تعبيرات Java 8 Lambda يمكن تنفيذها كما هو الحال في العديد من اللغات الأخرى (على سبيل المثال تعبيرات Lambda ، أو رائعة) ، ويتم التعامل مع هذه الحالة الخاصة منذ Java 7 مع try-with-resources و AutoClosable تيارات.

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

نصائح أخرى

يتم استخدام التنفيذ حول المصطلح عندما تجد نفسك مضطرًا لفعل شيء كهذا:

//... chunk of init/preparation code ...
task A
//... chunk of cleanup/finishing code ...

//... chunk of identical init/preparation code ...
task B
//... chunk of identical cleanup/finishing code ...

//... chunk of identical init/preparation code ...
task C
//... chunk of identical cleanup/finishing code ...

//... and so on.

لتجنب تكرار كل هذا الرمز الزائد الذي يتم تنفيذه دائمًا "حول" المهام الفعلية ، ستنشئ فئة تعتني به تلقائيًا:

//pseudo-code:
class DoTask()
{
    do(task T)
    {
        // .. chunk of prep code
        // execute task T
        // .. chunk of cleanup code
    }
};

DoTask.do(task A)
DoTask.do(task B)
DoTask.do(task C)

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

ألق نظرة على هذا المشنور للحصول على مثال C# ، و هذه المقالة للحصول على مثال C ++.

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

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

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

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

أرى أن لديك علامة Java هنا ، لذا سأستخدم Java كمثال على الرغم من أن النمط ليس خاصًا بالمنصة.

الفكرة هي أنه في بعض الأحيان يكون لديك رمز يتضمن دائمًا نفس Boilerplate قبل تشغيل الرمز وبعد تشغيل الرمز. مثال جيد هو JDBC. يمكنك دائمًا الحصول على اتصال وإنشاء عبارة (أو عبارة معدة) قبل تشغيل الاستعلام الفعلي ومعالجة مجموعة النتائج ، ثم تقوم دائمًا بنفس تنظيف Boilerplate في النهاية-وضع البيان والاتصال.

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

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

قد تكون على دراية ببعض الحالات الشائعة في جافا. واحد هو مرشحات servlet. آخر هو AOP حول النصيحة. والثالث هو مختلف فصول XXXTEMPHT في الربيع. في كل حالة ، لديك بعض كائنات التفاف التي يتم فيها حقن كود "مثير للاهتمام" (على سبيل المثال استعلام JDBC ومعالجة مجموعة النتائج). يقوم كائن Wrapper بإجراء جزء "قبل" ، ويحتج على الكود المثير للاهتمام ثم يقوم بجزء "بعد".

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

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

و لاحقا:

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

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

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

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

هذا يذكرني بـ نمط تصميم الإستراتيجية. لاحظ أن الرابط الذي أشرت إليه يتضمن رمز Java للنمط.

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

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

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

سأحاول أن أشرح ، كما أود أن تبلغ من العمر أربع سنوات:

مثال 1

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

  1. احصل على ورقة الغلاف
  2. احصل على سوبر نينتندو.
  3. لفها.

أو هذا:

  1. احصل على ورقة الغلاف
  2. احصل على دمية باربي.
  3. لفها.

.... AD Nauseam مليون مرة مع مليون هدايا مختلفة: لاحظ أن الشيء الوحيد مختلف هو الخطوة 2. إذا كانت الخطوة الثانية هي الشيء الوحيد الذي يختلف ، فلماذا يكرر سانتا الرمز ، أي لماذا هو خطوات التكرار 1 و 3 مليون مرة؟ مليون عرض يعني أنه يكرر الخطوات 1 و 3 مليون مرة بلا داع.

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

مثال رقم 2

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

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

إذا كنت تريد تعابير رائعة ، فهذا هو:

//-- the target class
class Resource { 
    def open () { // sensitive operation }
    def close () { // sensitive operation }
    //-- target method
    def doWork() { println "working";} }

//-- the execute around code
def static use (closure) {
    def res = new Resource();
    try { 
        res.open();
        closure(res)
    } finally {
        res.close();
    }
}

//-- using the code
Resource.use { res -> res.doWork(); }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top