» Home
  » Introduction
 

Home Index Chapter 2 Chapter 4

A Twisted Look at Object Oriented Programming in C#

By Jeff Louie
09/16/2002

I must admit that my first exposure to object oriented programming (OOP) was frustrating and difficult. As a hobbyist I have struggled through Z80 assembly and EPROM burners, BASIC, Turbo Pascal, Java, C++ COM and now C#. The move to event driven programming and then to object oriented programming presented major conceptual hurdles to my function driven sequential programming mindset. The “aha” moment when OOP made sense was most gratifying, but did not come quickly or easily. It has been a few years since I “got” the OOP mindset and I feel comfortable enough now to try to help fellow travelers with this journey. If OOP comes easily to you, feel free to skip this tutorial. If you are having problems getting your mind around objects and inheritance I hope this tutorial can help you. This tutorial does not represent a conventional teaching method. It assumes a passing knowledge of the C# language and familiarity with the Visual Studio .NET IDE. This is a work in progress and may require correction or revisions. 

Comments are actively requested (email: [email protected]). 

Useful Texts 

I highly recommend the following books. Much of my understanding of OOP has been gleamed from these “classic” texts and then reinforced from coding database projects in Java, C++ and C#. At all times I willfully try to avoid plagiarizing these authors, but my understanding of OOP is so closely tied to these texts that I must cite them as sources of knowledge right from the start! 

Object-Oriented Analysis and Design with Applications Grady Booch, Second Edition, Addison-Wesley, 1994, 589pp. 

Design Patterns Elements of Reusable Object-Oriented Software Gamma Helm, Johnson and Vlissides, Addison-Wesley, 1994, 395pp. 

Object-Oriented Software Construction Second Edition Bertrand Meyer, Prentice Hall, 1997, 1254pp. 

Of course, some of this material is a descendent of my writing from our now out of print book: 

Visual Café for Java Explorer Database Development Edition Brogden Louie and Tittle, Coriolis, 1998, 595pp.

Chapter 3 "Model–View/Controller"

Enough theory. It’s time to code! In this chapter, you will learn about the most basic design pattern, the Model–View/Controller architecture (M-VC). This lesson contains the complete source code of a Model class that encapsulates the complex math required to do mortgage calculations. You will then create a working Mortgage calculator as a Windows Form application. Finally, you will reuse the Model class to create a working calculator as a Web Form application. This is all working C# code that demonstrates the advantages of using the M-VC design pattern.

Model — View/Controller Architecture

A design pattern is a recurring solution that simplifies the design process. The thoughtful study of design patterns allows a developer to learn from the pioneering work of others. The M-VC design pattern is most important, I think, because it forces a programmer to completely rethink the approach to designing an application. The reward is reusable code. Since this is a hands on tutorial, I plan to show you how the M-VC architecture promotes code reuse by migrating a Windows Form solution to a browser based solution using a Web Form.

The Model — View/Controller architecture is a modification of the Model — View — Controller architecture used in the SmallTalk language. In the SmallTalk language, the application is divided into three parts. The Model encapsulates the application logic or algorithms. The View draws the presentation. The Controller responds to user or system events. The key concept is the separation of the application logic from the presentation and event handling code. The Model class is independent of the GUI and is ignorant of any implementation of the GUI. A good Model class should be able to function as part of a console application and support unit testing. In this chapter, you will build an application that separates the complex mathematics of a mortgage calculator (Model) from the presentation and event handling code (View/Controller). The code behind technique of Web Form programming further contributes to the separation of the View (HTML code) and Controller (code behind event handler) code.

There is no question that the complete separation of GUI code from the application logic is a difficult concept for many to grasp. It can be a painful learning experience as it forces a programmer to change the basic approach to application design. Many will fight the process, dragged into the new world of OOP kicking and screaming. But, when you see how much simpler a Model — View/Controller application is to maintain or migrate, you will _see_ the light. I promise.

The Model Class

The following code was adapted from our book "Visual Cafe for Java Explorer, Database Development Edition" Brogden, Louie, Tittel, Coriolis, 1998. The only real change from the Java version is the use of C#’s support for "properties." The Model class implements the algorithms of a mortgage calculator. The fact that the Java code was completely separate from the Java GUI greatly simplified the reuse of this 1997 code in this C# Windows Form application! 

