Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

OO Programming By Example

3.44/5 (21 votes)
13 Sep 20068 min read 1   1.3K  
Learning OOP by example.

Introduction

This article attempts to help you learn about OOP (Object Oriented Programming) by designing and building an example project. Hopefully, this will demonstrate what OO programming is and why it can be useful.

This article is based on the code that was used as part of an in-house introductory VB.NET training course. This course was aimed at developers who had previously only had VBA experience. I have tried to avoid OOP jargon words like polymorphism, encapsulation etc.

Often, OOP is illustrated by using problems that are very simple. The flaw with this approach is that while OOP is useful in making complex problems simpler, it can also just end up making simple problems more complicated.

OOP is also sometimes illustrated with abstract concepts (E.g., Circle Inherits from Shape, or a Cat class and Dog class both Inherit from an Animal class). I'm sorry but for me, all this does is add to the confusion!

The example I have chosen is of importing a text file. Importing text files is a problem that is very real to most of us VB developers; it's a problem that is both easy to understand in concept and yet is complex enough to showcase the benefits of OOP.

You could even use this example application as a basis for writing your own importer.

Our Example Problem...

These are the made-up requirements for our example project:

  • We need to support importing the same data from two different formats of files: a CSV file and a certain fixed-width formatted file. (See code download for example files.)
  • A user needs to be able to pick a file to import.
  • If there are problems during import, the program should provide detailed feedback for the user telling them which lines in the text file could not be imported and why.
  • Our app must allow the user to view the data after the import.
  • We also suspect that new file formats will need to be imported in the future.

These next two lines are examples of the same data but from the fixed-width format and the CSV (comma-separated-value) files:

1    Value A1            01/Jan/2006Y <-- example line from fixed-width format file

1,Value A1,01/Jan/2006,True           <-- example line from csv format file

Note: In a real world app, the data would most likely be saved to a database. For us to do that would require me making you create a database, and that sounds like hassle for both of us! I've decided to say that this is 'cough... beyond the scope of this article'.

Designing an OOP Solution, Where Do We Start?

In designing an OO (Object Oriented) application, one approach is to start with a list of features and then split those features up into classes that are going to be responsible for implementing those features.

In "good" OO applications, each individual object has as few responsibilities as possible, ideally just one main responsibility. So this is how we will describe our objects; in terms of their responsibilities.

This is not some magic formula that will give you a good design, but of all the ideas I've heard, it's the best one. The rest comes, like anything, with experience.

Describing the Solution

Let's have a look at what our project looks like then, and see what we can learn from it about OOP.

