Java 什么是“周围执行”成语?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/341971/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 13:39:05  来源:igfitidea点击:

What is the "Execute Around" idiom?

javalanguage-agnosticdesign-patternsidioms

提问by Tom Hawtin - tackline

What is this "Execute Around" idiom (or similar) I've been hearing about? Why might I use it, and why might I not want to use it?

我听说过的这个“周围执行”成语(或类似的)是什么?为什么我可以使用它,为什么我不想使用它?

采纳答案by Jon Skeet

Basically it's the pattern where you write a method to do things which are always required, e.g. resource allocation and clean-up, and make the caller pass in "what we want to do with the resource". For example:

基本上它是这样一种模式,你编写一个方法来做总是需要的事情,例如资源分配和清理,并使调用者传递“我们想要用资源做什么”。例如:

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);

The calling code doesn't need to worry about the open/clean-up side - it will be taken care of by executeWithFile.

调用代码不需要担心打开/清理端 - 它将由executeWithFile.

This was frankly painful in Java because closures were so wordy, starting with Java 8 lambda expressions can be implemented like in many other languages (e.g. C# lambda expressions, or Groovy), and this special case is handled since Java 7 with try-with-resourcesand AutoClosablestreams.

坦率地说,这在 Java 中很痛苦,因为闭包是如此冗长,从 Java 8 开始,lambda 表达式可以像在许多其他语言(例如 C# lambda 表达式或 Groovy)中一样实现,而这种特殊情况自 Java 7 起就使用try-with-resourcesAutoClosable流来处理。

Although "allocate and clean-up" is the typical example given, there are plenty of other possible examples - transaction handling, logging, executing some code with more privileges etc. It's basically a bit like the template method patternbut without inheritance.

尽管“分配和清理”是给出的典型示例,但还有很多其他可能的示例——事务处理、日志记录、执行一些具有更多权限的代码等。它基本上有点像模板方法模式,但没有继承。

回答by Bill Karwin

An Execute Around Methodis where you pass arbitrary code to a method, which may perform setup and/or teardown code and execute your code in between.

一种执行方法周围是你传递任意代码的方法,该方法可以进行安装和/或拆卸的代码和执行之间的代码。

Java isn't the language I'd choose to do this in. It's more stylish to pass a closure (or lambda expression) as the argument. Though objects are arguably equivalent to closures.

Java 不是我选择使用的语言。将闭包(或 lambda 表达式)作为参数传递更时尚。尽管对象可以说等同于闭包

It seems to me that the Execute Around Method is sort of like Inversion of Control(Dependency Injection) that you can vary ad hoc, every time you call the method.

在我看来,执行周围方法有点像控制反转(依赖注入),您可以在每次调用该方法时临时更改。

But it could also be interpreted as an example of Control Coupling (telling a method what to do by its argument, literally in this case).

但它也可以被解释为控制耦合的一个例子(告诉一个方法通过它的参数做什么,在这种情况下是字面意思)。

回答by e.James

The Execute Around idiom is used when you find yourself having to do something like this:

当您发现自己必须执行以下操作时,将使用 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.

In order to avoid repeating all of this redundant code that is always executed "around" your actual tasks, you would create a class that takes care of it automatically:

为了避免重复所有这些总是“围绕”实际任务执行的冗余代码,您将创建一个自动处理它的类:

//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)

This idiom moves all of the complicated redundant code into one place, and leaves your main program much more readable (and maintainable!)

这个习惯用法将所有复杂的冗余代码移动到一个地方,并使您的主程序更具可读性(和可维护性!)

Take a look at this postfor a C# example, and this articlefor a C++ example.

看看这篇文章的 C# 示例,以及这篇文章的 C++ 示例。

回答by e.James

I see you have a Java tag here so I'll use Java as an example even though the pattern isn't platform-specific.

我看到这里有一个 Java 标记,所以我将使用 Java 作为示例,即使该模式不是特定于平台的。

The idea is that sometimes you have code that always involves the same boilerplate before you run the code and after you run the code. A good example is JDBC. You always grab a connection and create a statement (or prepared statement) before running the actual query and processing the result set, and then you always do the same boilerplate cleanup at the end--closing the statement and connection.

这个想法是有时您的代码在运行代码之前和运行代码之后总是涉及相同的样板。一个很好的例子是 JDBC。在运行实际查询和处理结果集之前,您总是获取一个连接并创建一个语句(或准备好的语句),然后您总是在最后执行相同的样板清理——关闭语句和连接。

The idea with execute-around is that it's better if you can factor out the boilerplate code. That saves you some typing, but the reason is deeper. It's the don't-repeat-yourself (DRY) principle here--you isolate the code to one location so if there's a bug or you need to change it, or you just want to understand it, it's all in one place.

execute-around 的想法是,如果您能将样板代码分解出来会更好。这可以为您节省一些打字时间,但原因更深。这是不重复自己 (DRY) 原则——您将代码隔离到一个位置,因此如果存在错误或您需要更改它,或者您只是想了解它,这一切都在一个地方。

The thing that's a little tricky with this kind of factoring-out though is that you have references that both the "before" and "after" parts need to see. In the JDBC example this would include the Connection and (Prepared)Statement. So to handle that you essentially "wrap" your target code with the boilerplate code.

但是,这种分解有点棘手的是,您有“之前”和“之后”部分都需要查看的引用。在 JDBC 示例中,这将包括 Connection 和 (Prepared)Statement。所以为了处理这个问题,你本质上是用样板代码“包装”你的目标代码。

You may be familiar with some common cases in Java. One is servlet filters. Another is AOP around advice. A third is the various xxxTemplate classes in Spring. In each case you have some wrapper object into which your "interesting" code (say the JDBC query and result set processing) is injected. The wrapper object does the "before" part, invokes the interesting code and then does the "after" part.

您可能熟悉 Java 中的一些常见情况。一种是servlet 过滤器。另一个是围绕建议的 AOP。第三个是 Spring 中的各种 xxxTemplate 类。在每种情况下,您都有一些包装器对象,您的“有趣”代码(例如 JDBC 查询和结果集处理)被注入其中。包装器对象执行“之前”部分,调用有趣的代码,然后执行“之后”部分。

回答by Brian

This reminds me of the strategy design pattern. Notice that the link I pointed to includes Java code for the pattern.

这让我想起了策略设计模式。请注意,我指向的链接包含该模式的 Java 代码。

Obviously one could perform "Execute Around" by making initialization and cleanup code and just passing in a strategy, which will then always be wrapped in initialization and cleanup code.

显然,可以通过编写初始化和清理代码并只传递一个策略来执行“周围执行”,然后策略将始终包含在初始化和清理代码中。

As with any technique used to reduce code repetition, you should not use it until you have at least 2 cases where you need it, perhaps even 3 (a la the YAGNI principle). Keep in mind that the removing code repetition reduces maintenance (fewer copies of code means less time spent copying fixes across each copy), but also increases maintenance (more total code). Thus, the cost of this trick is that you are adding more code.

与任何用于减少代码重复的技术一样,在至少有 2 个需要它的情况下才应该使用它,甚至可能有 3 个(按照 YAGNI 原则)。请记住,删除代码重复会减少维护(更少的代码副本意味着在每个副本中复制修复所花费的时间更少),但也会增加维护(更多的总代码)。因此,这个技巧的代价是你添加了更多的代码。

This type of technique is useful for more than just initialization and cleanup. It's also good for when you want to make it easier to call your functions (e.g. you could use it in a wizard so that the "next" and "previous" buttons don't need giant case statements to decide what to do to go to the next/previous page.

这种类型的技术不仅仅用于初始化和清理。当您希望更轻松地调用您的函数时,它也很有用(例如,您可以在向导中使用它,以便“下一个”和“上一个”按钮不需要巨大的 case 语句来决定要执行的操作下一页/上一页。

回答by Florin

If you want groovy idioms, here it is:

如果你想要时髦的习语,这里是:

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

回答by Ben Liblit

See also Code Sandwiches, which surveys this construct across many programming languages and offers some interesting research'y ideas. Concerning the specific question of why one might use it, the above paper offers some concrete examples:

另请参阅Code Sandwiches,它在许多编程语言中调查了这种结构,并提供了一些有趣的研究想法。关于为什么要使用它的具体问题,上面的论文提供了一些具体的例子:

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.

每当程序操作共享资源时就会出现这种情况。用于锁、套接字、文件或数据库连接的 API 可能需要程序显式关闭或释放它先前获取的资源。在没有垃圾收集的语言中,程序员负责在使用前分配内存并在使用后释放它。通常,各种编程任务要求程序进行更改,在该更改的上下文中操作,然后撤消更改。我们称这种情况为代码三明治。

And later:

然后:

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.

代码三明治出现在许多编程情况中。几个常见的例子与稀缺资源的获取和释放有关,例如锁、文件描述符或套接字连接。在更一般的情况下,程序状态的任何临时更改都可能需要代码三明治。例如,基于 GUI 的程序可能会暂时忽略用户输入,或者操作系统内核可能会暂时禁用硬件中断。在这些情况下未能恢复早期状态将导致严重的错误。

The paper does not explore why notto use this idiom, but it does describe why the idiom is easy to get wrong without language-level help:

论文没有探讨为什么使用这个习语,但它确实描述了为什么这个习语在没有语言层面的帮助的情况下很容易出错:

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 bodycode, new control paths may arise that bypass the aftercode. In the simplest case, a maintainer need only add a returnstatement to a sandwich's bodyto introduce a new defect, which may lead to silent errors. When the bodycode is large and beforeand afterare widely separated, such mistakes can be hard to detect visually.

在存在异常及其相关的不可见控制流时,最常出现有缺陷的代码三明治。事实上,管理代码三明治的特殊语言特性主要出现在支持异常的语言中。

然而,异常并不是造成代码三明治缺陷的唯一原因。每当对主体代码进行更改时,可能会出现绕过代码的新控制路径。在最简单的情况下,维护者只需要return在三明治的主体中添加一条语句来引入新的缺陷,这可能会导致无声错误。当身体代码是大和之前之后被广泛分离,这种错误可能很难在视觉上检测。

回答by BKSpurgeon

I'll try to explain, as I would to a four year old:

我会试着解释,就像我对一个四岁的孩子一样:

Example 1

示例 1

Santa's coming to town. His elves code whatever they want behind his back, and unless they change things get a little repetitive:

圣诞老人要进城了。他的精灵在他背后编码他们想要的任何东西,除非他们改变,否则事情会变得有点重复:

  1. Get wrapping paper
  2. Get Super Nintendo.
  3. Wrap it.
  1. 获取包装纸
  2. 获得超级任天堂
  3. 包起来。

Or this:

或这个:

  1. Get wrapping paper
  2. Get Barbie Doll.
  3. Wrap it.
  1. 获取包装纸
  2. 得到芭比娃娃
  3. 包起来。

....ad nauseam a million times with a million different presents: notice that the only thing different is step 2. If step two is the only thing that is different, then why is Santa duplicating the code, i.e. why is he duplicating steps 1 and 3 one million times? A million presents means that he is needlessly repeating steps 1 and 3 a million times.

....令人作呕的一百万次不同的礼物:注意唯一不同的是第 2 步。如果第二步是唯一不同的,那么圣诞老人为什么要复制代码,即他为什么要复制步骤1 和 3 一百万次?一百万次礼物意味着他不必要地重复了一百万次第 1 步和第 3 步。

Execute around helps to solve that problem. and helps eliminate code. Steps 1 and 3 are basically constant, allowing for step 2 to be the only part that changes.

Execute around 有助于解决这个问题。并有助于消除代码。步骤 1 和 3 基本上是不变的,因此步骤 2 是唯一发生变化的部分。

Example #2

示例#2

If you still don't get it, here is another example: think of a sandwhich: the bread on the outside is always the same, but what's on the inside changes depending on the type of sandwhich you choose (.e.g ham, cheese, jam, peanut butter etc). Bread is always on the outside and you don't need to repeat that a billion times for every type of sandwhich you are creating.

如果你还是不明白,这里有另一个例子:想想三明治:外面的面包总是一样的,但里面的东西会根据你选择的三明治类型而变化(例如火腿、奶酪、果酱、花生酱等)。面包总是在外面,你不需要为你创造的每种类型的沙子重复十亿次。

Now if you read the above explanations, perhaps you will find it easier to understand. I hope this explanation helped you.

现在如果你读了上面的解释,也许你会发现它更容易理解。我希望这个解释对你有帮助。