Skip to main content
Engineering LibreTexts

10.7: Transform Conditionals into Registration

  • Page ID
    33215
  • \( \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 Improve the modularity of a system by replacing conditionals in clients by a registration mechanism.

    Problem

    How can you reduce the coupling between tools providing services and clients so that the addition or removal of tools does not lead to changing the code of the clients?

    This problem is difficult because:

    • Having one single place to look for all the kinds of tools makes it easy to understand the system and easy to add new tools.

    • However, every time you remove a tool, you have to remove one case in some conditional statement, else certain parts (tool clients) would still reflect the presence of the removed tools, leading to fragile systems. Then every time you add a new tool, you have to add a new conditional in all the tool clients.

    Yet, solving this problem is feasible because:

    • Long conditionals make it easy to identify the different type of tools used.

    Solution

    Introduce a registration mechanism to which each tool is responsible for registering itself, and transform the tool clients to query the registration repository instead of performing conditionals.

    Transforming conditionals in tool users by introducing a registration mechanism.
    Figure \(\PageIndex{1}\): Transforming conditionals in tool users by introducing a registration mechanism.

    Steps

    1. Define a class describing plug-in objects, i.e., an object encapsulating the information necessary for registering a tool. Although the internal structure of this class depends on the purpose of the registration, a plug-in should provide the necessary information so the tool manager can identify it, create instance of the represented tool and invoke methods. To invoke a tool method, a method or a similar mechanism like a block closure or inner class should be stored in the plug-in object.
    2. Define a class representing the plug-in manager, i.e., that manages the plug-in objects and that will be queried by the tool clients to check the presence of the tools. This class will certainly be a singleton since the plug-ins representing the tools available should not be lost if a new instance of the plug-in manager is created.

    3. For each case of the conditional, define a plug-in object associated with the given tool. This plug-in object should be created and registered automatically when the tool it represents is loaded, and it should be unregistered if and when the tool becomes unavailable. Sometimes information from the tool client should be passed to the tool. The current tool client can be passed as argument when the tool is invoked.

    4. Transform the entire conditional expression into a query to the tool manager object. This query should return a tool associated to the query and invoke it to access the wished functionality.

    5. Remove any tool client actions that directly activate tools. This behavior is now the responsibility of the plug-in manager.

    The client or the plug-in object may have the responsibility to invoke a tool. It is better to let the plug-in object having this responsibility because it already holds the responsibility of representing how to represent the tools and let the clients just says that they need a tool action.

    Example

    In Squeak [IKM+97], the FileList is a tool that allows the loading of different kinds of files, such as Smalltalk code, JPEG images, MIDI files, HTML, and so on. Depending on the suffix of the selected file, the FileList proposes different actions to the user. We show in the example the loading of the different file depending on their format.

    Before

    The FileList implementation creates different menu items representing the different possible actions depending on the suffix of the files. The dynamic part of the menu is defined in the method menusForFileEnding: which takes a file suffix as its argument and returns a menu item containing the label of the menu item and the name of the corresponding method that should be invoked on the FileList object.

    FileList>>menusForFileEnding: suffix
    
        (suffix = 'jpg') ifTrue:
            [↑MenuItem label:'open image in a window'.
                selector: #openImageInWindow].
        (suffix = 'morph') ifTrue:
            [↑MenuItem label: 'load as morph'.
                selector: #openMorphFromFile].
        (suffix = 'mid') ifTrue:
            [↑MenuItem label: 'play midi file'.
                selector: #playMidiFile].
        (suffix = 'st') ifTrue:
            [↑MenuItem label: 'fileIn'.
                selector: #fileInSelection].
        (suffix = 'swf') ifTrue:
            [↑MenuItem label: 'open as Flash'.
                selector: #openAsFlash].
        (suffix = '3ds') ifTrue:
            [↑MenuItem label: 'Open 3DS file'.
                selector: #open3DSFile].
        (suffix = 'wrl') ifTrue:
            [↑MenuItem label: 'open in Wonderland'.
                selector: #openVRMLFile].
        (suffix = 'html') ifTrue:
            [↑MenuItem label: 'open in html browser'.
                selector: #openInBrowser].
        (suffix = '*') ifTrue:
            [↑MenuItem label: 'generate HTML'.
                selector:#renderFile].
    

    The methods whose selectors are associated in the menu are implemented in the FileList class. We give two examples here. First the method checks if the tool it needs is available, if not it generates a beep, otherwise the corresponding tool is created and then used to handle the selected file.

    FileList>>openInBrowser
        Smalltalk at: #Scamper ifAbsent: [↑ self beep].
        Scamper openOnUrl: (directory url , fileName encodeForHTTP)
    
    FileList>>openVRMLFile
        | scene |
        Smalltalk at: #Wonderland ifAbsent: [↑ self beep].
        scene := Wonderland new.
        scene makeActorFromVRML: self fullName.
    

    After

    The solution is to give each tool the responsibility to register itself and let the FileList query the registry of available tools to find which tool can be invoked.

    Step1. The solution is to first create the class ToolPlugin representing the registration of a given tool. Here we store the suffix files, the menu label and the action to be performed when the tools will be invoked.

    Object subclass: #ToolPlugin
        instanceVariableNames: 'fileSuffix menuLabelName blockToOpen'
    

    Step 2. Then the class PluginManager is defined. It defines a structure to hold the registered tools and defines behavior to add, remove and find registered tool.

    Object subclass: #PluginManager
        instanceVariableNames: 'plugins '
    PluginManager>>initialize
        plugins := OrderedCollection new.
    PluginManager>>addPlugin : aPlugin
        plugins add: aRegistree
    
    PluginManager>>removePlugin: aBlock
    
        (plugins select: aBlock) copy
            do: [:each| plugins remove: each]
    
    PluginManager>>findToolFor: aSuffix
        "return a registree of a tool being able to treat file of format
        aSuffix"
    
        ↑ plugins
            detect: [:each| each suffix = aSuffix]
            ifNone: [nil] 
    

    Note that the findToolFor: method could take a block to select which of the plug-in objects satisfying it and that it could return a list of plug-in representing all the tools currently able to treat a given file format.

    Step 3. Then the tools should register themselves when they are loaded in memory. Here we present two registrations, showing that a plug-in object is created for each tool. As the tools need some information from the FileList object such as the filename or the directory, the action that has to be performed takes as a parameter the instance of the FileList object that invokes it ([:fileList |...] in the code below).

    In Squeak, when a class specifies a class (static) initialize method, this method is invoked once the class is loaded in memory. We then specialize the class methods initialize of the classes Scamper and Wonderland to invoke their class methods toolRegistration defined below:

    Scamper class>>toolRegistration
    
        PluginManager uniqueInstance
            addPlugin:
            (ToolPlugin
                forFileSuffix: 'html'
                openingBlock:
                    [:fileList |
                    self openOnUrl:
                        (fileList directory url ,
                            fileList fileName encodeForHTTP)]
                        menuLabelName: 'open in html browser')
    
    Wonderland class>>toolRegistration
    
        PluginManager uniqueInstance
            addPlugin:
            (ToolPlugin
                forFileSuffix: 'wrl'
                openingBlock:
                    [:fileList |
                    | scene |
                    scene := self new.
                    scene makeActorFromVRML: fileList fullName]
                menuLabelName: 'open in Wonderland')
    

    In Squeak, when a class is removed from the system, it receives the message removeFromSystem. Here we then specialize this method for every tool so that it can unregister itself.

    Scamper class>>removeFromSystem
    
        super removeFromSystem.
        PluginManager uniqueInstance
            removePlugin: [:plugin| plugin forFileSuffix = 'html']
    
    Wonderland class>>removeFromSystem
    
        super removeFromSystem.
        PluginManager uniqueInstance
            removePlugin: [:plugin| plugin forFileSuffix = 'wrl']
    

    Step 4. The FileList object now has to use the ToolsManager to identify the right plug-in object depending on the suffix of the selected file. Then if a tool is available for the given suffix, it creates a menu item specifying that the FileList has to be passed as argument of the action block associated with the tool. In the case where there is no tool a special menu is created whose action is to do nothing.

    FileList>>itemsForFileEnding: suffix
    
        | plugin |
        plugin := PluginManager uniqueInstance
            findToolFor: suffix ifAbsent: [nil].
        ↑ plugins isNil
            ifFalse: [Menu label: (plugin menuLabelName)
                actionBlock: (plugin openingBlock)
                withParameter: self]
            ifTrue: [ErrorMenu new
                label: 'no tool available for the suffix ', suffix]
    

    Tradeoffs

    Pros

    • By applying Transform Conditionals into Registration you obtain a system which is both dynamic and flexible. New tools can be added without impacting tool clients.

    • Tool clients no longer have to check whether a given tool is available. The registration mechanism ensures you that the action can be performed.

    • The interaction protocol between tools and tool clients is now normalized.

    Cons

    • You have to define two new classes, one for the object representing tool representation (plugin) and one for the object managing the registered tools (plugin manager).

    Difficulties

    • While transforming a branch of the conditional into a plug-in object, you will have to define an action associated with the tools via the plug-in object. To ensure a clear separation and full dynamic registration, this action should be defined on the tool and not anymore on the tool client. However, as the tool may need some information from the tool client, the tool client should be passed to the tool as a parameter when the action is invoked. This changes the protocol between the tool and the tool client from a single invocation on the tool client to a method invocation to the tool with an extra parameter. This also implies that in some cases the tool client class have to define new public or friend methods to allow the tools to access the tool client right information.

    • If each single conditional branch is associated only with a single tool, only one plug-in object is needed. However, if the same tool can be called in different ways we will have to create multiple plug-in objects.

    When the legacy solution is the solution

    If there is only a single tool client class, if all the tools are always available, and if you will never add or remove a tool at run-time, a conditional is simpler.

    Related Patterns

    Both Transform Conditionals into Registration and Transform Client Type Checks eliminate conditional expressions that decide which method should be invoked on which object. The key difference between the two patterns is that Transform Client Type Checks moves behavior from the client to the service provider, whereas Transform Conditionals into Registration deals with behavior that cannot be moved because it is implemented by an external tool.

    Script: Identifying simulated switches in C++

    This Perl script searches the methods in C++ files and lists the occurrences of statements used to simulate case statement with if then else i.e., matching the following expression: elseXif where X can be replaced by , //... or some white space including carriage return.

    #!/opt/local/bin/perl
    $/ = '::';
    # new record delim.,
    $elseIfPattern = 'else[\s\n]*{?[\s\n]*if';
    $linecount = 1;
    while (<>) {
        s/(//.*)//g; # remove C++ style comments
        $lc = (split /\n/) -- 1; # count lines
    
        if(/$elseIfPattern/) {
        # count # of lines until first
        # occurrence of "else if"
        $temp = join("",$`,$&);
        $l = $linecount + split(/\n/,$temp) -- 1;
        # count the occurrences of else--if pairs,
        # flag the positions for an eventual printout
        $swc = s/(else)([\s\n]*{?[\s\n]*if)
            /$1\n * HERE *$2/g;
        printf "\n%s: Statement with
            %2d else--if's, first at: %d",
            $ARGV, $swc, $l;
        }
        $linecount += $lc;
        if(eof) {
            close ARGV;
            $linecount = 0;
            print "\n";
        }
    }
    

    This page titled 10.7: Transform Conditionals into Registration 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?