1

我已經創建了一個動態構建基於Bean屬性的靜態URI的方法,最初它非常必要,然後我將它重構爲函數式樣,這是我第一次進行函數式編程。 這兩個命令和功能都在按預期工作,但是我對功能可讀性不滿意,功能接近一個超過此方法的殺死率,或者它可能是因爲我仍然是新手功能程序員!Java 8 Functional VS Imperative方法

你會如何重構這種方法更清潔的功能方式?

或者你會保持它的命令?功能重構

// 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); 
     } 
    }; 
} 

回答

5

大部分新代碼的冗長無關函數式編程後

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

所有這些方法已被添加。你已經重構了代碼,將每個lambda表達式放入它自己的方法中,當然,這破壞了lambda表達式的一個主要優點,即緊湊性。即使代碼足夠複雜以證明方法的創建,該方法應該執行實際工作,然後,可以在需要函數的地方使用方法引用。

該方法進一步受到不必要的(甚至不鼓勵,因爲在返回類型中)使用通配符。您還使用了詳細語法parameter -> { return expression; },其中parameter -> expression將是可能的。

還有其他問題,如不必要地創建用於每個異常類型的獨特catch子句,當所有做同樣的或創建Stream而不是直接流陣列之上或具有代碼重複之前包裹所述陣列分成List,所述最後一點適用於命令式和功能式。

你可以這樣寫:

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

沒有任何額外的方法。

這可能不是最好的選擇,因爲確實存在一個缺陷,即沒有元組或對的類型來保存要通過流的兩個值。通過使用Map.Entry作爲替身,而不是填充一個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(); 
} 

,或者

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())); 

有了這兩個變種,該propValue功能只計算一次每個元素,而不是第一個變體中的兩倍,以及您的原始代碼,其中兩項均爲null屬性值的檢查以及終端操作對其進行評估。

請注意,還有改進的餘地,例如,沒有理由在format操作之後添加":",因爲您可以首先將冒號作爲格式模式字符串的一部分。

這是否是對循環的改進,是您必須自行決定的。並非每個代碼都必須重寫爲功能樣式。至少,正如上面的例子所示,它不必比命令式代碼更大......

+0

非常好的信息!由於'propValue.apply(propDesc)'在最後2個選項中只被調用一次,所以消除變量'propValue'並使用Lambda表達式可能會更好,您怎麼看? –

+0

'.MAP(propDesc - > { \t \t \t嘗試{ \t \t \t \t \t \t對象值= propDesc.getReadMethod()調用(HotelOfferSearchCommand.this); \t \t \t \t \t \t返回新AbstractMap.SimpleImmutableEntry <>(propDesc.getName(),值); \t \t \t \t \t}趕上(ReflectiveOperationException E){ \t \t \t \t \t \t e.printStackTrace(); \t \t \t \t \t \t throw new RuntimeException(e); \t \t \t \t \t} \t \t} \t \t)' –

+1

可以內聯'propValue'功能;這是否更好(或更可讀)是值得商榷的。 「try ... catch」塊安靜笨拙。另一種方法是調用「方法」並處理檢查的異常的實用方法。調用實用程序方法可能會發生在lambda表達式中。 – Holger