Mah*_*ani 2 refactoring functional-programming naming-conventions java-8
我已经创建了一个基于Bean属性动态构建rest URI的方法,最初我必须将它重构为函数式,这是我第一次进行函数式编程.命令式和功能性都按预期工作,但我对功能可读性,功能性接缝以及此方法的过度杀戮感到不满意,或者可能是因为我仍然是新手功能程序员!
您如何将此方法重构为更清晰的功能方式?
或者你会保持它势在必行吗?
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.lang.reflect.Method;
import org.springframework.beans.BeanUtils;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.util.UriComponentsBuilder;
public String functionalBuildRestUri() throws Exception {
final UriComponentsBuilder uriBuilder = UriComponentsBuilder.newInstance().scheme("https")
.host("foo.com").path("/offers");
//here is the functional
List<PropertyDescriptor> propDescList = Arrays.asList(BeanUtils.getPropertyDescriptors(getClass()));
//this part is readable and precis, but to enable it had to add 4 methods
propDescList.stream().filter(notClassProp())
.filter(notNullPropValue())
.collect(Collectors.toMap(PropertyDescriptor::getName, propValue()))//conversion to map doesn't feel good to me how can I avoid it?
.forEach(buildRestParam(uriBuilder));
return uriBuilder.build().toUriString();
}
public String imperativeBuildRestUri() throws Exception {
final UriComponentsBuilder uriBuilder = UriComponentsBuilder.newInstance().scheme("https")
.host("foo.com").path("/offers");
PropertyDescriptor[] propDescArray = BeanUtils.getPropertyDescriptors(getClass());
for (PropertyDescriptor propDesc : propDescArray) {
String propName = propDesc.getName();
if (!propName.equals("class")) {
Method getPropMethod = propDesc.getReadMethod();
Object propValue = getPropMethod.invoke(this);
if (propValue != null) {
if(propValue instanceof Date){
String dateStr = new SimpleDateFormat(DATE_FORMAT).format((Date)propValue);
uriBuilder.queryParam(propName, ":"+dateStr);
}else{
uriBuilder.queryParam(propName, propValue);
}
}
}
}
return uriBuilder.build().toUriString();
}
Run Code Online (Sandbox Code Playgroud)
所有这些方法都是在功能重构之后添加的
// I couldn't avoid being imperative here, how can we refactor it to more functional style
private BiConsumer<String, Object> buildRestParam(final UriComponentsBuilder uriBuilder) {
return (propName, propValue) -> {
if (propValue instanceof Date) {
String dateStr = new SimpleDateFormat(DATE_FORMAT).format((Date) propValue);
uriBuilder.queryParam(propName, ":" + dateStr);
} else {
uriBuilder.queryParam(propName, propValue);
}
};
}
private Predicate<? super PropertyDescriptor> notNullPropValue() {
return propDesc -> {
return propValue().apply(propDesc) != null;
};
}
private Predicate<? super PropertyDescriptor> notClassProp() {
return propDesc -> {
return !propDesc.getName().equals("class");
};
}
private Function<? super PropertyDescriptor, ? extends Object> propValue() {
return (propDesc) -> {
try {
return propDesc.getReadMethod().invoke(HotelOfferSearchCommand.this);
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
e.printStackTrace();
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
};
}
Run Code Online (Sandbox Code Playgroud)
新代码的大部分冗长都与函数式编程无关.你已经重构了代码,将每个lambda表达式放入它自己的方法中,这当然会破坏lambda表达式的一个主要优点,即紧凑性.即使代码足够复杂以证明方法的创建,该方法应该执行实际工作,然后,您可以使用需要函数的方法引用.
这些方法进一步遭受了使用外卡的不必要的(甚至是沮丧的,如在返回类型中).你还用详细的语法parameter -> { return expression; }在那里parameter -> expression将是可能的.
还有其他问题,比如不必要地catch为每个异常类型创建一个distinct 子句,当所有这些都执行相同操作或将数组包装到a List之前,Stream而不是直接在数组上流式传输或者使用代码重复时,最后一点适用于两者,命令式变体和功能性变体.
你可以写:
public String functionalBuildRestUri() throws Exception {
final UriComponentsBuilder uriBuilder = UriComponentsBuilder.newInstance()
.scheme("https").host("foo.com").path("/offers");
Function<PropertyDescriptor, Object> propValue = propDesc -> {
try { return propDesc.getReadMethod().invoke(HotelOfferSearchCommand.this); }
catch(ReflectiveOperationException e) { throw new RuntimeException(e); }
};
Arrays.stream(BeanUtils.getPropertyDescriptors(getClass()))
.filter(propDesc -> !propDesc.getName().equals("class"))
.filter(propDesc -> propValue.apply(propDesc) != null)
.forEach(propDesc -> {
Object value = propValue.apply(propDesc);
if (value instanceof Date)
value = ":"+new SimpleDateFormat(DATE_FORMAT).format(value);
uriBuilder.queryParam(propDesc.getName(), value);
});
return uriBuilder.build().toUriString();
}
Run Code Online (Sandbox Code Playgroud)
没有任何额外的方法.
这可能不是最好的选择,因为确实有一个缺陷,缺少一个元组或对类型来保存两个值以通过流.通过使用Map.Entry作为替代,但不填充a Map,我们可以将操作表达为
public String functionalBuildRestUri() throws Exception {
final UriComponentsBuilder uriBuilder = UriComponentsBuilder.newInstance()
.scheme("https").host("foo.com").path("/offers");
Function<PropertyDescriptor, Object> propValue = propDesc -> {
try { return propDesc.getReadMethod().invoke(HotelOfferSearchCommand.this); }
catch(ReflectiveOperationException e) { throw new RuntimeException(e); }
};
Arrays.stream(BeanUtils.getPropertyDescriptors(getClass()))
.filter(propDesc -> !propDesc.getName().equals("class"))
.map(propDesc -> new AbstractMap.SimpleImmutableEntry<>(
propDesc.getName(), propValue.apply(propDesc)))
.filter(entry -> entry.getValue() != null)
.forEach(entry -> {
Object value = entry.getKey();
if (value instanceof Date)
value = ":"+new SimpleDateFormat(DATE_FORMAT).format(value);
uriBuilder.queryParam(entry.getKey(), value);
});
return uriBuilder.build().toUriString();
}
Run Code Online (Sandbox Code Playgroud)
或者,或者
Arrays.stream(BeanUtils.getPropertyDescriptors(getClass()))
.filter(propDesc -> !propDesc.getName().equals("class"))
.map(propDesc -> new AbstractMap.SimpleImmutableEntry<>(
propDesc.getName(), propValue.apply(propDesc)))
.filter(entry -> entry.getValue() != null)
.map(e -> e.getValue() instanceof Date?
new AbstractMap.SimpleImmutableEntry<>(e.getKey(),
":"+new SimpleDateFormat(DATE_FORMAT).format(e.getValue())):
e)
.forEach(entry -> uriBuilder.queryParam(entry.getKey(), entry.getValue()));
Run Code Online (Sandbox Code Playgroud)
使用这两个变体,propValue函数每个元素只评估一次,而不是第一个变体和原始代码中的两次,其中检查null属性值和终端操作都对它进行评估.
请注意,仍有改进的余地,例如,当您可以首先将冒号作为格式模式字符串的一部分时,没有理由":"在format操作之后添加.
这是否是对循环的改进,是你必须自己决定的事情.并非每个代码都必须重写为功能样式.至少,如上面的例子所示,它不必大于命令式代码......