In a nutshell, given three of four  mortgage parameters (principal, interest, period in months, payment), you can calculate the unknown parameter. Solving for interest is not a trivial mathematical solution. I readily admit that I had help solving that equation from my good friend, Dr. W. Carlini! The Model class has an "Init" method that takes all four parameters needed for a mortgage calculator. One and only one parameter must have a value of zero. The zero parameter acts as a marker for the unknown value. 

This class also demonstrates the use of two common programming idioms: the use of a public "IsValid" function to return the internal state of the object and the use of pre and post conditions to validate the input and output of an algorithm.

Using IsValid()

Note that all of the actual calculations occur on a call to the "Init" method, either directly or indirectly through the "args" constructor. If the input parameters and result appear valid, the "target" variable is set to a valid value. If the input parameters or result appear invalid, the "target" variable is set to -1. A public function "IsValid" is provided to the caller that returns the internal state of the object. The public function "IsValid" encapsulates or hides the internal validation logic. I would argue that the use of a public "IsValid" function is a common and useful programming idiom.

Pre and Post Conditions

The downside of separating out the algorithm from the GUI, is that both the Model class and the View/Controller class does input checking to insure runtime reliability and useful user feedback. The Model class implements a common programming construct, the use of pre and post conditions. In the Model class, the "Init" method statically validates the input parameters before passing them on to the DoAlgorithm  method (pre-conditions). The algorithm does not check for a divide by zero error, which is handled internally. After the calculation is complete, the "DoAlgorithm" method validates the result by calling Double.IsNaN (IsNotANumber) (post-conditions). The decision to turn off pre and post conditions in the release build (no check version) is beyond the scope of this tutorial.

The compulsive coder will note the the input validation scheme is not mathematically correct, rejecting interest rates of greater than 100%. Apparently the twisted class has a social agenda.

Complete Code Listing Model.cs

The downside of posting all of the code is that it goes on forever. Click here to skip the code. You can come back later.

/// <summary>
/// Class Model.cs
/// jlouie 07.07.02
/// Adapted from Model.java
/// "Visual Cafe for Java Explorer, Database Development Edition"
/// William Brogden, Jeffrey A. Louie, and Ed Tittel, Coriolis, 1998, 585pp.
/// Supplied "as is"
/// No warranty is expressed or implied
/// This code is for instructional use only
/// </summary>
public class Model 
{
	// internal class constants, not "versionable"
	private const int INVALID= -1;  // flags error
	private const int PRINCIPAL= 0;
	private const int INTEREST= 1;
	private const int MONTHS= 2;
	private const int PAYMENT= 3;
	private double[] arrayDb= new double[4];
	private int target= INVALID;
	private string message= "";
	/* // uncomment to run console self test
	// self test static method outputs state to console
	static void ConsoleDebug(Model model) 
	{
		if (model == null) 
		{
			System.Console.WriteLine("Null object.");
			return;
		}
		System.Console.WriteLine("Message: "+model.Message);
		System.Console.WriteLine("Result: "+model.Result);
		System.Console.WriteLine("Principal: "+model.Principal);
		System.Console.WriteLine("Interest: "+model.Interest);
		System.Console.WriteLine("Months: "+model.Months);
		System.Console.WriteLine("Payment: "+model.Payment);
	}
	*/
	/* // uncomment to run console self test
	// self test
	[STAThread]
	static void Main() 
	{ 
		// test internal consistency of algorithms
		Model model= new Model(100000,8.5,360,0);
		Model.ConsoleDebug(model); // payment = 768.9134584334
		model.Init(0,8.5,360,768.9134584334);
		Model.ConsoleDebug(model);
		model.Init(100000,0,360,768.9134584334);
		Model.ConsoleDebug(model);
		model.Init(100000,8.5,0,768.9134584334);
		Model.ConsoleDebug(model);
		System.Console.ReadLine();
	}*/
	 
