Click here to Skip to main content
Click here to Skip to main content

Comparing Declarative And Imperative Programming

, 17 Jun 2005
Rate this:
Please Sign up or sign in to vote.
A simple example comparing/contrasting the differences in imperative and declarative programming.

Introduction

This is a lightweight introduction to the differences between imperative and declarative programming. I'm not here to toot my horn about which is better, rather to present a simple example that illustrates what declarative programming is about by comparing and contrasting it to imperative programming. There is one thing I will say-- to me, declarative programming is more than just UI definition. If you go that route, you'll discover that there appears to be some fundamental differences in programming approach. When "programming" declaratively (as in, not just constructing object graphs), I find that I create a lot of abstracted helper classes. There are pros and cons to this. The pro is that I'm thinking more in terms of abstraction and re-use (remember those buzzwords from the 80's?). I also tend to think more in terms of re-usable functional blocks, especially when working with containers and workflows. The con is that all this abstraction is a performance hit, and there are a lot more assemblies that get distributed with the application. Declarative programming is not necessarily "lean and mean". In other words, it's not something you probably want to do for a quick and dirty app. However, having done declarative programming for years (not just in XML), in the long term, I think the combination of an abstraction layer driven by declarative code backed by application specific imperative code makes for very maintainable and extensible applications.

About this example

I'm somewhat reluctant to provide a download with this article. I'm using MyXaml as the declarative parser along with several abstraction layers to handle events, workflows, containers, and data binding. The point of this article is not the code, but the example comparing imperative C# code with a particular declarative style, namely XML parsed by MyXaml. If there's enough interest in the code, I might revise my view here and post it. Keep in mind, when looking at the declarative code, that first off, XML is only one of the many declarative syntax styles. And within the context of XML, the class-property/collection-class object graph style you will see here is only one variant. And then, within that, there are specific syntactical elements that again are specific to MyXaml. What I'm trying to convey with this article's example is not a set-in-stone declarative syntax, but rather the possibilities and differences in programming style when comparing imperative with declarative programming.

To design or not to design

Imperative programming has one great advantage over declarative programming: designers! In this example, I didn't use the Visual Studio designer for the imperative code, mainly because I wanted something leaner than what the designer produces. But this really is the bane of declarative programming-- the lack of designer support. And to make matters more difficult, declarative programming is rather open-ended with regards to your abstraction layer implementation. So, if you write an abstraction layer (say, workflows), then you're looking at writing a designer next to support your abstraction. With imperative programming, the need for designers is less because applications aren't necessarily built with this level of abstraction in mind. You'll see what I mean by this later, when we look at the workflow example.

Another problem is that third party toolkits, like Infragistics and DevExpress, are not easy to work with declaratively. I've discussed this issue on my blog and strongly recommend that anyone interested in using third party toolkits implement a declarative-friendly wrapper around the toolkit. My blog entries on docking managers is an excellent example of why you would create a wrapper around the docking manager.

The example

The example is a simple dialog that I designed for auto-generating a plug-in assembly. It demonstrates:

  • UI definition.
  • Data binding to interface between the UI and the business layer container.
  • Events.
  • Workflows.

Application initialization

Imperative code

Initializing the imperatively coded form is very simple:

using System;
using System.Windows.Forms;

namespace ImperativePlugInDialog
{
  public class ImperativeDemo
  {
    protected PluginContainer pluginContainer;
    protected Form form;

    [STAThread]
    static void Main() 
    {
      ImperativeDemo d=new ImperativeDemo();
      d.Init();
    }
  
    public void Init()
    {
      ... // see below
    }
  }
}

Declarative code

The initialization code for the declarative example is a bit more complicated:

using System;
using System.Windows.Forms;

using MyXaml.Core;
using MyXaml.MxHelpers;

namespace DeclarativePlugInDialog
{
  public class DeclarativeDemo
  {
    protected Parser parser;
    [MyXamlAutoInitialize] protected MxContainer newPluginContainer;
    [MyXamlAutoInitialize] protected Form newPluginDlg;

    [STAThread]
    static void Main() 
    {
      DeclarativeDemo d=new DeclarativeDemo();
      d.Init();
    }

    public void Init()
    {
      Parser.AddExtender("MyXaml.WinForms", "MyXaml.WinForms", 
                                         "WinFormExtender");
      parser=new Parser();
      parser.AddReference("App", this);
      Form form=(Form)parser.Instantiate("pluginDlg.myxaml", "*");
      parser.InitializeFields(this);
      form.ShowDialog();
    }
  }
}

