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.

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.