	// no arg constructor
	public Model(){;}
	// arg constructor
	public Model(double principal, double interest, int months, double payment) 
	{
		Init(principal, interest, months, payment);
	}
	// factored code, can be called after call to constructor
	// allowing reuse of instance of class
	// eg. object is _not_ immutable by design
	public void Init(double principal, double interest, int months, double payment) 
	{
		// reset flags
		target= INVALID;
		message= "";
		// store input into array of double
		arrayDb[PRINCIPAL]= principal;
		arrayDb[INTEREST]= interest;
		arrayDb[MONTHS]= (double)months;
		arrayDb[PAYMENT]= payment;
		// validate input
		// one, and only one, "value" must be zero --> target
		int zeros= 0;
		int tempTarget= INVALID;
		for (int i=0; i<4; i++) 
		{
			if (arrayDb[i] == 0) 
			{
				zeros++;
				tempTarget= i;
			}
		}
		if (zeros>1) 
		{
			message= "Too many zero parameters.";
			return;
		}
		if (zeros == 0) 
		{
			message= "One parameter must be zero.";
			return;
		}
		// validate interest
		if (interest > 100 || interest < 0) 
		{
			message= "Invalid interest.";
			return;
		}
		// validate months
		if (months < 0) 
		{
			message= "Invalid months.";
			return;
		}
		// validate principal
		if (principal < 0) 
		{
			message= "Invalid principal.";
			return;
		}
		// validate payment
		if (payment < 0) 
		{
			message= "Invalid payment.";
			return;
		}
		// input parameters appear valid
		target= tempTarget;
		DoAlgorithm(target);
	}
	// the actual amortization algorithm
	// m= P*i(1-(1+i)^-N)
	// i=r/1200
	// result= 0 --> marks error
	private void DoAlgorithm(int target) 
	{
		double result= 0;
		double P= arrayDb[PRINCIPAL];  // principal
		double i= arrayDb[INTEREST]/1200; // monthly percentage rate
		double N= arrayDb[MONTHS]; // loan period in months
		double m= arrayDb[PAYMENT]; // monthly payment
			
		if (target>= 0 && target< 4) // validate target
		{
			try 
			{
				switch (target) 
				{
					case PRINCIPAL:  // principal
						result= 1+i;
						result= 1/Math.Pow(result, N);
						result= ((1-result)/i)*m;
						break;
					case INTEREST:  // annual interest
						// algorithm fails if N*m >= P !!
						if ((N*m)<P) 
						{
							throw new ArithmeticException();
						}
						// factor out Interest function, too long
						result= CalcInterest(P,N,m);
						break;
					case MONTHS:  // loan period
						result= (1-(P*i/m));
						result= Math.Log(result);
						result= -result/Math.Log((1+i));
						break;
					case PAYMENT:  // monthly payments
						result= 1+i;
						result= 1/Math.Pow(result,N);
						result= (P*i)/(1-result);
						break;
						//default:
						//break;
				}
			}
			catch 
			{
				result= 0;
			}
		}
		// validate result
		if (Double.IsNaN(result)) 
		{
			result= 0;
		}
		if (result == 0) 
		{
			message= "Input Error.";
		}
		else // valid result
		{
			arrayDb[target]= result;
		}
	}
		
	// a complex iterative calculation for interest
	// thanks to Dr. W. Carlini (and Newton)for the solution
	// returns zero on error
	// ASSERT (N*m)>=P
	private double CalcInterest(double P, double N, double m) 
	{
		double temp= (m/P), answer= (m/P), diff= 100, numerator= 0, denominator= 0, 
accuracy= .00001;
		int index, maxIterations= 1000;
		try 
		{
			for (index= 0; ((diff>accuracy) && (index<maxIterations)); index++) 
			{
				temp= answer;
				numerator= (P*temp/m)+Math.Pow((1+temp),-N)-1;
				denominator= (P/m)-N*Math.Pow((1+temp),(-N-1));
			// if (denominator ==0 ){throw new ArithmeticException();}
				answer= temp-(numerator/denominator);
				diff= answer- temp;
				if (diff<0) 
				{
					diff= -diff;
				}
			}
			answer *= 1200;
			// validate answer
			if ((answer<0) || Double.IsNaN(answer) || 
(index == maxIterations)) 
			{
				throw new ArithmeticException();
			}
		}
		catch 
		{
			answer= 0;
		}
		return answer;
	}
	// default target is -1 (INVALID)
	public bool IsValid() 
	{
		return ((target>=PRINCIPAL) && (target<=PAYMENT)) ? true: false;
	}
	// Java "getter" code converted to C# get only properties
	public double Result 
	{
		get {
			if (IsValid()) 
			{
				return arrayDb[target];
			}
			else 
			{
				return 0.0;
			}
		}
	}
	public int Target 
	{
		get 
		{
			return target;
		}
	}
	public string Message 
	{
		get 
		{
			return message;
		}
	}
	public double Principal
	{
		get 
		{
			if (IsValid()) 
			{
				return arrayDb[PRINCIPAL];
			}
			else 
			{
				return 0.0;
			}
		}
	}
	public double Interest 
	{
		get 
		{
			if (IsValid()) 
			{
				return arrayDb[INTEREST];
			}
			else 
			{
				return 0.0;
			}
		}
	}
	public double Months
	{
		get 
		{
			if (IsValid()) 
			{
				return arrayDb[MONTHS];
			}
			else 
			{
				return 0;
			}
		}
	}
	public double Payment 
	{
		get 
		{
			if (IsValid()) 
			{
				return arrayDb[PAYMENT];
			}
			else 
			{
				return 0.0;
			}
		}
	}
}

