Что такое идиома Execute Around?

Что это за идиома "Execute Around" (или похожая), о которой я слышал? Почему я могу его использовать и почему я могу не захотеть его использовать?


person Tom Hawtin - tackline    schedule 04.12.2008    source источник
comment
Я не заметил, что это ты, Тэкс. В противном случае я мог бы ответить более саркастично;)   -  person Jon Skeet    schedule 04.12.2008
comment
Так что это, по сути, аспект, не так ли? Если нет, то чем он отличается?   -  person Lucas    schedule 20.11.2011


Ответы (8)


По сути, это шаблон, в котором вы пишете метод, позволяющий делать то, что всегда требуется, например. выделение ресурсов и очистка, и заставить вызывающего абонента передать «что мы хотим сделать с ресурсом». Например:

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

Хотя типичным примером является «выделение и очистка», существует множество других возможных примеров - обработка транзакций, ведение журнала, выполнение некоторого кода с дополнительными привилегиями и т. Д. Это в основном немного похоже на шаблон метода шаблона, но без наследования.

person Jon Skeet    schedule 04.12.2008
comment
Чем он отличается от конструктора и дескруктора, которые обрабатывают инициализацию и удаление? - person Nathan; 04.12.2008
comment
Это детерминировано. Финализаторы в Java не вызываются детерминированно. Также, как я сказал в последнем абзаце, он не только используется для выделения ресурсов и очистки. Возможно, вообще не потребуется создавать новый объект. Обычно это инициализация и отключение, но это может не быть выделение ресурсов. - person Jon Skeet; 05.12.2008
comment
Это как в C, где у вас есть функция, которую вы передаете в указатель на функцию, чтобы выполнить некоторую работу? - person Paul Tomblin; 05.12.2008
comment
Я расширил концепцию EA в Java, насколько это болезненно, и предложил изменения языка, которые упростили бы задачу, здесь: metatechnology.blogspot.com/2007/02/ Это было в начале 2007 года - похоже, что замыкания до сих пор не вошли в Java :-( - person philsquared; 14.02.2009
comment
Кроме того, Джон, вы имеете в виду замыкания в Java, которых до сих пор нет (если я это не пропустил). Вы описываете анонимные внутренние классы, а это не совсем одно и то же. Поддержка истинных закрытий (как было предложено - см. Мой блог) значительно упростила бы этот синтаксис. - person philsquared; 14.02.2009
comment
@ Фил: Я думаю, это вопрос степени. Анонимные внутренние классы Java имеют доступ к окружающей среде в ограниченном смысле - поэтому, хотя они и не являются полными замыканиями, я бы сказал, что они ограничены. Я бы определенно хотел видеть правильные закрытия в Java, хотя и проверено (продолжение) - person Jon Skeet; 14.02.2009
comment
исключения усложняют задачу. Я не совсем доволен какими-либо предложениями о закрытии, которые я видел. Они либо неуклюжи для гибкости, либо простые, но ограниченные :( - person Jon Skeet; 14.02.2009
comment
В Java 7 добавлена ​​функция try-with-resource, а в Java 8 добавлены лямбда-выражения. Я знаю, что это старый вопрос / ответ, но я хотел указать на это всем, кто задумывался над этим вопросом пять с половиной лет спустя. Оба этих языковых инструмента помогут решить проблему, для решения которой был изобретен этот шаблон. - person ; 12.08.2014
comment
@JonSkeet: Отличный ответ, но как насчет того, почему я могу не использовать его? часть? - person Burkhard; 17.07.2016
comment
@Burkhard: Не думаю, что я мог бы сказать здесь что-то конкретное: используйте его, когда он упрощает код; не используйте его, если это не так. - person Jon Skeet; 17.07.2016

Идиома 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 ++ .

person e.James    schedule 04.12.2008

См. Также Code Sandwiches, в котором исследуется эта конструкция на многих языках программирования и в предложениях. несколько интересных исследовательских идей. Что касается конкретного вопроса о том, почему его можно использовать, вышеупомянутая статья предлагает несколько конкретных примеров:

Такие ситуации возникают всякий раз, когда программа манипулирует общими ресурсами. API-интерфейсы для блокировок, сокетов, файлов или соединений с базой данных могут потребовать от программы явного закрытия или освобождения ранее полученного ресурса. В языке без сборки мусора программист несет ответственность за выделение памяти перед ее использованием и освобождение после ее использования. В общем, различные задачи программирования требуют, чтобы программа внесла изменение, работала в контексте этого изменения, а затем отменила это изменение. Мы называем такие ситуации кодовыми бутербродами.

И позже:

Бутерброды с кодом появляются во многих ситуациях программирования. Несколько распространенных примеров относятся к получению и освобождению ограниченных ресурсов, таких как блокировки, файловые дескрипторы или соединения сокетов. В более общих случаях для любого временного изменения состояния программы может потребоваться сэндвич с кодом. Например, программа на основе графического интерфейса пользователя может временно игнорировать вводимые пользователем данные, или ядро ​​ОС может временно отключать аппаратные прерывания. Невозможность восстановить предыдущее состояние в этих случаях вызовет серьезные ошибки.

В документе не исследуется, почему нельзя использовать эту идиому, но описывается, почему в этой идиоме легко ошибиться без помощи на уровне языка:

Сэндвичи с дефектным кодом чаще всего возникают при наличии исключений и связанных с ними невидимых потоков управления. Действительно, специальные языковые функции для управления сэндвичами с кодом возникают в основном в языках, поддерживающих исключения.

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

person Ben Liblit    schedule 17.04.2009
comment
Хорошее замечание, azurefrag. Я пересмотрел и расширил свой ответ, так что это действительно более самостоятельный ответ. Спасибо, что предложили это. - person Ben Liblit; 12.08.2014

Execute Around Method - это когда вы передаете произвольный код методу, который может выполнять настройку и / или разорвите код и выполните свой код между ними.

Я бы предпочел не использовать Java для этого. Более стильно передать в качестве аргумента замыкание (или лямбда-выражение). Хотя, возможно, объекты эквивалентны замыканиям.

Мне кажется, что метод Execute Around в некотором роде похож на инверсию управления (внедрение зависимостей), вы можете изменять ad hoc каждый раз, когда вызываете метод.

Но это также можно интерпретировать как пример Control Coupling (в данном случае буквально сообщая методу, что делать с помощью его аргумента).

person Bill Karwin    schedule 04.12.2008

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

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

Идея автономного выполнения заключается в том, что лучше исключить шаблонный код. Это сэкономит вам время на вводе текста, но причина глубже. Здесь действует принцип «не повторяйся» (DRY) - вы изолируете код в одном месте, поэтому, если есть ошибка, или вам нужно ее изменить, или вы просто хотите понять ее, все в одном месте.

Но вот что немного сложно при таком разложении на множители, так это то, что у вас есть ссылки, которые должны видеть как части «до», так и «после». В примере JDBC это будет включать в себя соединение и (подготовленный) оператор. Таким образом, чтобы справиться с этим, вы по существу "оборачиваете" свой целевой код стандартным кодом.

Возможно, вы знакомы с некоторыми типичными случаями в Java. Один из них - это фильтры сервлетов. Другой - АОП вокруг советов. Третий - это различные классы xxxTemplate в Spring. В каждом случае у вас есть некий объект-оболочка, в который внедряется ваш «интересный» код (скажем, запрос JDBC и обработка набора результатов). Объект-оболочка выполняет часть «до», вызывает интересующий код, а затем выполняет часть «после».

person Community    schedule 04.12.2008

Попробую объяснить, как четырехлетнему:

Пример 1

Санта едет в город. Его эльфы кодируют за его спиной все, что хотят, и, если они не изменят ситуацию, будут повторяться:

  1. Получите оберточную бумагу
  2. Купите Super Nintendo.
  3. Заверните.

Или это:

  1. Получите оберточную бумагу
  2. Купите куклу Барби.
  3. Заверните.

.... до тошноты миллион раз с миллионом разных подарков: обратите внимание, что отличается только шаг 2. Если второй шаг - единственное, что отличается, то почему Санта дублирует код, т.е. почему он дублирует шаги 1 и 3 миллион раз? Миллион подарков означает, что он без нужды повторяет шаги 1 и 3 миллион раз.

Казнь помогает решить эту проблему. и помогает устранить код. Шаги 1 и 3 в основном постоянны, поэтому изменяется только шаг 2.

Пример 2

Если вы все еще не понимаете его, вот еще один пример: представьте себе сэндвич: хлеб снаружи всегда один и тот же, но то, что внутри, меняется в зависимости от типа сэндвича, который вы выбираете (например, ветчина, сыр, джем, арахисовое масло и т. д.). Хлеб всегда снаружи, и вам не нужно повторять это миллиард раз для каждого типа создаваемого вами песка.

Теперь, если вы прочитаете приведенные выше объяснения, возможно, вам будет легче понять. Надеюсь, это объяснение вам помогло.

person BKSpurgeon    schedule 17.10.2016
comment
+ для фантазии: D - person Sir. Hedgehog; 23.11.2016

Это напоминает мне шаблон разработки стратегии. Обратите внимание, что ссылка, на которую я указал, включает код Java для шаблона.

Очевидно, что можно выполнить «Execute Around», создав код инициализации и очистки и просто передав стратегию, которая затем всегда будет заключена в код инициализации и очистки.

Как и любой другой метод, используемый для уменьшения повторения кода, вы не должны использовать его до тех пор, пока у вас не будет хотя бы 2 случаев, когда он вам нужен, возможно, даже 3 (по принципу YAGNI). Имейте в виду, что удаление повторения кода сокращает обслуживание (меньшее количество копий кода означает меньше времени, затрачиваемого на копирование исправлений для каждой копии), но также увеличивает объем обслуживания (больше общего кода). Таким образом, цена этой уловки состоит в том, что вы добавляете больше кода.

Этот тип техники полезен не только для инициализации и очистки. Это также хорошо, когда вы хотите упростить вызов своих функций (например, вы можете использовать его в мастере, чтобы кнопки «следующий» и «предыдущий» не нуждались в гигантских операторах регистра, чтобы решить, что делать, чтобы перейти к следующая / предыдущая страница.

person Brian    schedule 04.12.2008

Если вам нужны заводные идиомы, вот они:

//-- 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(); }
person Florin    schedule 17.04.2009
comment
Если мое открытие не удается (скажем, получение повторной блокировки), вызывается закрытие (скажем, освобождение повторной блокировки, несмотря на сбой соответствующего открытия). - person Tom Hawtin - tackline; 17.04.2009