Assuming a language with some inherent type safety (e.g., not JavaScript):
Given a method that accepts a SuperType
, we know that in most cases wherein we might be tempted to perform type testing to pick an action:
public void DoSomethingTo(SuperType o) {
if (o isa SubTypeA) {
o.doSomethingA()
} else {
o.doSomethingB();
}
}
We should usually, if not always, create a single, overridable method on the SuperType
and do this:
public void DoSomethingTo(SuperType o) {
o.doSomething();
}
… wherein each subtype is given its own doSomething()
implementation. The rest of our application can then be appropriately ignorant of whether any given SuperType
is really a SubTypeA
or a SubTypeB
.
Wonderful.
But, we’re still given is a
-like operations in most, if not all, type-safe languages. And that suggests a potential need for explicit type testing.
So, in what situations, if any, should we or must we perform explicit type testing?
Forgive my absent mindedness or lack of creativity. I know I’ve done it before; but, it was honestly so long ago I can’t remember if what I did was good! And in recent memory, I don’t think I’ve encountered a need to test types outside my cowboy JavaScript.
11
“Never” is the canonical answer to “when is type testing okay?”
There’s no
way to prove or disprove this; it is part of a system of beliefs about what
makes “good design” or “good object-oriented design.” It’s also hokum.
To be sure, if you have an integrated set of classes
and also more than one or two functions that need that kind of
direct type testing, you’re probably
DOING IT WRONG. What you really need is a method that’s implemented differently in
SuperType
and its subtypes. This is part and parcel of object-oriented
programming, and the whole reason classes and inheritance exist.
In this case, explicitly type-testing is wrong not because type testing is
inherently wrong, but because the language already
has a clean, extensible, idiomatic way of accomplishing type
discrimination, and you didn’t use it. Instead, you fell back to a
primitive, fragile, non-extensible idiom.
Solution: Use the idiom. As you
suggested, add a method to each of the classes, then let standard inheritance
and method-selection algorithms determine which case applies. Or if you can’t
change the base types, subclass and add your method there.
So much for the conventional wisdom, and on to some answers. Some cases where
explicit type testing makes sense:
-
It’s a one-off. If you had a lot of type discrimination to do,
you might extend the types, or subclass. But you don’t. You have just
one or two places where you need explicit testing, so it’s not worth
your while to go back and work through the class hierarchy to add
the functions as methods. Or it’s not worth the practical effort to
add the kind of generality, testing, design reviews, documentation,
or other attributes of the base classes for such a simple, limited
usage. In that case, adding a function that does direct testing is rational. -
You can’t adjust the classes. You think about subclassing–but you can’t.
Many classes in Java, for
instance, are designatedfinal
.
You try to throw in apublic class ExtendedSubTypeA extends SubTypeA {...}
and the compiler tells you, in no uncertain terms, that what you’re doing is
not possible. Sorry, grace and sophistication of the object oriented model!
Someone decided you can’t extend their types! Unfortunately, many of the standard library
arefinal
, and making classesfinal
is common design guidance.
A function end-run is what’s left available to you.BTW, this isn’t limited to statically typed languages. Dynamic language Python has a number of
base classes that, under the covers implemented in C, cannot really be modified.
Like Java, that includes most of the standard types. -
Your code is external. You are developing with classes and objects that come
from a range of database servers, middleware engines, and other codebases you can’t
control or adjust. Your code is just a lowly consumer of objects generated elsewhere.
Even if you could subclassSuperType
, you’re not going to be able to get those
libraries on which you depend to generate objects in your subclasses. They’re
going to hand you instances of the types they know, not your variants. This
isn’t always the case…sometimes they are built for flexibility, and they dynamically
instantiate instances of classes that you feed them. Or they provide a mechanism to
register the subclasses you want their factories to construct. XML parsers seem particularly good at
providing such entry points; see e.g. a JAXB example in Java
or lxml in Python. But most code bases do not
provide
such extensions. They’re going to hand you back the classes they were built with and
know about. It generally will not make sense to proxy their results into your custom
results just so you can use a purely object-oriented type selector.
If you’re going to do type discrimination, you’re going to have to do it
relatively crudely. Your type testing code then looks quite appropriate. -
Poor person’s generics/multiple dispatch. You want to accept a variety of different types to your
code, and feel that having an array of very type-specific methods isn’t graceful.
public void add(Object x)
seems logical, but not an array of
addByte
,addShort
,addInt
,addLong
,
addFloat
,addDouble
,addBoolean
,addChar
, andaddString
variants (to
name a few). Having functions or methods that take a high super-type and then
determine what to do on a type-by-type basis–they’re not going to win you the
Purity Award at the annual Booch-Liskov Design Symposium, but
dropping the Hungarian naming
will give you a simpler API. In a sense, youris-a
oris-instance-of
testing
is simulating generic or multi-dispatch in a language context that doesn’t natively
support it.Built-in language support for both
generics and
duck typing reduce the need
for type checking
by making “do something graceful and appropriate” more likely. The multiple
dispatch / interface selection
seen in languages like Julia and Go similarly replace direct type testing with
built-in mechanisms for type-based selection of “what to do.”
But not all languages support these. Java e.g. is generally single-dispatch,
and its idioms are not super-friendly to duck typing.But even with all these type discrimination features–inheritance, generics, duck typing,
and
multiple-dispatch–it’s sometimes just convenient to have a single, consolidated
routine that makes the fact that you are doing something based on the type of the
object clear and immediate. In metaprogramming
I have found it essentially unavoidable. Whether falling back to
direct type inquires constitutes “pragmatism in action” or “dirty coding”
will depend on your design philosophy and beliefs.
7
The main situation I’ve ever needed it was when comparing two objects, such as in an equals(other)
method, which might require different algorithms depending on the exact type of other
. Even then, it’s fairly rare.
The other situation I’ve had it come up, again very rarely, is after deserialization or parsing, where you sometimes need it to safely cast to a more specific type.
Also, sometimes you just need a hack to work around third party code you don’t control. It’s one of those things you don’t really want to use on a regular basis, but are glad it’s there when you really need it.
5
The standard (but hopefully rare) case looks like this: if in the following situation
public void DoSomethingTo(SuperType o) {
if (o isa SubTypeA) {
DoSomethingA((SubTypeA) o )
} else {
DoSomethingB((SubTypeB) o );
}
}
the functions DoSomethingA
or DoSomethingB
cannot easily be implemented as member functions of the inheritance tree of SuperType
/ SubTypeA
/ SubTypeB
. For example, if
- the subtypes are part of a library which you cannot change, or
- if adding the code for
DoSomethingXXX
to that library would mean to introduce a forbidden dependency.
Note there are often situations where you can circumvent this problem (for example, by creating a wrapper or adapter for SubTypeA
and SubTypeB
, or trying to re-implement DoSomething
completly in terms of basic operations of SuperType
), but sometimes these solutions are not worth the hassle or make things more complicated and less extensible than doing the explicit type test.
An example from my yesterdays work: I had a situation where I was going to parallelize the processing of a list of objects (of type SuperType
, with exactly two different subtypes, where it is extremely unlikely that there will ever be more). The unparallelized version contained two loops: one loop for objects of subtype A, calling DoSomethingA
, and a second loop for objects of subtype B, calling DoSomethingB
.
The “DoSomethingA” and “DoSomethingB” methods are both time intensive calculations, using context information which is not available at the scope of the subtypes A and B. (so it makes no sense to implement them as member functions of the subtypes). From the viewpoint of the new “parallel loop”, it makes things a lot easier by dealing with them uniformly, so I implemented a function similar to DoSomethingTo
from above. However, looking into the implementations of “DoSomethingA” and “DoSomethingB” shows they work very differently internally. So trying to implement a generic “DoSomething” by extending SuperType
with a lot of abstract methods was not really going to work, or would mean to overdesign things completely.
8
As Uncle Bob calls it:
When your compiler forgets about the type.
In one of his Clean Coder episodes he gave an example of a function call that is used to return Employee
s. Manager
is a sub-type of Employee
. Let’s assume we have an application service that accepts a Manager
‘s id and summons him to office 🙂 The function getEmployeeById()
return a super-type Employee
, but I want to check if a manager is returned in this use-case.
For example:
var manager = employeeRepository.getEmployeeById(empId);
if (!(manager is Manager))
throw new Exception("Invalid Id specified.");
manager.summon();
Here I’m checking to see if the employee returned by query is actually a manager (i.e. I expect it to be a manager and if otherwise fail fast).
Not the best example, but it’s uncle Bob after all.
Update
I updated the example as much as I can remember from memory.
4
When is type checking OK?
Never.
- By having the behavior associated with that type in some other function, you’re violating the Open Closed Principle, because you’re free to modify the existing behavior of the type by changing the
is
clause, or (in some languages, or depending on the scenario) because you can’t extend the type without modifying the internals of the function doing theis
check. - More importantly,
is
checks are a strong sign that you’re violating the Liskov Substitution Principle. Anything that works withSuperType
should be completely ignorant of what sub types there may be. - You’re implicitly associating some behavior with the type’s name. This makes your code harder to maintain because these implicit contracts are spread throughout the code and not guaranteed to be universally and consistently enforced like actual class members are.
All that said, is
checks can be less bad than other alternatives. Putting all common functionality into a base class is heavy-handed and often leads to worse problems. Using a single class that has a flag or enum for what “type” the instance is… is worse than horrible, since you’re now spreading the type system circumvention to all consumers.
In short, you should always consider type checks to be a strong code smell. But as with all guidelines, there will be times when you’re forced to choose between which guideline violation is the least offensive.
26
If you got a large code base (over 100K lines of code) and are close to shipping or are working in a branch that will later have to be merge, and therefore there is a lot cost/risk to changing a lot of cope.
You then sometimes have the option of a large refractor of the system, or some simple localised “type testing”. This creates a technical debt that should be paid back as soon as possible, but often is not.
(It is impossible to come up with an example, as any code that is small enough to be used as an example, is also small enough for the better design to be clearly visible.)
Or in other words, when the aim is to be paid your wage rather than to get “up votes” for the cleanness of your design.
The other common case is UI code, when for example you show a different UI for some types of employees, but clearly you don’t want the UI concepts to escape into all your “domain” classes.
You can you use “type testing” to decide what version of the UI to show, or have some fancy lookup table that converts from “domain classes” to “UI classes”. The lookup table is just a way of hiding the “type testing” in one place.
(Database updating code can have the same issues as UI code, however you tend to only have one set of database updating code, but you can have lots of different screens that must adapt to the type of object being shown.)
4
The implementation of LINQ uses lots of type checking for possible performance optimizations, and then a fallback for IEnumerable .
The most obvious example is probably the ElementAt method (tiny excerpt of .NET 4.5 source):
public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int index) {
IList<TSource> list = source as IList<TSource>;
if (list != null) return list[index];
// ... and then an enumerator is created and MoveNext is called index times
But there are lots of places in the Enumerable class where a similar pattern is used.
So perhaps optimizing for performance for a commonly-used subtype is a valid use. I am not sure how this could have been designed better.
2
There is an example that comes up often in games developments, specifically in collision detection, that is difficult to deal with without the use of some form of type testing.
Assume that all game objects derive from a common base class GameObject
. Each object has a rigid body collision shape CollisionShape
which may provide a common interface (to say query position, orientation, etc) but the actual collision shapes will all be concrete subclasses such as Sphere
, Box
, ConvexHull
, etc. storing information specific to that type of geometric object (see here for a real example)
Now, to test for a collisions I need to write a function for each pair of collision shape types:
detectCollision(Sphere, Sphere)
detectCollision(Sphere, Box)
detectCollision(Sphere, ConvexHull)
detectCollision(Box, ConvexHull)
...
that contain the specific math needed to perform an intersection of those two geometric types.
At each ‘tick’ of my game loop I need to check pairs of objects for collisions. But I only have access to GameObject
s and their corresponding CollisionShape
s. Clearly I need to know concrete types to know which collision detection function to call. Not even double dispatch (which is logically no different to checking for the type anyway) can help here*.
In practice in this situation the physics engines I’ve seen (Bullet and Havok) rely on type testing of one form or another.
I’m not saying this is necessarily a good solution, it’s just that it may be the best of a small number of possible solutions to this problem
* Technically it is possible to use double dispatch in a horrendous and complicated way that would require N(N+1)/2 combinations (where N is the number of shape types you have) and would only obfuscate what you’re really doing which is simultaneously finding out the types of the two shapes so I don’t consider this to be a realistic solution.
Sometimes you do not want to add a common method to all the classes because it really is not their responsibility to perform that specific task.
For example you want to draw some entities but do not want to add the drawing code directly to them (which makes sense). In languages not supporting multiple dispatch you might end up with the following code:
void DrawEntity(Entity entity) {
if (entity instanceof Circle) {
DrawCircle((Circle) entity));
else if (entity instanceof Rectangle) {
DrawRectangle((Rectangle) entity));
} ...
}
This becomes problematic when this code appears at several places and you need to modify it everywhere when adding new Entity type. If that is the case then it can be avoided by using the Visitor pattern but sometimes it is better to just keep things simple and not overengineer it. Those are the situations when type testing is OK.
The only time I use is is in combination with reflection. But even then is dynamic checking mostly, not hard-coded to a specific class (or only hard-coded to special classes such as String
or List
).
By dynamic checking I mean:
boolean checkType(Type type, Object object) {
if (object.isOfType(type)) {
}
}
and not hard-coded
boolean checkIsManaer(Object object) {
if (object instanceof Manager) {
}
}
Type testing and type casting are two very closely related concepts. So closely related that I feel confident in saying that you should never do a type test unless your intent is to type cast the object based on the result.
When you think about ideal Object-Oriented design, type testing (and casting) should never happen. But hopefully by now you’ve figured out that Object-Oriented programming is not ideal. Sometimes, especially with lower-level code, the code can’t stay true to the ideal. This is the case with ArrayLists in Java; since they don’t know at run-time what class is being stored in the array, they create Object[]
arrays and statically cast them to the correct type.
It’s been pointed out that a common need to type testing (and type casting) comes from the Equals
method, which in most languages is supposed to take a plain Object
. The implementation should have some detailed checks to make if the two objects are the same type, which requires being able to test what type they are.
Type testing also comes up frequently in reflection. Often you will have methods that return Object[]
or some other generic array, and you want to pull out all the Foo
objects for whatever reason. This is a perfectly legitimate use of type testing and casting.
In general, type testing is bad when it needlessly couples your code to how a specific implementation was written. This can easily lead to needing a specific test for each type or combination of types, such as if you want to find the intersection of lines, rectangles, and circles, and the intersection function has a different algorithm for each combination. Your goal is to put any details specific to one kind of object in the same place as that object, because that will make it easier to maintain and extend your code.
3
It’s acceptable in a case where you have to make a decision that involves two types and this decision is encapsulated in an object outside of that type hierarchy. For instance, let’s say you’re scheduling which object gets processed next in a list of objects waiting for processing:
abstract class Vehicle
{
abstract void Process();
}
class Car : Vehicle { ... }
class Boat : Vehicle { ... }
class Truck : Vehicle { ... }
Now let’s say our business logic is literally “all cars get precedence over boats and trucks”. Adding a Priority
property to the class doesn’t allow you to express this business logic cleanly because you’ll end up with this:
abstract class Vehicle
{
abstract void Process();
abstract int Priority { get }
}
class Car : Vehicle { public Priority { get { return 1; } } ... }
class Boat : Vehicle { public Priority { get { return 2; } } ... }
class Truck : Vehicle { public Priority { get { return 2; } } ... }
The problem is that now to understand the priority ordering you have to look at all the subclasses, or in other words you’ve added coupling to the subclasses.
You should of course make the priorities into constants and put them into a class by themselves, which helps keep scheduling business logic together:
static class Priorities
{
public const int CAR_PRIORITY = 1;
public const int BOAT_PRIORITY = 2;
public const int TRUCK_PRIORITY = 2;
}
However, in reality the scheduling algorithm is something that might change in the future and it might eventually depend on more than just type. For instance, it might say “trucks over a weight of 5000 kg get special priority over all other vehicles.” That’s why the scheduling algorithm belongs in its own class, and it’s a good idea to inspect the type to determine which one should go first:
class VehicleScheduler : IScheduleVehicles
{
public Vehicle WhichVehicleGoesFirst(Vehicle vehicle1, Vehicle vehicle2)
{
if(vehicle1 is Car) return vehicle1;
if(vehicle2 is Car) return vehicle2;
return vehicle1;
}
}
This is the most straightforward way to implement the business logic and still the most flexible to future changes.
1
Type testing is a tool, use it wisely and it can be a powerful ally. Use it poorly and your code will start to smell.
In our software we received messages over the network in response to requests. All deserialized messages shared a common base class Message
.
The classes themselves were very simple, just the payload as typed C# properties and routines for marshalling and unmarshalling them (In fact I generated most of the classes using t4 templates from XML description of the message format)
Code would be something like:
Message response = await PerformSomeRequest(requestParameter);
// Server (out of our control) would send one message as response, but
// the actual message type is not known ahead of time (it depended on
// the exact request and the state of the server etc.)
if (response is ErrorMessage)
{
// Extract error message and pass along (for example using exceptions)
}
else if (response is StuffHappenedMessage)
{
// Extract results
}
else if (response is AnotherThingHappenedMessage)
{
// Extract another type of result
}
// Half a dozen other possible checks for messages
Granted, one could argue that the message architecture could be better designed but it was designed a long time ago and not for C# so it is what it is. Here type testing solved a real problem for us in a not too shabby way.
Worth noting is that C# 7.0 is getting pattern matching (which in many respects is type testing on steroids) it can’t be all bad…
Take a generic JSON parser. The result of a successful parse is an array, a dictionary, a string, a number, a boolean, or a null value. It can be any of those. And the elements of an array or the values in a dictionary can again be any of those types. Since the data is provided from outside your program, you have to accept any result (that is you have to accept it without crashing; you can reject a result that isn’t what you expect).
1