The following are the classes that have been written as part of our project and their responsibilities:

  • The ImportForm class is responsible for interacting with the user. (See screenshot below.)
  • The DemoDataSet is a DataSet (another class) that is responsible for holding our data. (Remember, we're allowing the user to view the data after the import.)
  • The ValidationIssue class is a very simple class that is just responsible for holding the data for an individual issue that is encountered when importing data.
  • The ValidationIssuesList class is responsible for maintaining a list of ValidationIssue objects. This will be used to store all the problems our application encounters as it progresses through the import.

This is what our form looks like "in action", just after importing one of the fixed-width format files:

Screen shot of the form used by the user to import a file

This is the simple 'Made-up' dataset in the DataSet designer in Visual Studio. This is what we'll be importing the data into.

Screen shot of dataset in the designer

That's the simple stuff out of the way, now let's get on to actually processing the data from the text files...

We could have decided to write two different classes, each one being responsible for importing each of the two different file types. Alternatively, we might think that there is a lot in common in importing files, so we should write one big class that's responsible for importing all of the file types. In fact, with OOP, we can do both of these things at the same time, sharing the responsibilities out between three classes:

  • In our project, we have a class called TextFileImporter that is responsible for the generic file importing features, the bits that are common to all file imports (opening the file, reading through the file one line at a time, keeping track of the current line number, handling errors etc.).
  • The two classes CSVFileImporter, XYZFixedWidthFileImporter will be responsible for dealing with the specific bits for each of our CSV and fixed-width files, respectively.

To achieve this splitting of responsibility, we will make use of Inheritance. This gives us a very easy way of making the XYZFixedWidthFileImporter class and the CSVFileImporter class Inherit all the basic importing features from the generic TextFileImporter class. Then, we just need to Override the bits of the Import process that need to change for each specific class.

As you can see from the class diagram below, the TextFileImporter class has two subroutines, ImportFile and ImportLine. Together, these routines control the overall process of importing. ImportFile calls ImportLine in a loop as it reads through the file. Inside the ImportLine routine, ParseValuesFromLine is called.

This is a class diagram showing some of the main objects/classes in our project.

Class diagram

ImportFile and ImportLine are common to all formats of files that we need to import. The function ParseValuesFromLine, however, is not. This function must separate each value from a given string, and return an array of strings for the separate fields.

Our TextFileImporter is not responsible for importing all the specific formats of files, so in this case, it abdicates responsibility, using the MustOverride keyword, effectively throwing up its hands and saying "someone else is responsible for doing this, it's not my job".

VB
Class TextFileImporter

    Public Function ImportFile() As ValidationIssuesList

        'For each line in file, parse data into in-memory DataTable
        Using fileReader As New IO.StreamReader(m_filePath)

            Try

                Do While fileReader.Peek() >= 0
                    CurrentLineNumber += 1

                    'Read next line of text from file
                    Dim strLineText As String = fileReader.ReadLine

                    'Importing the line into the DataTable
                    ImportLine(strLineText)

                Loop

            Catch ex As Exception
                RecordImportIssueForCurrentLine(New _
                  ValidationIssue(String.Format("Error occurred" & _ 
                  " during import of file: {0}", ex.Message)))

            End Try

        End Using

        Return m_importIssues
    End Function

    Protected Sub ImportLine(ByVal LineText As String)

        Dim row As DataRow = m_data.NewRow
        Try
            'Parse data into a string array
            Dim astrValues As String()
            astrValues = ParseValuesFromLine(LineText)

            'Put values from string array into data
            PutValuesInRow(row, astrValues)

            'Validate data
            ValidateDataRow(row)

            'Store valid data into table
            m_data.Rows.Add(row)

        Catch ex As Exception
            RecordImportIssueForCurrentLine(New _
              ValidationIssue("Error during import: " & ex.Message))

        End Try

    End Sub

    Protected MustOverride Function ParseValuesFromLine(ByVal _
                           LineText As String) As String() 

End Class

The CSVFileImporter is one of the classes which is responsible for dealing with this, so it has a ParseValuesFromLine function that does just that, parse the string and return an array of separated strings, the values out of an individual line of text.

VB
Class CSVFileImporter
    Inherits TextFileImporter
                        
    Protected Overrides Function ParseValuesFromLine(ByVal _
                        LineText As String) As String()
        Dim values As String()

        'Simply use function of string to split 
        'a string up into a string array by a delimiter
        values = LineText.Split(","c)

        Return values
    End Function
End Class

Also, the XYZFixedWidthFileImporter is another class that is responsible for dealing with this same feature but for a different file format.

VB
Class CSVFileImporter
    Inherits TextFileImporter
                        
    Protected Overrides Function ParseValuesFromLine(ByVal _
                        LineText As String) As String()
        Dim astrValues(0 To 3) As String
        'Create string array long enough 
        'to hold values (4 values)

        If Not Len(LineText) = 37 Then
            Throw New ApplicationException("Line is not" & _ 
                  " correct length. Was '" & Len(LineText) & _
                  "' expected '37'")
        End If

        'break down line into array of string values
        astrValues(0) = LineText.Substring(0, 5)
        astrValues(1) = LineText.Substring(5, 20)
        astrValues(2) = LineText.Substring(25, 11) 'dd/mmm/yyyy
        astrValues(3) = LineText.Substring(36, 1)

        Return astrValues
    End Function
End Class

Because we have created different classes to deal each of the different file formats, we have created another responsibility, which is to decide which class to use to import the file that the user has selected. Of course, we could put this code in the form, but then again, the form already has a lot of responsibilities to deal with. Instead, we write another class whose sole responsibility is to create the appropriate object to import files with. This class is called the TextFileImporterFactory:

VB
Public Class TextFileImporterFactory
    Public Function CreateTextFileImporter(ByVal FilePath As String, _
           ByVal data As DataTable) As TextFileImporter

        Dim importer As TextFileImporter

        If Len(FilePath) = 0 Then
            Throw New ApplicationException("No File selected")

        ElseIf FilePath.ToLower.EndsWith("csv") Then
            importer = New CSVFileImporter(data, FilePath)

        ElseIf FilePath.ToLower.EndsWith("xyz") Then
            importer = New XYZFixedWidthFileImporter(data, FilePath)

        Else
            Throw New ApplicationException("File type not supported")

        End If

        Return importer
    End Function
End Class

Advantages of OOP

If we were to have designed this project without using the inheritance feature of OOP, then we would have had to have passed around variables describing what type of data we were importing. Also, many of our routines would have contained lots of If statements for coping with different file formats (If format ="csv" then do this, if format="xyz" then do something else).

Another advantage of this approach is that should we need to support another file format, we can most likely do it without changing any of our existing importing code! This is surely a good thing because if we don't change the code, we don't risk breaking it. The only thing we do need to do is add another class to support the specifics of the new format and change the TextFileImporterFactory class so that it creates that new class when appropriate.

Further Information (Design Patterns)

Common 'Design patterns' used in this project:

The TextFileImporterFactory uses the 'Factory' pattern.

The 'Strategy' pattern is also in use here; the ImportForm is the 'Context', the TextFileImporter is the 'Strategy', and the XYZFixedWidthFileImporter and the CSVFileImporter classes are the 'ConcreteStrategies'.

When you begin to realise what these patterns achieve for you, it helps you design new OO applications. For example, the 'Strategy' pattern can help your program cope with any set of complex algorithms that have behaviours in common, not just importing algorithms.

The idea for this example based approach to OOP comes from a book called 'Design Patterns Explained' by Alan Shalloway and James R Trott.

Exercises For You...

This project is not done... As a learning exercise, why don't you:

  • Improve the error handling. E.g., give a better error message than 'Index was outside the bounds of the array' when importing the test data with the errors.csv file.
  • If you want to learn about how .NET deals with inheritance and running the different bits of code from classes that inherit from each other, I recommend that you download the source code and step through it in the debugger. Remember to keep an eye on which class you're in as you step through.
  • Write a new XYZFixedWidthFileImporterV2 class to import the second fixed-width format text file (Test Data v2.xyz). Also modify the TextFileImporterFactory class so that it is able to create the XYZFixedWidthFileImporterV2 at the appropriate time. (Hint: The length of the Line of Text is different between for the two files, you may have to read the first line of the file to determine which object to create.)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here