Object-Oriented Programming Features


When a software project reaches a certain level of complexity, the sheer effort of organizing the source code, of remembering the internal workings of every function, overwhelms the effort of dealing with your problem domain. No single person can remember what all the functions do and how they fit together, and chaos results. This critical size isn’t very large, perhaps a five- programmer project, arguably less. To develop larger and more functional pieces of software—Microsoft Word for example—we need a better way of organizing code than providing global functions all over the place. Otherwise, the effort of picking our way through our spaghetti code overwhelms the work of figuring out how to process words.

Organizing the internal functionality of software projects is difficult.

The techniques of object-oriented programming were developed to solve this problem and allow larger, more complex programs to be developed. Exactly what someone means when he uses the term “object-oriented” is hard to pin down. The meaning depends heavily on the term’s usage context and the shared background of the listeners. It’s sort of like the word “love.” I once watched, amused, as two respected, relatively sober authors argued vehemently for half an hour over whether a particular programming technique truly deserved the description “object-oriented” or only the lesser “object-based.” But like love, most developers agree that object-oriented software is a Good Thing, even if they’re somewhat vague on why and not completely sure about what. As most people will agree that the word “love,” at the minimum, indicates that you like something a lot, so most programmers will agree that object-oriented programming involves at least the partitioning of a program into classes, which combine logically related sets of data with the functions that act on that data. An object is an individual instance of a class. Cat is a class, my pet Simba is an instance of the class, an object. If you do a good job of segregating your program’s functionality into classes that make sense, your developers don’t have to understand the functionality of the entire program. They can concentrate on the particular class or classes involved in their subset of it, with (hopefully) minimal impact from other classes.

The only way to successfully develop software projects that require the work of more than a few developers is to partition the projects into classes of objects.

Providing object-oriented functionality to a programmer has historically been the job of the programming language, and different languages have taken it to different levels. Standard COBOL, for example, doesn’t do it at all. Visual Basic provides a minimal degree of object-oriented functionality, essentially classes and nothing else. C++ and Java provide a high level of object-oriented features. Languages that want to work together seamlessly need to share the same degree of support for object-orientation, so the question facing Microsoft and developers was whether to smarten up Visual Basic and other non-object-oriented languages or dumb down C++ and other languages that did support object-orientation. Because the architects of .NET belong (as do I) to the school of thought that says object-orientation is the only way to get anything useful done in the modern software industry, they decided that object-oriented features would be an integral part of the common language runtime environment, and thus available to all languages. The two most useful object-oriented techniques provided by the common language runtime are inheritance and constructors. I’ll describe each of them in the following sections.

.NET provides all languages with the object- oriented features of inheritance and constructors.

Inheritance

Essentially no manufacturer in modern industry, with the possible exception of glassmakers, builds their products entirely from nature, starting with earth, air, fire, and water. Instead, almost everyone reuses components that someone else has built, adding value in the process. For example, a company that sells camper trucks doesn’t produce engine and chassis; instead they buy pickup trucks from an automaker and add specialized bodies to them. The automaker in turn bought the windshields from a glass manufacturer, who bought sand from a digger. We would like our software development process to follow this model, starting with generic functionality that someone else has already written and adding our own specialized attachments to it.

Essentially all modern economic processes involve adding value to existing components.

The object-oriented programming technique known as inheritance makes development of components much easier for programmers of software objects than it is for makers of physical objects. Someone somewhere uses a programming language that supports inheritance to write an object class, called the base class, which provides some useful generic functionality, say, reading and writing bytes from a stream. We’d like to use this basic functionality, with our own twists in it, in a class that reads and writes like the base class but that also provides statistics such as length. So we write a piece of software, known as the derived class, that incorporates the base class’s functionality but modifies it in some manner, either adding more pieces to it, replacing some portion of it while leaving the rest intact, or a combination of both. We do this by simply telling the compiler that our derived class inherits from the base class, using the syntax of our programming language. The compiler will automatically include the base class’s functionality in our derived class by reference. Think of it as cutting and pasting without actually moving anything. The derived class is said to inherit from, derive from, or extend the base class. The process is shown in Figure 2-12 with several intervening classes omitted for clarity.

Object-oriented programming provides this concept in software by means of inheritance.


Figure 2-12: Object-oriented programming inheritance.

