Java 8: Applying a list of methods on one object

Viewed 225

I have a particular part in code where all I want to do is the below, but I am at a loss to write in a way that doesn't involve code repetition. Is there a way that I can declare a list of methods, which can be then applied to productFeatureValidationDto. My current approach is noob-ish.

public ValidateProductFeatureResponse validateProductFeatureAgainstAllCriteria(ProductFeatureValidationDto productFeatureValidationDto) throws
        ApplicationException, ParseException {
    ValidateProductFeatureResponse response;
    response = this.validateProductFeatureA(productFeatureValidationDto);
    if(response.getStatus().equalsIgnoreCase(ResponseStatus.FAILURE.name())){
        return response;
    }
    response = this.validateProductFeatureB(productFeatureValidationDto);
    if(response.getStatus().equalsIgnoreCase(ResponseStatus.FAILURE.name())){
        return response;
    }
    response = this.validateProductFeatureA(productFeatureValidationDto);
    if(response.getStatus().equalsIgnoreCase(MPResponseStatus.FAILURE.name())){
        return response;
    }
    response = this.validateProductFeatureC(productFeatureValidationDto);
    if(response.getStatus().equalsIgnoreCase(MPResponseStatus.FAILURE.name())){
        return response;
    }
    response = this.validateProductFeatureD(productFeatureValidationDto);
    if(response.getStatus().equalsIgnoreCase(ResponseStatus.FAILURE.name())){
        return response;
    }
    response = this.validateProductFeatureE(productFeatureValidationDto);
    if(response.getStatus().equalsIgnoreCase(ResponseStatus.FAILURE.name())){
        return response;
    }
    response = this.validateProductFeatureF(productFeatureValidationDto);
    if(response.getStatus().equalsIgnoreCase(ResponseStatus.FAILURE.name())){
        return response;
    }
    return getResponseOnValidationSuccess(productFeatureValidationDto);
}

Thanks in advance.

3 Answers

if you can use spring framework.
at first you can define an interface like this.

public interface ValidateProduct{
    ValidateProductFeatureResponse validate(ProductFeatureValidationDto dto);
}

Your specific verification class implements this interface and register to srpingcontext

public ValidateProductFeatureResponse validateProductFeatureAgainstAllCriteria(ProductFeatureValidationDto productFeatureValidationDto) throws
        ApplicationException, ParseException {
    ValidateProductFeatureResponse response;
    Map<String, ValidateProduct> beansOfType = applicationContext.getBeansOfType(ValidateProduct.class);
    for (ValidateProduct value : beansOfType.values()) {
        response = value.validate(productFeatureValidationDto);
        if(response.getStatus().equalsIgnoreCase(ResponseStatus.FAILURE.name())){
            return response;
        }
    }
    return getResponseOnValidationSuccess(productFeatureValidationDto);
}

I would suggest following approach (schematic):

    /**
     * List of validation functions
     */
    private final static List<Function<ProductFeatureValidationDto, ProductFeatureValidationDto>> VALIDATIONS = new LinkedList<>();

    /**
     * Fill validations list
     */
    static {
        VALIDATIONS.add((source) -> {
            // test for feature A
            return source;
        });
        VALIDATIONS.add((source) -> {
            // test for feature B
            return source;
        });
        VALIDATIONS.add((source) -> {
            // test for feature C
            return source;
        });
    }
    
    /**
     * Predicate for failure determination
     */
    private final Predicate<ProductFeatureValidationDto> IS_FAILURE = (dto) ->
            dto.getStatus().equalsIgnoreCase(ResponseStatus.FAILURE.name());

    /**
     * Validation method
     */
    public ValidateProductFeatureResponse validateProductFeatureAgainstAllCriteria(
            ProductFeatureValidationDto dto
    ) throws ApplicationException, ParseException {
        
        // iterate over validation functions and invoke them on dto instance
        // filter stream by failed validations
        // stop on first match
        Optional<ProductFeatureValidationDto> dtoOptional = VALIDATIONS.stream()
                .map(action -> action.apply(dto))
                .filter(IS_FAILURE)
                .findFirst();
        
        // apply fuilure / success maping depending on result
        return dtoOptional.isPresent()
                ? getResponseOnValidationFailure(dto)
                : getResponseOnValidationSuccess(dto);
    }

I ended up composing my solution from the answers given by @Eiden and @Alexandra Dudkina. A big shoutout to them. Below is the crux of my whole solution.

So, I have two interfaces

  1. IProductFeature : This is a functional interface which has only one method validate. Every constraint needs to implement this.
  2. IProductValidationService: This is a contract specifying the core methods needed to be implemented. The method validateProductFeatureAgainstAllCriteria is part of the contract.

There is a config file where all the features have been imported and organised into lists as required for different kinds of products. This list has been kept in a map with the product type as key. So this is acting like a factory which is giving a list of constraint based on a given product type.

The concrete class implementing IProductValidationService gets the list from the config and then applies all the constraints in the list to the given dto. This way, I have separated all the concerns into separate portions. The practical advantages to this approach are:

  • You can write extensive test cases and documentation for individual features.
  • If in the future, there is a policy change in ProductFeatureB(for e.g.),all I have to do is create a new concrete class, call it ProductFeatureBV2 and change only config file. The policy changes can be documented as part of the class javadoc. This way without changing core validation method, I can deprecate ProductFeatureB. This makes the code extremely flexible.

Thanks a lot to the community for helping me getting this right. If there are further improvements to be made here, please suggest them.

Related