Top of the Model code.

Creating a Windows Form Application

I fired up the Visual Studio IDE, dragged a few controls around and wired up two event handlers. This is what I got:

You could certainly spiff up this application by adding radio buttons that let the user select the target of the calculation. You could then disable the appropriate input control, setting the value of the target control to zero. Interestingly, the Parse method happily accepts embedded commas.

In the application you simply create an instance of the Model class:

private Model model= new Model();

You then call the appropriate Model methods and properties in the buttonCalculate and buttonReset event handlers:

private void buttonCalculate_Click(object sender, System.EventArgs e)
{
    double principal= 0;
    double interest= 0;
    int months= 0;
    double payment= 0;
    bool isInputError= false;
    // validate user input, must allow zero
    try
    {
        principal= Double.Parse(textBoxPrincipal.Text);
        if (principal<0)
        {
            throw new Exception();
        }
    }
    catch
    {
        textBoxPrincipal.Text= "Invalid Input.";
        isInputError= true;
    }
    try
    {
        interest= Double.Parse(textBoxInterest.Text);
        if ((interest < 0) || (interest > 100))
        {
            throw new Exception();
        }
    }
    catch
    {
        textBoxInterest.Text= "Invalid Input.";
        isInputError= true;
    }
    try
    {
        months= Int32.Parse(textBoxPeriod.Text);
        if (months <0)
        {
            throw new Exception();
        }
    }
    catch
         textBoxPeriod.Text= "Invalid Input.";
         isInputError= true;
    }
    try
    {
        payment= Double.Parse(textBoxPayment.Text);
        if (payment < 0)
        {
            throw new Exception();
        }
    }
    catch
         textBoxPayment.Text= "Invalid Input.";
         isInputError= true;
    }
    if (isInputError)
    {
        return;
    }
    // valid user input
    model.Init(principal,interest,months,payment);
    if (model.IsValid())
    {
        textBoxPrincipal.Text= model.Principal.ToString();
        textBoxInterest.Text= model.Interest.ToString();
        textBoxPeriod.Text= model.Months.ToString();
        textBoxPayment.Text= model.Payment.ToString();
        textBoxMessage.Text= model.Message.ToString();
    }
    else
    {
        textBoxMessage.Text= model.Message.ToString();
        ResetControls();
    }
}
private void buttonReset_Click(object sender, System.EventArgs e)
{
    textBoxMessage.Text= "";
    ResetControls();
}
private void ResetControls()
{
    textBoxPrincipal.Text= "";
    textBoxInterest.Text= "";
    textBoxPeriod.Text= "";
    textBoxPayment.Text= "0";
    textBoxPrincipal.Focus();
}

Creating a Web Form Application

Just to prove how easy it is to reuse the Model class, I then fired up the IDE and built a browser based version of the mortgage calculator. Just think. If your client suddenly wakes up one day and ask for a browser based version of your application, you won’t be having a panic attack. By separating out the application logic from the GUI (view and event handling code), you are prepared for code reuse.

Here is a snapshot of the browser based calculator:

Now it’s your turn to do some coding. Just create a new Windows Form or Web Form application and copy and paste the Model class into your project. You will need to remove any embedded  carriage returns that were added to the actual code for easy HTML viewing. If you want to download the working projects, they are available at http://www.geocities.com/jeff_louie/download.html

Hopefully, I have convinced you of the need to separate out your application logic from the presentation and event handling code. I have reused the Model code from a Java application in both a Windows Form and Web Form application. Use this most basic design pattern, the Model — View/Controller architecture. It will grow on you!

All Rights Reserved Jeff Louie 2002