6

I have 2 kinds of objects in my system, Car and Road.

A Car can be of various types and have properties like tire, tire size, engine, etc.

A Road can be of multiple types and have properties like terrain type, slippery, etc.

Some type of cars cannot ride on certain roads, based on the road properties and car limitations/constraints, like a common car cannot drive on beach sand because it would get stuck with smaller tires and weaker engine, but a "monster truck" could, because it has bigger tires and a better engine.

Considering these restrictions, how should the responsibility of checking whether a car can ride on a specific road be determined?

I think that a Car should throw exceptions if it cannot ride, but I don't think a Car should know in which road is going to ride.

  • Should the road check the car's properties and tell if it's able to run on the road? I think this options falls to a procedural approach of getters/setters.

  • How would this scenario "scale" if a had multiple properties on a Car that could affect the capability of riding on a road?

How could I approach this with these interfaces?

export interface Car {
  engine: string;
  tire: string;
  tireSize: string;

  ride(): void // ride only if Road is compatible
}

export interface Road {
  terrain: string;
  isSlippery: boolean;
  cars: Car[];

  startRace(): void
  
}
12
  • 3
    There's no one right answer. Further, it could be a third object that handles traffic, or motion, or maybe maps.
    – Erik Eidt
    Jun 18, 2021 at 17:54
  • Thanks for the answer, could you provide a full example?
    – Thiago
    Jun 18, 2021 at 17:57
  • Maps often have road metadata like height restrictions, weight restrictions, etc..
    – Erik Eidt
    Jun 18, 2021 at 17:59
  • 2
    How do these two things fit into the larger application? What problem is the software supposed to solve? Jun 18, 2021 at 18:03
  • @whatsisname for now it's just 2 classes alone, but you could introduce a third object if you think is necessary
    – Thiago
    Jun 18, 2021 at 18:37

6 Answers 6

11

The more I think about this, the more I think it's a great question. Welcome to the art of design. Here's the thing, as other's have noted, there's no clear answer. I'll go even further and tell you that whatever you choose, you'll probably want to change something about how you went about it later.

This is a tough problem in Object Oriented Design. It's the kind of thing that is often pointed to as one of the failings of OO. The crux of the problem is that you either:

  1. Have roads know about cars
  2. Have cars know about roads
  3. Have some other thing that knows about roads and cars

All of these are unsatisfying from an OO perspective. Objects are supposed to only worry about themselves, not other things.

Philosophy aside, part of the challenge here is that we don't have enough context to know what your larger goals are here. Is the goal to create system that tells you if a car should attempt to drive on a given road? Then I would go with the car deciding. Mainly because IMO it's more natural to frame the question as whether a car has the requisite capability to drive on a given road. But this might be because I grew up in an area where unpaved, unmarked roads were easy to find. If you wanted to drive on it, you needed a capable vehicle. If I was a highway engineer, I might look at it in the opposite way: a road needs to be able to accommodate given classes of vehicles within a range of speeds.

Now if you were building something like a simulation where you have to model the interaction between a road and a car, I would probably go with the 3rd option: an independent observer that understands how cars interact with roads. I don't think this really what you are doing here, and it's a much more complex kind of question.

4

Without knowing where you want to go with this or what problem is to be solved I think I can still create a world that makes sense.

The first thing that comes to mind is a track. A track is made up of linkable segments. A track segment has road quality properties.

So the track has a collection of segments. A track segment has a collection of cars with a position of each (dictionary). The car does not know where it is.

TrackSegment would have properties vectors (that plot its relative path), PriorSegment and NextSegment.

Now you will need some kind of loop (thread) that iterates through your cars and repeatedly moves them to their next position within their segment (passing the car to the next segment as appropriate). The progress of the car in one time slot will depend on the road quality properties of the segment the car is on, combined with the properties of the car.

The determination of a car's progress is a big part of the processing that goes on in this model. I would not put that in either car or segment. I say let cars be cars and let segments be segments. I would rather introduce a Step() method to the Track class and have that perform the movement of cars. Step could take a time interval as an argument.

Track.Step() would iterate though its segments, then iterate though all cars in each segment and calculate the progress of each car. Probably in meters. You would have a formula for that which includes both car properties and segment properties.

Something simplified:

resistance = roadCondition / (wheelSize * tireWidth)
progress = (speed * dt) / (resistance - enginePower)
newSpeed = ?

You could go overboard with physics, taking into account the car's mass. The point is that movement is better off being dealt with as an external responsibility. You would have some orchestrator. I put the move thread in Track but this is an arbitrary choice. You may want to have a dedicated class for it.

2

There something incomplete in this design:

  • In Car.ride() your comment tells that the car only rides if Road is ok. But which Road? How does the Car know about the Road?
  • Is Road.startRace() really a responsibility of the Road? And how is the Road informed of the Car to consider in its cars collection?

Hence, a first important question for your design is: who knows about the Road AND the Car and adds the Car to the Road? I could for example imagine:

  • a CarRace organizes the race, if you’re developing a game.
  • a Ground object on which roads and cars are GPS-positioned, if you’re developing a real-time predictive traffic-congestion system

