Observer使用Lambda寫法對通知的例外處理

這是一段常見的Observer寫法的程式碼,FileWatcher是Subject負責確認檔案是否修改,而IFileChangedListener是Observer接收檔案改變通知;當檔案改變時,FileWatcher會執行doOnChange通知各個IFileChangedListener:

public class FileWatcher extends FileWatchdog {

	private List<IFileChangedListener> listeners = new ArrayList<>();
	static private Logger logger = LoggerFactory.getLogger(FileWatcher.class);

	public FileWatcher(String filename) {
		super(filename);
	}

	@Override
	protected void doOnChange() {
		try {
			if( listeners == null || listeners.isEmpty() )
				return;
			
			String newContent = new String(Files.readAllBytes(Paths.get(super.filename)));
			listeners.forEach(listener->listener.update(newContent));
		} catch( IOException e ){
			throw new RuntimeException(e);
		}
	}
	
	public void addListener(IFileChangedListener listener){
		listeners.add(listener);
	}
}
假設我有10個listener,當第三個listener執行update時發生了例外,工作是不是就會停止了呢? 答案是肯定的。本篇主要分享可能的例外處理方式。

最直接的方法就是把例外處理弄成一個區塊:

listeners.forEach(listener->{
	try {
		listener.update(newContent);
	} catch( Exception e ) {
		logger.warn("Update file info failed.", e);
	}
});
但隨著不同例外處理需求或流程,會變得難以閱讀而喪失原本Lambda的美意。

以Lambda方式實作時,將處理抽成method以增加可讀性是常使用的做法:

listeners.forEach(listener->updateEx(listener, newContent));
 
private void updateEx(IFileChangedListener listener, String newContent){
	try {
		listener.update(newContent);
	} catch( Exception e ) {
		logger.warn("Update file info failed.", e);
	}
}
接下來就是考慮重複使用的議題。

為了重複使用,我們可以將處理方式透過Wrapper方式實作並集中於Utility類別:

public class Errors {
	static private Logger logger = LoggerFactory.getLogger(Errors.class); 
	public static <T> Consumer<T> logException(Consumer<T> operation){
		return i -> {
			try {
				operation.accept(i);
			} catch (Exception e) {
				logger.warn("", e);
			}
		};
	}
}
 
listeners.forEach(Errors.logException(i->i.update(newContent)));
上面這種寫法只能針對unchecked exception,假如是checked exception,可以透過Functional Interface做自己的Consumer:
	@FunctionalInterface
	public interface ThrowingConsumer<T, E extends Exception> {
	    void accept(T t) throws E;
	}
 
	public static <T>  Consumer<T> logException(ThrowingConsumer<T, Exception> operation){
		return i -> {
			try {
				operation.accept(i);
			} catch (Exception e) {
				logger.warn("", e);
			}
		};
	}

假如不介意相依於第三方函式庫,可以參考durian。它針對lambda的functional interface提供了log、rethrow或者自訂義等例外處理方式,這部分之後我有時間會特別介紹。