An organizational approach to rapid application development

By: Borland Developer Support

Abstract: This article is about creating applications from object hierarchy code instead of starting each app with TForm1. By Roy E. Bourquin.

An organizational approach to rapid application development

By Roy E. Bourquin

This article is about creating applications from object hierarchy code instead of starting each app with TForm1.

Suppose the CFO comes to us and tells us that the company needs an application for the accounting department to automate the transaction process from the point of capital entry to the point of liability disbursement. Additionally, the CFO needs to have it in place two months before the quarterly report is due.

No problem! We simply launch our favorite Borland RAD tool. Now create an MDI form. Add the TClientSocket, TMainMenu, TToolBar, TSpeedButton, TStatusBar, TStringGrid, TQuery and a few TForm components for our child windows. Then fill in the blanks and shazam, we're ready to deploy the application to the accounting department.

Now that our application was completed on time and on budget we received the attention of the CEO, who requests that we create an application that automates the reporting of the assumption, cash flow, balance sheet, and income statements. The CEO needs to have it in place one month before the quarterly report is due.

No sweat. We sizzle up another application in the same fashion. This time we want to make a really good impression. So we perform the same routine as before, only this time we add TQuickRep for printing the reports and a web component to publish the information to the corporate Web site.

Now that we've automated the quarterly reporting requirement we're ready to relax and let our application do its thing. Then the Audit Department sees the quarterly reports online and requests that we build another reporting tool for auditing the information in our automated system.

So we start again...

RAPID APPLICATION DEVELOPMENT THAT IS TOO RAPID

Lets recap what we have done so far. When we bring up the IDE, the first thing that we see is a fresh form. Then we add components from the component palette. Additionally, to make things a little easier we added a few "helper" functions. These functions include a few financial, file I/O, and component manipulation members.

One of the functions that we added was a SetColumnName() function that would allow us to set column names for our StringGrid. Snippet 1.1 shows how we implemented this. Notice that our function belongs to the TForm1 class. This makes it a little hard for us to use this function again.

SNIPPET 1.1
void TForm1::SetColumnName(int li_Column, const AnsiString ls_Name)
{
  ColCheck(li_Column);
  StringGrid1->Cells[li_Column][0] = ls_Name;
}

STARTING WITH THE OBJECT HIERARCHY

Since we are the customers of our own work, we will always try to build our software as if it were an SDK. To do this we will start by creating an object hierarchy. I suppose we could call our base class IUnknown, TObject, root or something like that. For now we'll choose MyBaseClass.

The classes that will suit our needs are displayed in Hierarchy Diagram 2.1, which shows some of the basic functionality that our application will need. We can always add more object as our needs grow. Then we'll simply inherit from the appropriate base class.

After designing our objects, we start adding member prototypes. We'll make all our company proprietary code private members and wrap them up with appropriate public and protected members. And of course we document what each member is used for. Then we add the members. For now we just put a remark in each member with the TODO keyword. That way we can use the VIEW | TODO feature in our IDE and tackle the algorithm later. This will give us an opportunity to prioritize the members based on the project deadlines.

HIERARCHY DIAGRAM 2.1

HIERARCHY DIAGRAM 2.1

We would like to reinvent the wheel as much as possible, but since other venders have been more successful and distributing their operating systems than we have, we'll have to integrate our hierarchy with a platform that won't break with the next release of the operating system. The platform that we chose here was Object Pascal, since it has the crossplatform compatibility for multiple operating systems (namely 95/98/ME/NT and now Linux). Our new integrated hierarchy is displayed in Hierarchy Diagram 2.2.

HEIRARCHY DIAGRAM 2.2

HIERARCHY DIAGRAM 2.1

Let's take a look at the implementation of our financial class. Since we want to use those nifty Object Inspector features, we'll add a new component and inherit from TComponent. Snippet 2.1 shows our implementation of the prototype declaration.

SNIPPET 2.1
class PACKAGE TFinancial : public TComponent
{
private:
  double FLoanAmount;
  double FInterest;
  double FYears;
  AnsiString FMonthlyPayment;
  void __fastcall mpSetText(const AnsiString Value);
  AnsiString __fastcall mpGetText();
protected:
public:
  __fastcall TFinancial(TComponent* Owner);
  void __fastcall Amortize();
  __property AnsiString MonthlyPayment = {read = mpGetText, write=mpSetText};
__published:
  __property double LoanAmount = {read = FLoanAmount, write=FLoanAmount};
  __property double Interest = {read = FInterest, write=FInterest};
  __property double Years = {read = FYears, write=FYears};
};

