Home > Blogs > Applying Refactoring Techniques, Part 2

Applying Refactoring Techniques, Part 2

By  Oct 22, 2008

Topics: Programming

This is the second post about applying refactoring techniques. In the first post, we looked at the JsonViewer application, and how I had to refactor it to satisfy a change request. In this post we will look at a deeper refactoring of that same application.

    One of the things that Fowler talks about in Refactoring, is when you need to refactor. His answer is pretty much “all of the time.” One of those times is when a change needs to be done to a system. That is the kind of event that triggered my first refactoring of the JsonViewer code. But seeing the code made me realize that there was a lot more room for refactoring.
    What I really wanted to apply to the code was an Extract Class refacotring. I wanted to factor out the model of the application. In this case that was the string of JSON and the tree data structure that allowed it to be visualized. So I did some Extract Class, Extract Field, and Extract Method refactorizations. Here is what I got for the extracted class:

public class JsonModel
{
    import com.adobe.serialization.json.JSON;
    private static const ROOT_LABEL:String = "ROOT";
    private var _jsonString:String;
    private var _jsonModel:Object;
    public function JsonModel(json:String=null)
    {
        if (json)
        {
            this.jsonString = json;
        }
    }
    [Bindable]
    public function get jsonString():String
    {
        return _jsonString;
    }
    public function set jsonString(json:String):void
    {
        _jsonString = json;
        this.jsonModel = JSON.decode(json);
    }
    [Bindable]
    public function get jsonModel():Object
    {
        return _jsonModel;
    }
    public function set jsonModel(obj:Object):void
    {
        _jsonString = JSON.encode(obj);
        _jsonModel = buildModel(obj);
    }
   
    // static utilities
    private static function buildModel(obj:Object):Object
    {
        return makeModel(obj, ROOT_LABEL);
    }
    private static function makeModel(obj:Object, name:String) : Object
    {
        var model:Object = {};
        model.label = name;
        model.children = [];
        for (var prop:String in obj)
        {
            var value:Object = obj[prop];
            if (value is String || value is Number || value is Boolean)
            {
                var node:Object = {};
                node.label = prop;
                node.data = value;
                model.children.push(node);
            }
            else
            {
                model.children.push(makeModel(value,prop));
            }
        }
        return model;
    }       
}

None of this code is new. Well, maybe the getter/setter code is a little new. You might notice the Bindable annotations that I added to those getter methods. This is a little feature of ActionScript that allows an event to be fired when an attribute is changed. It makes it possible to do direct data binding to the Flex controls. Here is the MXML for the view now:


<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical" width="600" height="800"
backgroundGradientAlphas="[1.0, 1.0]"
backgroundGradientColors="[#0C0303, #0C0303]">
<mx:Panel width="100%" height="100%" layout="vertical" id="mainPanel" title="JSON Viewer" color="#12D1FA" backgroundColor="#49575A">
<mx:HBox width="100%" height="10%" color="#070707">
<mx:Label text="Enter URL:" color="#12D1FA"/>
<mx:TextInput width="50%" id="urlInput"/>
<mx:Button label="Load" click="loadUrl()"/>

<mx:HBox width="100%" height="100%" color="#070707">
<mx:TextArea id="jsonInput" height="100%" width="80%" text="{model.jsonString}"/>
<mx:VBox height="100%">
<mx:Button label="View" id="goBtn" click="displayTree(jsonInput.text)"/>
<mx:Button label="Clear" id="resetBtn" click="reset()"/>


<mx:HBox width="100%" height="100%" color="#080808">
<mx:Tree dataProvider="{model.jsonModel}" width="80%" height="100%"
id="jsonTree" change="selNode(event)"/>
<mx:TextArea id="valueBox" width="20%" height="100%"/>


What is important to note here is the TextArea (jsonInput) and Tree (jsonTree). These are now bound to the attributes of a JsonModel instance. So all we need to do is set attributes on the model and the view is automatically updated. Here is the various event handler code.


        import mx.controls.Alert;
       
        [Bindable]
        private var model:JsonModel = new JsonModel();
       
        // this is useful as standalone function because of try-catch
        private function displayTree(json:String):void
        {
            valueBox.text = null;
            try{
                model.jsonString = json;
            } catch (e:Error) {
                Alert.show("Error parsing JSON: " + e.message);
            }   
        }       
        private function reset():void
        {
            this.model = new JsonModel();
            valueBox.text = null;
        }
        private function selNode(event:Event):void
        {
            var node:Object = Tree(event.target).selectedItem;
            if ((node.data is Boolean && !Boolean(node.data)) || node.data)
                valueBox.text = Tree(event.target).selectedItem.data.toString();
            else
                valueBox.text = null;
        }
        private function loadUrl():void
        {
            var loader:URLLoader = new URLLoader(new URLRequest(urlInput.text));
            loader.addEventListener(Event.COMPLETE, function(e:Event):void{ displayTree(loader.data); });
            loader.addEventListener(IOErrorEvent.IO_ERROR, function(e:Event):void{ Alert.show("Error retrieving URL: " + urlInput.text); });
        }

This is greatly simplified. I did an Inline Method refactoring so that when either the JSON loads from a URL or from clicking on the View button, we just call the displayTree function. This clears the box (did this more as proof of concept, to make sure what was displaying in the box was always coming from data binding) and simply sets the jsonString attribute of the JsonModel instance. Data binding does the rest, the contents of the TextArea and Tree are both refreshed auto-magically. This little bit of elegance was only possible because of the refactoring.

Become an InformIT Member

Take advantage of special member promotions, everyday discounts, quick access to saved content, and more! Join Today.

Other Things You Might Like

Xamarin Unleashed

Xamarin Unleashed