Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to navigate a complex tree of dissimilar objects?

For example:

class Vehicle {
    Collection<Axle> axles;
}

class Axle {
    Collection<Wheel> wheels;
}

class Wheel {
    // I think there are dually rims that take two tires -- just go with it
    Collection<Tire> tires;
}

class Tire {
    int width;
    int diameter;
}

I have a service through which I can get a collection of all Vehicle objects I know about. Now say I have a tire of a specific width and diameter, and I want to find a Vehicle which can take it. The simplistic way is to have a set of four nested loops, like so:

for (Vehicle vehicle : vehicles) {
    for (Axle axle : vehicle.getAxles()) {
        for (Wheel wheel : axle.getWheels()) {
            for (Tire tire : wheel.getTires()) {
                if (tire.width == targetWidth
                 && tire.diameter == targetDiameter) {
                    // do something
                    break;
                }
            }
        }
    }
}

Is there a good design pattern for this? Or a better data structure to use? Would it be better to just keep an index somewhere of tire information mapped to vehicles?

edit: answering questions from comments

Do you have control over the structure of the data you receive from the service?

Yes

Do you need to search for different tires multiple times in the same data?

Yes

Is performance an issue?

Not especially

When you find the tire, do you just need to know which vehicle contains it or do you also need the axle and wheel?

Sometimes just the vehicle, sometimes just the axle -- two different contexts

Do you need the reference to the tire that was found?

Yes, in the cases where I need the axle

edit2: Extending the metaphor further, to explain the two contexts above:

Context 1 -- I want to know the vehicle, so I can send a worker out to collect the vehicle and bring it back

Context 2 -- I want to know the axle and tire, because I am at the vehicle trying to do the work

like image 549
Sam Jones Avatar asked Sep 17 '15 23:09

Sam Jones


3 Answers

You could flatten out the loops by using Java 8 streams.

vehicles.stream()
    .flatMap(vehicle -> vehicle.getAxles().stream())
    .flatMap(axle -> axle.getWheels().stream())
    .flatMap(wheel -> wheel.getTires().stream())
    .filter(tire -> tire.width == targetWidth
             && tire.diameter == targetDiameter)
    .forEach(tire -> {
        // do something
    });

The nice thing about streams is that you could insert additional filter, filter, findAny, etc., calls pretty easily anywhere in the sequence.

like image 95
John Kugelman Avatar answered Oct 30 '22 23:10

John Kugelman


I would inverse your logic and move the question into the Vehicle, unless of course you'd like to keep your objects thin for any other reason (in which case I'd personally wrap them with a thicker object to add any behaviour needed)

class Vehicle {
    ...

    public Tire acceptsTire(Tire tire) {

    }
} 

from here on there are several possibilities, depending on how important this piece of business logic is in your domain in general.

  1. If you'll have several actions you could probably just iterate as you had done in your sample. Or possibly in the same way as I suggested, keep cascade the question to the correct component. As long as you can live with the time complexity of doing this that should be alright.
  2. If this check is something you'd usually do then you could have a reference to the type of tires you hold in the vehicle directly, this could be either your Tire collection, or you could pass a TireSpecification instance when constructing the Vehicle if for any reason you need to keep these separated (Your intention is not very clear in the question, is the tire on the car or just an spec of what could fit?)
like image 34
Sebastian Piu Avatar answered Oct 31 '22 00:10

Sebastian Piu


Without changing your data structure you won't be able to make significant difference. You can add some syntactic sugar with lambdas, but it is essentially the same solution.

Things you could look at:

  • Your model allows for Vehicles with zero axles or hundred. While it depends on your business model it seems to weird.
  • Your model allows to have different axles in your vehicle, different wheels. Is it really necessary? Make sure which elements of your model should have their separate identity (currently each object has it) and which is just a value object.
  • Make sure you really need such detailed model. Currently you have two classes (Axle,Wheel), which only hold collections of inner objects. If they will be just simple JavaBean object with getAllInnerTypes() then you should consider removal of this class. It may even be the case that tire information should be stored almost directly in Vehicle class.
like image 32
Marcin Szymczak Avatar answered Oct 30 '22 22:10

Marcin Szymczak