5

I have something that looks like this:

 public boolean isValidObject(SomeObject obj){
    if(obj.getField() == null){
      LOG.error("error code 1");
      return false;
    }
    if(obj.getField().getSize() > 500){
      LOG.error("error code 2");
      return false;
    }
    ......
    if(someCondition()){
     log something
     return false;
    }

    return true;
}

What is the cleanest way of writing this in java 8 with lambdas?

Mihai
  • 115
  • 1
  • 1
  • 7

6 Answers6

14

Use polymorphism for this. Create a class for every logical validator and chain them in the list. Here is nice answer with something you need: https://stackoverflow.com/a/23501390/1119473

public interface Validator<SomeObject>{
    public Result validate(SomeObject object);
}

implementation:

public class SomeFieldSizeValidator implements Validator<SomeObject> {

@Override
public Result validate(SomeObject obj) {
    // or you can return boolean true/false here if it's enough
    return obj.getField().getSize() > 500 ? Result.OK : Result.FAILED;
    }
}

Calling validation chain:

List<Validator> validators = ... create ArrayList of needed Validators
for (Validator v : validators) {
if (!v.validate(object)) { 
  ... throw exception, you know validator and object here
}
Community
  • 1
  • 1
Gondy
  • 4,925
  • 4
  • 40
  • 46
  • 1
    This is actually the correct solution (or to use a library that does the same thing). I don't know why it was downvoted. It would be improved by adding an example implementation of `Validator` that is specific to OP's problem and how to call that. Anyway, +1 from me – nhouser9 Mar 20 '17 at 18:14
  • 1
    I didn't downvote, but turning each condition into a new class is great if you are paid per line of code but why turn one method into 50 classes. Is this really a good idea? – Peter Lawrey Mar 20 '17 at 18:17
  • 2
    Nobody is paid for lines of code. By using patterns you are creating readable code. Author said, "I want to get rid of 50 if/else statements." -- this does the job nicely. – Gondy Mar 20 '17 at 18:19
  • Funny, two folks, same idea. And if I had a real keyboard, my answer would have come in first :-) and plus1 for the code samples I left out. – GhostCat Mar 20 '17 at 18:22
  • What if your validation becomes more complex? What if you need to add 200 more classes? I would not use this in a real world project, even if I were paid per loc. – Kayaman Mar 20 '17 at 18:35
  • In a real world you usually validate other parameters in one class. No one said 1 if = 1 class. You create AddressValidator with more complex logic, not a StreetLengthLessThan50Validator or ZipOnlyNumbersValidator. That doesn't make sense. – Gondy Mar 20 '17 at 18:40
  • 1
    By the way: Since `Validator` is a single-abstract-method class, the validators are writable as lambdas, thus reducing lines of code. For example: `Validator v = obj -> obj.getField().getSize() > 500 ? Result.OK : Result.FAILED;` – Thomas Fritsch Mar 20 '17 at 18:58
4

I might return the error but this would still use a few if's

public String isValidObject(SomeObject obj){
    if (obj.getField() == null) return "error code 1";
    if (obj.getField().getSize() > 500) return "error code 2";
    ......
    if (someCondition()) return "something";
    return OK;
}

This way you could unit test this method to see if it return the error you expect for different invalid objects.

I want to get rid of 50 if/else statements.

If you have 50 conditions and they all value different results you will need to do 50 checks. You could change the structure like this.

static final Map<Predicate<SomeObject>, String> checks = new LinkedHashMap<>();
static {
    checks.put((Predicate<SomeObject>) o -> o.getField() == null, "error code 1");
    checks.put((Predicate<SomeObject>) o -> o.getField().getSize() > 500, "error code 2");
}

public String isValidObject(SomeObject obj) {
    for (Predicate<SomeObject> test : checks.keySet())
        if (test.test(object))
            return checks.get(test);
    return OK;
}

However, personally this is not clearer and would be harder to debug e.g. breakpoint.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • 2
    You have solved this with lambdas nicely, but what is logical point of this code? I see some validation here. What if your validation become more complex? Happy debugging.. I would not use this code in real world project – Gondy Mar 20 '17 at 18:24
4

Use java.util.function.Predicate interface:

Predicate<SomeObject> p1 = (SomeObject so ) -> so.getField()!=null;
Predicate<SomeObject> p2 = (SomeObject so ) -> so.getField().getSize() > 500;

...

 SomeObject someObject = new SomeObject();
 Predicate<SomeObject> fullPredicate = p1.and(p2).and( ...


 boolean result = fullPredicate.test(someObject);

Except this will give you 50 Predicate one-line definitions, they'll just be a bit more compact.

dev8080
  • 3,950
  • 1
  • 12
  • 18
3

I recommend a solution that uses a different approach: consider using Validator objects. Meaning: instead of putting all your checks into the same method, you put each check in its own class!

You define some Validator interface that provides a validate method. When validation fails, that method is supposed to throw some ValidationException (and that exception could contain an error code + message).

And then you create many small classes, each one implementing that interface.

Final step: you create a list in which you put one object of each impl class. And now your code boils down to iterating that list, and applying each impl after the other.

This decouples your validation steps, and adding new/other checks becomes super easy.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
2

If you specifically wish to use lambdas, they mesh nicely with an enum:

public enum SomeValidators {
    E1 (1, o -> o.getField() == null),
    E2 (2, o -> o.getField().getSize() > 500)
    ;

    final int code;
    final Predicate<SomeObject> predicate;

    SomeValidators(int code, int predicate) {
        this.code = code;
        this.predicate = predicate;
    }
}

You can then use it to replicate your if-else if flow as follows:

boolean isValidObject(SomeObject o) {
    Optional<SomeValidators> firstError = 
        Arrays.stream(SomeValidators.values())
        .filter(v -> v.predicate.apply(o))
        .findFirst();

    firstError.ifPresent(e -> LOG.error("error code " + e.code));
    return firstError.isPresent();
}
Misha
  • 27,433
  • 6
  • 62
  • 78
1

I'm not sure how lambdas can be useful here.

If you are using lot of 'if else' to handle lot of business rules then one option is to try some rule engines. One easy and best option is EasyRules

Easy rules are handy and easy to implement. This will make your business logic code look very clean.

Pons
  • 1,101
  • 1
  • 11
  • 20