OOP best practices – use object fields dynamically

Let’s say I am working with bicycles and I need to group them by different criterias.

class Bicycle {
    String brand { get; set; }
    Date purchaseDate { get; set; }
}

class GroupedBicycles {
    String groupingCriteria { get; set; }

    /*
        will contain all bikes with this criteria
        for example, if grouped by brand, all bikes in this list will have current brand
    */
    List<Bicycle> bicycles { get; set; }
}

class BicyclesByBrand extends GroupedBicycles { 

}

class BicyclesByPurchaseDate extends GroupedBicycles {
    Date groupingCriteria { get; set; } // will contain purchase date   
}

List<BicyclesByBrand> groupBicyclesByBrand(List<Bicycle> bicycles) {
    List<BicyclesByBrand> result = new List<BicyclesByBrand>();

    // Here is my own logic for grouping bicycles by criteria
    for (Bicycle bike : bicycles) {
        Boolean thisBikeIsGrouped = false;

        for (BicyclesByBrand bikesByBrand : result) {
            if (bikesByBrand.groupingCriteria == bike.brand) { // **MARK1 will explain below**
                bikesByBrand.bicycles.add(bike);
                thisBikeIsGrouped = true;
                break;
            }
        }

        if (!thisBikeIsGrouped) {
            BicyclesByBrand bikesByBrand = new BicyclesByBrand();
            bikesByBrand.groupingCriteria = bike.brand;
            bikesByBrand.bikes = new List<Bicycle> { bike };
        }
    }

    return result;
}

the issue for me is that for grouping bikes by purchase date, I need to copy the same method and change only line marked as MARK1
Field needs to be changed from brand to purchaseDate.

Is there a way in apex to create this method only once, but pass field for grouping as parameter? I believe this is possible in java by using reflection.

I expect this method to have following signature:

List<GroupedBicycles> groupBicyclesList<Bicycle> bicycles, String fieldCriteria)

I have already implemented displaying this data in visualforce, I have one apex:repeat that uses base class as var attribute, and in controller different implementations are assigned depending on grouping selected, so I am aiming to do minimal changes of data structure.

Answer

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())),
            ...
            };
}

Attribution
Source : Link , Question Author : Vladyslav K , Answer Author : Keith C

Leave a Comment