The Idea
Java 8 introduced functional programming support, this is a powerful feature which was missing from earlier versions. One of the benefits of functional programming is that it can be used to implement decorator pattern without the use of inheritance. One common requirement is to implement some kind of rate limiting for web services. Now, ideally you would want separation of concerns between the actual business logic and rate limitation logic. With Java 8, we can use function references to implement this separation of concerns and implement the decorator pattern.
The code
The code fragment below shows the implementation of the pattern. It is an example of integration with the Lyft API. The full source code is available here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | /** * Generic method which can invoke any function without applying rate limit * * @param method the function to invoke or apply the each map input to * @param inputList The list of Maps, each of which contains the key value pair of service parameters * @param <R> Generic Return object type in the list * @param <K> Type of Key in Map * @param <V> Type of Value in Map * @return A list with object type <V> */ private <R, K, V> List<R> invokeWithoutRateLimit(Function<Map, R> method, List<Map<K, V>> inputList) { List<R> returnList = new ArrayList<>(); inputList.stream().forEach(m -> { returnList.add(method.apply(m)); }); return returnList; } /** * Generic method which can invoke any function with applying rate limit * It uses RxJava and Blocking invocation * * @param method the function to invoke or apply the each map input to * @param inputList The list of Maps, each of which contains the key value pair of service parameters * @param <R> Generic Return object type in the list * @param <K> Type of Key in Map * @param <V> Type of Value in Map * @return A list with object type <V> */ private <R, K, V> List<R> invokeWithRateLimit(Function<Map, R> method, List<Map<K, V>> inputList) { List<R> returnList = new ArrayList<>(); Observable.zip(Observable.from(inputList), Observable.interval(RATE_LIMIT, TimeUnit.SECONDS), (obs, timer) -> obs) .doOnNext(item -> { R result = method.apply(item); returnList.add(result); } ).toList().toBlocking().first(); return returnList; } /** * This method accepts a list of coordinates and returns the estimated * fare for different lyft rides * * @param costRequestList The list of coordinates * @param invokeWithRateLimit Apply Rate limiting * @return A list of Prices per request */ public List<CostEstimates> getCostEstimates(List<Map<String, Float>> costRequestList, boolean invokeWithRateLimit) { if (invokeWithRateLimit) { return invokeWithRateLimit(this::getCostEstimate, costRequestList); } else { return invokeWithoutRateLimit(this::getCostEstimate, costRequestList); } } |
The highlighted code above shows how to pass method reference to methods invokeWithRateLimit() and invokeWithoutRateLimit(), each of these methods then adds some custom preprocessing logic (like rate limitation using RxJava) after which it invokes the supplied method by using the apply() method. This implementation of the decorator pattern is much easier to grasp, than going via the inheritance route.
You can use the following link to view the entire code on Github repository.
Lyft-Client on Github.