Skip to main content
Engineering LibreTexts

10.4: Factor out State

  • Page ID
    32424
  • \( \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}}\)

    Intent Eliminate complex conditional code over an object’s state by applying the State design pattern.

    Problem

    How do you make a class whose behavior depends on a complex evaluation of its current state more extensible?

    This problem is difficult because:

    • There are several complex conditional statements spread out over the methods of the object. Adding new behavior may affect these conditionals in subtle ways.

    • Whenever new possible states are introduced, all the methods that test state have to be modified.

    Yet, solving this problem is feasible because:

    • The object’s instance variables are typically used to model different abstract states, each of which has its own behavior. If you can identify these abstract states, you can factor the state and the behavior out into a set of simpler, related classes.

    Solution

    Apply the State pattern, i.e., encapsulate the state-dependent behavior into separate objects, delegate calls to these objects and keep the state of the object consistent by referring to the right instance of these state objects (see Figure \(\PageIndex{1}\)).

    As in Transform Self Type Checks, transform complex conditional code that tests over quantified states into delegated calls to state classes. Apply the State pattern, delegating each conditional case to a separate State object. We invite the reader to read State and State Patterns for a deep description of the problem and discussion [GHJV95] [ABW98] [DA97]. Here we only focus on the reengineering aspects of the pattern.

    Transformation to go from a state pattern simulated using explicit state conditional to a situation where the state pattern has been applied.
    Figure \(\PageIndex{1}\): Transformation to go from a state pattern simulated using explicit state conditional to a situation where the state pattern has been applied.

    Steps

    1. Identify the interface of a state and the number of states.

      If you are lucky, each conditional will partition the state space in the same way, and the number of states will equal the number of cases in each conditional. In case the conditionals overlap, a finer partitioning will be required.

      The interface of a state depends on how the state information is accessed and updated, and may need to be refined in the subsequent steps.

    2. Create a new abstract class, State, representing the interface of the state.

    3. Create a new class subclass of State for each state.

    4. Define methods of the interface identified in Step 1 in each of the state classes by copying the corresponding code of the conditional to the new method. Do not forget to change the state of the instance variable in the Context to refer to the right instance of State class. The State methods have the responsibility to change the Context so that it always refers to the next state instance.

    5. Add a new instance variable in the Context class.

    6. You may have to have a reference from the State to the Context class to invoke the state transitions from the State classes.

    7. Initialize the newly created instance to refer to a default state class instance.

    8. Change the methods of the Context class containing the tests to delegate the call to the instance variable.

    Step 4 can be performed using the Extract Method operation of the Refactoring Browser. Note that after each step, the regression tests should still run. The critical step is the last one, in which behavior is delegated to the new state objects.

    Tradeoffs

    Pros

    • Limited Impact. The public interface of the original class does not have to change. Since the state instances are accessed by delegation from the original object, the clients are unaffected. In the straightforward case the application of this pattern has a limited impact on the clients.

    Cons

    • The systematic application of this pattern may lead to an explosion in the number of classes.

    • This pattern should not be applied when:

      • there are too many possible states, or the number of states is not fixed

      • it is hard to determine from the code how and when state transitions occur.

    When the legacy solution is the solution

    This pattern should not be applied lightly.

    • When the states are clearly identified and it is known that they will not be changed, the legacy solution has the advantage of grouping all the state behavior by functionality instead of spreading it over different subclasses.
    • In certain domains, such as parsers, table-driven behavior, encoded as conditionals over state, are well-understood, and factoring out the state objects may just make the code harder to understand, and hence to maintain.

    Known Uses

    The Design Patterns Smalltalk Companion [ABW98] presents a step-by-step code transformation.


    This page titled 10.4: Factor out State 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?