gpt4 book ai didi

java - 创建模拟库

转载 作者:行者123 更新时间:2023-12-01 22:52:32 25 4
gpt4 key购买 nike

我想创建一个实现 InvocationHandler 的模拟库类来自 Java 反射的接口(interface)。

这是我创建的模板:

import java.lang.reflect.*;
import java.util.*;

class MyMock implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// todo
}

public MyMock when(String method, Object[] args) {
// todo
}

public void thenReturn(Object val) {
// todo
}
}

when 和 thenReturn 方法是链式方法。

然后when方法注册给定的模拟参数。

thenReturn方法为给定的模拟参数注册预期的返回值。

此外,如果代理接口(interface)调用方法或使用未注册的参数,我想抛出 java.lang.IllegalArgumentException。

这是一个示例界面:

interface CalcInterface {
int add(int a, int b);
String add(String a, String b);
String getValue();
}

这里我们有两个重载 add方法。

这是一个测试我想要实现的模拟类的程序。

class TestApplication {     
public static void main(String[] args) {
MyMock m = new MyMock();
CalcInterface ref = (CalcInterface) Proxy.newProxyInstance(MyMock.class.getClassLoader(), new Class[]{CalcInterface.class}, m);

m.when("add", new Object[]{1,2}).thenReturn(3);
m.when("add", new Object[]{"x","y"}).thenReturn("xy");

System.out.println(ref.add(1,2)); // prints 3
System.out.println(ref.add("x","y")); // prints "xy"
}
}

这是我目前为检查 CalcInterface 中的方法而实现的代码:

class MyMock implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
int n = args.length;
if(n == 2 && method.getName().equals("add")) {
Object o1 = args[0], o2 = args[1];
if((o1 instanceof String) && (o2 instanceof String)) {
String s1 = (String) o1, s2 = (String) o2;
return s1+ s2;
} else if((o1 instanceof Integer) && (o2 instanceof Integer)) {
int s1 = (Integer) o1, s2 = (Integer) o2;
return s1+ s2;
}
}
throw new IllegalArgumentException();
}

public MyMock when(String method, Object[] args) {
return this;
}

public void thenReturn(Object val) {

}
}

这里我只检查名称为 add 的方法并且有 2 个参数,它们的类型为 StringInteger .

但我想创建这个 MyMock以一般方式上课,支持不同的接口(interface),而不仅仅是CalcInterface ,并且还支持不同的方法,而不仅仅是 add我在这里实现的方法。

最佳答案

您必须将构建器 逻辑与要构建的对象分开。 when 方法必须返回一些记住参数的东西,以便 thenReturn 的调用仍然知道上下文。

例如

public class MyMock implements InvocationHandler {
record Key(String name, List<?> arguments) {
Key { // stream().toList() creates an immutable list allowing null
arguments = arguments.stream().toList();
}
Key(String name, Object... arg) {
this(name, arg == null? List.of(): Arrays.stream(arg).toList());
}
}
final Map<Key, Function<Object[], Object>> rules = new HashMap<>();

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
var rule = rules.get(new Key(method.getName(), args));
if(rule == null) throw new IllegalStateException("No matching rule");
return rule.apply(args);
}
public record Rule(MyMock mock, Key key) {
public void thenReturn(Object val) {
var existing = mock.rules.putIfAbsent(key, arg -> val);
if(existing != null) throw new IllegalStateException("Rule already exist");
}
public void then(Function<Object[], Object> f) {
var existing = mock.rules.putIfAbsent(key, Objects.requireNonNull(f));
if(existing != null) throw new IllegalStateException("Rule already exist");
}
}
public Rule when(String method, Object... args) {
Key key = new Key(method, args);
if(rules.containsKey(key)) throw new IllegalStateException("Rule already exist");
return new Rule(this, key);
}
}

这已经能够按字面执行您的示例,但也支持类似

MyMock m = new MyMock();
CalcInterface ref = (CalcInterface) Proxy.newProxyInstance(
CalcInterface.class.getClassLoader(), new Class[]{CalcInterface.class}, m);

m.when("add", 1,2).thenReturn(3);
m.when("add", "x","y").thenReturn("xy");
AtomicInteger count = new AtomicInteger();
m.when("getValue").then(arg -> "getValue invoked " + count.incrementAndGet() + " times");

System.out.println(ref.add(1,2)); // prints 3
System.out.println(ref.add("x","y")); // prints "xy"
System.out.println(ref.getValue()); // prints getValue invoked 1 times
System.out.println(ref.getValue()); // prints getValue invoked 2 times

请注意,当您想添加对简单值匹配之外的规则的支持时,散列查找将不再起作用。在这种情况下,您必须求助于一种数据结构,您必须线性搜索匹配项。

上面的示例使用了较新的 Java 功能,如 record 类,但如果需要,为以前的 Java 版本重写它应该不会太难。


也可以重新设计此代码以使用真正的构建器模式,即在创建实际处理程序/模拟实例之前使用构建器来描述配置。这允许处理程序/模拟使用不可变状态:

public class MyMock2 {
public static Builder builder() {
return new Builder();
}
public interface Rule {
Builder thenReturn(Object val);
Builder then(Function<Object[], Object> f);
}
public static class Builder {
final Map<Key, Function<Object[], Object>> rules = new HashMap<>();

public Rule when(String method, Object... args) {
Key key = new Key(method, args);
if(rules.containsKey(key))
throw new IllegalStateException("Rule already exist");
return new RuleImpl(this, key);
}
public <T> T build(Class<T> type) {
Map<Key, Function<Object[], Object>> rules = Map.copyOf(this.rules);
return type.cast(Proxy.newProxyInstance(type.getClassLoader(),
new Class[]{ type }, (proxy, method, args) -> {
var rule = rules.get(new Key(method.getName(), args));
if(rule == null) throw new IllegalStateException("No matching rule");
return rule.apply(args);
}));

}
}
record RuleImpl(MyMock2.Builder builder, Key key) implements Rule {
public Builder thenReturn(Object val) {
var existing = builder.rules.putIfAbsent(key, arg -> val);
if(existing != null) throw new IllegalStateException("Rule already exist");
return builder;
}
public Builder then(Function<Object[], Object> f) {
var existing = builder.rules.putIfAbsent(key, Objects.requireNonNull(f));
if(existing != null) throw new IllegalStateException("Rule already exist");
return builder;
}
}
record Key(String name, List<?> arguments) {
Key { // stream().toList() createns an immutable list allowing null
arguments = arguments.stream().toList();
}
Key(String name, Object... arg) {
this(name, arg == null? List.of(): Arrays.stream(arg).toList());
}
}
}

可以像这样使用

AtomicInteger count = new AtomicInteger();
CalcInterface ref = MyMock2.builder()
.when("add", 1,2).thenReturn(3)
.when("add", "x","y").thenReturn("xy")
.when("getValue")
.then(arg -> "getValue invoked " + count.incrementAndGet() + " times")
.build(CalcInterface.class);

System.out.println(ref.add(1,2)); // prints 3
System.out.println(ref.add("x","y")); // prints "xy"
System.out.println(ref.getValue()); // prints getValue invoked 1 times
System.out.println(ref.getValue()); // prints getValue invoked 2 times

关于java - 创建模拟库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73999625/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com