在不更改原始方法

时间:2016-01-21 01:35:46

标签: java lambda pagination java-8 mockito

我有一个用例,我需要在不更改现有签名的情况下向现有服务调用(返回结果列表)添加分页输入(即页码和页面大小)(因为它会破坏现有客户端)。实现这一目标的一种方法是我们在threadlocal中设置输入,并让实现读取threadlocal并执行其分页逻辑。从代码的角度来看,它看起来像:

try {
    PaginationKit.setPaginationInput(pageSize, pageNumber); // Set threadlocal
    List<SpecialObject> results = specialService.getSpecialObjs(); //results count will be equal to pageSize value
} finally {
    PaginationKit.clearPaginationInput(); // Clear threadlocal
}

从客户的角度来看,这根本不是优雅的,我想把这个功能包装成一些更好的语法糖。我有两种方法,我想知道这是否是一个足够通用的用例,已经在其他地方解决了。有许多这样的服务,并试图为每个服务设置一个装饰器是不可取的。

方法1: 我喜欢Mockito.when(methodCall).thenReturn(result)种糖的模拟风格。 因此代码可能如下所示:

SpecialService specialService = PaginationDecorator.prepare(SpecialService.class); // Get a spy that is capable of forwarding calls to the actual instance
List<SpecialObject> results = PaginationDecorator.withPageSize(pageSize).onPage(pageNumber).get(specialService.getSpecialObjs()).get(); // The get() is added to clear the threadlocal

我试图从Mockito借用代码来创建间谍,但OngoingStubbing<T>接口在随后的调用链/创建代码中相互交织,并且闻到了我应该避免的东西。

方法2: 使用java.util.Function捕获方法调用并接受另外两个参数pageNumber和pageSize来使用threadlocals。代码可能看起来像

List<SpecialObject> results = PaginationDecorator.withPaging(specialService.getSpecialObjs(), pageSize, pageNumber);

PaginationDecorator.java:

public static List<T> withPaging(Function<U, List<T>> call, int pageSize, int pageNumber) {
    try {
        PaginationKit.setPaginationInput(pageSize, pageNumber); // Set threadlocal
        return call.apply(); // Clearly, something is missing here!
    } finally {
        PaginationKit.clearPaginationInput(); // Clear threadlocal
    }
}

我无法在此明确制定如何正确使用呼叫。

有人可以告诉我:

  • 这两个中哪一个是更好的方法
  • 如果可以使用其他方法作为其他地方的食谱
  • 建议实施方法1或2的方法。对我来说,个人#2看起来更干净(如果有效)。

随意批评这种方法并提前感谢阅读!

P.S。:我也很喜欢iterator recipe in this question,但仍然需要语法糖的主要问题。

3 个答案:

答案 0 :(得分:2)

您的第二个变体不起作用,因为您使用了错误的接口(Function需要输入参数)并且没有语法来创建函数实例,而只是一个普通的调用表达式。