because it involves initializing the parser, adding an extender, and adding "App" as a reference the parser can use to resolve event wire-up (more on events later). A MyXaml specific thing is the ability to auto-initialize fields in the specified instance with values from the reference dictionary that's created during parsing.

The user interface

Imperative code

In the imperative world, the user interface is created by the code (usually with a designer!) and defines the form, child controls, and event handlers (this is the content of the Init method mentioned above):

form=new Form();
form.SuspendLayout();

#region FormControls

Label lbl=new Label();
lbl.Location=new Point(10, 10);
lbl.Size=new Size(80, 20);
lbl.Text="Plug-in name:";
lbl.TextAlign=ContentAlignment.MiddleRight;
form.Controls.Add(lbl);

TextBox tbPluginName=new TextBox();
tbPluginName.Location=new Point(95, 10);
tbPluginName.Size=new Size(150, 20);
form.Controls.Add(tbPluginName);

lbl=new Label();
lbl.Location=new Point(10, 35);
lbl.Size=new Size(80, 20);
lbl.Text="Namespace:";
lbl.TextAlign=ContentAlignment.MiddleRight;
form.Controls.Add(lbl);

TextBox tbNamespaceName=new TextBox();
tbNamespaceName.Location=new Point(95, 35);
tbNamespaceName.Size=new Size(150, 20);
form.Controls.Add(tbNamespaceName);

lbl=new Label();
lbl.Location=new Point(10, 60);
lbl.Size=new Size(80, 20);
lbl.Text="Class name:";
lbl.TextAlign=ContentAlignment.MiddleRight;
form.Controls.Add(lbl);

TextBox tbClassName=new TextBox();
tbClassName.Location=new Point(95, 60);
tbClassName.Size=new Size(150, 20);
form.Controls.Add(tbClassName);

lbl=new Label();
lbl.Location=new Point(10, 85);
lbl.Size=new Size(80, 20);
lbl.Text="Create in:";
lbl.TextAlign=ContentAlignment.MiddleRight;
form.Controls.Add(lbl);

TextBox tbPath=new TextBox();
tbPath.Location=new Point(95, 85);
tbPath.Size=new Size(215, 20);
form.Controls.Add(tbPath);

Button btnPath=new Button();
btnPath.Location=new Point(315, 85);
btnPath.Size=new Size(30, 20);
btnPath.Text="...";
btnPath.FlatStyle=FlatStyle.System;
btnPath.Click+=new EventHandler(OnGetPath);
form.Controls.Add(btnPath);

Button btnOK=new Button();
btnOK.Location=new Point(270, 10);
btnOK.Size=new Size(80, 25);
btnOK.FlatStyle=FlatStyle.System;
btnOK.Text="OK";
btnOK.Click+=new EventHandler(OnOK);
form.Controls.Add(btnOK);

Button btnCancel=new Button();
btnCancel.Location=new Point(270, 35);
btnCancel.Size=new Size(80, 25);
btnCancel.FlatStyle=FlatStyle.System;
btnCancel.Text="Cancel";
form.Controls.Add(btnCancel);

form.Text="New Plug-in (Imperative Demo)";
form.StartPosition=FormStartPosition.CenterScreen;
form.ClientSize=new Size(360, 120);
form.AcceptButton=btnOK;
form.CancelButton=btnCancel;

form.ResumeLayout();

#endregion

Declarative code

The declarative code requires namespace mapping:

<?xml version="1.0" encoding="utf-8"?>
<!--<span class="code-comment"> (c) 2005 MyXaml All Rights Reserved --></span>
<MyXaml xmlns="System.Windows.Forms, 
        System.Windows.Forms, 
        Version=1.0.5000.0, 
        Culture=neutral, 
        PublicKeyToken=b77a5c561934e089"
  xmlns:myxaml="MyXaml.Core"
  xmlns:mouse="Clifton.Tools.Events"
  xmlns:mxh="MyXaml.MxHelpers"
  xmlns:flow="MyXaml.MxFlowPanel"
  xmlns:ev="MyXaml.MxEventVector"
  xmlns:wf="MyXaml.MxWorkflow"
  xmlns:def="Definition"
  xmlns:ref="Reference">

which is similar to adding the necessary references to your imperative program. The format for the namespace mapping is MyXaml specific and slightly different from what you'll see in Microsoft's XAML. You can read more on both elsewhere.

The user interface, declaratively defined, builds an object graph of the Form and its child controls:

