8

I have a large number of classes with an abstract base class (A) that contains behaviour that must be supported by all the sub classes say [B..I].

In my code, I end up with a collection of objects that is created based on input from an external system. These objects belong to subclasses described above and they go through various operations in my code.

All of these operations require some core and common behaviour so I use the abstract base class and I can call methods on a collection of objects using only the base type ( A.DoTheThing() ).

The problem is, only a small subset of these types must support a common set of functionality. If I add the methods to the base class with a default implementation that does nothing or returns null then I'm going towards a God class. Only a few subtypes would override these methods but I can keep using the base type to process the collection. The large group of subclasses would do nothing in response to method calls based on the default empty implementation in the base class. The ones that override the default behaviour would do what they need to do.

If I don't want to put behaviour where it mostly does not belong, I'd have to define an interface (X) and implement it for the small subset of subtypes that'll be included in the collection. However, I now have a collection based on the type A and at some point after using methods from A, I need to perform operations on the subset of objects that implement X. The only option I'm left with is to filter the collection based on instanceof(X) and call relevant methods.

Which one is the lesser evil and do I have another choice here?

4
  • 9
    I'm having difficulty trying to understand what precisely you are asking. Could you perhaps edit your question to illustrate your problem with a short code snippet? It doesn't have to compile, but it would help to show how your objects would be used, what class hierarchy you have, …. Thanks!
    – amon
    Oct 27, 2016 at 8:58
  • 2
    Default implementations are usually only for programmers convenience, so in case you need a new subclass with only limited functionality you can start with the default implementation instead of the abstract base class. If you really don't know what is in your collection, I would do the filtering. Oct 27, 2016 at 9:07
  • 2
    Your question is contradictory. You first state "All of these operations require some core and common behaviour" and then later "The problem is, only a small subset of these types must support a common set of functionality". So if you figure out which it actually is, your supposed problem should disappear.
    – gardenhead
    Oct 28, 2016 at 1:35
  • A visitor pattern may also be appropriate here, depending.
    – wds
    Oct 28, 2016 at 5:22

8 Answers 8

15

Don't avoid things just because they are considered evil - understand first why they are considered evil, and then decide how to avoid that evilness.

You are warned about these evil things because they are usually the easy, straightforward way to solve the problem - otherwise people wouldn't always attempt to do them and the warning would be redundant. The problem is that if you don't understand why method A is evil, you may end up using method B which is evil for the exact same reason as method A, but because method B is more awkward than A it was not very popular so nobody felt the need to warn you of it.