你有几个选择

  1. 使用Supplier。该接口描述了一个没有参数的函数并返回一个值。

    public static <T> T withPaging(Supplier<T> call, int pageSize, int pageNumber) {
        try {
            PaginationKit.setPaginationInput(pageSize, pageNumber); // Set threadlocal
            return call.get();
        } finally {
            PaginationKit.clearPaginationInput(); // Clear threadlocal
        }
    }
    

    我们只是允许任何返回类型提高其多功能性,而不是坚持返回List<T>。它包括返回List某事物的可能性。

    然后,我们可以使用方法参考

    List<SpecialObject> results=PaginationDecorator.withPaging(
                        specialService::getSpecialObjs, pageSize, pageNumber);
    

    或lambda表达式:

    List<SpecialObject> results=PaginationDecorator.withPaging(
                        () -> specialService.getSpecialObjs(), pageSize, pageNumber);
    
  2. 保持Function,但允许调用者传递必需的参数

    public static <T,R> R withPaging(
        Function<T,R> call, T argument, int pageSize, int pageNumber) {
    
        try {
            PaginationKit.setPaginationInput(pageSize, pageNumber); // Set threadlocal
            return call.apply(argument);
        } finally {
            PaginationKit.clearPaginationInput(); // Clear threadlocal
        }
    }
    

    现在,调用者必须提供函数和值。由于预期的方法是实例方法,因此可以将接收器实例视为函数参数

    然后,可以再次指定该函数,作为(现在未绑定)方法引用

    List<SpecialObject> results=PaginationDecorator.withPaging(
            SpecialService::getSpecialObjs, specialService, pageSize, pageNumber);
    

    或lambda表达式:

    List<SpecialObject> results=PaginationDecorator.withPaging(
            ss -> ss.getSpecialObjs(), specialService, pageSize, pageNumber);
    
  3. 两者都有替代方案,使用AutoCloseable和try-with-resource,而不是try…finally。将助手类定义为:

    interface Paging extends AutoCloseable {
        void close();
        static Paging withPaging(int pageSize, int pageNumber) {
            PaginationKit.setPaginationInput(pageSize, pageNumber);
            return ()->PaginationKit.clearPaginationInput();
        }
    }
    

    并像

    一样使用它
    List<SpecialObject> results;
    try(Paging pg=Paging.withPaging(pageSize, pageNumber)) {
        results=specialService.getSpecialObjs();
    }
    

    优点是,这不会破坏有关您的预期操作的代码流,即与lambda表达式不同,您可以修改受保护代码中的所有局部变量。如果您忘记将withPaging的结果放在正确的try(…)语句中,最近的IDE也会发出警告。此外,如果抛出异常而另一个异常发生在清理中(即clearPaginationInput()),则与finally不同,辅助异常不会掩盖主要异常,而是通过addSuppressed进行记录。

    这就是我喜欢的。

答案 1 :(得分:1)

您的第二种方法似乎更简洁,更轻松。至于实现,您可以使用Java的Proxy类。这似乎很容易。我不知道任何库会以某种方式使它更容易。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class PagingDecorator {
    public static void setPaginationInput(int pageSize, int pageNumber) {
    }

    public static void clearPaginationInput() {
    }

    public static <T> T wrap(final Class<T> interfaceClass, final T object, final int pageSize, final int pageNumber) {
        if (object == null) {
            throw new IllegalArgumentException("argument shouldn't be null");
        }

        ClassLoader classLoader = object.getClass().getClassLoader();
        return (T) Proxy.newProxyInstance(classLoader, new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                setPaginationInput(pageSize, pageNumber);
                try {
                    return method.invoke(object, args);
                } catch (InvocationTargetException e) {
                    throw e.getTargetException();
                } catch (Exception e) {
                    throw e;
                } finally {
                    clearPaginationInput();
                }
            }
        });
    }

    public static <T> T wrap(final T object, final int pageSize, final int pageNumber) {
        if (object == null) {
            throw new IllegalArgumentException("argument shouldn't be null");
        }

        Class<?>[] iFaces = object.getClass().getInterfaces();

        //you can use all interfaces, when creating proxy, but it seems cleaner to only mock the concreate interface that you want..
        //unfortunately, you can't just grab T as interface here, becuase of Java's generics' mechanic
        if (iFaces.length != 1) {
            throw new IllegalArgumentException("Object implements more than 1 interface - use wrap() with explicit interface argument.");
        }

        Class<T> iFace = (Class<T>) iFaces[0];
        return wrap(iFace, object, pageSize, pageNumber);
    }

    public interface Some {
    }

    public static void main(String[] args) {
        Some s = new Some() {};

        Some wrapped1 = wrap(Some.class, s, 20, 20);
        Some wrapped2 = wrap(s, 20, 20);
    }
}

答案 2 :(得分:0)

您正在使用Java 8,对吗?也许你可以添加一个默认方法而不需要任何实现(听起来很奇怪,我知道),如下所示:

// Your current service interface
interface ServiceInterface <T> {

    // Existing method
    List<T> getObjects();

    // New method with pagination atributes
    default List<T> getObjects(int pageSize, int pageNumber) {
        throw new UnsupportedOperationException();
    }
}

新服务(使用分页支持)必须覆盖此方法。在我看来,这样你就可以保持简单&#34;。