java - 什么是“执行”这个成语?

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

8个解决方案

138 votes

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

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-resources和AutoClosable流。

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

Jon Skeet answered 2019-05-09T20:53:19Z

44 votes

当你发现自己不得不做这样的事情时,会使用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 ++示例。

e.James answered 2019-05-09T20:54:03Z

7 votes

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

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

使用execute-around的想法是,如果你可以分解样板代码,那就更好了。 这可以节省你一些打字,但原因更深。 这里是不重复自己(DRY)的原则 - 你将代码隔离到一个位置,所以如果有错误或者你需要改变它,或者你只想了解它,它就在一个地方。

对于这种因子分解来说有点棘手的事情是你有“前”和“后”部分需要看到的参考。 在JDBC示例中,这将包括Connection和(Prepared)语句。 因此,为了处理您实际上使用样板代码“包装”目标代码。

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

Willie Wheeler answered 2019-05-09T20:54:57Z

7 votes

另请参阅Code Sandwiches,它跨许多编程语言调查这个结构,并提供一些有趣的研究想法。 关于为何可以使用它的具体问题,上述文件提供了一些具体的例子:

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

然后:

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

本文没有探究为什么不使用这个成语,但它确实描述了为什么没有语言级别的帮助,成语很容易出错:

有缺陷的代码三明治在出现时最常出现   异常及其相关的隐形控制流程。 确实,   管理代码三明治的特殊语言功能主要出现在   支持异常的语言。

但是,异常并不是代码有缺陷的唯一原因  三明治。 每当对正文代码进行更改时,都会有新的控制路径   可能会出现绕过后代码。 在最简单的情况下,a   维护者只需要在三明治的身体上添加一个return的声明   引入新缺陷,可能导致无声错误。 当身体   代码很大,前后分开很多,这样的错误   很难在视觉上检测到。

Ben Liblit answered 2019-05-09T20:56:04Z

6 votes

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

Java不是我选择这样做的语言。传递闭包(或lambda表达式)作为参数更为时髦。 虽然对象可以说相当于闭包。

在我看来,Execute Around方法有点像控制反转(依赖注入),每次调用方法时都可以随意改变。

但它也可以被解释为控制耦合的一个例子(告诉一个方法,通过它的论证做什么,在这种情况下确实如此)。

Bill Karwin answered 2019-05-09T20:56:48Z

3 votes

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

显然,可以通过创建初始化和清理代码并仅传入策略来执行“Execute Around”,然后将始终包含在初始化和清理代码中。

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

这种类型的技术不仅仅用于初始化和清理。 当你想让它更容易调用你的函数时也很好(例如你可以在向导中使用它,以便“下一个”和“前一个”按钮不需要巨大的case语句来决定如何去做 下一页/上一页。

Brian answered 2019-05-09T20:57:34Z

3 votes

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

例1

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

拿包装纸

得到超级任天堂。

包裹它。

或这个:

拿包装纸

得到芭比娃娃。

包裹它。

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

执行有助于解决该问题。 并帮助消除代码。 步骤1和3基本上是不变的,允许步骤2成为唯一改变的部分。

例#2

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

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

BKSpurgeon answered 2019-05-09T20:59:06Z

0 votes

如果你想要groovy成语,这里是:

//-- 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(); }
Florin answered 2019-05-09T20:59:30Z