Next we'll complete the algorithm for our financial members. The implementation is given in Snippet 2.2.

SNIPPET 2.2
void __fastcall TFinancial::Amortize()
{
  bool lb_zero = false;

  if (FLoanAmount <= 0) lb_zero = true;
  if (FInterest <= 0) lb_zero = true;
  if (FYears <= 0) lb_zero = true;
  if (lb_zero) return;
  FInterest = FInterest/100;
  FYears = FYears*12;
  MonthlyPayment = FormatFloat("$0.00", FLoanAmount/((1-(1/(pow((1+(FInterest/12)),FYears))))/(FInterest/12)));

}

void __fastcall TFinancial::mpSetText(const AnsiString Value)
{
  FMonthlyPayment = Value;
}

AnsiString __fastcall TFinancial::mpGetText()
{
  return FMonthlyPayment;
}

Now let's revisit our work from Snippet 1.1. This time we inherited from TStringGrid so that our SetColumnName() member belongs to our new StringGrid object instead of our TForm class. Snippet 2.3 shows the prototype declaration and new StringGrid members. Since we can't perform multiple inheritance and we want our financial functions to be available to our new StringGrid, we've implemented the financial object.

SNIPPET 2.3
class PACKAGE TNewStringGrid : public TStringGrid
{
private:
  int GetColumn(AnsiString ls_Name);
  void ColCheck(int li_Column);
  void RowCheck(int li_Row);
protected:
public:
  TComboBox *GridDropDown;
  TFinancial *Financial;
  void SetColumnName(int li_Column, const AnsiString ls_Name);
  void UpdateCell(int li_Row, const AnsiString Name, const AnsiString ls_Value);
  void UpdateCell(int li_Row, const AnsiString Name, int li_Value);
  __fastcall TNewStringGrid(TComponent* Owner);
  void __fastcall EV_Click(TObject *Sender);
__published:

};

void TNewStringGrid::SetColumnName(int li_Column, const AnsiString ls_Name)
{
  ColCheck(li_Column);
  Cells[li_Column][0] = ls_Name;
}

void TNewStringGrid::UpdateCell(int li_Row, const AnsiString ls_Name, const AnsiString ls_Value)
{
  RowCheck(li_Row);
  Cells[GetColumn(ls_Name)][li_Row] = ls_Value;
}

void TNewStringGrid::UpdateCell(int li_Row, const AnsiString ls_Name, int li_Value)
{
  RowCheck(li_Row);
  Cells[GetColumn(ls_Name)][li_Row] = IntToStr(li_Value);
}

int TNewStringGrid::GetColumn(AnsiString ls_Name)
{
  int li_Column;
  for (li_Column = 0; li_Column < ColCount; li_Column++)
    if (Cells[li_Column][0] == ls_Name) return li_Column;
  return -1; //Perhaps Throw Exception instead.
}

void TNewStringGrid::ColCheck(int li_Column)
{
  if (li_Column > ColCount) ColCount = li_Column;
}

void TNewStringGrid::RowCheck(int li_Row)
{
  if (li_Row > RowCount) RowCount = li_Row;
}

Now bring on the next project. Let's just hope that it needs a StringGrid that performs amortization calculations.

SUMMARY

We started by looking at what happens when we build applications using the TForm component as our building block. Since we created some useful functions that we wanted to implement in other projects, we decided to start with an object hierarchy and determined the type of objects that we would need. Then we added members to each object. We found other useful objects that were already complete and integrated them into our hierarchy. The steps for our development cycle are given below:

  1. Start by creating a generic object hierarchy for the project.
  2. Determine the types of components that will be used.
  3. Inherit from those objects and build classes with the additional functionality and wrapper functions that will be used as prototypes.
  4. Fill in the todo comments for each member (that way, we can delegate them).
  5. Prioritize the member functions from the todo list in order of importance.
  6. Test/QA each member upon completion and check them off the todo list.
  7. Put the project together.

Who knows, maybe one day in the future we'll be able to work directly from an object hierarchy RAD hybrid. We'll be able to drag and drop objects, which will automatically create classes. We'll be able to connect the objects to perform inheritance. Then we will be able to add members to each object and check a box like published, virtual, int, etc., which will automatically generate the text for us. Then we will add comments, history and algorithms. This will allow us to print out documentation and collaborate through the network with our peers, all through the IDE. It will be just like the good ol' days of telnet and keep it small and simple, and we'll be able to work from our home office again. No more spaghetti and meatballs. (I should talk!) All we need is a huge monitor.


Server Response from: ETNASC04