Functional programming Java8函数式与命令式方法

Functional programming Java8函数式与命令式方法,functional-programming,java-8,refactoring,naming-conventions,Functional Programming,Java 8,Refactoring,Naming Conventions,我已经创建了一个基于Bean属性动态构建rest-URI的方法,最初它是必需的,然后我将它重构为函数式风格,这是我第一次做函数式编程。 命令式和函数式都按预期工作,但我对函数的可读性不满意,函数式接缝是这种方法的致命一击,或者可能是因为我还是一个函数式程序员新手 您将如何将此方法重构为更干净的函数方式 或者你会坚持这一点吗 import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetExceptio

我已经创建了一个基于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();
}
导入java.beans.PropertyDescriptor;
导入java.lang.reflect.InvocationTargetException;
导入java.text.simpleDataFormat;
导入java.util.array;
导入java.util.Date;
导入java.util.List;
导入java.util.function.BiConsumer;
导入java.util.function.function;
导入java.util.function.Predicate;
导入java.util.stream.collector;
导入java.lang.reflect.Method;
导入org.springframework.beans.BeanUtils;
导入org.springframework.format.annotation.DateTimeFormat;
导入org.springframework.web.util.UriComponentsBuilder;
公共字符串函数lbuildresturi()引发异常{
最终UriComponentsBuilder uriBuilder=UriComponentsBuilder.newInstance().scheme(“https”)
.host(“foo.com”).path(“/offers”);
//这是功能性的
List-propDescList=Arrays.asList(BeanUtils.getPropertyDescriptors(getClass());
//这一部分是可读的和精确的,但是为了使它能够使用,必须添加4个方法
propDescList.stream().filter(notClassProp())
.filter(notNullPropValue())
.collect(Collectors.toMap(PropertyDescriptor::getName,propValue())//转换为map让我感觉不太好,我怎么能避免呢?
.forEach(buildrestpram(uriBuilder));
返回uriBuilder.build().toUriString();
}
公共字符串命令ebuildresturi()引发异常{
最终UriComponentsBuilder uriBuilder=UriComponentsBuilder.newInstance().scheme(“https”)
.host(“foo.com”).path(“/offers”);
PropertyDescriptor[]propDescArray=BeanUtils.getPropertyDescriptors(getClass());
对于(PropertyDescriptor propDesc:propDescArray){
字符串propName=propDesc.getName();
如果(!propName.equals(“类”)){
方法getPropMethod=propDesc.getReadMethod();
Object propValue=getPropMethod.invoke(此);
if(propValue!=null){
if(propValue instanceof Date){
字符串dateStr=新的SimpleDataFormat(日期\格式).FORMAT((日期)propValue);
queryParam(propName,“:”+dateStr);
}否则{
queryParam(propName、propValue);
}
}
}
}
返回uriBuilder.build().toUriString();
}
所有这些方法都是在函数重构之后添加的

// 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);
        }
    };
}
//我无法避免在这里使用命令,我们如何将其重构为更具功能性的样式
专用双消费者buildRestParam(最终UriComponentsBuilder uriBuilder){
返回(propName,propValue)->{
if(propValue instanceof Date){
字符串dateStr=新的SimpleDataFormat(日期\格式).FORMAT((日期)propValue);
queryParam(propName,“:”+dateStr);
}否则{
queryParam(propName、propValue);
}
};
}

私有谓词新代码的大部分冗长与函数式编程无关。您已经重构了代码,将每个lambda表达式放入它自己的方法中,这当然破坏了lambda表达式的主要优点之一,即紧凑性。即使代码复杂到足以证明创建方法的合理性,该方法也应该执行实际工作,然后,您可以在需要函数的地方使用方法引用

这些方法还受到不必要的通配符使用的影响(甚至不鼓励使用返回类型)。您还使用了详细语法
parameter->{return expression;}
,其中可以使用
parameter->expression

还有其他问题,如为每个异常类型不必要地创建一个不同的
catch
子句,当所有异常类型都执行相同的操作时,或者在创建
流之前将数组包装到
列表中,而不是直接在数组上进行流式处理或进行代码复制,最后一点适用于这两种类型,命令式变体和功能性变体

你可以写:

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();
}
或者,或者

    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()));
Arrays.stream(BeanUtils.getPropertyDescriptors(getClass()))
.filter(propDesc->!propDesc.getName().equals(“类”))
.map(propDesc->new AbstractMap.SimpleImmutableEntry(
propDesc.getName(),propValue.apply(propDesc)))
.filter(entry->entry.getValue()!=null)
.map(e->e.getValue()实例的日期?
新的AbstractMap.SimpleImmutableEntry(e.getKey(),
“:”+新的SimpleDataFormat(日期\格式)。格式(e.getValue()):
(e)
.forEach(entry->uriBuilder.queryParam(entry.getKey(),entry.getValue());
对于这两个变量,每个元素只对
propValue
函数求值一次,而不是第一个变量和原始代码中的两次,其中,检查
null
属性值和终端操作都对其求值

请注意,仍有改进的余地,例如,当您可以首先将冒号作为格式模式字符串的一部分时,没有理由在
格式
操作之后添加
:“

这是否是对循环的改进,是你自己决定的事情。并非所有代码都必须重写为函数式。至少,正如上面的例子所示,它不必比impe大
    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()));