I can't find it, but I remember seeing avoiding singleton by making all the class' fields static so that each new object of that class will use the same state(but there is no single instance so it's not a singleton!)

Anyways, this is what you are doing with that master base class. Downcasting is not "evil" in your case, but that master base class suffers from the same problem that downcasting usually suffers from!

Why is downcasting considered evil?

Consider this:

public abstract class Base {
    // bla bla bla
}

public class ImplA extends Base {
    // bla bla bla
}

public class ImplB extends Base {
    // bla bla bla
}

// somewhere else

void doit(Base base) {
    if (base instanceof ImplA) {
        ImplA implA = (ImplA)base;
        // ImplA specific code
    } else if (base instanceof ImplB) {
        ImplB implB = (ImplB)base;
        // ImplB specific code
    }
}

Why is this bad? Because it doesn't handle ImplC. But there is no ImplC!!!

Well, there is no ImplC now, but nothing stops me - or someone else - from writing it next year, making it extend Base. And then they will create an instance of ImplC, and that instance will be passed to doit - which will probably handle it wrong. Because, while we don't know what ImplC means and how doit should handle it, if doit needs special code for ImplA and special code for ImplB, we should assume it'll need special code for ImplC. But you may not be able to add that code(because doit may be part of a third party library), or you may simply not know you need to, because there is no compilation warning that doit does not have special code to handle ImplC. You'll realize eventually though, after a few hours/days of debugging trying to figure out why your program doesn't work...

This is why downcasting is frowned upon, and it's recommended to favor polymorphism and method overriding:

public abstract class Base {
    // bla bla bla
    public abstract void doit();
}

public class ImplA extends Base {
    // bla bla bla
    @Override
    public void doit() {
        // ImplC specific code
    }
}

public class ImplB extends Base {
    // bla bla bla
    @Override
    public void doit() {
        // ImplB specific code
    }
}

With this design, ImplC can have it's own override of doit with it's own specific code, and if you forget to write it you'll get a compilation error.

The problem with the master base class

I argue that your base class suffers from a similar problem - you need to modify the base class to add new functionality. It's not a worst case here, since you have access to the code and you won't forget to add the base methods because you need to use them. But still - you are avoiding downcasting by creating a construct that suffers from downcasting's general problem...

Downcasting is OK in this case

The problem with doit was that it was meant to deal with any Base, but in practice only handled the known types of Base - ImplA and ImplB.

Your case is different. You are looping over a collection of A, looking for, let's say, only instances of B, downcasting them to B and using them as B. This loop is not meant to handle all instances of A - it's only meant to handle the Bs in the collection. C, D, ... I will have their own loops, probably elsewhere, that deal with them. And if you add a new subclass J it'll also need new loop(s) - but writing these loops is a fundamental part of subclassing A, not some random method somewhere that needs to be amended.

What if you need to handle multiple subclasses in the same loop?

If you find yourself writing something like this:

for (A a : theBigCollection) {
    if (a instanceof B) {
        B b = (B)a;
        // B specific code
    } else if (a instanceof G) {
        G g = (G)a;
        // G specific code
    }
    // Other subclasses are not handled here
}

You are repeating doit's problem - if this loop needs to handle both A and G, how do we know it doesn't need to handle J?

In this case, you should try to bundle this behavior with a mid subclass or with an interface - let's call it X:

public interface X {
    // bla bla bla
}

public class B extends A implements X {
}

public class G extends A implements X {
}

// the loop from before
for (A a : theBigCollection) {
    if (a instanceof X) {
        X x = (X)a;
        // X specific code
    }
}

Now if J needs to, it can implement X and get handled in this loop. This is probably what you need, since you mentioned a small set of subclasses implementing the same methods. You can have multiple such interfaces if you need to.

Epilogue

The problem with downcasting is the possibility of adding new subclasses that will go through the same code paths but won't have specialized code that handles them. So when considering whether or not you should use downcasting always think what will happen if someone adds a new subclass.

I believe this is a good place to use the wider interpretation of the Zero-One-Infinity Rule. In any code "unit", you should handle zero, one or infinite(==all possible) subclasses of A:

  • zero: You don't use A in that code. Not interesting
  • one: One subclass, or one interface that you try to cast to. But not "one" as in "I have one apple" - it should be one as in "God is one". It should be conceptually impossible to add alternative downcasts, no matter what other subclasses will be added there you are still allowed to change the one type to some interface - as long as it remains one.
  • infinity: You deal with all possible subclasses of A - which means you don't downcast and use A itself.

Note that there may be multiple levels - in my last example I was first downcasting to X(one), and then writing code for X which will work on all possible subclasses of X(infinity).

3
  • Awesome answer. Question though: what if the line } else if (a instanceof G) { would have multiple if instead of else if? Which approach would you take and why? Think of interfaces HasFeatureA and HasFeatureB.
    – Flavius
    Nov 12, 2019 at 0:58
  • @Flavius From my personal experience, unlike the else if version, with the multiple ifs version the code for feature A and the code for feature B will usually have different interaction with the outside world. Maybe they'll be invoked from entirely different places, maybe from the same place but interact with different local variables. These are separate "things", not multiple instances of the same thing - so each of them fits into the "one" category and they should remain multiple ifs.
    – Idan Arye
    Nov 13, 2019 at 12:58
  • There are, of course, cases where each of these features has the same interface to the rest of the code. In these cases the polymorphism model won't help you much, because you can't inherit multiple versions of the same method - not without writing some more code in each class that inherits it (which can, sometimes, be a viable solution). So you may want to create a registry for the features, and iterate over all the features in that registry to apply each of them on a.
    – Idan Arye
    Nov 13, 2019 at 13:00
10

It seems you have already recognized you could use some polymorphic behavior. You have collections of objects and it would be handy to just order each object to "DoTheThing()" even though the details of "doing the thing" vary.

It seems it bothers you that most of those objects could have a no-operation implementation for that behavior. I don't think that's a problem at all. Having a default method that does nothing saves you the trouble of checking the type of an object either with instanceof or with some custom method before calling a method.

Going towards a God class is a separate issue. Perhaps you should evaluate how well your base class follows principles like S and I of SOLID.

Is it really important to pass all objects of the current base type A in a single collection to the methods that will order the objects to DoTheThing()? If it's not, then perhaps those objects that really can DoTheThing() could be passed in a separate collection. Then they could be of another type, by implementing an interface or otherwise, and you wouldn't need to check their type before calling DoTheThing().

1
6

My advice would be to create a meaningful domain model and follow it.

If it makes sense that an instance of class (A) can DoTheThing - then put the function in class (A), but if you are doing this just for your convenience, this is probably a bad decision. The relations between the classes/interfaces should be meaningful and easy to understand.

3

It's reasonable to use an instanceof test in filtering, because the logical scope of the filter is outside the behavior of the class.

Let's start with what I hope is an uncontroversial example: the endpoint of a message queue, which has to dispatch messages based on destination address. The contents of the message are opaque to the filter: it's just doing a string comparison of the address field.

Now something that's closer to your problem: an XML DOM is often implemented with a base class of Node, that is subclassed into Element, Comment, ProcessingInstruction, &c. Typically you will want to process only a subset of the possible node types, so you filter a list of nodes by its type. The DOM actually defines a nodeType value that can be used for filtering, but in practice an instanceof is what people use (at least partly because it's null-safe).

The place that I'd avoid instanceof is to include it in the logic of a method (eg: a method that takes any object, and uses instanceof to perform different operations). In this case, it is almost always better to create polymorphic methods, because that makes the behavior more transparent.