<Form def:Name="newPluginDlg"
  Text="New Plug-in (Declarative Demo)"
  StartPosition="CenterScreen"
  FormBorderStyle="FixedDialog"
  ClientSize="360, 120"
  AcceptButton="{btnOk}"
  CancelButton="{btnCancel}">
  <Controls>
    <flow:TableLayoutPanel Location="10, 10"
                           Size="250, 70"
                           ColumnWidths="80, 150"
                           RowHeights="20, 20, 20"
                           ColumnStyles="Fixed, Fixed"
                           RowStyles="Fixed, Fixed, Fixed"
                           VerticalSpacing="5"
                           HorizontalSpacing="5">
      <Controls>
        <Label Text="Plug-in name:" TextAlign="MiddleRight"/>
        <TextBox def:Name="tbPluginName"/>
        <Label Text="Namespace:" TextAlign="MiddleRight"/>
        <TextBox def:Name="tbNamespaceName"/>
        <Label Text="Class name:" TextAlign="MiddleRight"/>
        <TextBox def:Name="tbClassName"/>
      </Controls>
    </flow:TableLayoutPanel>
    <Panel Location="10, 85" Size="335, 20">
      <Controls>
        <Label Location="0, 0" Size="80, 20" Text="Create in:"
               TextAlign="MiddleRight"/>
        <TextBox def:Name="tbPath" Location="85, 0" Size="215, 20"/>
        <Button Location="305, 0" Size="30, 20" Text="..." FlatStyle="System"
                MxEventVector="Click; workflowProc.Execute(SelectPluginFolder)"/>
      </Controls>
    </Panel>
    <Button def:Name="btnOk" Location="270, 10" Text="OK" Size="80, 25"
            FlatStyle="System" Click="{App.CreatePlugin}"/>
    <Button def:Name="btnCancel" Location="270, 35" Text="Cancel"
            Size="80, 25" FlatStyle="System"/>
  </Controls>
</Form>

You'll notice that I didn't use a TableLayoutPanel in the imperative code. It's not that I couldn't, it's just that, unless you're using .NET 2.0 or have a component available in the designer, you probably wouldn't. With declarative code, this is an example of thinking more about the problem, in this case, defining all those locations and sizes. With imperative code, you don't think about it at all because the designer is so easy to use. As I mentioned before, a declarative approach makes you think about these things a bit more, if for no other reason, than to save some typing.

There are some MyXaml specific syntactical elements here. For example, the "def:" prefix tells the parser to remember the instance by its Name attribute value. The curly braces {} tell the parser that an object of that name is being referenced, or, in the above case, an event is being wired up. I'll discuss the MxEventVector property later, but only briefly.

Business layer container

I'm using a business layer container with data binding as a better programming practice than simply pulling the information directly out of the control properties. For a simple dialog like this, it's sort of unnecessary, but let's think a bit beyond just this example.

Imperative code

In the imperative code, I have to create a class with properties and events that the binding mechanism can hook into so that I achieve two-way data binding:

#region Containers
public class PluginContainer
{
  protected string pluginName;
  protected string pluginNamespaceName;
  protected string pluginClassName;
  protected string pluginPath;

  public event EventHandler PluginNameChanged;
  public event EventHandler PluginNamespaceNameChanged;
  public event EventHandler PluginClassNameChanged;
  public event EventHandler PluginPathChanged;

  public string PluginName
  {
    get {return pluginName;}
    set
    {
      pluginName=value;
      if (PluginNameChanged != null)
      {
        PluginNameChanged(this, EventArgs.Empty);
      }
    }
  }

  public string PluginNamespaceName
  {
    get {return pluginNamespaceName;}
    set
    {
      pluginNamespaceName=value;
      if (PluginNamespaceNameChanged != null)
      {
        PluginNamespaceNameChanged(this, EventArgs.Empty);
      }
    }
  }

  public string PluginClassName
  {
    get {return pluginClassName;}
    set
    {
      pluginClassName=value;
      if (PluginClassNameChanged != null)
      {
        PluginClassNameChanged(this, EventArgs.Empty);
      }
    }
  }

  public string PluginPath
  {
    get {return pluginPath;}
    set
    {
      pluginPath=value;
      if (PluginPathChanged != null)
      {
        PluginPathChanged(this, EventArgs.Empty);
      }
    }
  }

  public PluginContainer()
  {
    pluginName=String.Empty;
    pluginNamespaceName=String.Empty;
    pluginClassName=String.Empty;
    pluginPath=String.Empty;
  }
}
#endregion

So, we're done with that class, and we can use it in the next section, Data binding.

Declarative code

In the declarative code, I certainly could utilize the imperative class directly, instantiate it, and construct a data binding object graph. But why? I'd like to define the container declaratively:

