OOP best practices - use object fields dynamically

Not entirely sure I follow what you are aiming to do but this approach comes to mind...

I suggest you move the selection to a separate class that implements a Filter interface:

public class Bikes {

    public interface Filter {
        Boolean accept(Bicycle b);
    }

    public static Bicycle[] filter(Bicycle[] bicycles, Filter filter) {
        Bicycle[] results = new Bicycle[] {};
        for (Bicycle b : bicycles) {
            if (filter.accept(b)) results.add(b);
        }
        return results;
    }
}

You can the write filters as required e.g.:

public class BrandFilter implements Bikes.Filter {
    private String brand;
    public BrandFilter(String brand) {
        this.brand = brand;
    }
    public Boolean accept(Bicycle b) {
        return b.brand == brand;
    }
    public override String toString() {
        return 'Brand ' + brand;
    }
}

or:

public class PurchaseDateFilter implements Bikes.Filter {
    private Date purchaseDate;
    public PurchaseDateFilter(Date purchaseDate) {
        this.purchaseDate = purchaseDate;
    }
    public Boolean accept(Bicycle b) {
        return b.purchaseDate == purchaseDate;
    }
    public override String toString() {
        return 'Purchase Date ' + purchaseDate.format();
    }
}

or if you are using sub-classing you can use instanceof and casts as needed. Or you can write one filter class that accepts many parameters; the key point is to separate out the filtering from other logic.

So to find all bikes of a brand:

Bicycle[] input = ...
Bicycle[] output = Bikes.filter(input, new BrandFilter('Cinelli'));

You can also create and and or filters so combinations can be applied.

PS

If you want to keep your GroupedBicycles class:

public class GroupedBicycles {
    public String groupingCriteria { get; set; }
    public List<Bicycle> bicycles { get; set; }
    public GroupedBicycles(Bicycle[] allBicycles, Bicycles.Filter filter) {
        groupingCriteria = filter.toString();
        bicycles = Bikes.filter(allBicycles, filter);
    }
}

public GroupedBicycles[] grouped {get; set;}

private void init() {
    Bicycle[] allBicycles = ...
    grouped = new GroupedBicycles[] {
            new GroupedBicycles(allBicycles, new BrandFilter('Cinelli')),
            new GroupedBicycles(allBicycles, new BrandFilter('Condor')),
            new GroupedBicycles(allBicycles, new PorchaseDateFilter(Date.today())),
            ...
            };
}

You can use JSON to mimic reflection for simple objects:

class GroupedBicycles {
    Object groupValue;
    Bicycle[] bikes = new Bicycle[0];
    GroupedBicycles(Object value) {
        groupValue = value;
    }
}

GroupedBicycles[] groupBicycles(Bicycle[] bikes, String field) {
    Map<Object, GroupedBicycles> results = new Map<Object, GroupedBicycles>();
    for(Bicycle bike: bikes) {
        String bikeJson = JSON.serialize(bike);
        Object fieldValue = ((Map<String, Object>) JSON.deserializeUntyped(bikeJson)).get(field);
        if(!results.containsKey(fieldValue)) {
            results.put(value, new GroupedBicycles(fieldValue));
        }
        results.get(fieldValue).bikes.add(bike);
    }
    return results.values();
}

Since we don't have "real" reflection, this is about the best you can do.

Note that if you use Object instead of specific data types, you can get away with not having to have specific subclasses of GroupedBicycles. You'd just have to find a way to process the groupValue based on type later, perhaps by implementing a custom sort algorithm, etc, if you needed to.

Tags:

Oop

Apex