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

发布于 2024-07-10 08:23:02 字数 53 浏览 7 评论 0原文

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

乖乖哒 2024-07-17 08:23:02

基本上,在这种模式中,您编写一个方法来执行始终需要的操作,例如资源分配和清理,并让调用者传入“我们想要对资源执行的操作”。 例如:

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 开始,lambda 表达式可以像许多其他语言一样实现(例如 C# lambda 表达式或 Groovy),并且从 Java 7 开始使用 try 来处理这种特殊情况-with-resourcesAutoClosable 流。

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

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.

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-resources and AutoClosable streams.

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 pattern but without inheritance.

ぽ尐不点ル 2024-07-17 08:23:02

当您发现自己必须执行以下操作时,可以使用“执行周围”习惯用法:

//... 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++ 示例。

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

//... 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 post for a C# example, and this article for a C++ example.

贪了杯 2024-07-17 08:23:02

执行周围方法是您将任意代码传递给方法的地方,该方法可以执行设置和/或拆解代码并在其间执行您的代码。

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

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

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

An Execute Around Method is 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.

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

把人绕傻吧 2024-07-17 08:23:02

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

只要程序操作共享资源,就会出现这种情况。
用于锁、套接字、文件或数据库连接的 API 可能需要
程序显式关闭或释放之前的资源
获得的。 在没有垃圾回收的语言中,程序员是
负责在使用内存之前分配内存并释放它
使用后。 一般来说,各种编程任务都需要
进行更改的计划,在该更改的背景下进行操作,以及
然后撤消更改。 我们将这种情况称为“代码三明治”。

然后:

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

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

有缺陷的代码三明治最常出现在存在以下情况的情况下:
异常及其相关的不可见控制流。 的确,
管理代码三明治的特殊语言功能主要出现在
支持异常的语言。

但是,异常并不是导致代码缺陷的唯一原因
三明治。 每当对 body 代码进行更改时,新的控制路径
可能会出现绕过after代码的情况。 在最简单的情况下,一个
维护者只需要在三明治的主体中添加一个return语句即可
引入新的缺陷,这可能会导致无声错误。 当身体
代码很大,之前之后相距很远,这样的错误
肉眼很难察觉。

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:

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.

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.

The paper does not explore why not to 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 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.

笔落惊风雨 2024-07-17 08:23:02

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

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

围绕执行的想法是,如果您可以分解出样板代码,那就更好了。 这可以节省您的打字时间,但原因更为深刻。 这里就是“不重复”(DRY)原则——您将代码隔离到一个位置,这样如果出现错误或者您需要更改它,或者您只是想理解它,它都在一个位置。

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

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

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

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.

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.

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.

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.

怀中猫帐中妖 2024-07-17 08:23:02

我将尝试解释,就像向四岁孩子解释一样:

示例 1

圣诞老人来到镇上。 他的精灵们在他背后编写他们想要的任何东西,除非他们改变,否则事情就会变得有点重复:

  1. 拿包装纸,
  2. 超级任天堂
  3. 包起来。

或者这样:

  1. 获取包装纸
  2. 获取芭比娃娃
  3. 包起来。

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

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

示例#2

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

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

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

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

Or this:

  1. Get wrapping paper
  2. Get Barbie Doll.
  3. Wrap it.

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

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.

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

霊感 2024-07-17 08:23:02

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

显然,可以通过制作初始化和清理代码并仅传递策略来执行“执行周围”,然后该策略将始终包装在初始化和清理代码中。

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

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

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

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.

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.

饭团 2024-07-17 08:23:02

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

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

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(); }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文