I have a setup of a class that represents a building. This building has a floor plan, which has bounds.
The way I have it setup is like this:
public struct Bounds {} // AABB bounding box stuff
//Floor contains bounds and mesh data to update textures etc
//internal since only building should have direct access to it no one else
internal class Floor {
private Bounds bounds; // private only floor has access to
}
//a building that has a floor (among other stats)
public class Building{ // the object that has a floor
Floor floor;
}
These objects have their own unique reasons to exist as they do different things. However there is one situation, where I want to get a point locally to the building.
In this situation I am essentially doing:
Building.GetLocalPoint(worldPoint);
This then has:
public Vector3 GetLocalPoint(Vector3 worldPoint){
return floor.GetLocalPoint(worldPoint);
}
Which leads to this function in my Floor
object:
internal Vector3 GetLocalPoint(Vector3 worldPoint){
return bounds.GetLocalPoint(worldPoint);
}
And then of course the bounds object actually does the math required.
As you can see these functions are pretty redundant as they just pass on to another function lower down. This doesn't feel smart to me – it smells like bad code thats going to bite me in butt some where down the line with code mess.
Alternatively I write my code like below but I have to expose more to public which I kinda don't want to do:
building.floor.bounds.GetLocalPoint(worldPoint);
This also starts to get a bit silly when you go to many nested objects and leads to large rabbit holes to get your given function and you may end up forgetting where it is – which also smells like bad code design.
What is the correct way to design all this?
Best Answer
Never forget the Law of Demeter:
This code violates the LOD. Your current consumer somehow is required to know:
floor
bounds
GetLocalPoint
methodBut in reality, your consumer should only be handling the
building
, not anything inside of the building (it shouldn't be handling the subcomponents directly).If any of these underlying classes change structurally, you're suddenly also required to change this consumer, even though he may be several levels up from the class you actually changed.
This starts infringing on the separation of layers you have, as a change affects multiple layers (more than just its direct neighbors).
Suppose you introduce a second type of building, one without a floor. I can't think of an real-world example, but I'm trying to show a generalized use case, so let's assume that
EtherealBuilding
is such a case.Because you have the
building.GetLocalPoint
method, you are able to change its workings without the consumer of your building being aware of it, e.g.:What's making this harder to understand is that there is no clear use case for a building without a floor. I don't know your domain and I can't make a judgment call on if/how that would occur.
But development guidelines are generalized approaches that forgo specific contextual applications. If we change the context, the example becomes clearer:
Which proves the point why the method on the
Player
class (or any of its derived classes) has a purpose, even if its current implementation is trivial.