Software Engineering
design-patterns interfaces enum switch-statement
Updated Wed, 22 Jun 2022 01:55:53 GMT

Name of this enum-based design pattern to get the type


I have been using a pattern in a lot of places (mainly C#) that I would like to know the name of.

Here is an example of it in C#:

public enum ThingType
{
    A,
    B,
    C
}
public interface IThing
{
    ThingType Type
    { get; }
}
public class ThingA : IThing
{
    public ThingType Type => ThingType.A;
}
public class ThingB : IThing
{
    public ThingType Type => ThingType.B;
}
public class ThingC : IThing
{
    public ThingType Type => ThingType.C;
}

As long as all implementations of IThing have a corresponding member in the enum, I can safely cast to the actual type of an IThing after checking the value of IThing.Type.

public void HandleThing(IThing thing)
{
    switch(thing.Type)
    {
        case ThingType.A:
            ThingA a = (ThingA)thing;
            // Doing something with a...
            break;
        case ThingType.B:
            ThingB b = (ThingB)thing;
            // Doing something with b...
            break;
        case ThingType.C:
            ThingC c = (ThingC)thing;
            // Doing something with c...
            break;
    }
}

I apologize if this question is a duplicate, I went through a few pages of search results for multiple different search phrases and I couldn't find this question already.




Solution

This pattern is called type discriminator.

It was very useful before OOP languages, for example to simulate polymorphic types with the help of discriminated unions. It is still heavily used and justified:

  • in relational database when several different types of records are grouped in a same table (aka single table inheritance on the database side, because records have a very similar structure and differ mainly in their behavior);
  • in heterogeneous and distributed developments when several languages are used, and you cannot count on cross-language polymorphism: objects are then serialized in one language and deserialized in another;
  • in event-driven programming, when the event generation and the event processing is decoupled.

This pattern is however not to be recommended in OOP as a first choice if you're not in one of the situation above. It might lead to an antipattern when it encourages people to think in a case-based manner with lots of specific details instead of abstracting the problem and using a truly polymorphic design:

  • The discriminator forces you to manually implement the polymorphism.
  • Whenever you add a new type, youll have to update your enum and inspect/extend every module or function where the enum is used. So its not easily extensible.
  • On the contrary, truly polymorphic design allows you to derive a new class, and everything you need to change is in that class. If the base class is used 1000 times across hundreds of modules, you wont have to worry at all as long as you respect the interface and its contract.




Comments (5)

  • +0 – Thank you, I'm still trying to figure out how we could have avoided this pattern in our specific use-case with C#. — Mar 05, 2020 at 18:29  
  • +1 – @Romen its difficult to answer in general. Sometimes you can change form do something with x to x, do something with yourself" in which case youd have a method that each x would implement. Sometimes its a mix doing some common things in handle but call a thing method to cope with the differences between a,b,c. Sometimes youd look for a double dispatch, etc... — Mar 05, 2020 at 18:51  
  • +0 – In my one particular use of this enum+interface, the "do something with x" is more like "build the GUI at run-time based on specific members of x's subtype". The X class implementations are in a separate library so they don't even know that there is a GUI at all. Adding a common interface method would require adding a dependency on the GUI library and passing the GUI into the interface method on X. The requirements of the library preclude having any assembly references to a specific GUI framework. — Mar 05, 2020 at 18:55  
  • +0 – @Romen Im not here to judge nor to convince you to change the way. I just point out the usual problems. And give you the name ;-) — Mar 05, 2020 at 19:02  
  • +0 – Meh, this isn't an anti-pattern. If the enum wasn't there, people would do manual "is T" checks, which are even more fragile and un-serializable. — Mar 05, 2020 at 19:07