Что такое идиома «Казнь вокруг»?

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

Я не заметил, что это ты, Тэкс. В противном случае я мог бы ответить более саркастично;)

Jon Skeet 04.12.2008 23:53

Так это в основном аспект, верно? Если нет, то чем он отличается?

Lucas 20.11.2011 06:06
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
155
2
23 154
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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 .

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

Чем это отличается от конструктора и деструктора, которые обрабатывают инициализацию и удаление?

Nathan 04.12.2008 23:56

Это детерминировано. Финализаторы в Java не вызываются детерминированно. Кроме того, как я сказал в последнем абзаце, это не Только, используемый для выделения ресурсов и очистки. Возможно, вообще не потребуется создавать новый объект. Обычно это «инициализация и разборка», но это может не быть выделением ресурсов.

Jon Skeet 05.12.2008 00:01

Это как в C, где у вас есть функция, которую вы передаете в указатель на функцию, чтобы выполнить некоторую работу?

Paul Tomblin 05.12.2008 00:03

Я расширил концепцию EA в Java, насколько это болезненно, и предложил изменения языка, которые упростили бы задачу, здесь: metatechnology.blogspot.com/2007/02/… Это было в начале 2007 года - похоже, что замыкания до сих пор не вошли в Java :-(

philsquared 14.02.2009 14:39

Кроме того, Джон, вы имеете в виду замыкания в Java, которых до сих пор нет (если я это не пропустил). Вы описываете анонимные внутренние классы, а это не совсем одно и то же. Поддержка истинных закрытий (как было предложено - см. Мой блог) значительно упростила бы этот синтаксис.

philsquared 14.02.2009 14:50

@ Фил: Я думаю, это вопрос степени. Анонимные внутренние классы Java имеют доступ к окружающей среде в ограниченном смысле - поэтому, хотя они и не являются «полными» замыканиями, я бы сказал, что они «ограниченные» замыкания. Я бы определенно хотел видеть правильные закрытия в Java, хотя и проверено (продолжение)

Jon Skeet 14.02.2009 18:00

исключения усложняют задачу. Я не совсем доволен какими-либо предложениями о закрытии, которые я видел. Они либо неуклюжи для гибкости, либо простые, но ограниченные :(

Jon Skeet 14.02.2009 18:01

В Java 7 добавлена ​​функция try-with-resource, а в Java 8 добавлены лямбда-выражения. Я знаю, что это старый вопрос / ответ, но я хотел указать на это всем, кто задумывался над этим вопросом пять с половиной лет спустя. Оба этих языковых инструмента помогут решить проблему, для решения которой был изобретен этот шаблон.

user439793 12.08.2014 18:40

@JonSkeet: Отличный ответ, но как насчет «почему я могу не использовать его?» часть?

Burkhard 17.07.2016 11:16

@Burkhard: Не думаю, что я мог бы сказать здесь что-то конкретное: используйте его, когда он упрощает код; не используйте его, если это не так.

Jon Skeet 17.07.2016 11:21

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

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

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

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

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

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

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

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

Однако при таком разложении на множители возникает небольшая сложность в том, что у вас есть ссылки, которые необходимо видеть как "до", так и "после". В примере JDBC это будет включать Connection и (Prepared) Statement. Таким образом, чтобы справиться с этим, вы по существу "оборачиваете" свой целевой код стандартным кодом.

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

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

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

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

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

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

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

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

Tom Hawtin - tackline 17.04.2009 08:51

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

Such situations arise whenever a program manipulates shared resources. APIs for locks, sockets, files, or database connections may require a program to explicitly close or release a resource that it previously acquired. In a language without garbage collection, the programmer is responsible for allocating memory before its use and releasing it after its use. In general, a variety of programming tasks call for a program to make a change, operate in the context of that change, and then undo the change. We call such situations code sandwiches.

И позже:

Code sandwiches appear in many programming situations. Several common examples relate to the acquisition and release of scarce resources, such as locks, file descriptors, or socket connections. In more general cases, any temporary change of program state may require a code sandwich. For example, a GUI-based program may temporarily ignore user inputs, or an OS kernel may temporarily disable hardware interrupts. Failure to restore earlier state in these cases will cause serious bugs.

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

Defective code sandwiches arise most frequently in the presence of exceptions and their associated invisible control flow. Indeed, special language features to manage code sandwiches arise chiefly in languages that support exceptions.

However, exceptions are not the only cause of defective code sandwiches. Whenever changes are made to body code, new control paths may arise that bypass the after code. In the simplest case, a maintainer need only add a return statement to a sandwich’s body to introduce a new defect, which may lead to silent errors. When the body code is large and before and after are widely separated, such mistakes can be hard to detect visually.

Хорошее замечание, azurefrag. Я пересмотрел и расширил свой ответ, так что это действительно более самостоятельный ответ. Спасибо, что предложили это.

Ben Liblit 12.08.2014 21:38

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

Пример 1

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

  1. Получите оберточную бумагу
  2. Получите Супер Нинтендо.
  3. Заверни это.

Или это:

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

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

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

Пример # 2

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

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

+ для фантазии: D

Sir. Hedgehog 23.11.2016 16:30

Другие вопросы по теме