Skip to main content
Engineering LibreTexts

10.1: Transform Conditionals to Polymorphism

  • Page ID
    32421
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    After duplicated code, data containers and god classes, one of the most striking signs of misplaced responsibilities in object-oriented software is the occurrence of large methods consisting almost entirely of case statements that test the type of some argument.

    Although case statements are not inherently bad, in object-oriented code they are frequently a sign that the object doing the testing is assuming responsibilities that would better be distributed to the objects being tested. Big conditionals arise naturally over time, just as duplicated code does. As the software is adapted to handle new cases, these cases pop up as conditionals in the code. The problem with these big conditionals is that they can make the code much more fragile in the long term.

    Forces

    The following forces are at play:

    • As requirements change over time, classes in a software system will have to be adapted to handle new, special cases.

    • Adding new classes or subclasses to a system clutters the namespace.

    • The quickest way to adapt a working piece of software to handle a new requirement, is often to add a conditional test for the special case at some point in the code.

    • Over time, a simple design tends to get cluttered with many conditional tests for special cases.

    • Case statements group all the variants into a single place instead of spreading the different cases across different classes. However, they lead to design that is less flexible if the case statement appears in more than one place.

    • In some programming languages, case statements are a more conventional idiom to implement varying behavior than polymorphism.

    Large conditionals are often a sign that behavior implemented by clients should probably be be shifted to the provider classes. Typically a new method will be introduced to the provider hierarchy, and the individual cases of the conditional statement will each move to one of the provider classes.

    Although the symptom is readily recognizable, the technical details and the preferred solution may differ considerably. In particular, when the provider hierarchy already exists, and the conditions explicitly check the class of the provider instance, the refactoring is relatively straightforward. But often the provider hierarchy does not exist, and the conditions test attributes that only implicitly model type information. Furthermore, the conditionals may occur not only in external clients, but even in the provider hierarchy itself.

    Overview

    Transform Conditionals to Polymorphism is a pattern language that describes how to redistribute responsibilities to eliminate these large conditionals, thereby reducing coupling between classes, and improving flexibility in the face of future changes.

    This pattern language consists of six patterns which address the most common problems that occur when conditionals are used to simulate polymorphism. Transform Self Type Checks and Transform Client Type Checks address the most typical cases that arise when explicit type checks are performed. Transform Conditionals into Registration occurs less frequently. We also include Factor out State, Factor out Strategy and Introduce Null Object, not in order to copy three established design patterns (State, Strategy, and Null Object) but rather to show how these design patterns may apply in a reengineering context to eliminate type-checking conditionals.

    Figure \(\PageIndex{1}\) summarizes the relationships and the differences between the patterns.

    Relationships between the patterns constituting Transform Conditionals to Polymorphism.
    Figure \(\PageIndex{1}\): Relationships between the patterns constituting Transform Conditionals to Polymorphism.
    • Transform Self Type Checks eliminates conditionals over type information in a provider class by introducing subclasses for each type case. The conditional code is replaced by a single polymorphic method call to an instance of one of the new subclasses.

    • Transform Client Type Checks transforms conditionals over type information in a client class by introducing a new method to each of the provider classes. The conditional is replaced by a single polymorphic call to the new method.

    • Factor out State handles a special case of Transform Self Type Checks in which the type information that is being tested may change dynamically. A State object is introduced in the provider class to model the changing state, and the conditional is replaced by a call to a method of the new State object.

    • Factor out Strategy is another special case of Transform Self Type Checks in which the algorithms to handle the various provider cases is factored out by introducing a new Strategy object. The key difference with Factor out State is that the algorithm rather than the state may vary dynamically.

    • Introduce Null Object addresses the special case of Transform Client Type Checks in which the test performed checks whether or not the provider is defined. The conditional is eliminated by introducing a Null Object which implements the appropriate default behavior.

    • Transform Conditionals into Registration addresses the situation in which the conditional is responsible for starting up an external tool based on some attribute of an object to be handled. The solution is to introduce a lookup service where tools are registered as plug-ins. The conditional is then replaced by a simple lookup for the registered plug-in. The solution is then fully dynamic because new plug-ins can be added or removed without any changes to the tool users.


    This page titled 10.1: Transform Conditionals to Polymorphism is shared under a CC BY-SA license and was authored, remixed, and/or curated by Serge Demeyer, Stéphane Ducasse, Oscar Nierstrasz.

    • Was this article helpful?