<mxh:MxContainer def:Name="newPluginContainer">
  <mxh:MxObjects>
    <mxh:MxObject def:Name="pluginName" Type="System.String"/>
    <mxh:MxObject def:Name="pluginNamespaceName" Type="System.String"/>
    <mxh:MxObject def:Name="pluginClassName" Type="System.String"/>
    <mxh:MxObject def:Name="pluginPath" Type="System.String"/>
  </mxh:MxObjects>
</mxh:MxContainer>

So, here I'm using a couple of abstraction layers--MxContainer and MxObject. The container manages the collection of objects and the MxObject class implements a variety of conversion methods and the event handler.

Data binding

The next step is to bind the container's properties to the control properties, after instantiating the container.

Imperative code

pluginContainer=new PluginContainer();
tbPluginName.DataBindings.Add("Text", pluginContainer, "PluginName");
tbNamespaceName.DataBindings.Add("Text", pluginContainer,
       "PluginNamespaceName");
tbClassName.DataBindings.Add("Text", pluginContainer, "PluginClassName");
tbPath.DataBindings.Add("Text", pluginContainer, "PluginPath");

Declarative code

<mxh:MxBinder def:Name="newPluginBinding">
  <mxh:MxBindings>
    <mxh:MxBinding Target="{tbPluginName}" PropertyName="Text" 
                   DataSource="{pluginName}" DataMember="AsString"/>
    <mxh:MxBinding Target="{tbNamespaceName}" PropertyName="Text"
                   DataSource="{pluginNamespaceName}" DataMember="AsString"/>
    <mxh:MxBinding Target="{tbClassName}" PropertyName="Text"
                   DataSource="{pluginClassName}" DataMember="AsString"/>
    <mxh:MxBinding Target="{tbPath}" PropertyName="Text"
                   DataSource="{pluginPath}" DataMember="AsString"/>
  </mxh:MxBindings>
</mxh:MxBinder>

Here I'm using another helper so that I can separate out the binding from the UI definition. I much prefer this approach when doing declarative coding rather than putting the data binding graph directly into the UI graph. And, since the .NET 1.1 Binding class doesn't have a parameter-less constructor, I'd have to use a helper class anyways.

Events

There are two events that I'll illustrate--clicking on OK button and clicking on the "..." button, which brings up the FolderBrowserDialog.

Imperative code

Wiring up the events in imperative code is very simple:

btnPath.Click+=new EventHandler(OnGetPath);
btnOK.Click+=new EventHandler(OnOK);

and the event handlers are straightforward enough:

private void OnOK(object sender, EventArgs e)
{
  MessageBox.Show(pluginContainer.PluginName, "Creating plugin:");
  form.Close();
}

private void OnGetPath(object sender, EventArgs e)
{
  FolderBrowserDialog pluginFolderDlg=new FolderBrowserDialog();
  pluginFolderDlg.Description="Select folder for plug-in:";

  pluginFolderDlg.SelectedPath=pluginContainer.PluginPath;
  pluginFolderDlg.ShowDialog();
  pluginContainer.PluginPath=pluginFolderDlg.SelectedPath;
}

Declarative code

In the declarative code example, I want to give you a flavor for two things-- vectoring an event to any method, not just an event handler, and using a workflow instead of imperative code.

I'm going to take the simple event first--wiring up the OK button's Click event:

<Button def:Name="btnOk"
        Location="270, 10"
        Text="OK"
        Size="80, 25"
        FlatStyle="System"
        Click="{App.CreatePlugin}"/>

This is straightforward enough. The application instance provides a CreatePlugin method that does exactly what the imperative code does, the only difference being how the container is referenced (which is discussed later):

public void CreatePlugin(object sender, EventArgs e)
{
  MessageBox.Show(newPluginContainer["pluginName"].AsString,
      "Creating plugin:");
  newPluginDlg.Close();
}

The workflow event handler is a bit more complicated.

MxEventVector="Click; workflowProc.Execute(SelectPluginFolder)

In the declarative code, I want to be able to wire up an event directly to a method in another instance, without having to write an imperative event handler to pass the event on to the instance method. In this particular case, I want the Click event to be vectored to the Execute method of the workflow processor and have it execute a specific workflow. The MxEventVector property is an "extended" property that the parser allows because in the declarative code, I have instantiated an MxEventVector instance that tells the parser "I know what to do with this property when I see it":

<ev:MxEventVector def:Name="eventVector"/>

This is a MyXaml specific thing-- being able to extend properties of other classes. But it illustrates some interesting things you can do with declarative programming. Underlying all this is some functionality that creates an event handler with the appropriate delegate signature, binds the event, and vectors the event to the specified instance method. The details of all this is beyond the scope of this article.

