2

I need to be able to sort an object by multiple conditions, and these sorts need to be configurable from the application.properties file (as in it should be possible to specify in the file the sorts to apply and the order).

So in my design I created a comparator for each one of the sorts, and then an enum with a supplier such as:

public enum CarSort {
    ENGINE(CarEngineComparator::new), BRAND(CarBrandComparator::new);

    private final Supplier<Comparator<Car>> constructor;

    CarSort(Supplier<Comparator<Car>> constructor){
        this.constructor = constructor;
    }

    Comparator<Car> newComparator() {
        return constructor.get();
    }
}

With this design, I then would be able to load the sorts from a properties file:

myapp.cars.sorts=BRAND,ENGINE

And chain the sorts with something like:

    Stream<Comparator<Car>> comparators = sorts.stream().map(CarSort::newComparator);
    return comparators
            .reduce(Comparator::thenComparing)
            .map(comparator -> filteredCars.sorted((comparator.reversed())))
            .orElse(filteredCars)
            .collect(Collectors.toList());

The problem I have at the moment is that one of the comparators requires two parameters, so I don't think this solution holds anymore, but I can't think of any clean alternatives at the moment.

Do you know if it would be possible to adapt my current design to fit this requirement, or have any alternative viable solution?

EDIT: I'm going to add more details. Let's say the specific comparator I need to add sorts cars based for example on how far they are from the client. So it could be something like this (details are not important really, only the fact that it needs an additional parameter):

public class CarDistanceComparator implements Comparator<Car> {

    private Location origin;

    CarDistanceComparator(Location origin) {
        this.origin = origin;
    }

    @Override
    public int compare(Car o1, Car o2) {
        double distanceToFirstCar = DistanceService.calculateDistance(origin, o1.getLocation());
        double distanceToSecondCar = DistanceService.calculateDistance(origin, o2.getLocation());
        return Double.compare(distanceToFirstCar, distanceToSecondCar);
    }
}

So then what I would like is to be able to sort by BRAND,ENGINE,DISTANCE (as specified in the config file) for multiple customers. Meaning that I would need to pass a different argument each time to the DistanceComparator.

1

Your approach would work fine if the creation of your comparators were simply a question of creating a new instance of it. Now that you require multiple parameters, this complicates the issue. What makes this complex is that enum deals with a set number of possibilities. By adding a parameter, this is no longer the case, or if you insist on using enums, you'd have to complicate your setup needlessly to make it work anyway.

Consider the possible alternative:

Lets say we have a factory ComparatorFactory. When you need a new comparator, you simply call its method getComparator passing the name. The ComparatorFactory will search for several properties based on that name:

comparator.<name>=<full comparator class name with parameterless constructor>
comparator.<name>.params=param1,param2,param3
comparator.<name>.params.param1=<param1value>
comparator.<name>.params.param2=<param2value>
comparator.<name>.params.param3=<param3value>

So here you'd replace <name> with the name of the comparator in question. All the params would be listed under comparator.<name>.params (or absent in which case there are none).

For each parameter found, after initial creation of the instance, your ComparatorFactory would attempt to assign the value to it using a setter by the same name (there is plenty of native bean support for such things without much difficulty).

In this way it is both fully configurable as well as transparent in your program, which is what you want. And, if you prefer, you can associate an enum to each comparator name to make it easier to call upon in your program.

This approach also has the added advantage that your Comparators can be put in its own library and doesn't have to be directly referenced by your project. It must only be present in the classpath when the program is launched.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.