25

Should a class know about its subclasses? Should a class do something that is specific for a given subclass for instance?

My instincts tells me that is a bad design, it seems like an anti-pattern of some sort.

m4design
  • 375
  • 3
  • 7
  • 6
    Usually no, but in special cases it's useful. – CodesInChaos Nov 26 '13 at 15:20
  • It *is* an anti-pattern, a well known one called: "Liskov Substitution Principle" or sometimes just LSP, read about it at https://en.wikipedia.org/wiki/Liskov_substitution_principle – Jimmy Hoffa Nov 26 '13 at 16:21
  • 6
    @JimmyHoffa - "Liskov Substitution Principle" is not the name of an anti-pattern. An anti-pattern may violate LSP, but it's not even certain that this would be true here. It's possible that, even if a type knows it's subtypes, that the subtypes can still be substituted for their parent with contravariance and covariance. – Matthew Flynn Nov 26 '13 at 18:46
  • 4
    @MatthewFlynn Perhaps I'm using LSP a little loosely but in my definition it's not the fact that they all fulfill the requirements of eachother that complies with LSP, it's a requirement that they have a decoupling such that not only do they agree with eachother but you could implement another subclass that doesn't violate LSP. If the hierarchy is self-aware then an external implementation couldn't possibly meet LSP without altering the base class. Perhaps that's not strictly LSP but it's mighty close. and yes I should have said LSP *violation* is the anti-pattern – Jimmy Hoffa Nov 26 '13 at 18:54
  • 2
    The few times I have encountered this, I refactored that knowledge into a method which could be overridden in a subclass (either forced by making it abstract or pure virtual, or providing a sensible default implementation that could be overridden). –  Nov 28 '13 at 07:27
  • @JohnGaughan: The fact that a base class can be used to perform some operation X using a particular sequence of method calls, and a derived class *can* be used in the same way, in no way implies that a derived class might not also have a *much better* means of doing X which is not available in the base. For example, calling `Count` on a reference of type `IEnumerable` which happens to identify an `int[100000000]` could iterate through one hundred million items, but calling `ICollection.Count` will be *much* faster. – supercat Jul 10 '14 at 23:52
  • @supercat which is agreeing with me. By allowing the subclass to override functionality, that allows it to change the implementation to be more efficient (to use your example). –  Jul 11 '14 at 21:01
  • @JohnGaughan: If some derived types offer a way of doing something that's better than any means available via the base type, how could code hope to exploit such a feature on objects that support it, but also accept objects without it, other than by testing whether an object it receives is an instance of a type that supports the feature? If the feature doesn't exist in the base class, there won't be anything for a derived class to override. – supercat Jul 11 '14 at 21:30
  • @supercat consider an example where I create a List class. The default implementation to get an element at a particular index might use an iterator to traverse the list to the specified element, which is guaranteed to work for all Lists but might not be the fastest method for some implementations. Now I subclass an ArrayList, which knows it can use direct array access which is constant time. –  Jul 11 '14 at 21:39
  • @JohnGaughan: Suppose it was decided that the base collection type should add a method "Copy up to N items to an array, starting with item I". Would there be anything improper about the new default base type implementation of such a method making use of indexing facilities of types which had them? Future versions of the types with indexing facilities might implement the method themselves, but having the base type "know" about them would allow it to add support even if those other types were implemented by someone else and weren't updated at the same time. – supercat Jul 11 '14 at 22:16
  • @supercat that could work, but that is not what I was talking about. As long as the design is solid and makes sense, that would be fine. –  Jul 16 '14 at 14:50

5 Answers5

35

The answer implied by the concept of classes is "no".

Either whatever action, data or relation you're handling is part of all subclasses - then it should be handled in the superclass without checking the actual type. Or it applies only to some subclasses - then you'd have to perform run-time type checks to do the right thing, the superclass would have to be changed whenever someone else inherits from it (or it might silently do the wrong thing), changes in the derived classes can break the unchanged superclass, etc.

In short, you get a number of bad consequences which are usually bad enough to reject such solutions out of hand. If several of your subclasses do the same thing and you want to avoid code duplication (practically always a good thing), a better solution is to introduce a mid-level class from which all those subclasses can inherit the code.

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
16

Not only should it not know, it simply can't! Usually, a class can be extended anytime, anywhere. It can be extended by classes that didn't even exist when it was written.

Some languages allow extending classes to be controlled by the superclass. In Scala, a class can be marked as sealed, which means that it can only by extended by other classes within the same compilation unit (source file). However, unless those subclasses are also sealed or final, the subclasses can then be further extended by other classes.

In Scala, this is used to model closed algebraic data types, so the canonical Haskell List type:

data List a = Nil | Cons a (List a)

can be modeled in Scala like this:

sealed trait List[+A]

case object Nil extends List[Nothing]

final case class Cons[+A] extends List[A]

And you can guarantee that only those two sub-"classes" exist because List is sealed and thus cannot be extended outside of the file, Cons is final and thus cannot be extended at all and Nil is an object which cannot be extended anyway.

But this is a specific use case (modeling algebraic data types via inheritance) and even in this case, the superclass doesn't actually know about its subclasses. It's more a guarantee to the user of the List type that if he does a case discrimination of Nil and Cons, there won't be any other alternative popping up behind his back.

Jörg W Mittag
  • 101,921
  • 24
  • 218
  • 318
4

The simple answer is No.

It makes code brittle and voilates two basic principles of object oriented programming.

  • Liskov Substitution principle
  • Open Close principle
1

Yes, sometimes. For example, when there is a limited number of subclasses exist. A visitor pattern is an illustration of usefulness of this approach.

Example: an abstract syntax tree (AST) nodes of some well-defined grammar can all be inherited from a single Node class implementing a visitor pattern to handle all node types.

lorus
  • 497
  • 1
  • 3
  • 10
  • 9
    No, no and no. This is not useful and never a good solution. – Sulthan Nov 26 '13 at 14:39
  • 1
    @Sulthan I have worked with ASTs outside of the classroom, and I believe lorus does not truly understand the question. A Visitor is not a type of node on an AST, it is a separate object. It can and should know about the grammar objects. But a high level AST node should not. –  Nov 28 '13 at 07:30
  • 2
    @JohnGaughan The term "knows" confuses me. The base `Node` class, of course, does not contain any direct references to its subclasses. But it contains an `accept` method with a `Visitor` parameter. And `Visitor` contains a `visit` method per each subclass. So, despite `Node` has no direct references to its subclasses, it "knows" about them indirectly, through `Visitor` interface. They all coupled together through it. – lorus Nov 28 '13 at 10:03
  • 2
    @Sulthan Never say never. Option type is a valid example of the case when supreclass knows about descendant one. But as Jorg said it would be marked as sealed. – Pavel Voronin Dec 02 '13 at 20:05
  • Then are agreeing: a visitor needs to know what it is visiting. –  Dec 04 '13 at 02:09
1

If I write a component for a firm and later, after I've left, someone extends it for their own purposes should I be informed about that?

No!

Same with classes. Trust your instincts.

Daniel Hollinrake
  • 740
  • 1
  • 5
  • 13