Programmers who are new to object-orientation are sometimes unaware that by using the dynamic dispatch facilities of the language they can reduce the amount of code they have to write and maintain. A particular anti-pattern (Anti-Patterns Home Website, Wikipedia Article) is the use of a tree of if-then-else statements to test an object's type at run-time so that the correct piece of code can be called. This article uses C#, but the discussion also applies to Java.
Spotting this anti-pattern is easy, just look for lists of run-time type checks:
Types A and B are not related, i.e., they don't share a supertype (except Object, which isn't useful to us in this case). The parameter 'o' is tested to see if it is of type A and if it is, 'o' is cast to type A before doSomethingWithA is invoked.
The disadvantage with this approach is that the above code has to be edited if a new type is introduced. The addition of type C would require a new 'if' statement and method to be written and tested, requiring processObject to be retested as well. As the doSomething methods are not defined as part of the types they manipulate the engineer has extra work to do. They have to pass more parameters to their methods (the reference to 'o'), and the code in the doSomething method is likely to be more verbose than equivalent code defined as part of the type (because code defined outside the type has to perform calls on it to access the data it wants to process). The engineer that writes the above code also has to decide what to do if 'o' is neither A nor B, so processObject isn't type-safe.
processObject is just "object management" code, unnecessary program overhead that helps the engineer track what to do with their objects at run-time. However, this is the job of the language's type system. Doing it manually in this way makes re-use much harder as information on what to do with a given program type has been explicitly coded.
Refactoring the Anti-Pattern
The reason that the engineer has decided they need to test the type of the object at run-time is because its possible types (A and B) share no useful supertype. To refactor this anti-pattern, the first step is to relate the two types through a common interface type (CallInterface) which defines a single method 'call':
The implementation of A's call method then becomes whatever was in its doSomething method. To call the right method, we can then do this:
and the correct call method will be invoked, depending on the run-time type of callable (either A or B). As the language ensures that the correct call method will be invoked, we can completely remove the processObject method. To add type C, we have it implement CallInterface, create one at run-time and just invoke its call method. The above code will work with any type as long as it implements CallInterface. We can then improve the above two lines of code to abstract over the type of object we are creating by using the factory pattern (Factory Method Overview, Wikipedia Article).
Engineers need to check that they don't write code that can be removed by making effective use of the language's type system. Using the type system in this way reduces the amount of code that has to be written, tested and maintained.