使用函數語言程式設計重構模板模式

banq發表於2019-01-30

為了實際說明模板模式在哪些情況下有用,我們假設我們有一個類如下的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() );




 

相關文章