-2

Consider we have something like this:

A person, which has a physical body with arms, each arm has a hand, each hand fingers, and each finger a fingernail.

Another example: we have a car and a car-wheel in it.

So now I want to tune the fingernail a little bit, or the car-wheel, in both cases I want to change the color.

I go to a Person-Tuning-Garage, in which case I have to dive through the object-graph to get to the object ("fingernail") i'm interested in:

person.getBody().getArm("left").getHand().getFinger(4).getFingernail()

I don't want to let the person itself decide which fingernail I am going to tune.

In the car scene, I would allow the user to rotate the car-wheel, so the user has to ask the car object for the car-wheel to rotate it; here we have another problem.

If the user wants to rotate the wheel, before letting the user rotate it, the wheel must ask the car if there are no stones at the care-tire and only then the car wheel is allowed to rotate. Bumps in the street also forbid rotating the wheel from the car-side. So we have a cyclic dependency.

My questions are:

  • Is it good design to ask deep into the object-graph to get to the destiny object to work on?

  • Is the cyclic dependency in the car-wheel scene acceptable?

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
  • 1
    Answer to question #1: it can be reasonable to have such a deeply nested object graph for certain kind of software. If this is "good" or "bad" design is nothing one can tell from looking at an artificial, contrived example like the one in this question, but only from the context of a real system real requirements, where you have some criteria to evaluate what to consider as "good" or "bad". And to #2: same answer, simply replace *"deeply nested object graph"* by *"cyclic dependency"*. – Doc Brown Mar 31 '21 at 18:51
  • to #1 the question is more if that way of accessing the destiny object is good or bad, @DocBrown, But if there are criteria, on which the reason you wrote (writing such deeply nested object graph), that is "bad", how you would refactor it? (then i can better evaluate doing like so or so) And one meta-question, what is on my question wrong, that you downvotet it?, that i can do it the next time in a better way^^ – Robin Kreuzer Mar 31 '21 at 18:58
  • 1
    The biggest issue with this question is IMHO the wrong assumption there is a general answer to it without any real-world context. – Doc Brown Mar 31 '21 at 19:01
  • ok maybe i should change the question, how we can refactor code like this.....? @DocBrown, and having a wrong assumption can be clarified in the answers, so i don't think that this is a reason to downvote^^ (But yes, you know me now a little bit, so i think it's 'doch' (german, there is no english word for it) ok^^) – Robin Kreuzer Mar 31 '21 at 19:02
  • 1
    Refactoring without any goal makes no sense - but without any real-world context, there are no goals. Refactoring is not an end in itself, it is a means to an end. In this case, maybe the object graph is good as it is, so why refactor it? – Doc Brown Mar 31 '21 at 19:05
  • hm ok, but now i readed my question again and i think there is still a real-world problem: a person which wants to tune its fingernail?, @DocBrown – Robin Kreuzer Mar 31 '21 at 19:12
  • I am talking about a real-world programming problem, and the context of a real software system. There are virtually dozens of different ways how the methods on this object graph could be designed to get fingers and fingernails of a person or to change their color. And there are also a lot of different ways how the object graph itself could look like. None of those ways is automatically "better" or "worse" without knowing the context. – Doc Brown Mar 31 '21 at 19:18
  • ... see also: [Why questions about “the correct way” are too broad](https://softwareengineering.meta.stackexchange.com/questions/8965/why-questions-about-the-correct-way-are-too-broad) – Doc Brown Mar 31 '21 at 19:20
  • @DocBrown: " show us part of your project, tell us why you think it's not SOLID" cite from the location you linked..... yeah i can show a lot of why this is not so good and this also^^, but mostly then we came up with a suspense/paradox which is not resolvable^^, so i really looking for good-practice, things from which i can say its a good pattern doing it like so and not so^^, to my example here i can say, exposing internals (get.get.get.get) is not object oriented, so its bad, but i don't have a clue how to change the code beeing still OO and still have that functionality i want – Robin Kreuzer Mar 31 '21 at 19:27
  • I would recommend to create a real program around your example first: try to build a real simulator for a nail saloon, just for exercising purposes, but with some GUI, some tests, maybe a database or some persistence mechanism. Then implement the classes objects you need for it and see how far the deeply nested object graph will bring you. That should give you some more context. – Doc Brown Mar 31 '21 at 19:41

1 Answers1

1

In short

This design hides a tight coupling between the classes in the hierarchy, which will make it difficult to evolve.

It's difficult to analyse if benefits could outweight this major inconvenience in your special case. But in general, I'd advise another more flexible approach.

Some more details

If the context (e.g. a client class) uses a path such as :

person.getBody().getArm("left").getHand().getFinger(4).getFingernail()

it is required to know not only about Person (probably an immediate neighbour or a parameter to method) and Fingernail (which could be defended if it needs to operate on fingernails), but it also needs to know everything in-between.

As a consequence, if tomorrow you decide to change anything to the way a Person is composed, you'd be in trouble. For example:

  • The tiniest change in the Body API, for example to use an enum leftMember, rightMember instead of a string, would require to change a lot of classes, even if they are not at all interested in the exact structure.
  • Making the body model more accurate, adding a forearm and a couple of knuckles in between the body and the nail, would again need to change a lot of the existing code, even if these changes are irrelevant details.
  • If you want to make your nail processing more generic, e.g. for managing the equivalent of "fingernails" for cats, octopuses and bots, you'll be stuck, because all of the sudden, you'd need to cope with a wide array of different possible body structures.

In fact, all this relates to the principle of least knowledge.

There are a couple of possible solutions:

  • If the fingernail was just an example to explain that you're interested in terminal nodes, you may consider modelling the body using the composite pattern. It allows to process the body parts uniformely, without haveing to know the exact details.
  • You could also think of using the visitor pattern to iterate over your structure. The visitor would still be very coupled to the design, but at least you'd isolate the mechanism to traverse the body.
  • Ideally, you should tell the body what it should do instead of asking for its part and doing yourself. Normally I'd start with there, but you seemed to eliminate this possibility. Maybe something to consider as well.
  • Last but not the least, deep hierarchies of components tend to make software very inflexible, since new combinations require new code. A lighter alternative is then to flatten the structure, using a dynamic "entity-component-system" pattern.
Christophe
  • 74,672
  • 10
  • 115
  • 187
  • the point 2 from bottom: "Ideally [...] well"; doesn't this let the body grow up to a good class, or move responsibility into the body, which not really belongs to the body? and a second question, how would a visitor isolate the mechanism to traverse the body, maybe you can give there a little bit more detail^^; if you are a "fan" of the Law of demeter, maybe you have an answer to my question here: https://softwareengineering.stackexchange.com/questions/423533/tell-one-but-ask-the-others (for short see my edit 2 and 3 in that question, on which i look for a solution) – Robin Kreuzer Mar 31 '21 at 23:11
  • 1
    @RobinKreuzer I'm a fan of pragmatism, much more than of the principle of least knowledge :-) Principles in laws only provide general guidance, that do not always address the specific needs in our design. To come back on that question: the solution of adding methods in the body, to request fingernails, and let it forward it to the arms and so on is not a good alternative either, because it couples the classes tightly together through the forwarding chain. It misses interface segregation principle. Personally, I'd intuitively opt for a composite which allows to balance well both needs. – Christophe Mar 31 '21 at 23:22
  • so you would decide for point 4 from bottom; ok yeah i will read it tomorrow and evaluate how it can fit to the problem. But so far, thank you for your answer; maybe tomorrow there i come up with some more questions to that topic here; but feel free to add a few more informations to point 3 and 4 from bottom^^; i'm not sure how you would tell the body (not making him to a good-class) doing what should be done (point 3 from bottom)^^ – Robin Kreuzer Mar 31 '21 at 23:32
  • @RobinKreuzer: I would actually recommend against *any* of the suggested changes as long as you don't have a real system around this. Blindly refactoring to an entity component system, or adding visitor or composite patterns can be completely overdesign, or perfectly appropriate - you will not be able to tell without enough context. – Doc Brown Apr 01 '21 at 07:47
  • 1
    @DocBrown Indeed, I suggested in the second para to “*analyse if benefits could outweigh the major inconvenience*”: It’s always more a question of pragmatic trade-off between advantages and inconveniences (or more precisely cost/benefit/risk) than rigid application of principles. – Christophe Apr 01 '21 at 09:54
  • @RobinKreuzer The 3rd is what you tried to avoid. But there are cases where it is perfectly justified, i.e. if it belongs to the responsibilities of the parent object. For example, instead of calling `car.engine.engine_control.ignite()`, it would seem perfectly logical to add a method at the level of the car: `car.turn_on()`, that would then forward the call to the all relevant components, e.g. engine, but also radio, gps and other embedded devices. For the 4th, I'd suggest to read the article. it's quite typical in the gamin industry; it uses composition over inheritance to the extreme – Christophe Apr 01 '21 at 21:39
  • so now i had time, to think again and more about it^^ the last example with car.turn_on is clear, but maybe you want to give the client more control? You wrote that you would tell the body what to do, in that case i have again no really control by the client doing all the nice things with a fingernail, which offers the fingernail throug its methods^^, ok i could grow up the body to a big facade offering all the functionality it have in its internal like body,colorLeftFingernail(green)^^, but a other question, what do you think about that: body.getLeftFingernail().anyMethodIWantToExecute();? – Robin Kreuzer Apr 04 '21 at 22:06
  • please go a little bit into the deep with the visitor-approach; im not sure how you would there isolate the meachnism to traverse the body by "inverting" the logic^^.... the component-pattern is i think not the right approach for this?! and yeah the entity-component-approach i also know, its a nice thing^^ @DocBrown of course i would not overdesign my software^^, but its always good knowing his options^^ – Robin Kreuzer Apr 04 '21 at 22:09