Skip to main content
Engineering LibreTexts

10.3: Transform Client Type Checks

  • Page ID
    32423
  • \( \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 Reduce client/provider coupling by transforming conditional code that tests the type of the provider into a polymorphic call to a new provider method.

    Problem

    How do you reduce the coupling between clients and providers of services, where the clients explicitly check the type of providers and have the responsibility to compose providers code?

    This problem is difficult because:

    • Adding a new subclass to the provider hierarchy requires making changes to many clients, especially where the tests occur.

    • Clients and providers will tend to be strongly coupled, since clients are performing actions that should be the responsibility of the providers.

    Yet, solving this problem is feasible because:

    • The conditionals tell you to which classes you should transfer behavior.

    Solution

    Introduce a new method to the provider hierarchy. Implement the new method in each subclass of the provider hierarchy by moving the corresponding case of the clients conditional to that class. Replace the entire conditional in the client by a simple call to the new method.

    Detection

    Apply essentially the same techniques described in Transform Self Type Checks to detect case statements, but look for conditions that test the type of a separate service provider which already implements a hierarchy. You should also look for case statements occurring in different clients of the same provider hierarchy.

    • C++: Legacy C++ code is not likely to make use of run-time type information (RTTI). Instead, type information will likely be encoded in a data member that takes its value from some enumerated type representing the current class. Look for client code switching on such data members.
    • Ada: Detecting type tests falls into two cases. If the hierarchy is implemented as a single discriminated record then you will find case statements over the discriminant. If the hierarchy is implemented with tagged types then you cannot write a case statement over the types (they are not discrete); instead an if-then-else structure will be used.

    • Smalltalk: As in Transform Self Type Checks, look for applications of isMemberOf: and isKindOf:, and tests like self class = anotherClass.

    • Java: Look for applications of the operator instanceof, which tests membership of an object in a specific, known class. Although classes in Java are not objects as in Smalltalk, each class that is loaded into the virtual machine is represented by a single instance of java.lang.Class. It is therefore possible to determine if two objects, x and y belong to the same class by performing the test:

      x.getClass() == y.getClass()

      Alternatively, class membership may be tested by comparing class names:

      x.getClass().getName().equals(y.getClass().getName())

    Steps

    1. Identify the clients performing explicit type checks.

    2. Add a new, empty method to the root of the provider hierarchy representing the action performed in the conditional code (see Figure \(\PageIndex{1}\)).

    3. Iteratively move a case of the conditional to some provider class, replacing it with a call to that method. After each move, the regression tests should run.

    4. When all methods have been moved, each case of the conditional consists of a call to the new method, so replace the entire conditional by a single call to the new method.

    5. Consider making the method abstract in the provider’s root. Alternatively implement suitable default behavior here.

    Transformation of explicit type check used to determine which methods of a client should be invoked into polymorphic method calls.
    Figure \(\PageIndex{1}\): Transformation of explicit type check used to determine which methods of a client should be invoked into polymorphic method calls.

    Other Steps to Consider

    • It may well be that multiple clients are performing exactly the same test and taking the same actions. In this case, the duplicated code can be replaced by a single method call after one of the clients has been transformed. If clients are performing different tests or taking different actions, then the pattern must be applied once for each conditional.

    • If the case statement does not cover all the concrete classes of the provider hierarchy, a new abstract class may need to be introduced as a common superclass of the concerned classes. The new method will then be introduced only for the relevant subtree. Alternatively, if it is not possible to introduce such an abstract class given the existing inheritance hierarchy, consider implementing the method at the root with either an empty default implementation, or one that raises an exception if it is called for an inappropriate class.

    • If the conditionals are nested, the pattern may need to be applied recursively.

    Tradeoffs

    Pros

    • The provider hierarchy offers a new, polymorphic service available to other clients as well.

    • The code of the clients is now better organized and does not have to deal anymore with concerns that are now under the responsibility of the provider.

    • All the code concerning the behavior of a single provider is now together in a single location.

    • The fact that the provider hierarchy offers a uniform interface allows providers to be modified without impacting clients.

    Cons

    • Sometimes it is convenient to see the code handling different cases in a single location. Transform Client Type Checks redistributes the logic to the individual provider classes, with the result that the overview is lost.

    Difficulties

    • Normally instances of the provider classes should be already have been created so we do not have to look for the creation of the instances, however refactoring the interface will affect all clients of the provider classes and must not be undertaken without considering the full consequences of such an action.

    When the legacy solution is the solution

    Client type checks may nevertheless be the right solution when the provider instance does not yet exist or when its class cannot be extended:

    • An Abstract Factory object may need to test a type variable in order to know which class to instantiate. For example, a factory may stream objects in from a text file representation, and test some variable that tells it which class the streamed object should belong to.
    • Software that interfaces to a non-object-oriented library, such as a legacy GUI library, may force the developer to simulate the dispatch manually. It is questionable whether, in such cases, it is cost-effective to develop an object-oriented facade to the procedural library.

    • If the provider hierarchy is frozen (e.g., because the source code is not available), then it will not be possible to transfer behavior to the provider classes. In this case, wrapper classes may be defined to extend the behavior of the provider classes, but the added complexity of defining the wrappers may overwhelm any benefits.

    Example

    Before

    The following C++ code illustrates misplaced responsibilities since the client must explicitly type check instances of Telephone to determine what action to perform. The code in bold highlights the difficulties with this approach.

    class Telephone {
    public:
        enum PhoneType {
            POTSPHONE, ISDNPHONE, OPERATORPHONE
        };
        Telephone() {}
        PhoneType phoneType() { return myType; }
        
    private:
        PhoneType myType;
    protected:
        void setPhoneType(PhoneType newType) { myType = newType; }
    };
    
    class POTSPhone : public Telephone {
    
    public:
        POTSPhone() { setPhoneType(POTSPHONE); }
        void tourneManivelle();
        void call();
    };
    ...
    
    class ISDNPhone: public Telephone {
    public:
        ISDNPhone() { setPhoneType(ISDNPHONE);}
        void initializeLine();
        void connect();
    };
    ...
    
    class OperatorPhone: public Telephone {
    public:
        OperatorPhone() { setPhoneType(OPERATORPHONE); }
        void operatorMode(bool onOffToggle);
        void call();
    };
    
    void initiateCalls(Telephone ** phoneArray, int numOfCalls) {
        for(int i = 0; i<numOfCalls ;i++ ) {
            Telephone * p = phoneArray[i];
    
            switch(p-->phoneType()) {
            case Telephone::POTSPHONE: {
                POTSPhone *potsp = (POTSPhone *) p;
                potsp-->tourneManivelle(); potsp-->call();
                break;
            }
            case Telephone::ISDNPHONE: {
                ISDNPhone *isdnp = (ISDNPhone *) p;
                isdnp-->initializeLine();
                isdnp-->connect();
                break;
            }
            case Telephone::OPERATORPHONE: {
                OperatorPhone *opp = (OperatorPhone *) p;
                opp-->operatorMode(true);
                opp-->call();
                break;
            }
            default: cerr << "Unrecognized Phonetype" << endl; 
            };
        }
    }
    
    Transforming explicit type checks to polymorphic method invocations.
    Figure \(\PageIndex{2}\): Transforming explicit type checks to polymorphic method invocations.

    After

    After applying the pattern the client code will look like the following. (See also Figure \(\PageIndex{2}\)).

    class Telephone {
    public:
        Telephone() {}
        virtual void makeCall() = 0;
    };
    
    Class POTSPhone : public Telephone {
        void tourneManivelle();
        void call();
    public:
        POTSPhone() {}
        void makeCall();
    };
    void POTSPhone::makeCall() {
        this-->tourneManivelle();
        this-->call();
    }
    
    class ISDNPhone: public Telephone {
        void initializeLine();
        void connect();
    
    public:
        ISDNPhone() { }
        void makeCall();
    };
    void ISDNPhone::makeCall() {
        this-->initializeLine();
        this-->connect();
    }
    
    class OperatorPhone: public Telephone {
        void operatorMode(bool onOffToggle);
        void call();
    public:
        OperatorPhone() { }
        void makeCall();
    };
    void OperatorPhone::makeCall() {
        this-->operatorMode(true);
        this-->call();
    }
    void initiateCalls(Telephone ** phoneArray, int numOfCalls) {
        for(int i = 0; i<numOfCalls ;i++ ) {
            phoneArray[i]-->makeCall();
        }
    }
    

    Rationale

    Riel states, “Explicit case analysis on the type of an object is usually an error. The designer should use polymorphism in most of these cases” [Rie96]. Indeed, explicit type checks in clients are a sign of misplaced responsibilities since they increase coupling between clients and providers. Shifting these responsibilities to the provider will have the following consequences:

    • The client and the provider will be more weakly coupled since the client will only need to explicitly know the root of the provider hierarchy instead of all of its concrete subclasses.

    • The provider hierarchy may evolve more gracefully, with less chance of breaking client code.

    • The size and complexity of client code is reduced. The collaborations between clients and providers become more abstract.

    • Abstractions implicit in the old design (i.e., the actions of the conditional cases) will be made explicit as methods, and will be available to other clients.

    • Code duplication may be reduced (if the same conditionals occur multiply).

    Related Patterns

    In Transform Client Type Checks the conditional is made on the type information of a provider class. The same situation occurs in Introduce Null Object where the conditional tests over null value before invoking the methods. From this point of view, Introduce Null Object is a specialization of Transform Client Type Checks.

    Transform Conditionals into Registration handles the special case in which the client’s conditional is used to select a third object (typically an external application or tool) to handle the argument.

    Replace Conditional with Polymorphism is the core refactoring of this reengineering pattern, so the reader may refer to the steps described in [FBB+99].


    This page titled 10.3: Transform Client Type Checks 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?