Вагиф Абилов (object) wrote,
Вагиф Абилов
object

Class and interface inheritance

Корпоративный эпистолярий.


Yesterday we had a discussion regarding class and interface inheritance, and since it is clear that there is more than one approach to this topic, let me share with you my view on this.

I have lost my faith in class inheritance long time ago, when I was mostly working with MFC. It looks like I am not alone. Try to design new classes:

class ExtendedString : String { … }
class ExtendedTrace : Trace { … }
class ExtendedSqlDataAdapter : SqlDataAdapter { … }

How did it go? It didn’t! You just can’t. All these classes are sealed. .NET carefully prevent you from extending such classes. But why? Can’t .NET team mind their own business? Why a hell can’t I make my own string class with extended features?

I see two reasons for doing this. First, all the above classes are used as parameters passed to methods from other classes. And if I make my own ExtendedString, what can StringBuilder method do with it if I pass it to one of its methods? Second, .NET is very strict about architecture and design. It tries to prevent you from selecting a wrong track. And class inheritance in most cases _is_ a wrong track.

But why?

I remember MFC application class CWinApp. All MFC applications had to derive from this one. In one of my projects I needed to use a third party grid. It came with its own application class: CGridApp. I had to derive from that one. But I already had my own CMyWinApp class with many additions. A grid is just one of the functions of my application, it is not its nature – but still third party software vendor forced me to inherit all my grid-enabled applications from their class. What if my application has a grid and plays media? And I buy another class library that forces me to inherit from CMediaApp. What do I do?

Here we face the biggest problem of class inheritance: inability to naturally support multiple inheritance. Let’s say I have the following classes:

class Car {…}
class PassengerCar { public int Seats; }
class Truck { public int MaxLoad; }
class Toy { public int MinAge; }

Now I have a toy truck. What should I use as a base class for it?

My point: nothing. Period. A toy truck is not a real car, and although it’s a toy, the concept “toy” does not really give much information about its properties. A doll is a toy and a model of a railway with stations, bridges and trains is also a toy. How much do they have in common?

IMO the only maintainable approach to this is to have interfaces ICar, IPassengerCar, ITruck and IToy (of course if we need them as interfaces), and to derive ToyTruck from ITruck and ICar.

You can say that sometimes it is possible to identify the “main nature” of an object. Probably. But it can also be misleading. Let’s say you have an interface IOccupation and classes derived from it: Engineer and GarbageMan. Fine. Now what if a person has two jobs: as an engineer and a garbage-man? What will you use as a base class? His main activity? What if they are equal to him?

So when should it be possible to use class inheritance?

- When the base class is an abstract class. Examples: XmlReader, DataAdapter. These are classes with incomplete functionality that are not intended to be used directly. Note that SqlDataAdapter that inherits from DataAdapter is marked as sealed. You can’t further extend it!

- When the derived class can be expressed as “is a … and can only be a …” So if you have a class for server objects and you are designing a client with similar features, although it is very tempting to inherit from a server class and get these functions “for free”, you should consider containment instead. They are different animals. But you can derive from System.Exception and other exception classes: by creating your own exceptions you just add properties, NullArgumentException “is an exception and can only be an exception”. It just adds extra information about the argument.

- When both based and derived classes are always instantiated directly and not passed as arguments, i.e. they represent some worker objects: readers, writers, loaders etc. Such classes have limited set of functions, used in a limited scope and have a short lifetime: “do a simple job”. Once they become more complicated, you should consider interface inheritance instead. For example, if you have classes Reader and Writer then you can’t have a derived class that both reads and writes.

And you know what is my most hated .NET class? It’s ServicedComponent. It’s a class, not an interface. This means once I derive from it, I’m stuck. Luckily, Indigo is going to change it and replace with an attribute-based approach.
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 4 comments