Що таке ідіома "Виконати навколо"?


151

Що це за ідіома "Виконати навколо" (або подібне), про яке я чув? Чому я можу використовувати його і чому я не хочу його використовувати?


9
Я не помічав, що це ти, так. В іншому випадку я міг би бути більш саркастичним у своїй відповіді;)
Джон Скіт

1
Так це в основному аспект правильно? Якщо ні, як це відрізняється?
Лукас

Відповіді:


147

В основному це шаблон, коли ви пишете метод робити речі, які завжди потрібні, наприклад розподіл ресурсів та очищення, і змушує абонента передати "те, що ми хочемо зробити з ресурсом". Наприклад:

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 можна реалізувати, як у багатьох інших мовах (наприклад, C # лямбда-вирази або Groovy), і цей особливий випадок обробляється з Java 7 з try-with-resourcesта AutoClosableпотоками.

Хоча типовий приклад "розподілити та очистити", хоча є багато інших можливих прикладів - обробка транзакцій, ведення журналів, виконання деякого коду з більшою кількістю привілеїв тощо. Це в основному схоже на шаблон шаблону методу, але без успадкування.


4
Це детерміновано. Фіналізатори на Java не називаються детерміновано. Також, як я вже говорив в останньому абзаці, він використовується не лише для розподілу ресурсів та очищення. Можливо, зовсім не знадобиться створювати новий об’єкт. Це, як правило, "ініціалізація та скорочення", але це може бути не розподілом ресурсів.
Джон Скіт

3
Так це як у C, де у вас є функція, яку ви передаєте в покажчик функції, щоб виконати якусь роботу?
Пол Томблін

3
Крім того, Джон, ви посилаєтесь на закриття в Java - яких досі немає (якщо я цього не пропустив). Те, що ви описуєте, - це анонімні внутрішні класи - які не зовсім одне й те саме. Підтримка справжнього закриття (як було запропоновано - див. Мій блог) значно спростить цей синтаксис.
фільтрований

8
@Phil: Я думаю, що це питання ступеня. В анонімних внутрішніх класах Java є доступ до навколишнього середовища в обмеженому розумінні - тому, хоча вони не є "повними" закриттями, вони би сказали "обмежене" закриття. Я, звичайно, хотів би побачити належні закриття на Java, хоча перевірено (продовжено)
Джон Скіт

4
Java 7 додала пробні ресурси, а Java 8 додала лямбда. Я знаю, що це старе питання / відповідь, але я хотів зазначити це для всіх, хто переглядає це питання через п’ять з половиною років. Обидва ці мовні засоби допоможуть вирішити проблему, яку цю схему було винайдено для вирішення.

45

Ідіома Execute Around використовується тоді, коли виникає необхідність зробити щось подібне:

//... 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 ++.


7

Метод Execute Around - це те, коли ви передаєте довільний код методу, який може виконати налаштування та / або зняти код і виконати код між ними.

Java не є мовою, в якій я б вирішив це зробити. Більш стильно передати аргумент закриття (або лямбда-вираз). Хоча об'єкти, ймовірно, еквівалентні закриттям .

Мені здається, що метод Execute Around - це подібний до інверсії управління ( введення залежності), яку ви можете змінювати ad hoc щоразу, коли ви викликаєте метод.

Але це також може бути інтерпретоване як приклад з’єднання управління (розповідання методу, що робити за його аргументом, буквально в цьому випадку).


7

Я бачу, що у вас є тег Java, тому я використовую Java як приклад, хоча шаблон не залежить від платформи.

Ідея полягає в тому, що іноді перед кодом і після запуску коду у вас є код, який завжди включає одну і ту ж плиту котла. Хороший приклад - JDBC. Ви завжди захоплюєте з'єднання і створюєте оператор (або підготовлений оператор) перед тим, як запустити фактичний запит і обробити набір результатів, а потім в кінці завжди будете робити те саме очищення панелі котлів - закриваючи оператор і з'єднання.

Ідея у виконанні навколо полягає в тому, що краще, якщо ви зможете визначити код котла. Це рятує вас від певного набору, але причина глибша. Тут діє принцип "не повторювати себе" (DRY) - ви виділяєте код в одне місце, тому, якщо є помилка або вам потрібно змінити її, або ви просто хочете зрозуміти, все це в одному місці.

Те, що трохи складне при такому виді факторингу, - це те, що у вас є посилання, які повинні бачити і частини "до" і "після". У прикладі JDBC це буде включати заяву про з'єднання та (підготовлену). Отже, щоб вирішити, що ви по суті "загортаєте" свій цільовий код кодом котла.

Можливо, ви знайомі з деякими поширеними випадками на Java. Один - фільтри сервлетів. Інша - АОП навколо порад. Третя частина - це різні класи xxxTemplate навесні. У кожному випадку у вас є якийсь об’єкт обгортки, в який вводиться ваш "цікавий" код (скажімо, обробка запитів JDBC і набір результатів). Об'єкт обгортки виконує частину "до", викликає цікавий код, а потім виконує частину "після".


7

Дивіться також Сендвічі коду , який досліджує цю конструкцію на багатьох мовах програмування та пропонує цікаві дослідницькі ідеї. Щодо конкретного питання, чому можна використовувати його, вищенаведений документ пропонує деякі конкретні приклади:

Такі ситуації виникають, коли програма маніпулює спільними ресурсами. API для замків, сокетів, файлів або підключень до бази даних може вимагати від програми явного закриття або випуску ресурсу, який він раніше придбав. Мовою без збору сміття програміст несе відповідальність за розподіл пам’яті до її використання та звільнення після її використання. Загалом, різноманітні завдання програмування вимагають, щоб програма вносила зміни, діяла в контексті цієї зміни, а потім скасовувала зміни. Ми називаємо такі ситуації бутербродами з кодом.

І пізніше:

Бутерброди з кодом з'являються у багатьох ситуаціях програмування. Кілька поширених прикладів стосуються придбання та випуску обмежених ресурсів, таких як блокування, дескриптори файлів або з'єднання з гніздом. У більш загальних випадках будь-яка тимчасова зміна стану програми може вимагати сендвіч-коду. Наприклад, програма на основі GUI може тимчасово ігнорувати вводи користувачів, або ядро ​​ОС може тимчасово відключити апаратні переривання. Якщо не відновити попередній стан у цих випадках, це спричинить серйозні помилки.

У статті не досліджено, чому б не використовувати цю ідіому, але він описує, чому ідіому легко помилитися без допомоги рівня мови:

Сендвічі з несправними кодами виникають найчастіше за наявності винятків та пов'язаного з ними невидимого контрольного потоку. Дійсно, особливі мовні функції для управління бутербродами з кодом виникають головним чином у мовах, які підтримують винятки.

Однак винятки - не єдина причина дефектних бутербродів з кодом. Всякий раз , коли вносяться зміни в тілі коду, нові шляхи управління можуть виникнути в обході після коду. У найпростішому випадку обслуговуючому персоналу потрібно лише додати returnзаяву до тіла сендвіч, щоб ввести новий дефект, що може призвести до мовчазних помилок. Коли код тіла великий і до і після того , як вони широко розділені, такі помилки можуть бути важко виявити візуально.


Хороший момент, лазурний луг. Я переглянув і розширив свою відповідь, щоб вона справді була більш самодостатньою відповіддю. Дякуємо, що запропонували це.
Бен Лібліт

4

Я спробую пояснити, як це було б чотирирічному:

Приклад 1

Санта приїжджає до міста. Його ельфи кодують все, що вони хочуть за його спиною, і якщо вони не змінить речі, вони повторюються трохи:

  1. Дістайте обгортковий папір
  2. Отримайте Super Nintendo .
  3. Загорніть його.

Або це:

  1. Дістайте обгортковий папір
  2. Отримайте ляльку Барбі .
  3. Загорніть його.

.... ad nauseam мільйон разів з мільйоном різних подарунків: зауважте, що єдине, що відрізняється - це крок 2. Якщо другий крок - це єдине, що відрізняється, то чому Санта дублює код, тобто чому він дублює кроки 1 і 3 мільйон разів? Мільйон подарунків означає, що він непотрібно повторювати кроки 1 і 3 мільйон разів.

Виконати навколо допомагає вирішити цю проблему. і допомагає усунути код. Кроки 1 і 3 в основному постійні, що дозволяє кроку 2 бути єдиною частиною, яка змінюється.

Приклад №2

Якщо ви все одно цього не отримаєте, ось ще один приклад: подумайте про бутерброд: хліб ззовні завжди однаковий, але те, що знаходиться на внутрішній стороні, змінюється залежно від типу бутерброду, який ви виберете (.ег шинка, сир, варення, арахісове масло тощо). Хліб завжди зовні, і вам не потрібно повторювати це мільярд разів на кожен тип бутерброда, який ви створюєте.

Тепер, якщо ви прочитаєте вищезгадані пояснення, можливо, вам це буде легше зрозуміти. Я сподіваюся, що це пояснення вам допомогло.


+ для уяви: D
Сер. Їжак

3

Це мені нагадує схему розробки стратегії . Зауважте, що посилання, на яке я вказував, містить код Java для шаблону.

Очевидно, що можна виконати "Execute Around", зробивши код ініціалізації та очищення та просто пройшовши стратегію, яка завжди буде обгорнута кодом ініціалізації та очищення.

Як і будь-яка методика, що використовується для зменшення повторення коду, ви не повинні використовувати його, поки у вас є хоча б два випадки, коли вам це потрібно, можливо, навіть 3 (а-ля принцип YAGNI). Майте на увазі, що видалення повтору коду зменшує технічне обслуговування (менша кількість копій коду означає менший витрачений час на копіювання виправлень для кожної копії), але також збільшує обслуговування (більше загального коду). Таким чином, вартість цього фокусу полягає в тому, що ви додаєте більше коду.

Цей тип техніки корисний для більш ніж просто ініціалізації та очищення. Це також добре, коли ви хочете полегшити виклик своїх функцій (наприклад, ви можете використовувати їх у майстрі, щоб кнопки "наступний" та "попередній" не потребували гігантських заяв, щоб вирішити, що робити наступна / попередня сторінка.


0

Якщо ви хочете похмурі ідіоми, ось це:

//-- 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(); }

Якщо моє відкрите завершення не вдається (скажімо, придбання блоку реєранта), закривається виклик (скажімо, звільнення блокування повторного агента, незважаючи на те, що відкритий збіг не відповідає).
Том Хоутін - тайклін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.