JAL Computing

C++COMProgramming .NET Mac Palm CPP/CLI Hobbies

 

Home
Up

Chapter 34 "A Refactored Calculator"

Well this m_C_v Calculator project is ready for refactoring. Here is a definition of refactoring from Wikepedia:

Wikepedia: Refactoring

In software engineering, the term refactoring means modifying source code without changing its external behavior, and is sometimes informally referred to as "cleaning it up". In extreme programming and other agile methodologies refactoring is an integral part of the software development cycle: developers alternate between adding new tests and functionality and refactoring the code to improve its internal consistency and clarity. Automated unit testing ensures that refactoring does not make the code stop working.

In Chapter 31 we implemented the basic m_C_v architecture and learned how to use Events to indirectly notify the Views. In Chapter 32, we added functionality by implementing the IViewable and IViewableEvents interfaces. We also added another View class, FormView, to better delineate the "domain of responsibility". In Chapter 33, we added multiple controllers to support user privileges. In the ever constant struggle to simplify coding, the next step is to look for code segments that are used repeatedly, that are tedious to implement and prone to error. As we discussed in Chapter nine, the twisted coder often finds himself/herself coding to an interface, writing an abstract class that implements the interface and then writing a concrete class that extends from the abstract class. Here again is our IViewable and IViewableEvents interface:

    public abstract class MCV
    {
        public delegate void ClearHandler(object source, EventArgs e);
        public delegate void RefreshHandler(object source, EventArgs e);
        public delegate void EnableHandler(object source, EventArgs e);
        public delegate void DisableHandler(object source, DisableEventArgs e);
        public delegate void ResetHandler(object source, EventArgs e);
        public class DisableEventArgs : EventArgs
        {
            public readonly string Message = "";
            public DisableEventArgs(string message)
            {
                this.Message = message;
            }
        }
        public interface IViewable
        {
            bool Clear();
            bool Refresh();  // IViewable should store state for refresh
            bool Enable();
            bool Disable(string message);
            bool Reset(); // reset state to initial state
        }
        public interface IViewableEvents
        {
            DisableHandler GetDisableHandler();
            EnableHandler GetEnableHandler();
            ClearHandler GetClearHandler();
            RefreshHandler GetRefreshHandler();
            ResetHandler GetResetHandler();
        }

We can now write an abstract class, the AbstractMVCView class, which contains a partial implementation of the IViewable interface and a complete implementation of the IViewableEvents interface. Only the following IViewable methods remain to be implemented by the concrete class that derives from the AbstractMCVView class:

        // abstract IViewable methods
        public abstract bool Clear();
        public abstract bool Refresh();
        public abstract bool Reset();

Here is the AbstractMCVView class code that contains a lot of boiler plate code that can be inherited by the concrete classes FormDerivedView and MortgageTotalDerivedView.

public abstract class AbstractMCVView : MCV.IViewable, MCV.IViewableEvents
    {
        private bool isEnabled = true;
        private string disableMessage = "";
        public bool Enabled
        {
            get { return this.isEnabled; }
            set { this.isEnabled = value; }
        }
        public string DisableMessage
        {
            get { return this.disableMessage; }
            set {
                if (value != null)
                {
                    this.disableMessage = value;
                }
                else
                {
                    this.disableMessage = "";
                }
            }
        }
        // abstract IViewable methods
        public abstract bool Clear();
        public abstract bool Refresh();
        public abstract bool Reset();
        // concrete IViewable methods
        public virtual  bool Enable()
        {
            this.isEnabled = true;
            return true;
        }
        // reset and clear
        public virtual bool Disable(string message)
        {
            this.isEnabled = false;
            if (message != null)
            {
                disableMessage = message;
            }
            else
            {
                disableMessage = "";
            }
            this.Reset();
            this.Clear();
            return true;
        }
        // Provide methods that return delegates for IViewable handlers
        public virtual MCV.DisableHandler GetDisableHandler()
        {
            return new MCV.DisableHandler(AbstractMCVView_OnDisableEvent);
        }
        public virtual MCV.EnableHandler GetEnableHandler()
        {
            return new MCV.EnableHandler(AbstractMCVView_OnEnableEvent);
        }
        public virtual MCV.ClearHandler GetClearHandler()
        {
            return new MCV.ClearHandler(AbstractMCVView_OnClearEvent);
        }
        public virtual MCV.RefreshHandler GetRefreshHandler()
        {
            return new MCV.RefreshHandler(AbstractMCVView_OnRefreshEvent);
        }
        public virtual MCV.ResetHandler GetResetHandler() 
        {
            return new MCV.ResetHandler(AbstractMCVView_OnResetEvent);
        }
        // Actual event handlers
        public virtual void AbstractMCVView_OnDisableEvent(object source, MCV.DisableEventArgs e)
        {
            this.Disable(e.Message);
        }
        public virtual void AbstractMCVView_OnEnableEvent(object source, EventArgs e)
        {
            this.Enable();
        }
        public virtual void AbstractMCVView_OnClearEvent(object source, EventArgs e)
        {
            this.Clear();
        }
        public virtual  void AbstractMCVView_OnRefreshEvent(object source, EventArgs e)
        {
            this.Refresh();
        }
        public virtual void AbstractMCVView_OnResetEvent(object source, EventArgs e)
        {
            this.Reset();
        }
    }

Here is the new version of the MortgageTotalView class called MortgageTotalDerivedView that inherits from our AbstractMCVView class:

 public class MortgageTotalDerivedView : AbstractMCVView
    {
        private Mortgage.Calculation defaultCalculation = new Mortgage.Calculation(0, 0, 0, 0, Mortgage.INVALID, "");
        private Mortgage.Calculation lastCalculation;
        private TextBox tb;
        public Mortgage.Calculation Calculation
        {
            get { return this.lastCalculation; }
        }
        // Constructor
        // ASSERT tb != null
        public MortgageTotalDerivedView(TextBox tb) 
        {
            if (tb == null)
            {
                throw new ArgumentNullException();
            }
            this.tb = tb;
            this.lastCalculation = defaultCalculation;
        }
        // implement IViewable methods
        public override bool Clear()
        {
            tb.Clear();
            return true;
        }
        // refresh from LOCAL data
        public override bool Refresh()
        {
            if (lastCalculation.IsValid())
            {
                RefreshView(lastCalculation);
                return true;
            }
            else
            {
                this.Clear();
                return false;
            }
        }
        public override bool Reset()
        {
            this.lastCalculation = defaultCalculation;
            return true;
        }
        // helper method for Refresh
        private void RefreshView(Mortgage.Calculation mc)
        {
            if (Enabled)
            {
                if ((tb != null) && mc.IsValid())
                {
                    lastCalculation = mc;  // store last Mortgage.Calculation
                    double total = mc.Payment * mc.Months;
                    tb.Text = String.Format("${0:N}", total);
                }
                else
                {
                    tb.Text = "Error.";
                }
            }
            else
            {
                tb.Text = DisableMessage;
            }
        }
        public Mortgage.NotifyHandler GetNotifyHandler()
        {
            return new Mortgage.NotifyHandler(MortgageTotalView_OnNotifyEvent);
        }
        public void MortgageTotalView_OnNotifyEvent(object source, Mortgage.MortgageEventArgs e)
        {
            Mortgage.Calculation c = e.calculation;
            // restore from LOCAL data
            RefreshView(c);
        }
    }

By refactoring out the much of the boiler plate code into the AbstractMCVView class, we make it easier to code a View class that implements IViewable and IViewableEvents. 

More Candidates

There are certainly other opportunities for refactoring in this project. For instance, each Controller in this project creates a new instance of the Model class. If the Model class was expensive to instantiate, it might be better for the Form1 class to instantiate the Model class and then pass a Model reference to each Controller. In Chapter 36, the GetCalculation method is rewritten using the "Try, out" idiom as TryGetCalculation. Finally, one of these days I need to fix the variable naming in the Mortgage algorithm :).

Download Project

Here again is the latest build: M_C_V.zip

- 30 -

 
Send mail to [email protected] with questions or comments about this web site. Copyright © 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 © 
Last modified: 08/04/09
Hosted by www.Geocities.ws

1