Then, a second important question: how do we know if a Road is suitable for the Car? Can the road tell? Can the car tell? Can the both together tell, using some kind of double dispatch? Or can a third object, that knows them both, tell? Currently we don’t even know if the car is able to determine if it should raise an exception. So, this question is key to design the interaction between the involved objects in a way to ensure consistency. I could for example imagine that:

  • Car knows about some physical road properties it can deal with.
  • Road obviously knows its own physical properties.
  • A mediating third object queries the Road and tell tells the Car.
  • But this is only one example, because a CarRace could from the scratch check compatibility of cars added to the race with the road, using some general rules based on car type and road type (e.g. a F1 car cannot participate to a rally in a forest). This would prevent the car of being in an impossible situation.
  • A more complex design would be Car having a 3DObject proxy that it observes, Road composed a set of RoadSegment, which each have a 3DSurface with different physical characteristics,. A physics engine would then makes calculations on all those 3D proxies (including collision detection with other cars). The Car is informed of the physics, as it’s an observer of its proxy. It could raise exception if despite change of direction and speed there is no longer any change in position.

As you see, there are many ideas ranging from very simple to very complex. It all depend on what you intend to do and the answers to the two questions above. (These are of course all rough ideas, that will need further fine tuning once you’ve chosen the general approach).

4
  • that makes sense, i actually put the ride() and startRace() to start this discussion, because i don't know who should be passed to who, or if i should have a third object, that start the race and checks if the car is suitable, how should this third object be modeled?
    – Thiago
    Jun 18, 2021 at 20:27
  • @NBAYoungCoder Cars and roads are indeed very complex. A car exists without a road, and a road without a car. But there may only be one car at a given position on the road and you can collide with cars on close position. If you look at it, is the car at a position on the road, or is the car and the road on a 3 D surface, and just happen to be one on the other (3rd object = ground). What if the car goes of the road ? Now the race is definitively nit managed by the car (because several cars are involved. A race is probably of the responsibility of your Race class?
    – Christophe
    Jun 18, 2021 at 20:52
  • @NBAYoungCoder Finally objects do not need to reflect the real life responsibilities (unless it’s a simulation). So the question is: what is your goal and what are you trying to achieve. In games, very often some 3d physical engine is managing a part of the behavior that we would expect to be the responsibility of the objects.
    – Christophe
    Jun 18, 2021 at 20:53
  • @NBAYoungCoder I have slightly edited my answer to develop some ideas and examples about possible third objects in this design. I hope this addresses the additional question in your comment.
    – Christophe
    Jun 19, 2021 at 8:25
1

I'm guessing that you're actually not writing a racing simulation, so I'm going to disregard this talk of cars and roads.

So, your question is this. You have two kinds of objects: C objects and R objects. Given a C object and an R object, how should your software determine whether or not the objects are compatible with each other?

And the answer is that there is no general answer. The best design is going to depend on a lot of details about how your software works.

If a C object is a parser, and an R object is a document, then the parser should probably be able to determine for itself whether or not it's capable of parsing the document. Therefore, all of the logic for determining that should probably be in the implementation of the C objects, not in the implementation of the R objects.

If a C object is some type of client object and an R object represents a connection to a server, then the process of determining whether or not the client object is compatible with the server may be a complex process involving several rounds of communication between the client and the server.

If you really are implementing a racing simulation, and a C object represents a car and an R object represents a road, then it may not matter a whole lot which object contains the logic for determining what will happen. If roads only have a few distinguishing properties, but cars react to roads in complex ways, then it would be reasonable to put all of the logic inside of the car objects. If cars only have a few distinguishing properties but roads are complex, it would be reasonable to put all of the logic inside of the road objects. And if neither of those is the case, maybe the logic for determining what happens shouldn't be in either object.

There are too many possibilities for me to be able to write an answer covering all of them.

1

Real life most of the times adapts itself to a model design as much efficient as possible. You should always keep reality into account when you design your classes, in this case if you don't have a Driver or a supervisor you should consider adding one. If you are modelling a self driving car the driving software should be abstracted from the car structure itself, if you don't enforce such logical separation you risk going on in the development to unwittingly add a lot of circular dependencies.

Let's try and explain what I mean. The road conditions aren't always the same, they keep changing. If a camera sees a bump in the road, which class is responsible to change the status of the Road? which class is responsible to change the Drive mode of the Car? The Car itself?

Another case. Black ice is the ice that is not clearly visible. The Wheels start skidding. Here in real life you have a circular dependency because usually the anti-skid system slows down immediately the wheels, that is a shortcut needed to have a fast reaction, but then checking the trajectory of the Car on the road and eventually adjusting the wheel is not a responsibility of the Car itself.

0

Let’s say your car had properties width, height, maxWeight, actWeight and your road has properties maxwidth, maxheight, maxActWeight, maxActHeight. With that information it’s easy to check if the car can drive on the road (you need some other properties obviously).

Now it’s up to you to put the decision into the car object, the road object, some other object, or some free function. The car and road objects obviously need to provide all the needed properties.

PS. Why exceptions? “Can this car drive on this road” is just a yes/no question.

PS. If a road has a low bridge, a truck may be able to drive on the road except under the bridge - makes things interesting. Consider a removal truck wanting to go to a home very close to that bridge. Things get interesting… And you want to distinguish between legally allowed and physically possible.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.