Objekt-Suche mit mehreren optionalen Parametern

dflasjjs

Bekanntes Mitglied
Hi,

ich habe ein Object (Customer) mit den Attributen: name, email, city und status.
Mehrere dieser Objekte befinden sich in einem Vector<Customer>.

Jetzt habe ich eine Suchfunktion bauen, welche mir nach Eingabe eines ODER mehrer Parameter eine Ergebnisliste auswirft.

Aktuell steh ich vor dem Problem, dass ich einen If-Verteiler einbaue, den ich exponentiell erweitern muss, wenn man ein vierter Parameter hinkommt:

Code:
public Vector<Customer> getCustomerBySearch(String emailName, String city, CustomerStatus status) {

   Vector<Customer> res = new Vector<Customer>();

   Vector<Customer> resultNameEmail = getCustomerBySearchNameEmail(emailName);
   Vector<Customer> resultCity = getCustomerBySearchCity(city);
   Vector<Customer> resultStatus = getCustomerBySearchStatus(status);

   if (!emailName.equals("") && !city.equals("") && status != null) {
      resultNameEmail.retainAll(resultCity);
      resultNameEmail.retainAll(resultStatus);
      res = resultNameEmail;
   } 

   return res;
}

Ich müsste jetzt ja für jede Kombination aus den Parametern so einen If-Kasten bauen, jetzt nur 8 bzw. 7, mit einem vierten Parameter schon 16 usw. wie kann ich das eleganter lösen?
 
Indem du statt für jeden Parameter eine fertige search Methode aufrufst, du einfach selber über den Vector iterierst und innerhalb der Schleife eben alles gesetzten Parameter testest....
 
Zusätzlich zu dem, was @Thallius geschrieben hat, könntest Du die Tests innerhalb der Iteration verallgemeinern: https://en.wikipedia.org/wiki/Specification_pattern. Ab Java 8 s. java.util.function.Predicate.

Das ist ja wirklich geiler Scheiß, hilft mir auf jeden Fall schon mal weiter.
Ich habe jetzt folgendes:

Code:
public Vector<Customer> getCustomerBySearch(Predicate<Customer> p) {

   Vector<Customer> res = new Vector<Customer>();
   for (Customer c : getCustomers()) {
        if (p.test(c)) {
          res.add(c);
       }
    }
   Collections.sort(res, Customer.getFullNameReverseComparator());

   return res;
}

Funktioniert auch super, allerdings ist der Aufruf jetzt ja das entscheidende.

Im Prinzip stehe ich hier jetzt vor dem selben Problem, ich müsste einen Verteiler machen oder bekomme ich das eleganter hin?
Code:
getCustomerBySearch(p -> p.equalsName(searchTerm))
Was ich will ist ja sowas:
Code:
getCustomerBySearch( if(name) {p -> p.equalsName(name)} && if(searchTerm) {p -> p.equalsCity(city)} && if(searchTerm) {p -> p.equalsStatus(status)})
Geht das irgendwie elegant mit Predicate?


OK, habs jetzt anders gelöst.

Code:
getCustomerBySearch(p -> p.equalsName(searchTerm) && p.equalsEmail(searchTerm) && p.equalsCity(city) && p.equalsStatus(status)))

Und in den Vergleichmethoden gebe ich einfach true zurück, wenn der String leer ist. So habe ich das selbe Ergebnis.

Vielen Dank euch beiden! 🙂
 
Zuletzt bearbeitet:
p.equalsName(searchTerm) && p.equalsEmail(searchTerm)
Name und Email sollen gleich sein?

Geht das irgendwie elegant mit Predicate?

Du kannst z. B. folgendes machen (skizziert):
Java:
public class Customer {
...
    public static Predicate<Customer> byMail(String term) {
        return p -> mail.equals(term);
    }
...
}
// und dann etwas wie
getCustomersBySearch(Customer.byName(searchTerm)
    .and(Customer.byMail(searchTerm))
    .and(...));

Das ist ja wirklich geiler Scheiß
LOL. Noch "geiler wird der Scheiß", wenn Du Streams verwendest (java.util.stream.Stream), dann kannst Du Dir das Programmieren der Schleife sparen:
Java:
Vector<Customer> customers = ...;
List<Customer> filtered = customers.stream()
    .filter(Customer.byName(searchTerm)
        .and(Customer.byMail(searchTerm))
        .and(...))
    .collect(Collectors.toList());

Falls Du mit den Customers "irgendwas" vorhast, könntest Du auch
Java:
public void forMatching(Predicate<Customer> p, Consumer<Consumer> c) {
     customers.stream().filter(p).forEach(c);
}
implementieren und dann
Java:
customers.forMatching(Customer.byName(searchTerm), c -> doSomething(c));
verwenden. Der Phantasie sind kaum Grenzen gesetzt.
 
Das Ganze funktioniert aber alles nur, wenn immer alles Suchparameter gesetzt sind, was ich für nicht realitätsnah halte. Normalerweise würde ich sagen, dass beliebige PArameter gesetzt sein können und die anderen NULL. Das man dann eben auch nur nach Stadt suchen kann oder nur nach Name und Alter oder was auch immer es noch für Parameter geben wird.

Ausserdem wird es nicht gerade schneller, wenn man für jeden Parameter einmal komplett über das Array iterieren muss anstatt innerhalb einer Schleife alles abzufragen.

Gruß

Claus
 
Ehrlich gesagt würde ich diese Suche nie wieder anfassen wollen, wenn ich sie einmal programmiert habe. Wenn sich das Objekt ändert (also Attribute hinzukommen), dann ändert sich im besten Fall nur das Objekt (also Customer) und nicht die Suche, also bestenfalls werden weniger Klassen verändert. Das könnte in Projekten u.a. Einfluss auf den Testscope haben. Aus diesem Grund hier ein Vorschlag von mir:

Java:
package javaforum.org.dflasjjs;

import java.lang.reflect.Field;
import java.util.Vector;
import java.util.stream.Collectors;

public class Customer {
    private String name;
    private String email;
    private String city;

    public Customer(String name, String email, String city) {
        this.name = name;
        this.email = email;
        this.city = city;
    }
 
    public boolean isMaskedBy(Customer customer) {
        for(Field field : Customer.class.getDeclaredFields()) {
            try {
                Object object1 = field.get(this);
                Object object2 = field.get(customer);
           
                if(object2 != null && ! object2.equals(object1)) {
                    return false;
                }
            } catch (IllegalArgumentException | IllegalAccessException ex) {
                // handle it
            }
        }
   
        return true;
    }
 
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
   
        for(Field field : Customer.class.getDeclaredFields()) {
            try {
                sb.append(field.get(this));
            } catch (IllegalArgumentException | IllegalAccessException ex) {
                // handle it
            }
        }
   
        return sb.toString();
    }
 
    public static void main(String[] args) {
        Vector<Customer> customers = new Vector<>();

        customers.add(new Customer("Max Mustermann", "max.mustermann@gmx.de", "Berlin"));
        customers.add(new Customer("Martina Musterfrau", "matina.musterfrau@web.de", "München"));
        customers.add(new Customer("Marc Musterbub", "marc.musterbub@gmail.com", "Köln"));

        Customer mask = new Customer(null, null, "München");

        // Diese Suche bleibt unverändert, falls sich Customer ändert
        System.out.println(customers.stream()
                 .filter(customer -> customer.isMaskedBy(mask))
                 .collect(Collectors.toList())
        );
    }
}

PS.: Muss es wirklich Vector sein? Vector ist deprecated
 
Zuletzt bearbeitet:

Neue Themen


Zurück
Oben