IT Education PK


Welcome to IT Education PK

Complete Code Listing Model.cs

Thursday, December 2, 2010


The downside of posting all of the code is that it goes on forever.

/// <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 by clicking here.
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!

0 comments:

Post a Comment