1
  • wanted to say thanks. You're spot on with your example realted to DOM nodes. I chose Idan's answer based on the time he spent on writing the details but your response is valuable too since it gives a real life example of using instanceof in a widely applicable programming model.
    – mahonya
    Nov 5, 2016 at 8:50
2

There are other options as well:

  • Use a boolean property or method instead of directly using instanceOf; this to allow the using client code to make decisions, yet with still more abstraction than directly using an instanceOf class test.

    abstract class BaseA { virtual bool hasFoo () { return false; } }


  • Use a method that returns an interface or null instead of the boolean method. If the interface is returned, clients use it, and if not, they don't.

    abstract class BaseA { virtual IFoo getFoo () { return null; } }

  • In combination with the above, use multiple levels of class hierarchy, so at the root, the interface-returning method returns null, but below some level(s) in the hierarchy, the interface-returning method returns itself.

    abstract class SubC extends BaseA, IFoo { virtual IFoo getFoo () { return this; } abstract void DoFooThing (); // provide methods of IFoo } class SubD extends SubC { override void DoFooThing () { ... } }

The reason to do this would be if there would be numerous methods/operations in IFoo. If there is only one, you might as well just provide the DoThing () in the base class.

3
  • 1
    -1. You are not addressing the problems of downcasting, just implementing your own downcasting instead of using the language's facilities.
    – Idan Arye
    Oct 28, 2016 at 1:15
  • @IdanArye, there is neither downcasting nor instanceOf in the solutions I've presented; the solutions are not "implementing one's own downcasting" but rather merely using OOP virtual methods and overrides to provide abstractions as an alternative of what OP is looking for. We can claim this as useless or unhelpful, but it would be incorrect to say that using overrides is downcasting -- that's an essence of OOP: to get class-based differentiation using virtual methods.
    – Erik Eidt
    Oct 28, 2016 at 1:39
  • You are replacing obj is IFoo with obj.hasFoo() and obj as IFoo with obj.getFoo(). Yes, theoretically you could override these methods to do what ever you want, but any alternative implementation of them would be confusing, misleading and awkward, if not downright wrong.
    – Idan Arye
    Oct 28, 2016 at 2:02
2

The usual preferred alternative here is to keep 'em separated. In other words, instead of using instanceof to filter out your objects that implement only the expanded functionality, always store those objects both in the original collection and in a completely separate collection of interface X objects. This should be quite simple to do at object creation time, and usually enables splitting up of a lot of dual-responsibility calling code as well.

The design principle at work here is called the interface segregation principle. It's the 'I' in the SOLID principles. The occasional method that does nothing is acceptable, and sometimes even desirable. The null object pattern other answers have mentioned is one such case.

When you have a whole group of methods like that is not such a case, however. Forcing a large number of classes to depend on methods they do not use makes your code overly-coupled, and makes your abstract class overly large, which will lead to maintenance problems down the road.

2

I think this is a very interesting question and I have encountered variations of this problem on several occasions. I have used several solutions to this kind of problem all with cons and pros:

Type Filtering

There is nothing inherently bad with applying logic if an object implements a certain interface as you suggest. As a developer you are often "allergic" to if-statements, but if it suits your purpose and its well structured then its fine.

The Visitor Pattern

This pattering is specifically designed to address these kind of problems. I personally dislike the pattern since its a bit non-transparent and the fact that you need to extend your classes to support the pattern itself, but sometimes it can be very useful.

The Strategy Pattern

You could iterate the objects in you collection and use the strategy pattern to apply a suitable strategy to each object. This is similar to type filtering, but it can be more transparent and the code will most likely be cleaner.

1

It is difficult to give a useful answer without specific or concrete examples of the signatures of your classes.

But I will share with you an exercise that could end up with you realizing that the exceptional implementors may not be so exceptional after all

This is my anecdote.

I used to have a base Tank class that received a temperature value from a sensor to calculate some product volume inside it, since some fluids expand with temperature.

Later in time a specialized type of tank arrived that had different temperature sensors at different heights. Such a tank should receive an array of temperatures and calculate an average before calculating the volume.

My first thought was: "good!, now I have to use instanceof and cast to the proper type (creating a hard coupling in the process) in order to process the list of tanks.

But then I put an extra thinking on it and I realized that the original basic tank could receive an array of temperatures also, only that its data feed will always feed it a single-item array. What I thought as a single temp was really an array of temps with a single item. The base tank had a single sensor, but the other type of tank was a refrigerated one that needed several sensors and different heights.

So my suggestion is to further analyze if the difference between the classes that best fit to the abstract class and the ones that doesn't is akin to the ones in the example I gave.

Hope it helps.

2
  • 1
    If you had to use instanceof, that means you were feeding the temperatures to the tanks from external code, right? This sounds like a violation of the LSP - what happens if that external code, which does not know which tank is which, feeds a basic tank with more than one temperature?
    – Idan Arye
    Oct 28, 2016 at 1:19
  • @IdanArye A basic tank would use the temperature at position 0 of the array. Oct 28, 2016 at 8:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.