2.1. Our First Independent Class

A class may represent an independent abstraction, or it may represent a specialization of a more general abstraction. For example, FileStream and MemoryStream are both specialized class definitions of the System.IO namespace Stream class. A stream represents a general flow of data either into or out of our program. It is an abstract class because although it defines the behavior of a stream (the public interface); it does not provide a complete implementation. The completion of the stream implementation is left to the more specialized file and memory stream classes, which define the input/output medium. Both the file and the memory stream classes are called subtypes of the stream class type. This type/subtype relationship is at the heart of object-oriented programming (OOP). We look at OOP in detail in Chapter 3.

Before we define relationships between classes, we first need to feel comfortable in building them. That's what we'll do in this chapter: look at how to make independent classes. An independent class is one that provides a complete implementation of its functionality. Examples of independent abstractions include the DateTime and Buffer classes in the System namespace.

In Chapter 1 we worked through the general implementation of a program to count the words in a file. In this section we need to turn that work into the design of a WordCount class.

Where do we start?

The first thing we need to do is identify the set of operations that the class performs. These become the class member functions. One can always argue whether two or more functions should be combined, or whether a function should be factored into multiple functions. In general, however, a function is best organized to perform a single task. For the WordCount class, I have identified the following four operations:

  1. openFiles(), which confirms the validity of the text file supplied by the user and if valid, it opens it. In addition, it opens the output file to hold the word count. Optionally it opens a file to hold the trace output.

  2. readFile(), which reads the text, tucking it away for subsequent manipulation.

  3. countWords(), which separates the text into individual words and computes the occurrence count.

  4. writeWords(), which outputs the occurrence count of the words into the designated file in dictionary order.

In addition, there is an initialization task when we first create a WordCount class object, and a deinitialization task when the object is no longer needed. We'll look at the issues surrounding these operations in separate sections.

Once we have decided on the set of member functions, we need to identify the interface for each. For a member function, an interface consists of (1) the return type of the function and (2) the function parameter list, or signature.

A parameter list allows us to pass objects into a function. These parameters either are operated on or provide information that is extracted from within the function. The return type specifies the kind of object being passed back from the function. This object usually represents the result of the internal computation, although it may also represent the success/failure status of the operation. (Remember that in C# (and .NET programming in general), the convention is to throw an exception rather than to return a status code such as HRESULT.)

In the design of class member functions, we can often dispense with both an explicit return value and the set of parameters. This is possible because a class object can maintain its own state through class data members. Rather than passing in or returning values, a member function can operate on the internal members of the class object through which it is invoked. This often makes for a simpler programming model.

Once we have decided on the name, return type, and signature of the member functions, we next have to decide on an access level for each. That is, should a function be declared public, making it accessible to the entire program, or should it be declared private, in which case only the other member functions of the class can invoke it? (Object-oriented programming introduces a protected access level; we look at that in Chapter 3.)

At first glance it seems that each of our member functions should be declared public, allowing the user to—in any order—open, read, count, and write through the WordCount object. To allow this flexibility, however, would complicate our implementation because the invocation order of the methods is dependent. For example, it is hardly useful for a user to request a count of the words if a file has not even been opened yet. An alternative strategy is to package an invocation of the entire sequence in a single public function, which for this example I've named processFile(). The four member functions that it invokes in turn are declared private.

Let's see what we have so far. Here is a first iteration of a WordCount class definition:

using System;
public class WordCount
{
    public void processFile()
    {
          openFiles();
          readFile();
          countWords();
          writeWords();
    }

    private void countWords()
    {
      Console.WriteLine( "!!! WordCount.countWords()" );
    }

    private void readFile()
    {
      Console.WriteLine( "!!! WordCount.readFile()" );
    }

    private void openFiles()
    {
      Console.WriteLine( "!!! WordCount.openFiles()" );
    }

    private void writeWords()
    {
      Console.WriteLine( "!!! WordCount.writeWords()" );
    }
}

Member functions must be defined inside the class definition. The order of their declaration is not significant. The compiler does not need to have seen the declaration of the member function before it can be invoked. Each member function is provided with an explicit access level. By default, if a member function (or class data member) does not have an explicit access level, it is treated as a private member.

A class that is defined either within a namespace or within the global declaration space can be declared as either private or internal. (Internal access means that the class is visible only within the assembly in which it occurs. We look at assemblies in more detail in Chapter 8.) A class without an explicit access level is treated as having internal access.

Although this implementation of our class is not very functional, it is complete. Before we proceed further with the implementation, it is probably useful to see that it compiles and that we can invoke the public processFile() method successfully. I've always found myself more productive when I incrementally add functionality to a working program rather than when I wait until I have coded everything before seeing if any of it works.

To execute the program, we need to provide a program entry point. Here's a stripped-down version that creates a WordCount object and invokes processFile():

using System;
public class WordCountEntry
{
   static public void Main()
   {
      Console.WriteLine( "Beginning WordCount program ... " );

         WordCount theObj = new WordCount();
         theObj.processFile();

      Console.WriteLine( "Ending WordCount program ... " );
   }
}

This class, together with the WordCount class, constitutes a complete C# program.

Before continuing with our exploration of the C# class mechanism, let's open a Visual Studio project and execute the program.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.206.25