Workflows

Workflows usually do not fall under imperative programming concepts. They are typically a scripted sequence of instructions, where each instruction references a method that has been imperatively coded. The idea with workflows is that they string together general purpose and application specific functionality to create an application/user specific action. Workflows make it easy to tailor the work being done for different needs, describe meta-level logic control, and in decouple discrete functional blocks. For example, you can use a workflow to span plug-ins without requiring the application to link, at build time, the different modules. If done right, workflows add very little processing overhead because they simply act as a rather dumb instruction queue, maybe with some basic logic for testing, branching, and looping. The real work is done in the imperative code.

I've chosen a workflow style to illustrate how the imperative event handler OnGetPath can be coded declaratively:

<FolderBrowserDialog def:Name="pluginFolderDlg" 
    Description="Select folder for plug-in:"/>

<wf:MxWorkflowProcessor def:Name="workflowProc" DataContainer="{MyXamlDefs}">
  <wf:Workflows>
    <wf:Workflow Name="SelectPluginFolder">
      <wf:Statements>
        <wf:Set Target="{pluginFolderDlg}" Property="SelectedPath"
                Value="*pluginPath.AsString"/>
        <wf:Call Target="{pluginFolderDlg}" Method="ShowDialog"/>
        <wf:Set Target="{pluginPath}" Property="AsString"
                Value="*pluginFolderDlg.SelectedPath"/>
      </wf:Statements>
    </wf:Workflow>
  </wf:Workflows>
</wf:MxWorkflowProcessor>

The first line of XML instantiates the FolderBrowserDialog class-- this is done when the plug-in dialog is created. The workflow illustrates how the last three lines of the imperative code example above are now coded in XML. So, here we are crossing the line-- using a declarative syntax to describe what is usually imperative code. This isn't really the best example, but it does illustrate the idea, I hope, of using workflows.

In the above declarative example, you again see a MyXaml-specific style: using an asterisk to denote a reference (exactly the opposite of what it means in C++) that is resolved when the statement is executed, rather than when the object graph describing the workflow is instantiated (that's what the curly braces do).

Referencing container properties

Referencing an imperative container property is straightforward. It offers compile time checking of spelling and property conversion. Referencing a declarative container property is more time consuming and is prone to errors, both spelling and property conversion, that are not detected until runtime.

Imperative code

MessageBox.Show(pluginContainer.PluginName, "Creating plugin:");

Simple, right? Just use the container's desired property.

Declarative code

MessageBox.Show(newPluginContainer["pluginName"].AsString, "Creating plugin:");

This isn't as simple, because we're using an indexer whose value is the name of the "property" to return, and we're having to state the desired type as well. Partly this is due to the underlying MxObject implementation, and partly it's because of the abstraction layer imposed by the declarative container.

Conclusion

I've read a lot about how declarative programming is the cat's meow because, for example, UI definition takes up less lines of code. Well, don't fall into that trap of thinking. After all, there are hundreds of lines of parser code supporting the declarative UI definition. Instead, think about where you can take advantage of declarative programming, not just in terms of object graph definition, but also other aspects of programming--containers, events, workflows, plug-in architectures, etc. Where do you want to abstract your functionality to gain flexibility in maintenance and/or extensibility? How do you want to manage configuration information? What sort of things can be described better declaratively than imperatively? The nice thing about declarative programming is that you can pick and choose how you want to use it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Marc Clifton

United States United States
Marc is the creator of two open source projets, MyXaml, a declarative (XML) instantiation engine and the Advanced Unit Testing framework, and Interacx, a commercial n-tier RAD application suite.  Visit his website, www.marcclifton.com, where you will find many of his articles and his blog.
 
Marc lives in Philmont, NY.

Comments and Discussions

 
GeneralOther Samples PinmemberFábio Batista18-Jun-05 12:42 
GeneralHmmm... PinmemberDaniel Turini17-Jun-05 8:56 
GeneralRe: Hmmm... PinmemberMichael P Butler17-Jun-05 9:46 
GeneralRe: Hmmm... PinprotectorMarc Clifton17-Jun-05 9:56 
QuestionAnd what about functional programming? PinmemberNemanja Trifunovic17-Jun-05 6:57 
AnswerRe: And what about functional programming? PinprotectorMarc Clifton17-Jun-05 7:19 
AnswerRe: And what about functional programming? PinmemberHugo Hallman19-Jun-05 23:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140721.1 | Last Updated 17 Jun 2005
Article Copyright 2005 by Marc Clifton
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid