Make abstract classes invisible; or: hiding my banana

Background: I'm still new to C #, 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 . Each living creature can use this class as a base and inherit functionality that is common to all living organisms.

When I worked on some of these derived classes, I learned that bananas and people are very similar . Although this doesn't make much sense, and they don't look like anything, they apparently have most of their "functionality."

Code duplication is bad, so I wrote a new class to reduce maintenance costs. This class is called: BananaHuman . The Human and Banana BananaHuman inherit from BananaHuman .


Problem:

I have no problem with my banana Man (i.e., I understand what this means and why it exists). But in the end, other people (even those who don’t understand (fully) inheritance) will need to use my LivingCreatures.dll. And they won’t understand why intellisense offers BananaHuman when they type “B”.

And consider the following code snippet:

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

But imagine how weird / confusing this is if we replace Living Organism with BananaHuman .

I cannot make BananaHuman private , protected or protected internal (Elements defined in the namespace cannot be explicitly declared in this way). I also can not do this internal , because Human and Banana have both public . If I try this, I get an error message saying that there is an inconsistent accessibility problem.

I feel like I'm missing the obvious, but what should I do? I leave a couple of options / questions:

  • Can BananaHuman be "hidden" to avoid confusion?
  • Should I rewrite BananaHuman for something very long and technical, for example DnaRelatedOrganismsType[X] , where "X" describes their unique relationship?
  • Should I just remove BananaHuman , allow Human and Banana inherit from LivingOrganism and perform additional maintenance when something needs to be changed?
  • Is there another solution that I completely lost?

I searched around and could not find a fixed pattern for this situation. I found this question with a similar name, but I do not know if the answers are applicable, because it seems that he is asking for something completely different.

Any help is much appreciated!

+6
source share
2 answers

You can use EditorBrowsableAttribute and apply it to your class. This will cause the class to disappear from Intellisense if people use your .dll. If you have a link to your project instead of dll, it will still be visible.

Use as:

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

So, if you give me your .dll, I would not see BananaHuman popup in Intellisense. But if I looked at the Banana or Man class, I would still see that it is inherited from BananaHuman , because it is. The EditorBrowsable attribute simply makes it disappear from Intellisense, whatever you want.

+5
source

The existing answer is an excellent technical solution to the specific problem of hiding BananaHuman from intellisense. But since the OP also asks for a design change, I think it also gives a quick answer as to the question of why the existence of BananaHuman is a smell of code, and this should probably be a candidate for refactoring.


You may have heard of the abbreviation SOLID for five important design principles. BananaHuman works against two of these: the Single Responsibility Principle (SRP) and the Open / Closed Principle (OCP).

Bananas and people can share a lot of DNA, but like code, they should also evolve and probably evolve separately from each other. The same DNA cannot always be completely separated. The PSA states that the class should have only one responsibility, or - equivalently - there should be only one reason for the change. But BananaHuman will always automatically have at least two possible reasons for the change - a change in specifications for Banana or a change in specifications for Human .

Why does this apply specifically to BananaHuman , but not to all common base classes? Since the base class must represent one clearly defined concept, like any other class. So, for example, Mammal would only need to change if the functions that make up the concept of changing mammals. If a particular mammal evolved to lose its hair, this would change the class of animals, not the base class of Mammal . BananaHuman , on the other hand, is defined as “features common to both a banana and a person,” so it will always be associated with at least two, not one, concepts. Similarly, there can be several things in common between a banana and a person that have nothing to do with each other. Turning all of these into one class reduces cohesion and brings more responsibilities into one place.

OCP states that a program object (such as an interface, class, or method) should be open for extension, but closed for modification when adding or changing requirements. For example, if you added another living organism that has the same features as Banana and Human , you will need to change the name. Or, if it shares only some features, you will have to navigate through the base classes, perhaps even encounter multiple inheritance problems if this happens several times. I am sure that there are many other situations that can also lead to OCP violations.


So what should you do?

Well, if you read above and thought that the characteristic of BananaHuman was unfair, and that in fact it really corresponds to a very clearly defined concept, just like Mammal does ... renames it what it really is! That is all you need to do, and you are probably good. It doesn't matter if the long name is (although ideally concise is better, and you should make sure that the length does not mean that you mix several things together into one line of words).

If this is not the answer, then take a look at the idea of ​​composition over inheritance. For example, if you have several living organisms that all have lungs, instead of creating the LivingOrganismWithLungs class, create the Lungs class, and every living organism with lungs contains an instance. If you can highlight common functions in your own classes, then you will get a much more pleasant solution.

If this is truly impossible (rarely, but it can happen), then BananaHuman may be the best option. To evaluate SRP and OCP problems, your decision should be assessed as a Do Not Repeat Yourself (DRY) violation.

0
source

Source: https://habr.com/ru/post/952180/


All Articles