Library tutorials & articles
A Twisted Look at Object Oriented Programming in C#
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;
}
}
}
}
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!
Related articles
Related discussion
-
C# video Editing/rendering
by pkuchaliya (0 replies)
-
How to Fill DataSet with more records (around 1 lakh) in a faster way
by Jayaram P (0 replies)
-
Can't print on the network with MSADESS ??
by anatha1 (2 replies)
-
Very Urgent regarding deleting the images from a folder
by Nanosteps (6 replies)
-
DataGridViewComboBoxColumn not showing values
by sachinkalse (0 replies)
Related podcasts
-
Object-Oriented Programming in Ruby
In this episode, I talk with Scott Bellware about object-oriented programming in Ruby, and Ruby's object model. This is taken from a private conversation, and the audio quality suffers at times. Much thanks to Scott for allowing this to be released.This episode of the Alt.NET Podcast is bro...
thanks for the useful info
regards
If you have tried developing large extensible business applications where data needs are in the gigabytes if not terabytes in any static OO based programming language, then you are in for a lot of headaches. OOP does not lend itself well to this type of development as so called real world objects do not translate well into OO objects, most so called real world objects in the business world translate to data. This fact is lost on many managers, developers and analysts. Most applications in the real world are business related and not some academic project. I have been privy to many failed projects because of the selection of high level development language invariably they have always been statically typed. The one overlooked programming paradigm that has been neglected because of the OO hype is table or data driven programming. This form lends itself well to business related applications and can be used in conjunction with any programming paradigm but work wonders with a dynamically type languages. It is easily extensible, scalable and can be easily modified without throwing hundreds of users of the network.
As for Interfaces, function overloading, generics, templates, reflection, RTTI, default parameters, adding scripting etc, these are kludges to make a static language do what a dynamic language already does, and done badly at that.
When I first learned OO programming I was primarily excited by code re-use on the object side of the equation. Objects considered as providers of services to other code that acts as a client.
After many years of developing OO application I eventually grew to see the vast web of code dependencies and to realize that every piece of client code is itself serving some other code i.e. tier 1 calls tier 2 calls tier 3 (tier 2 code is being served by tier 3 but is serving tier 1). I realized that my objects provided a great deal of code re-use but what about all the code that used those objects? That is where Interfaces come in. Thanks to an interface all of your code that uses object A can be swapped out to use object B, C, D, or E regardless of the object type and therefore ancestry. Regardless of object implementation.
In another sense, Interfaces are more "fair". If an object can perform the services declared in the interface then it can be used by code that uses the interface. This means no strict binding to particular object hierarchies and that can save a lot of headaches.
Read the article even if you work with OO, give it to people that are just starting OO.
I'd really like to see this article as the core of explaining more OO topics, in the same format.
Workaround:
Save printable page as complete web page.
Edit default.css file in _files subfolder of saved page.
Change the CODE.Pre line to:
CODE.pre {
PADDING-RIGHT: 7pt; PADDING-LEFT: 7pt; WIDTH: 100%; COLOR: #000000; PADDING-TOP: 7pt; BACKGROUND-COLOR: #fbedbb
}
(removes the display:block)
RRIOS:
Thanks for the excellent write up on this concept. It has actually helped quite a bit to give me a better perspective on the subject of Interfaces. I particularly liked your analogy of Royal family vs democracy.
Thanks!
Inheritance is rigid. It´s like being born into the royal family. Sure, you get alot of privileges without doing anything, but you carry all the baggage and stigmas that come with your family name. You can´t JOIN the royal family. You have to be born into it.
Interfaces are democratic. A system that declares an interface is saying "I dont care who you are or how big or small you are: if you agree to these rules you may join the club". Think of your drivers license. The DVM declares an interface and ANYONE can use the road system as long as they stick to the rules (implement the interface).
In programming terms, suppose you are using two systems, in one you have created a Vehicle class and have several child classes like Car, Bicycle, and Bus. In the other you have cooking items, say for a recipe system. You may have classes like Flour, Eggs, Carrot. These two groups of classes have nothing to do with each other.
You create a third point of sale system for a retail store, and you plan to sell among other things, bicycles and flour. Your point of sale system could declare an IRetail interface that defines to other classes what they need to implement in order to participate in the Point of Sales system like Price(), TaxKey() and UPC().
This means you can reuse your Bicycle and Flour classes, just by adding the implementation of the IRetail interface in order to use them in the Point of Sale system. This new system will be capable of handling any kind of object as long as it implements IRetail.
So Interfaces are an advantage to both the system that declares the interface (they dont care what class of object they are handling) and to the classes that implement them (they can participate in any system as long as they agree to the rules).
http://www.jroller.com/page/haruki_zaemon/20031201#adapter_classes
An interface allows a developer, but more specifically a designer, to say "These features must be implemented." It is then up to the developer to work out the details of how to deliver on those features/abilities. It creates a boundary of expectation from other users of the object.
They can say "Oh I i see it implements IInterface, therefore it will definitely have X, Y and Z". It is quite useful when you wnat to implement concepts in multiple objects instead of concrete functionality. For instance, say you had two types of collections one was a tree and one was a jagged array of parent/child IDs (which in many ways is a tree represented another way but they are syntactically different). If you wanted to implement something like "GetParent", you couldn't (well maybe you could, but go with me for the sake of example) implement one generic function for "GetParent" that would work for both the Jagged Array and the Tree. YOu could however have both objects implement an interface, and then have the developer do the syntactic details of returning an items parent.
Anyway, more half baked analogies.
One of the questions that still persists for me, (and something I have never seen a discussion on) is what real benefit Interfaces offer; it seems they offer some, but that it is somewhat limited. Let me give an analogy, in the form of an old joke. An engineer, a phycicist and an economist were on a desert island with a can of pineapple that had survived the boat wreck. Both the engineer and the physicist made multiple attempts using their scientific knowledge to open the can of pineapple, but to no avail. After some time the economist remarked: "This shouldn't be a difficult problem to solve." "Well, tell us", replied the engineer and physicist in unison. Said the economist, " Assume a can opener."
This is what economists do. They make assumptions and think they have contributed something. Is this not like Interfaces, whose job, it appears, is to make policy statements without ever getting around to implementing anything? All the same amount of coding has to be done whether the interface is used or not; in fact there may be considerable duplication of code if minor changes to an implementation are needed. So what has been achieved? Not nearly as much, it seems, as inheritance would offer, were full inheritance of pure methods possible. This is perhaps why an Abstract class seems to be more useful.
I would appreciate anyone who can resolve my perplexity here. I could learn something.
Pretty good article. Its the first time i acutally laughed and smiled while reading your article. Very good read..
There are a few inaccuracies though just to point out...
1.
MyClass c;
c.someMethod()
does not throw a null reference exception. It actually throws a compiletime error "Use of unassigned local variable". In c# variables much be assigned before use or something like that..
2.
array[] = new array[10]
only returns an array of null references if it you are declaring an array of ref type. If it is a value type, the values will be assigned their default values..
and 3.
comparing type compatibility in most cases should use "as" instead of "is" because if you use is before casting the type, you in effect check the type twice, once before the cast, and once during the cast..
Its just an optimisation really..
Other than that, like i said before, best read i have ever had. Learned a few new words too like "gumption". Who would have thought..
This intro to C-Sharp is a very good tutorial, but its only an intro. The naming of variables and methods need to be standardized for easier readability. It also does not cover data driven dynamic web apps.
...it is a good article and Microsoft does advocate clearer naming of Object variables.
It's a shame you never LEARNED proper grammer.
This thread is for discussions of A Twisted Look at Object Oriented Programming in C#.