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 that I know of. Now say that I have a tire of a certain width and diameter, and I want to find a vehicle that can take it. The simplest way is to have a set of four nested loops, for example:

 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 template for this? Or is it better to use a data structure? Would it be better to just keep the index somewhere in the tire information displayed on vehicles?

edit : answers to questions from comments

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

Yes

Do you need to look for different tires many times in the same data?

Yes

Is performance a problem?

Not particularly

When you find a tire, you just need to know which car contains it, or do you also need an axle and a wheel?

Sometimes just a vehicle, sometimes just an axis - two different contexts

Do you need a link to the found tire?

Yes, in cases where I need an axis

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

Context 1 - I want to know the vehicle, so I can send an employee to collect the car and return it.

Context 2 - I want to know the axis and the tire because I am in a car trying to get the job done.

+7
java loops design-patterns
source share
4 answers

You can smooth the loops 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 good thing about threads is that you can insert additional filter , filter , findAny , etc., quite easily anywhere in the sequence.

+2
source share

I would reverse your logic and move the question to Vehicle , unless, of course, you want your objects to be thin for any other reason (in this case I would personally wrap them with a thicker object to add any behavior)

 class Vehicle { ... public Tire acceptsTire(Tire tire) { } } 

there are several possibilities, depending on how important this part of the business logic is in your domain as a whole.

  • If you have multiple actions, you can probably just iterate over the way you did in your example. Or, perhaps, just as I suggested, continue to cascade the question to the correct component. For now, you can live with time complexity to make it good.
  • If this check is what you usually do, then you can refer to the type of tires that you keep in the car directly, it can be your Tire collection, or you can pass an instance of TireSpecification when building Vehicle , if for any reason you need to keep these finishes (your intention is not entirely clear in the question, is it a tire on a car or just a specification of what can fit?)
+2
source share

Without changing the data structure, you cannot distinguish significantly. You can add syntactic sugar with lambdas, but this is essentially the same solution.

Things you could see:

  • Your model allows Vehicles with zero axles or a hundred. Although it depends on your business model, it seems odd.
  • Your model allows you to have different axles in your car, different wheels. Is it really necessary? Make sure that the elements of your model must have their own separate identification (now every object has it) and which is just an object of value.
  • Make sure you really need such a detailed model. Currently, you have two classes ( Axle , Wheel ) that contain only collections of internal objects. If they are just simple JavaBean objects with getAllInnerTypes() , then you should consider removing this class. Perhaps even if the bus information should be stored almost directly in the Vehicle class.
+1
source share

While there are not too many elements and / or performance is not a big problem, I will probably just go with nested loops (or threads from John's answer).

Since you have two search contexts, you can pass the appropriate action to the search method - something like this (using loops in this case):

 interface TireAction { void doSomething(Vehicle v, Axle a, Tire t); } void findTireAndPerform(int targetWidth, int targetDiameter, TireAction action) { 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) { action.doSomething(vehicle, axle, tire); break; } } } } } } void someMethod() { ... findTireAndPerform(width, diameter, (v, a, t) -> { // send worker to 'v' }); ... findTireAndPerform(width, diameter, (v, a, t) -> { // work on 'a' and 't' }); } 
0
source share

All Articles