0

What are best practices for cleaner code style when accessing nested members on higher abstraction levels.

class A{
  B b;
  void Do(){b.c.d.e.f.g.value;}
}

class B{public C c;}
class C{public D d;}
class D{public E e;}
class E{public f f;}
class F{public g g;}
class G{public int value;}
  • 3
    Such code can indicate that you have modeled the problem domain incorrectly, in particular that you might be conflating multiple problem domains that should be kept separate. Or, this might actually be OK. It depends. There's no way to tell with this abstract example. – amon Jun 5 '18 at 8:24
  • @amon Ad modeling: Do you mean, that it could be that value simply belongs higher up in the abstraction hierarchy? Ad problem domains: Do you mean that this would indicate that it could indicate more that the abstraction hierarchy should be less deep but more flat, e.g. class A{void Do{b.Do();}? – Kevin Streicher Jun 5 '18 at 8:30
  • 1
    @amon is correct. The real question is, how did you end up in this situation? It rarely happens in well designed code. – Frank Hileman Jun 5 '18 at 23:54
  • @FrankHileman Yes. However, the reality is is a lot of badly designed code exists for many different reasons. Be it legacy/3rd party/plain bad code - either written by one self or by a co-worker. It happens often enough books beeing written about it (e.g. Clean Code). Robert C. Martin mentions this case, but sadly I've read this specific chapter shortly after posting the question. In most cases I've come across this mutates bottom up. Don't get me wrong: It is bad code and only happens because of bad design. – Kevin Streicher Jun 6 '18 at 6:38
5

The key to "accessing nested members on higher abstraction levels" is to use "higher abstraction levels". You aren't doing so here: B through F are referenced directly within A. They are all tightly coupled and there is no abstraction going on at all.

The solution is to use abstraction, so B becomes:

class B
{
    public int Value => ...
}

How B delivers Value becomes an implementation detail; ie it's not tied to the API and is free to change without affecting code that calls b.Value. It is abstracted away.

So if B to G reside in one assembly and A resides in another, you might implement this as:

public class B
{
    ...
    public int Value => c.d.e.f.g.Value;
}

or you might to perform the same abstraction repeatedly, such that B has no idea where C gets Value from etc:

public class B
{
    ...
    public int Value => c.Value;
}

Each level of abstraction reduces coupling, but it introduces repetition and complexity. So as with everything else in programming, you need to strike the correct balance for each time you use this technique and that balance point will change each time.

  • Is this still the solution when the lower hierarchy member is visible in the higher hierarchy Type? e.g. class A{B b; void Do(){b.c.Count;)} class B{public List c;} felt like less clutter/duplication than class A{B b; void Do(){b.Count;)} class B{public List c; public int Count => c.Count;} as the List is public/visible in A. However when the List c is private it feels like the perfect way to do it. Thinking about this it might indiciate that a lower hierarchy class (e.g. B) reveals too much about itself in the first place (e.g. List c should be private)? – Kevin Streicher Jun 5 '18 at 8:39
  • 2
    @NoxMortem, if the list is public, it's not encapsulated ans so cannot be abstracted. Thus this technique does indeed only work when the the item to be abstracted is private. – David Arno Jun 5 '18 at 9:33
  • 1
    This is a good answer, however, the real question is how such convoluted code occurred in the first place? You mention abstraction levels; I think the concept of abstraction levels may not be understood at all by the original poster. – Frank Hileman Jun 5 '18 at 23:56
  • @FrankHileman I'd like to believe I understand the concept of abstraction levels but failed to point out, that those classes should represent Abstraction Levels - but clearly aren't. The code occured because of bad design, 'mindless' or 'inexperienced' programmers and my job now is to turn this mess into less mess. – Kevin Streicher Jun 6 '18 at 6:42
  • 2
    @NoxMortem I mistakenly assumed you were talking about your own design. David is right in that they do form abstraction levels implicitly -- or rather, that is what is expected when we start to go down the path he recommends. If they are not abstraction levels. there may be some other solution that avoids the entire chain of calls. – Frank Hileman Jun 6 '18 at 14:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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