|
|
| Home | Forum |
| The Object oriented programming in .NET |
|
This article will focus on defining the concepts of object orientation as they relate to software development in general. There have been more than a few books written on object-oriented programming, so this article will not attempt to deliver a full treatise on a subject well deserving of hundreds of pages. Instead, we will cover only the ground that we need to cover so that programmers new to object-oriented programming and programmers with no OOP experience at all will have a good backdrop of knowledge for exploring the .NET Framework Class Library. In years past, many developers have debated whether Visual Basic was an object-oriented language. Instead of investigating any of these prior claims, arguments, or discussions, let's focus instead on the here and now. Visual Basic .NET supports the major traits of an object-oriented language, including the capability to: Wrap data and behavior together into packages called classes (this is a trait known as encapsulation) Define classes in terms of other classes (a trait known as inheritance) Override the behavior of a class with a substitute behavior (a trait known as polymorphism) We'll examine each one of these traits in detail. We'll also examine ways in which you will see these concepts at work inside of the .NET Framework. ClassesWrapping Data and Behavior Together Classes are blueprints or specifications for actual objects that we will create in our code. They define a standard set of attributes and behaviors. Because classes only define a structure or intent, they are virtual in nature. For instance, a class cannot hold data, it can't receive a message, and in fact can't do any processing at all. This is because classes are only meant to be object factories. Just like real engineering blueprints of a building, they only exist to construct something else. When we program, this "something else" we are trying to construct is an object. An object can hold data, can receive messages, and can actually carry out processing. While you don't typically use the term class in your everyday (non-programming) life, we are all certainly familiar with the concept of objects. These are the things that surround us day in and day out; they are the nouns in our universe. We are used to interacting with objects. For example, you place a plate on your table for dinner. The plate has food on ita few different types of food, in fact. We can see that all of these things have distinct properties: The plate is white with a faint flower pattern, and the food has a particular texture, taste, and smell. We also expect that objects will allow us to interact with them in different ways. Just like in the real world, code objects (we also call them instances) are actual physical manifestations of classes. Classes as Approximations Note There are some general guidelines for when to use properties versus methods (and vice versa), but probably the best advice is to just be consistent. Most of the time, these rules will help steer you to the correct decision: --Use a method if you are going to be passing in more than a few parameters. --If you find yourself writing a method called GetXXX or SetYYY, chances are good this should be a property instead. --Methods are more appropriate than properties if there will be many object instantiations or inter-object communication inside of the function. --Properties, when implemented, should be stateless with respect to one another. In other words, a property should not require that another property be set before or after it is set. If you find this kind of dependency inside of a property, it should probably be a method instead. Classes are typically constructed to mimic, or approximate, real-world physical structures or concepts. By using classes in your code, you can simplify both your architecture and your understanding of the code; this is due to the inherent approachability of objectsyour mind is used to deal with objects. For example, which do you suppose would make more intuitive sense to you? You are writing code to move an icon from the left side of the screen to the right side of the screen. The procedural programming way would probably have you calling some API function (maybe it's called SystemDskRsrcBlit) and passing parameters into the function call. But, what if you were free to do this: --Create an icon object --Tell it to MoveLeft The object-oriented way just seems to make more sense to usit seems to appeal to the way that our minds are wired. Note The difference between the system that we are programming and the real-world process that we are modeling is often referred to as the semantic gap. You could summarize some of what we have been talking about here by saying that object- oriented programming aims to reduce the semantic gap between programming and the real world. Of course, just because the basic premises of objected-oriented programming are simple to understand doesn't mean that the actual programming of object-oriented systems is trivial. Once you can work your way through the syntax and condition yourself to think in an object-like fashion while actually designing your applications, some of the perceived complexity associated with software development will begin to fade. Talking Between Classes A core tenet of object-oriented systems is that classes think for themselves. A particular class knows how it should react to an incoming message; the calling class isn't forced to understand how or why the receiving class behaves the way that it does. This is the essence of information hiding. In information hiding, an object hides its internal machinations from other objects (see Figure 3.1). Information hiding is important because it helps reduce the overall design complexity of an application. In other words, if Class A doesn't have to implement code to understand how Class B operates, we have just reduced the complexity of the code. As programmers, we initiate a message from one class to another by calling a method or property on the target class. Part of this message that we send encapsulates any parameters or data needed by the receiving class to execute the action. Thus, we have classes in an object-oriented programming environment. A physical manifestation of a class in the programming world consists of code that defines these attributes and behaviors through property and method routines. In this book, our focus on the Framework Class Library will introduce you to new classes in each chapter. They will exhibit all of the traits and characteristics of the classes that we have just defined. Now, let's move on and discuss the next OO trait of Visual Basic .NETinheritance.
By looking at them, it quickly becomes clear that we could hierarchically structure these classes by abstracting their common traits into a parent class. These three classes would then be child classes of that one parent class. Figure 3.3 shows how this results in a tree structure for our classes. Another way to think about this is called sub-typing. Children classes can often be thought of as different "types" of the parent class (an HR employee is a type of Employee, and so on). This hierarchical structure is typical of well-engineered class librariesthe Framework Class Library is organized in just such a way. Note If you examine Figure 3.3, you will see that our arrows point from the child classes to the parent class. This may not seem intuitive to you; after all, aren't we creating a child class from a parent class? This notation is advocated because it shows that the child knows about the parent, but the parent does not (necessarily) know about the child. Inheritance by Natural Relationship We have said that a class can inherit the traits of another class. We have also said that the traits of a class are implemented as property and method routines. When these routines are inherited between classes, it obviously means we are assuming the actual source code of one class into another. Thus, we have code reuse. If we look back at Figure 3.3 we can see how each line of code that was written to implement the parent class methods can be reused by each of the child classes. Code reuse in this fashion becomes a powerful rapid application development enabler. If we needed to change some lines of code in one of the parent class methods, the change would be immediately realized in its children classes. This also allows us to build up complexity in a child class by inheriting from potentially simple base classes. Note There are many different ways to express the inheritance relationship: parent to child, super-class to sub-class, ancestor class to descendant class, generalized class to specialized class, and so on. In this book, anytime we use these terms you should know that we are just referring back to this basic concept of inheritance relationship. Figure 3.4 shows class nomenclature, in ancestor/descendant terms, against a class tree. Figure 3.4 Inheritance nomenclature examples. We now know that identifying logical relationships between objects will help us out in the area of code reuse. But the examples we have talked about so far have been based on relationships between objectsan appraisal of one object being a type of another object. If, however, you approach inheritance by first looking at its end result, you'll find that you can end up with an entirely different perspective. Let's look at an example: Let's say that we have a class that defines operations for a specific type of printer. We'll call this class InkJet. Intuitively, we sense a parent class that would most likely be called Printer. Introducing a Printer parent class produces the inheritance that we see in Figure 3.5. Inheriting from the Printer class is a good solution for us because it already defines some basic operations (line feed, paper out, and so on) that we can use as building blocks for our InkJet class. At the same time, we will add some of our own behaviors that are specific to inkjet printers. But what if we had the requirement for some low-level communication code that would send an error signal across a parallel port? Also, what if that code was already available to us in yet another class? Figure 3.6 shows how we could inherit from a fictional ParallelPort object to leverage the SendErrorSignal code that we need. Figure 3.5 Inheritance based on relationship. This is subtly different from what we were doing before because it is very difficult to envision a logical relationship between a parallel port object and an inkjet object. After all, an inkjet printer is not a type of a parallel portthere is no obvious hierarchical relationship to draw between the two. In this case, we would be implementing inheritance to get at raw code reuse. This doesn't do anything for us in terms of making our code easier to understandit does not reinforce a relationship between abstract classes and real-world objects. Note Inheriting for pure code reuse in the absence of a sub-type relationship is certainly something that you can do with classes, but isn't always the best approach. You gain code reuse at the expense of increased complexity in your system (and therefore, a corresponding increase in the effort required to understand your system). In .NET, we advocate implementing an interface instead of using class inheritance to represent this relationship; you still get the desired code reuse without complicating your class relationships. What if you decided to proceed ahead with class inheritance, and decided to inherit from both the Printer and the ParallelPort class? This is called multiple inheritance. Multiple inheritance is not supported by the .NET runtime. |
|