The .NET Framework uses inheritance to provide all kinds of system functionality, from the simplest string conversions to the most sophisticated Web services. To explore inheritance further, let’s start as always with the simplest example we can find. The time component I wrote previously in this chapter offers a good illustration of .NET Framework inheritance. Even though I didn’t explicitly write code to say so, our time component class derives from the Microsoft-provided base class System.Object. You can see that this is so by examining the component with ILDASM, as shown in Figure 2-13. All objects in the .NET system, without exception, derive from System.Object or another class that in turn derives from it. If you don’t specify a different base class, System.Object is implied. If you prefer a different base class, you specify it by using the keyword Inherits in Visual Basic, as shown in Listing 2-6, or the colon operator in C#.

Every .NET object inherits from the system base class System.Object.

click to expand
Figure 2-13: ILDASM showing inheritance from System.Object.

Listing 2-6: Explicit declaration of inheritance.

start example
 Public Class WebService1 Inherits System.Web.Services.WebService
end example

In more complex cases, the Visual Studio .NET Object Browser shows us the inheritance tree. This case is too simple for it to handle. Figure 2-14 shows the Object Browser.

click to expand
Figure 2-14: The Visual Studio Object Browser showing the inheritance tree.

OK, our time component inherits functionality from System.Object, but how do we know what was in the will? We find that out with a little old- fashioned RTFM (Read The Funny Manual, more or less). When we do that, we find that our base class has the public methods shown in Table 2-1. That means that our derived class, the time component, knows how to do these things even though we didn’t write code for them.

Table 2-1: Public Methods of System.Object

Method name

Purpose

Equals

Determines whether this object is the same instance as a specified object.

GetHashCode

Quickly generates and returns an integer that can be used to identify this object in a hash table or other indexing scheme.

GetType

Returns the system metadata of the object.

ToString

Returns a string that provides the object’s view of itself.

The Equals method determines whether two object references do or do not refer to the same physical instance of an object. This determination was surprisingly difficult to make in COM and could easily be broken by an incorrectly implemented server, but in .NET our objects inherit this functionality from the base class. I’ve written a client application that creates several instances of our time component and demonstrates the Equals method, among others. It’s shown in Figure 2-15.

click to expand
Figure 2-15: Client program demonstrating System.Object features inherited by time component.

