Что такое идиома «Казни вокруг»? - PullRequest
145 голосов
/ 04 декабря 2008

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

Ответы [ 8 ]

139 голосов
/ 04 декабря 2008

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

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 потоков.

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

44 голосов
/ 04 декабря 2008

Идиома «Выполнить вокруг» используется, когда вам приходится делать что-то вроде этого:

//... 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 голосов
/ 17 апреля 2009

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

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

и позже:

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

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

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

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

7 голосов
/ 04 декабря 2008

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

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

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

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

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

7 голосов
/ 04 декабря 2008

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

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

Мне кажется, что метод Execute Around - это что-то вроде Inversion of Control (Внедрение зависимостей), что вы можете изменять ad hoc каждый раз, когда вызываете метод.

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

3 голосов
/ 18 октября 2016

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

Пример 1

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

  1. Получить оберточную бумагу
  2. Get Super Nintendo .
  3. Оберните это.

Или это:

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

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

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

Пример № 2

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

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

3 голосов
/ 05 декабря 2008

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

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

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

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

0 голосов
/ 17 апреля 2009

Если вы хотите отличные идиомы, вот они:

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