Fog Creek Software
Discussion Board




Welcome! and rules

Joel on Software

OOP help...

I'm a long-time procedural programmer and am trying to improve my OO skills and figure out how its all supposed to work. I've read a lot of theory, but am having some trouble putting it all into a real project. Let me give a relatively simple real-world scenario and ask how we would handle this using OO techniques.

"Customer Data Import"
Procedural steps:
Open a text file containing new customer data.
Open a database containing existing customer data.
Loop through the text file, and for each customer:
Read the new customer data
Look up the customer number in the database
Perform a series of calculations based on the data
Store the results of the calculation in the database
Also log the results in another text file

We also want the UI to display status information (# processed, # found, # not found, etc.)

My first procedural approach used lots of public variables and publically declared objects, and a big giant loop that did all the work. As it got more and more complex, it was nearly unmaintainable.

My second attempt: I have an object for Customer, database, and textfile. I can call customer.read, customer.lookup, customer.calculate, all that cool oo stuff. I'm still having a couple of problems, though:

1) Still have a lot of public variables, its just much, much harder to update them from all the parts of the code. The status counters, for example, could be updated from Customer class or the primary Sub.

2) I end of passing a lot of objects around as parameters. When I call customer.read() I have to pass the txtFile object as a parameter. When I call customer.Lookup() I have to pass the database object AND the counters object (for keeping track of progress) as parameters. My code smells, but I'm not sure how to reorganize it.

I would be very interested in the approach you might take for a situation like this. One constraint here is that because of the size and type of the database, I only want to open it once and simply use the same database objects anywhere I need it.

Thanks for any thoughts you might contribute!

Brad Corbin
Friday, August 20, 2004

> The status counters, for example, could be updated from Customer class or the primary Sub.

Google for the "Singleton" pattern, and/or use "static" methods and objects.

For example if you have one bunch of status counters for the entire program (that can be called from anywhere), then making them static lets you call them from anywhere ... they're like a global variable.

> When I call customer.read() I have to pass the txtFile object as a parameter. When I call customer.Lookup() I have to pass the database object AND the counters object (for keeping track of progress) as parameters.

I might do it like this:

class CustomerFile
{
  TextFile m_text_file;
  CustomerFile(TextFile text_file)
  {
    m_text_file = text_file;
  }
  CustomerData read()
  {
    //TODO ... read from text file, return as a CustomerData instance
  }
}

struct CustomerData
{
  int m_customer_id;
  string m_customer_address;
  ...etc...
}

class CustomerDatabase
{
  Database m_database;
  TextFile m_output_log_file;
  CustomerFile(Database database,TextFile output_log_file)
  {
    m_database = database;
    m_output_log_file = output_log_file;
  }
  void process_customer(CustomerData customer)
  {
    //TODO: find specified customer in the database, perform series of calculations, store calculations in database, and log results in text file
  }
}

void main()
{
  //open the CustomerFile
  CustomerFile customer_file = new CustomerFile(new TextFile("input.txt"));
  //open the CustomerDatabase
  CustomerDatabase customer_database = new CustomerDatabase(new Database(), new TextFile("output.txt"));
  //do the work
  for (;;)
  {
    CustomerData customer = customer_file.read();
    if (customer == null)
      return; //end of work
    customer_database.process_customer(customer);
  }
}

Note that I'm passing TextFile and Database objects only once: to the constructor of the class that's designed to use it.

I recommend a book titled _Refactoring_ by Fowler. Someone else said it's too advanced for a beginner when I recommended it elsewhere, but I still recommend it: it shows the ways in which you can change an[y] existing OO design to make it "better".

Christopher Wells
Friday, August 20, 2004

Wow, thanks, Christopher. Lot of good stuff, there.

So using some public properties in a form attached to the project isn't THAT much different than creating a static object.

(In VB6 all forms are globally visible -- I know, I know, vb6)

So for passing objects around, i like your suggestion of passing it once as part of the constructor.

I guess I could also make a public property, and pass that in at a later time if I didn't think I'd have all the info at creation.

How about this idea:
Instead of passing three separate data objects (textfile, ACT database, Access database), I could create a data object with properties for each of the different types, then just pass one object as a parameter. Good idea? or not?

Brad Corbin
Monday, August 23, 2004

> So using some public properties in a form attached to the project isn't THAT much different than creating a static object. (In VB6 all forms are globally visible -- I know, I know, vb6)

Perhaps; I can't confirm that because I don't know VB6.

> I guess I could also make a public property, and pass that in at a later time if I didn't think I'd have all the info at creation.

Yes you could.

You "shouldn't" -- I don't mean that you "mustn't", but avoid doing it if you can ... in my example, I see no point in constructing a CustomerDatabase object until after you have a Database (or you could pass the DB login parameters to the CustomerDatabase class, and let the CustomerDatabase constructor construct its own Database member data).

I say "shouldn't" because in procedural (C-style) programming one of the kinds of programming errors you can make is declare a file handle and then attempt to read from it, before you have opened the underlying file ... because C doesn't have constructors, you often define a variable, and then initialize it, and then use it ... whereas C++ (or .NET) does have constructors, which allow you to define-and-initialize an object in a single call to "new" ... the advantage of this being that you don't have partially-initialized objects lying around.

> How about this idea: ... Good idea? or not?

Sorry I don't see what you're suggesting: perhaps you could post some pseudocode as I did.

My guess was that a problem with your design was that you have a Customer class which knows about (i.e. which calls methods of) both the TextFile and the Database classes ... so, if the TextFile interface becomes more complicated, then the Customer class (which uses it) becomes more complicated ... ditto if the Database class becomes more complicated ... or if the Counters class becomes more complicated ... or if the calculations becomes more complicated ... etc.

I did a "divide and conquer" so that CustomerDatabase knows about Database and about CustomerData but doesn't know about TextFile, and vice versa.

Christopher Wells
Monday, August 23, 2004

*  Recent Topics

*  Fog Creek Home