使用函数式编程重构模板模式

19-01-30 banq
                   

为了实际说明模板模式在哪些情况下有用,我们假设我们有一个类如下的Resource类:

public class Resource {
    public Resource() {
        System.out.println("Resource created");
    }
     
    public void useResource() {
        riskyOperation();
        System.out.println("Resource used");
    }
    
    public void employResource() {
        riskyOperation();
        System.out.println("Resource employed");
    }
     
    public void dispose() {
        System.out.println("Resource disposed");
    }
     
    private void riskyOperation() {
        if ( new Random().nextInt( 10 ) == 0) {
            throw new RuntimeException();
        }
     }
 }

我们的Resouce有一个构造函数,一些访问它的方法以及另一个在不再使用该对象时如何处理它的方法,这些方法可能会像使用数据库或网络连接那样进行冒险操作,然后最终可能会失败抛出RuntimeException。这里通过从10中随机抛出异常来模拟失败的可能性。无论这些方法调用的结果如何,在完成使用资源之后调用dispose()是强制性的,以便释放连接和其他使用了工件,从而避免了内存,连接文件指针泄漏。在这种情况下,使用资源如下...

Resource resource = new Resource();
resource.useResource();
resource.employResource();
resource.dispose();

这是错误的, 因为useResoure()或employResource()方法可能最终抛出异常,然后阻止正确处理资源。显然,使用此Resouce的正确方法是这样的:

Resource resource = new Resource();
try {
    resource.useResource();
    resource.employResource();
} finally {
    resource.dispose();
}

问题是我们无法保证每次使用我们的资源都会按照这种模式正确处理。我们不希望冒被资源被滥用的可能性 - 或者我们希望提供一个API,强制Resource对象的客户端始终处置它。换句话说,我们希望确保资源的客户端始终按照以下模式使用它:

openResource();
try {
    doSomethingWithResource();
} finally {
   closeResource();
}

这里我们刚刚定义了一个代码模板,然后我们可以将它放在一个抽象类中:

public abstract class AbstractResourceManipulatorTemplate {
    protected Resource resource;
 
    private void openResource() {
        resource = new Resource();
    }
 
    protected abstract void doSomethingWithResource();
 
    private void closeResource() {
        resource.dispose();
        resource = null;
    }
 
    public void execute() {
        openResource();
        try {
            doSomethingWithResource();
        } finally {
            closeResource();
        }
    }
}

此抽象模板封装了我们要强制执行的使用模式,并提供了一种抽象方法,其中不同的具体实现可以定义如何与Resource进行交互。

public class ResourceUser extends AbstractResourceManipulatorTemplate {
    @Override
    protected void doSomethingWithResource() {
        resource.useResource();
    }
}

public class ResourceEmployer extends AbstractResourceManipulatorTemplate {
    @Override
    protected void doSomethingWithResource() {
        resource.employResource();
    }
}

通过这种方式,在这些具体实现上调用execute()会在Resource上执行各自doSomethingWithResource()方法体中定义的操作,同时确保每次使用它的资源也将被正确处理。

new ResourceUser().execute();
new ResourceEmployer().execute();

提供模板来强制我们的资源的客户端以正确的方式使用它的想法是正确的,但正如我们已经看到的其他模式,GoF书中描述的纯OOP实现是冗长和繁琐的。使用单个方法接受资源消费者,可以直接获得相同的结果。

public static void withResource( Consumer<Resource> consumer) {
    Resource resource = new Resource();
    try {
        consumer.accept( resource );
    } finally {
        resource.dispose();
    }
}

请注意,此方法的主体等同于抽象模板的execute()方法所做的,唯一的区别是模板的抽象方法提供的自由度现在通过传递给withResouce()方法的Consumer实现。 。然后,可以用简单的lambda表达式替换定义如何使用Resource的具体模板实现。

withResource( resource -> resource.useResource() );
withResource( resource -> resource.employResource() );