views:

232

answers:

8

Can you provide scenarios when we inherit from a class, it works for a while, but then something else changes and introduces a bug? I came up with the following situation:

  • to implement Rectangle-with-hole, we inherit from class Rectangle. In the constructor, we check that the hole is inside the rectangle
  • Later someone adds a new method Resize to class Rectangle. They don't check if the hole is still inside.
  • After Resize, we can have Rectangle-with-holes with holes not inside rectangles, which is a bug.

What are the other problems I should be careful if I choose to use object inheritance in C#.

+5  A: 

What you are describing is one of the pitfalls of inheritance.

Another pitfall is a deep inheritance hierarchy. Stackoverflow thread on composition over inheritance.

Chuck Conway
wish i could vote this up more.
Matt Briggs
A: 

Don't call a virtual method in a constructor. See Eric Lippert's posts about it (part 1 and part 2).

Darin Dimitrov
+2  A: 

This is known as the brittle base class problem.

Another potential problem with inheritance in general is when you would like to use your own base class but a framework requires a specific base class (e.g., ContextBoundObject) instead of an interface.

Mark Cidade
A: 

Think of LSP (Liskov Subtitution Principle)

saurabh
How does this answe help?
Gabe
+1 for the reference to Liskov.
Robaticus
My Rectangle-with-hole is still a Rectangle, is it not? How is this principle violated?
AlexKuznetsov
+5  A: 

There are many ways a change to a base class can affect derived class behaviour. What if the author suddenly decided to make the class sealed for example?

Like any interface it needs to be stable if consumers are to not require modification.

The primary "rule" with inheritance is the Liskov Substition Principle. This states that the derived class should be substitutable for the base class or other classes derived from it.

This case breaks this rule on the face of it, as a rectangle with a hole is not a rectangle.

Usually it's best to use interfaces to divide behaviour into sensible implementable chunks. Such interfaces are generally named with adjectives rather than nouns. For example, it makes sense for both a rectangle and a rectangle with a hole to be rendered, so an interface might be IRenderable. If it's resizable you might have a IResizable, etc. These could be aggregated into an IShape, but you want to be careful that your definition of Shape defines just the behaviours in your problem domain.

Directly deriving from another class can be dangerous as you're then bound by the behaviour of that class. If you genuinely need to do that it can be best to extract the implementation you need into a common base class (e.g. Rectangle : RectangleImplementation, IShape).

mancaus
+1  A: 

That's why the prescient C# language designers added the sealed keyword to the language. Use judiciously.

And use code contracts to test your invariants to get an early warning about breakage.

Hans Passant
Hans, what do you mean by "use code contracts to test your invariants to get an early warning about breakage"?
AlexKuznetsov
Link: http://devjourney.com/blog/code-contracts-part-4-object-invariants/
Hans Passant
Nice link, thanks!
AlexKuznetsov
A: 

Better use -Decorator Pattern in these types of cases. This provides better way of extension.

This can be implemented using the following diagram:alt text

The rectangle object will be created and wrapped into a HoleDecorator object which will be responsible for providing the hole. When resizing of the Rectangle is done, the Resizing of HoleDecorator will be called, this will call the Resize of the Rectangle object first and then calls the AddedBehavior for the Hole Decorator which will specify what to be done to the hole when the main component is resized.

Chinjoo
Can specify how would your implementation of Rectangle-with-hole look like?
AlexKuznetsov
A: 

I think this is not related with c# or inheritance. No matter you do, this is inevitable problem of software development.

Best and simply solution is build your code daily and perform unit testing.

Ertan
We always use unit tests and CI, but I don't see how that could help in this case. We don't create unit tests against situations which do not exist yet. THere is no way we could have a unit test anticipating a Resize method before it was developed. Can you suggest how it could happen?
AlexKuznetsov
Unit tests usually calls methods and compares inputs and outputs. I'm doing scenario based unit tests for similar problems. With your example; after rectangle created, object passed to Rectangle tests (performs resize,move etc) tests and passes it's Rectangle-With-Hole tests (performs boundary checks) with same context. It's fully depends to developer vision and not perfect but sometimes works better than method testing. BTW it's not replacing method testing, it's just a addional test.
Ertan