Prototyping and Refactoring

VERSION 2 Published

Created on:Sep 27, 2007 10:31 AM by curl - Last Modified:  Sep 27, 2007 4:37 PM by curl

An hour in the life of a Curl developer

I’ve been working on a test driver application, and needed to make a simple progress display, presenting counts of tests run and their outcome.

The test framework communicates by firing events at a target. Consumers subscribe to monitor progress by adding event handlers (e.g. to log the results).

So its straightforward to rapidly prototype the progress display. React to the notifications by incrementing counter variables, and refreshing corresponding display elements.

Something like this:


{value
    let test-target:TestTarget = {TestTarget}    || counters
    let n-success:int = 0
    let n-failure:int = 0

    || displays
    let success-display:TextDisplay =
        {TextDisplay {splice display-styling}}
    let failure-display:TextDisplay =
        {TextDisplay {splice display-styling}}

    || coordination
    {test-target.add-event-handler
        {on e:TestMessage do
            {switch e.result
             case "success" do
                {inc n-success}
                set success-display.value =
                    {String n-success}
             case "failure" do
                {inc n-failure}
                set failure-display.value =
                    {String n-failure}
            }}}

    || layout
    {VBox spacing = 6px, margin = 6px, border-width = 1px,

        || driver commands
        {HBox spacing = 6px,
            {CommandButton label = {message Run},
                {on Action do
                    {test-target.run-tests}}},
            {CommandButton label = {message Reset},
                {on Action do
                    set n-success = 0
                    set n-failure = 0
                    {success-display.unset-value}
                    {failure-display.unset-value}
                }},
            {Fill}},

        || progress display
        {HBox spacing = 6px,
            success-display,
            failure-display,
            {Fill}}
    }
}

However, some code is duplicated for each category, and does not have any separation of concerns:

  • Model (the counter variables),
  • View (the display elements), and
  • Controller (change management).

The rapid protoype can be refactored to use the Curl data binding archecture, which facilitates a more declarative MVC approach.

We need a data provider for the counters (something that implements DataBindingContext). Now the model can be decoupled from the view, and the controller simplified.

Less code, scales great.


{value
    let test-target:TestTarget = {TestTarget}

    || counters
    let counters:ItemCounter = {ItemCounter}

    || coordination
    {test-target.add-event-handler
        {on e:TestMessage do
            {counters.note-item e.result}
        }}

    || layout
    {VBox spacing = 6px, margin = 6px, border-width = 1px,

        || driver commands
        {HBox spacing = 6px,
            {CommandButton label = {message Run},
                {on Action do
                    {test-target.run-tests}}},
            {CommandButton label = {message Reset},
                {on Action do
                    {counters.clear}
                }},
            {Fill}},

        || progress display
        {HBox spacing = 6px,
            data-binding-context = counters,
            {TextDisplay {splice display-styling},
                {bind value to "success"}},
            {TextDisplay {splice display-styling},
                {bind value to "failure"}},
            {Fill}}
    }
}

Note that we no longer need variables to identify the display elements, so they can be explicitly refreshed. The ‘bind’ clause declares the relationship, and the data-binding-context takes care of the change propagation. The driver command to rest the counters is simplified, as well.

So how do we make the ItemCounter?

Its just an association of items to counts, so is simply an instance of a standard Curl HashTable collection.
That provide the base model. It needs to be extended in two ways. First, to note an item, and increment the corresponding count. Second, to implement DataBindingContextprotocol, so that changes to the model are propagated to consumers.



{define-class public ItemCounter
  {inherits {HashTable-of String, int}, DataBindingContext}

  {constructor public {default ...}
    {construct-super.{HashTable-of String, int} {splice ...}}
  }
  {method {note-item item:String}:void
    {if {self.key-exists? item} then
        {inc self[item]}
     else
        set self[item] = 1
    }
    {self.refresh}
  }
  || DataBindingContext protocol
  {method public {get-data selector:any}:(any, bool)
    {return
        {type-switch selector
         case item:String do
            {self.get-if-exists item}
         else
            (0, false)
        }}
  }
  || HashTable-of protocol
  {method public {clear}:void
    {super.clear}
    {self.refresh}
  }
}

The simple implementation of the ItemCounterabstraction illustrates several useful aspects of the Curl language, including

  • multiple inheritance
  • multiple value returns
  • parameterized types

It also illustrates a few of the built in components and protocols, and how easily they can be extended.

There remain some possibilities for further refactoring.
But I’ll leave those for later.
Average User Rating
(0 ratings)




There are no comments on this document