3.6. Declaring an Abstract Base Class

Query represents the base class abstraction from which all the actual query types manipulated within our application are inherited. The user should never create an actual instance of a Query object. Rather, a Query object in our program always addresses one of the inherited query types. To enforce this usage model, we modify the Query class definition with the abstract keyword:

abstract public class Query { ... }

The abstract keyword tells both the compiler and readers of our program that it is illegal to create an actual Query class instance. For example, the following attempt to create a Query object triggers a compile-time error:

// error: Query is abstract
Query q = new Query();

Rather, a Query object can be used only to address one of the derived-class query types:

// OK: Query addresses a derived-class object
Query q = new AndQuery();

The two primary operations of the Query class hierarchy are to evaluate the query and to display the matching lines, if any. We'll name these methods eval() and display_solution().

The implementation of eval() is specific to each derived-class query type. It therefore needs to be declared as a virtual function. How do we do that within a base class? We have two choices: We provide either a default implementation or simply an abstract placeholder.

If there is a meaningful default implementation of the method, we provide that definition and specify the function using the virtual keyword:

abstract public class Query
{
    virtual public void eval()
            { /* default implementation */ }
}

The virtual keyword identifies the method as type dependent. It also indicates that a definition of the method is associated with the function and serves as the default implementation for a subsequently derived class that does not provide its own instance.

The derived class may override the inherited base-class instance with its own implementation. A derived class, however, is not required to override the virtual function. The implementation of the base-class method may also be appropriate for a derived-class instance. In that case the derived class does not provide its own instance, but reuses (inherits) that of the base class. For example, whenever we define a class, we have the option of either overriding the virtual ToString() method of the Object base class or reusing its default implementation.

If, however, there is no meaningful default base-class implementation of the method, the method is declared with the abstract keyword and no function body is provided:

abstract public class Query
{
    // no function body is provided ...
    abstract public void eval();
}

An abstract function is also treated as a virtual function. Its declaration simply indicates that no meaningful implementation exists. An abstract method serves as a placeholder. It introduces the interface—that is, the method's name, access level, signature, and return type. The implementation is provided by each of the derived-class instances.

In the case of an abstract function, there is no default implementation for a derived-class instance to inherit. If the derived class does not provide a definition of the method, the derived class is also an abstract class, and no instance of that class can be directly created.

Why would we ever want to introduce an abstract derived class? An abstract derived class provides a way of subpartitioning a class hierarchy. For example, let's modify our class hierarchy so that the AndQuery and OrQuery classes are derived from an abstract BinaryQuery class. This change allows us to factor data members or methods common only to binary queries into a single, shared class:

abstract public class BinaryQuery : Query
{
    // inherits the abstract eval() method
}

public class AndQuery : BinaryQuery
{
    // OK: an implementation of eval() here
}

We say that both the Query and the BinaryQuery classes serve as abstract classes of the hierarchy, while AndQuery (and OrQuery) serves as a concrete class of the application domain.

The abstract keyword is required in the declaration of any class that either introduces or inherits an abstract method (or property or indexer). If we forget to specify the abstract keyword, its absence triggers a compile-time error.

Is the introduction of an abstract BinaryQuery class a better design? We can't really say until we understand how we use the AndQuery and OrQuery classes. If there are circumstances in which we manipulate the two queries collectively as binary queries, or if a shared set of operations is unique to binary queries but is not applicable to the other query types, this design makes sense. This sort of refactoring of a class hierarchy is common throughout the lifetime of the class hierarchy.

Properties and indexers can also be declared as either abstract or virtual. Let's look at an example of each.

One property associated with a Query object is the solution set—that is, the lines within the text that match the user query—which is represented by an array of integers. The solution-set array may or may not be present, depending on whether the query object has invoked eval(). If it is not set, the property must return null; otherwise it returns a handle to the array. We want to allow users to read the solution set, but we don't want users to modify it. Therefore, we specify only the get accessor:

abstract public class Query
{
    virtual public int [] Solution
    {
          get
          {
                 return null;
          }
    }
}

The rule is that the virtual or abstract keyword is applied to the property (or indexer) as a whole rather than to the individual accessors. If the property is declared abstract, the accessor is not provided with a code block:

abstract public class Query
{
    abstract public int [] Solution
    {
        get;
    }
}

An indexer looks pretty much the same—for example,

abstract public class NumericSequence
{
    abstract public int this[ int index ]
    {
       get;
       set;
    }
}

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

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