当前位置:  开发笔记 > 编程语言 > 正文

什么是"执行"成语?

如何解决《什么是"执行"成语?》经验,为你挑选了5个好方法。

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



1> Jon Skeet..:

基本上,这是你编写方法来执行总是需要的事情的模式,例如资源分配和清理,并使调用者传入"我们想要对资源做什么".例如:

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 with try-with-resourcesAutoClosablestreams开始处理.

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


@Phil:我认为这是程度问题.Java匿名内部类可以在有限的意义上访问它们周围的环境* - 所以当它们不是"完全"闭包时,我会说它们是"有限"的闭包.我当然希望在Java中看到正确的闭包,尽管已经检查过(续)
这是确定性的.Java中的终结器不是确定性的.正如我在最后一段中所说,它不仅仅用于资源分配和清理.它可能根本不需要创建新对象.它通常是"初始化和拆除",但可能不是资源分配.
所以它就像在C中你有一个函数,你传递一个函数指针来做一些工作?
另外,Jon,你指的是Java中的闭包 - 它仍然没有(除非我错过了它).你所描述的是匿名的内部类 - 它们并不完全相同.真正的闭包支持(正如已经提出的 - 参见我的博客)将大大简化该语法.
Java 7添加了try-with-resource,Java 8添加了lambdas.我知道这是一个古老的问题/答案,但我想在五年半之后为任何看这个问题的人指出这一点.这两种语言工具都有助于解决此模式发明修复的问题.

2> e.James..:

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



3> Bill Karwin..:

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

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

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

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



4> Willie Wheel..:

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

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

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

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

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



5> Ben Liblit..:

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

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

然后:

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

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

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

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

推荐阅读
TXCWB_523
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有