Nested Predicate

looparda

Top Contributor
Ich hatte die Idee eines kleinen Utilities, um Predicates zu haben, die sich jedoch erst über Getter zum gewünschten Attribut hangeln müssen, um dann eine Aussage machen zu können.
Für einen einzigen Getter ist es einfach (siehe Simple), aber ich würde gerne beliebig verschachteln (siehe Complex).
Gleiches könnte man auch mit Optional, map und anymatch erreichen (siehe complexThreeLevel_OptionalReferenceImpl_greenLeaf_accepts)
Aber mit meinem Code bekomme ich es nicht umgesetzt. Hat jemand eine Idee wie man es lösen könnte? Gibt's sowas schon?


Ich habe ein simples Beispiel extrahiert: ein Tree, der einen Branch hat, welches ein Leaf hat das grün ist oder eben nicht. Mit Hilfe eines Predicate<Tree> möchte ich Aussagen über Tree machen, die von Leaf::isGreen abhängig sind.
[CODE lang="java" title="NestedPredicateTest"]mport static org.assertj.core.api.Assertions.*;

import java.util.Optional;
import java.util.function.Predicate;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import lombok.Builder;
import lombok.Data;

class NestedPredicateTest {

@Data
@Builder
static class Tree {
Branch branch;
}
@Data
@Builder
static class Branch {
Leaf leaf;
}
@Builder
@Data
static class Leaf {
private boolean isGreen = true;
}

@Nested
class Simple {
@Test
void simpleTwoLevel_greenLeaf_accepts() {
final Branch branch = Branch.builder().leaf(Leaf.builder().isGreen(true).build()).build();

Predicate<Branch> branchHasGreenLeaf = NestedPredicate.of(Branch::getLeaf).then(Leaf::isGreen);

assertThat(branchHasGreenLeaf).accepts(branch);
}

@Test
void simpleTwoLevel_noGreenLeaf_rejects() {
final Branch branch = Branch.builder().leaf(Leaf.builder().isGreen(false).build()).build();

Predicate<Branch> branchHasGreenLeaf = NestedPredicate.of(Branch::getLeaf)
.then(Leaf::isGreen);

assertThat(branchHasGreenLeaf).rejects(branch);
}
}

@Nested
class Complex {

final Tree tree = Tree.builder().branch(Branch.builder().leaf(Leaf.builder().isGreen(true).build()).build()).build();

@Test
void complexThreeLevel_OptionalReferenceImpl_greenLeaf_accepts() {

Predicate<Tree> treeHasBranchAndHasGreenLeafOptional = (_tree) -> Optional.of(_tree.getBranch()).map(Branch::getLeaf).stream().anyMatch(Leaf::isGreen);

assertThat(treeHasBranchAndHasGreenLeafOptional).accepts(tree);
}

@Test
void complexThreeLevel_greenLeaf_accepts() {

Predicate<Tree> treeHasBranchAndHasGreenLeaf = t -> true;
//NestedPredicate.<Tree,Branch>of(Tree::getBranch)
//.nested(Branch::getLeaf)
//.then(Leaf::isGreen);

Assertions.fail("This usage is not working yet");
assertThat(treeHasBranchAndHasGreenLeaf).accepts(tree);
}
}

}
[/CODE]

[CODE lang="java" title="NestedPredicate"]import java.util.function.Function;
import java.util.function.Predicate;

public class NestedPredicate<T, C> {

private final Function<T, C> of;

public NestedPredicate(Function<T, C> of) {
this.of = of;
}

public static <T, C> NestedPredicate<T, C> of(Function<T, C> of) {
return new NestedPredicate<>(of);
}

public Predicate<T> then(Predicate<C> then) {
return (it) -> then.test(of.apply(it));
}

public NestedPredicate<T, C> nested(Function<T, C> mapper) {
return of(mapper);
}

}
[/CODE]
 
Beste Antwort
Vorschlag (funktioniert im Endeffekt wie Function):
Java:
public interface NestedPredicate<T,C> {
    C apply(T t);

    static <T,C> NestedPredicate<T,C> of(Function<T,C> mapper) {
        return (T t) -> mapper.apply(t);
    }

    default <V> NestedPredicate<T,V> nested(NestedPredicate<C,V> mapper) {
        return (T t) -> mapper.apply(apply(t));
    }

    default Predicate<T> then(Predicate<C> predicate) {
       return (T t) -> predicate.test(apply(t));
    }
}

mihe7

Top Contributor
Vorschlag (funktioniert im Endeffekt wie Function):
Java:
public interface NestedPredicate<T,C> {
    C apply(T t);

    static <T,C> NestedPredicate<T,C> of(Function<T,C> mapper) {
        return (T t) -> mapper.apply(t);
    }

    default <V> NestedPredicate<T,V> nested(NestedPredicate<C,V> mapper) {
        return (T t) -> mapper.apply(apply(t));
    }

    default Predicate<T> then(Predicate<C> predicate) {
       return (T t) -> predicate.test(apply(t));
    }
}
 
Beste Antwort

httpdigest

Top Contributor
Also... man kann sehr einfache Dinge auch sehr kompliziert machen. :)
Statt:
Java:
final Branch branch = Branch.builder().leaf(Leaf.builder().isGreen(true).build()).build();
Predicate<Branch> branchHasGreenLeaf = NestedPredicate.of(Branch::getLeaf).then(Leaf::isGreen);
assertThat(branchHasGreenLeaf).accepts(branch);
einfach zu schreiben:
Java:
Branch branch = Branch.builder().leaf(Leaf.builder().isGreen(true).build()).build();
assertTrue(branch.getLeaf().isGreen());
geht nicht, weil?
 

httpdigest

Top Contributor
und man in der Realität noch um jeden Getter Null-Checks einbaut.
Nein, das tut man nicht.
Dann schlägt der Test dort auch fehl, nämlich mit einer NPE.
Und, wenn der Test dann dort mit einer NPE fehlschlägt, dann untersucht man, was das eigentliche Problem war.
Ansonsten werden deine Tests irgendwann so komplex und kompliziert, dass sie die ganze Anwendung zu starr und rigide gegen Änderungen machen, weil du dann sehr lange brauchst, um Tests anzupassen.
 

looparda

Top Contributor
Der Test spiegelt meinen Client Code wieder, also so wie ich ihn im Rest meiner Anwendung benutzen möchte, und dort will man sicher keine NPE. Also: Ich will den Code nicht in meinen anderen Tests verwenden. Das hier ist einfach der Unit-Test für mein NestedPredicate. Du beziehst dich gerade darauf wie ich meinen Testcode vereinfachen kann, indem ich kein Predicate und keine Null-Checks benutze, was aber eben vorbei an den Anforderungen und am Client-Code ist.
 

mihe7

Top Contributor
@httpdigest, worauf es vemutlich hinausläuft, ist:
Java:
Predicate<Tree> treeHasGreenLeaf = tree -> {
    Branch b = tree.getBranch();
    if (b != null) {
        Leaf l = b.getLeaf();
        return l != null && l.isGreen();
    }
    return false;
};
// vs
Predicate<Tree> treeHasGreenLeaf = NestedPredicate.of(Tree::getBranch).nested(Branch::getLeaf).then(Leaf::isGreen);
 

Ähnliche Java Themen

Neue Themen


Oben