Sometimes your component doesn’t want everything it inherits from a base class, just like human heirs. You love the antique table your Aunt Sophie left you, but you aren’t real crazy about her flatulent bulldog (or vice versa). Software inheritance generally allows a derived class to override a method that it inherits from the base class: that is, provide a replacement for it. A good example of this is the method System.Object.ToString, which tells an object to return a string for display to a programmer who is debugging the application. The implementation that we inherit from System.Object simply returns the name of the derived class, which isn’t that illuminating. To make our component easier to debug, we’d like this method to return more detailed information. For example, an object that represents an open file might return the name of that file, or a System.Boolean object return its value “true” or “false”. We do that by overriding the base class’s method, as shown in Listing 2-7. We write a method in our derived class that has the same name and parameters as the method in the base class, specifying the keyword Overrides (override in C#) to tell the compiler to replace the base class’s implementation with our derived class’s new one.

You can override a base class’s methods to replace part of its functionality.

Listing 2-7: Overriding base class method.

start example
‘ This method overrides the ToString method of the ‘ universal base class System.Object. Public Overrides Function ToString() As String ‘ Call the base class’s ToString method and get the result. ‘ You don’t have to do this if you don’t want to. I did, ‘ for demo purposes. Dim BaseResult As String BaseResult = MyBase.ToString ‘ Construct response string with base class’s string plus ‘ my own added information. The net result here is that ‘ I’m piggybacking on the base class, not completely ‘ replacing it. Return "You have reached the overriding class. " + _ "The base class says: " + BaseResult End Function
end example

If your derived class wants to provide its own functionality in addition to that of the base class—rather than instead of the base class—it can call the overridden base class’s method explicitly. In Visual Basic, the base class is accessible through the named object MyBase, and in C# it’s called base. The sample component calls the base class to get its string and then appends its own string to that of the base class. The result is that the component is piggybacking on the base class’s functionality rather than completely replacing it.

An overriding method can access the base class’s method that it overrides.

Most classes can serve as base classes for derivation. A few cannot, such as System.String. These classes are marked as NotInheritable in Visual Basic and sealed in C#. A class designer does this with classes that have fragile innards that he’s worried another programmer might break. For example, the String class has been highly optimized behind the scenes to improve its performance, and its designers don’t want to worry about breaking derived classes if they ever change this code. Other classes must serve only as base classes for derivation; you can’t instantiate them directly. These are marked as MustInherit in Visual Basic and abstract in C#. An example of an abstract base class is System.IO.Stream. A designer makes a class abstract to force a common design pattern on a set of derived classes, by means of abstract methods described in the next paragraph.

Some classes can’t serve as bases, but others have to.

Most base class methods can be overridden, as I’ve shown you, but some of them can’t and a few of them must be. A base class method must contain the keyword Overridable in Visual Basic or virtual in C# if you want to allow this. The method System.Object.GetType is not so written, and therefore cannot be overridden. The designers of the class hierarchy thought this class’s functionality was too important for proper operation of many parts of the system to allow anyone to monkey with it. An abstract base class, on the other hand, contains methods that must be overridden, marked as MustOverride in Visual Basic or abstract in C#. For example, the abstract base class System.IO.Stream contains the abstract method Read. Classes that derive from it, such as System.IO.MemoryStream and System.IO.FileStream, must provide their own implementations of this method. Clients therefore see a common set of functions from all stream-derived classes.

Some methods can’t be overridden, while others must be.

Much is made of the ability of .NET to provide cross-language inheritance, that is, to allow a class written in one language, Visual Basic, for example, to derive from a base class written in another language, say C#. COM couldn’t provide this feature because the differences between language implementations were too great. However, the standardized IL architecture of the common language runtime allows .NET applications to use it. In fact, the simple time component example does exactly that with no effort on my part. I guarantee you that the System.Object class is written in one language and not any other, yet every .NET object, without exception and regardless of language, inherits from it.

.NET inheritance works between different languages.

Object Constructors

As the Good Rats sang a couple of decades ago, “birth comes to us all.” As humans mark births with various rituals (religious observances, starting a college fund), so objects need a location where their birth-ritual code can be placed. Object-oriented programming has long recognized the concept of a class constructor, a function called when an object is created. (Object- oriented programming also uses the concept of a class destructor, a function called when the object is destroyed, but this concept has been replaced in .NET with the system garbage collector described in the next section.) Different languages have implemented constructors differently—C++ with the class name, Visual Basic with Class_Initialize. As with so many features that have varied widely among languages, the rituals for object creation had to be standardized for code written in different languages to work together properly.

Objects need a standard place for putting initialization code.

In .NET, Visual Basic lost its Class_Initialize event, and the model looks much more like a C++ model, primarily because parameterized constructors are needed to support inheritance. Every .NET class can have one or more constructor methods. This method has the name New in Visual Basic .NET or the class name in C#. The constructor function is called when a client creates your object using the new operator. In the function, you place the code that does whatever initialization your object requires, perhaps acquiring resources and setting them to their initial state. An example of a constructor is shown in Listing 2-8.

.NET object classes provide for initialization through an object constructor.

Listing 2-8: Constructor declaration example.

start example
Public Class Point Public x, y As Integer ‘ Default constructor accepts no parameters, ‘ initializes member variables to zero Public Sub New() x = 0 y = 0 End Sub ‘ This constructor accepts two parameters, initializing ‘ member variables to the supplied values. Public Sub New(ByVal newx As Integer, ByVal newy As Integer) x = newx y = newy End Sub End Class 
end example

One of the more interesting things you can do with a constructor is allow the client to pass parameters to it, thereby allowing the client to place the object in a particular state immediately upon its creation. For example, the constructor of an object representing a point on a graph might accept two integer values, the X and Y location of that point. You can even have several different constructors for your class that accept different sets of parameters. For example, our Point object class might have one constructor that accepts two values, another that accepts a single existing point, and yet a third that accepts no parameters and simply initializes the new point’s members as zero. An example is shown in Listing 2-9. This flexibility is especially useful if you want to make an object that requires initialization before you can use it. Suppose you have an object that represents a patient in a hospital, supporting methods such as Patient.ChargeLotsOfMoney and Patient.Amputate (whichLimb). Obviously, it is vital to know which human being each individual instance of this class refers to or you might remove money or limbs from the wrong patient, both of which are bad ideas, the latter generally more so than the former. By providing a constructor that requires a patient ID—and not providing a default empty constructor—you ensure that no one can ever operate on an unidentified patient or inadvertently change a patient’s ID once it’s created.

Object constructors can accept different sets of parameters, allowing an object to be created in a particular state.

Listing 2-9: Constructor call example.

start example
Dim foo As New Point ( ) Dim bar As New Point (4, 5)
end example

start sidebar
Tips from the Trenches

The technique of requiring a nondefault constructor breaks in several design cases. A .NET class that deserializes itself from XML (see Chapter 7) or a .NET class accessed by a COM client (see later in this chapter) requires a constructor that has no parameters. You can have as many more as you like, but you must have one constructor that takes no parameters.

end sidebar




Introducing Microsoft. NET
Introducing Microsoft .NET (Pro-Developer)
ISBN: 0735619182
EAN: 2147483647
Year: 2003
Pages: 110

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net