Unsere Geschäftslogik ist voll von Ja/Nein bzw. Wenn/Dann Entscheidungen. Wir benutzen diese Fragen für die Validierung („handelt es sich um eine gültige Telefonnummer?“), für Ablauflogik („Wenn die Bestellung freigegeben wurde, mache weiter mit A, sonst mit B“), für Filter („gib mir aus diesem Array alle Bestellungen deren Betrag größer sind als 5k€“) und vieles andere mehr.
Das Problem: wir behandeln diese Geschäftsregeln nicht als vom Vorfall unabhängig zu beantwortende Fragen, sondern als direkt zum jeweiligen Code gehörende Bedingung. Die fachliche Anforderung „Ermittele die Volljährigkeit eines Kunden, bevor eine Bestellung platziert wird“, erfolgt dann gerne direkt im Code:
public void placeOrder(Customer customer, Object product) { Calendar instance = Calendar.getInstance(); instance.setTime(customer.getBirthday()); instance.add(Calendar.YEAR, 18); if (instance.getTime().before(new Date())) { // do it } }
Erfahrene Entwickler vermeiden dies selbstverständlich gerne und instinktiv, indem sie den entsprechenden Code in eine „private boolean istVolljährig()“ Methode verschieben oder sich eine „CustomerHelper“ bzw. „OrderUtil“ Klasse schaffen, in der diese Prüfung per public static zur Verfügung gestellt wird.
Dieser Blog soll eine interessante und leider wenig bekannte Alternative aufzeigen: Die Verwendung eines Predicate. Dieses Interface stammt ursprünglich aus dem commons-collections von Apache, sollte wegen des fehlenden Generics-Support jedoch bevorzugt in der Re-Implementierung collections-generic verwendet werden.
Ein Prädikat ist eine atomare Aussage über ein Objekt, die resultierende Gegenfrage ist mit Ja/Nein beantwortbar. Da die implementierende Klasse sich auf die Beantwortung dieser Frage konzentriert, erreichen wir eine gute Trennung der Zuständigkeiten und vermeiden die Lack of Cohesion, die umfangreiche Util-Methoden schnell annehmen.
Um das obige Beispiel wieder aufzugreifen:
public class IsAdultPredicate implements Predicate{ @Override public boolean evaluate(Customer customer) { Calendar instance = Calendar.getInstance(); instance.setTime(customer.getBirthday()); instance.add(Calendar.YEAR, 18); return instance.getTime().before(new Date()); } }
Diese Umsetzung ist nicht nur gut wiederverwertbar, sondern auch sehr gut und einfach zu testen, bzw. per TDD zu schreiben. Die „placeOrder“ Methode wird unter dieser Verwendung zu:
public void placeOrder(Customer customer, Object product) { if (new IsAdultPredicate().evaluate(customer)) { // do it } }
Das ist jedoch noch lange nicht alles, was wir gewinnen, wenn wir unsere Fragen auf Predicates umstellen. Die PredicateUtils ermöglichen beispielsweise die einfache Verknüpfung von Predicates zu höheren logischen Konstrukten, so liefert
AndPredicate.getInstance(new PredicateA(), new PredicateB());
dann
a.evaluate() && b.evaluate()
Die CollectionUtils ermöglichen den Einsatz von Predicates als Filterkriterium für Mengenoperationen. Folgender Code erlaubt das Iterieren über volljährige Kunden:
List customers = getCustomers(); FilterIterator adultCustomers = new FilterIterator(customers.iterator(), new IsAdultPredicate()); while(adultCustomers.hasNext()) { Customer adultCustomer = adultCustomers.next(); }
Auf die gleiche Art und Weise können Predicates auch zur Suche in Collections eingesetzt werden, zur Bildung von Untermengen usw. Auch für BeanValidation können Predicates eine hervorragende Unterstützung sein, wenn der @AdultCustomer-Validator das bereits existierende und getestete Predicate wiederverwertet.
Dies war ein erster kurzer Ausflug in die Welt von commons-collections, der hoffentlich Lust auf mehr machen konnte. In einem Folge-Beitrag werden wir demonstrieren, wie Predicates in Kombination mit Transformern ihre volle Wirkung entfalten.