Reputation: 1826
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:
BananaHuman
to avoid confusion? BananaHuman
to something very long and technical such as DnaRelatedOrganismsType[X]
, where "X" describes their unique relation? BananaHuman
, let Human
and Banana
inherit from LivingOrganism
and do the extra maintenance when something needs changing? 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
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
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