Just J for now
Just J for now

Reputation: 1826

Making abstract classes invisible; or: hiding my BananaHuman

Background: I am still a C# novice and this is my first big project with inheritance. The following story is a simplified example of my current situation:

Suppose I have a class called LivingOrganism. Every living creature can use this class as base and inherit the functionality that is common to all living organisms.

When I was working on some of these derived classes, I found out that bananas and humans are very similar. Although it doesn't make much sense and they look nothing alike, they apparently have most of their "functionality" in common.

Duplication in code is bad, so I wrote a new class to reduce maintenance costs. This class is called: BananaHuman. My Human class and Banana class inherit from BananaHuman.


Problem:

I have no problem with my BananaHuman (i.e. I understand what it means and why it exists). But eventually, other people (even those who don't (fully) understand inheritance) will have to use my LivingCreatures.dll. And they won't understand why intellisense suggests BananaHuman when they type 'B'.

And consider the following piece of code:

//Valid and makes sense.
foreach(LivingOrganism foo in cityOfNeyYork) { /*embedded statement*/ }

But imagine how weird/confusing it would look if we substitute Living Organism with BananaHuman.

I can't make BananaHuman private, protected or protected internal (Elements defined in a namespace cannot be explicitly declared that way). I also can't make it internal, because Human and Banana have to be public. If I try this, I get an error message saying there is an inconsistent accessibility issue.

I feel like I am missing the obvious, but what can/should I do? I am left with a couple of options/questions:

  1. Is it possible to "hide" BananaHuman to avoid confusion?
  2. Should I rewrite BananaHuman to something very long and technical such as DnaRelatedOrganismsType[X], where "X" describes their unique relation?
  3. Should I just delete BananaHuman, let Human and Banana inherit from LivingOrganism and do the extra maintenance when something needs changing?
  4. Is there another solution I am completely missing?

I searched around and couldn't quite find a "fixed pattern" for this situation. I found this question with a similar title, but I don't know if the answers are applicable because it appears that he is asking something completely different.

Any help is greatly appreciated!

Upvotes: 7

Views: 389

Answers (2)

Ben Aaronson
Ben Aaronson

Reputation: 6975

The existing answer is a great technical solution to the specific problem of hiding the BananaHuman from intellisense. But since the OP also asks about changing the design, I think it's also within the scope of the question to give a quick answer about why the existence of BananaHuman is a code smell and it should probably be a candidate for refactoring.


You may have heard of the SOLID acronym for five important design principles. BananaHuman runs counter to two of them: the Single Responsibility Principle (SRP) and the Open/Closed Principle (OCP).

Bananas and humans may share a lot of DNA, but just like code, they should also be expected to evolve, and probably evolve separately from each other. That same DNA may not always be exactly shared. The SRP states that a class should only have one responsibility or- equivilently- should only have one reason to change. But BananaHuman will always automatically have at least two possible reasons to change- a change in the specifications for Banana or a change in specifications for Human.

Why is this the case specifically for BananaHuman, but not for all general base classes? Because a base class should represent one single well-defined concept, just like any other class. So for example Mammal would only have to change if the features making up the concept of a mammal change. If one particular mammal evolved to lose its hair, it's that animal's class that would change, not the base Mammal class. BananaHuman, on the other hand, is by definition "the features common to both a banana and a human", so it will always be coupled to at least two rather than one concepts. Likewise, there may be several things in common between a banana and a human which don't have much else to do with each other. Shoving these all into a single class reduces cohesion and piles more responsibilities into one place.

The OCP states that a software entity (such as an interface, class or method) should be open to extension but closed to modification when requirements are added or change. For example if you added another living organism sharing the same traits as Banana and Human, you'd have to change the name. Or if it only shared some of the traits, you'd have to shuffle around the base classes, potentially even running into multiple inheritance problems if this came up multiple times. I'm sure there are plenty of other situations which would lead to OCP violations too.


So what should you do?

Well, if you read the above and thought that the characterization of BananaHuman was unfair, and that actually it does map to a very well defined concept just like Mammal does then... rename it to what it actually is! That's all you need to do, and you're probably good to go. It doesn't matter if the name is long (though ideally concise is better, and you should make sure length doesn't indicate you're jamming multiple things together into one series of words).

If that's not the answer, then look into the idea of composition over inheritance. For example, if you have multiple living organisms which all have lungs, instead of creating a LivingOrganismWithLungs class, create a Lungs class, and have every living organism with lungs contain an instance. If you can separate out the common features into their own classes like this then you have a much nicer solution.

If those are both really not possible (rare, but it can happen) then BananaHuman may be the best option left. It would have to be up to your judgement to evaluate the SRP and OCP problems versus the Don't Repeat Yourself (DRY) violation.

Upvotes: 1

SynerCoder
SynerCoder

Reputation: 12776

You can use the EditorBrowsableAttribute and apply it to your class. This will make you class disappear from Intellisense if people are using your .dll. If you have your project referenced instead of the dll it will still be visible.

Use like:

[EditorBrowsable(EditorBrowsableState.Never)]
public class BananaHuman
{
    //....
}

So if you would give me your .dll I wouldn't see BananaHuman pop up in Intellisense. But if I would inspect the Banana or Human class I would still see it inherited from BananaHuman because that IS the case. The EditorBrowsable attribute just makes it disappear from Intellisense, what is what you want.

Upvotes: 5

Related Questions