Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual Basic 2005 Express Edition - From Novice To Professional (2006)

.pdf
Скачиваний:
386
Добавлен:
17.08.2013
Размер:
21.25 Mб
Скачать

140

C H A P T E R 5 M O R E - A D V A N C E D O B J E C T O R I E N T A T I O N

The situation gets even more confusing if you leave Describe() in the OffRoader class as public. When you do that, if you call Describe() on an OffRoader object, you get the new message printed out. However, if you treat the OffRoader as a Car object, you’ll see the old message printed when Describe() is called.

I know developers who love this feature, but to me it violates the number one rule that you all should adhere to when writing code: the code should be obvious! When you have a bunch of objects subclassing each other, and some of them hiding methods, some using Shadows to temporarily override them and so on, the code gets very confusing very quickly. My advice: use Overrides, MustInherit, and NotInheritable where you can.

Let’s take a look at Shadows in action.

Try It Out : Using Shadows

Start up a new console project. When the code editor appears, change the code so it looks like this:

Module Module1

Sub Main()

Dim truck As New OffRoader

Dim car As Car = CType(truck, Car)

truck.Describe()

car.Describe()

Console.ReadLine()

End Sub

End Module

Public Class Car

Public Sub Describe()

Console.WriteLine("I am a generic car")

End Sub

End Class

Public Class OffRoader

Inherits Car

Private Shadows Sub Describe()

Console.WriteLine("I am an offroader")

End Sub

End Class

C H A P T E R 5 M O R E - A D V A N C E D O B J E C T O R I E N T A T I O N

141

This is pretty much the same code that you just saw. In addition, the Main() subroutine includes code to instantiate the class and use it. Let’s take a look:

Sub Main()

Dim truck As New OffRoader

Dim car As Car = CType(truck, Car)

truck.Describe()

car.Describe()

Console.ReadLine() End Sub

The code here first creates an instance of the OffRoader class, called truck. A second variable, of type Car, is then set to point at the same single object. The Describe() method then gets called on both truck and Car. Run the code and you’ll see the output in Figure 5-7.

Figure 5-7. When you run the code, the same message appears for both calls to Describe().

Now, this may seem odd. After all, OffRoader creates a Describe() method that shadows the base Car.Describe() method. So, surely that means we should see two different output messages.

The reason we don’t is subtle. Take a look at the definition of Describe() in the OffRoader class:

Private Shadows Sub Describe()

Console.WriteLine("I am an offroader")

End Sub

142

C H A P T E R 5 M O R E - A D V A N C E D O B J E C T O R I E N T A T I O N

This method is Private. The other Describe() method, the one in the Car object, is Public. So, what you did here is create a Describe() method that replaces the one in Car but that is Public. Confused? What happens at runtime is that because the new Describe() method is Private, the runtime tries to find Describe() in a subclass. In our case this means it finds Describe() in Car and calls it. The problem with this approach can be demonstrated by adding a new method to the OffRoader class. Go ahead and make the following highlighted change:

Module Module1

Sub Main()

Dim truck As New OffRoader

Dim car As Car = CType(truck, Car)

truck.ShowMe()

car.Describe()

Console.ReadLine()

End Sub

End Module

Public Class Car

Public Sub Describe()

Console.WriteLine("I am a generic car")

End Sub

End Class

Public Class OffRoader

Inherits Car

Private Shadows Sub Describe()

Console.WriteLine("I am an offroader")

End Sub

Public Sub ShowMe()

Describe()

End Sub

End Class

Here, you’ve added a method to OffRoader called ShowMe(). This method in turn just calls Describe(). Run the code again and you’ll see the output in Figure 5-8.

C H A P T E R 5 M O R E - A D V A N C E D O B J E C T O R I E N T A T I O N

143

Figure 5-8. Now when you run the application, you see two messages.

This time you see two messages. Why? Well, ShowMe() in OffRoader calls Describe(). It is able to access the private shadowed Describe() method, so you see two messages.

So, given all these problems with Shadows, why even use it? Well, there is a valid use. You can use Shadows to protect your methods. If you are subclassing and expect that a method in a base class may change, or even that a new method may be added with the name of a method you are using, Shadows can protect you from that change, and thus protect code that uses your own class.

Interfaces

Abstract classes provide us with a way to force people using the class to implement some functionality. Another way of thinking about it is that an abstract class binds a subclass to support a specific interface. Visual Basic 2005 also has a specific Interface type for just this purpose. Unlike an abstract class, you can’t put functionality of any kind inside an Interface. An Interface just specifies a set of method signatures that a supporting class must provide.

Let’s take a look (this won’t be a complete example, but it is handy to see how to create an interface type and to see the support that the Visual Basic 2005 Express IDE gives you when it comes to implementing it).

144

C H A P T E R 5 M O R E - A D V A N C E D O B J E C T O R I E N T A T I O N

Try It Out: Interfaces

Create a new console project and add a new Car.vb class to the project, just as before. When the editor appears, erase all the code except for the namespace definition. Your code window will look like the one in Figure 5-9.

Figure 5-9. Erase all the code in the Car.vb class so that just the namespace definition remains.

Now go ahead and declare the interface:

Public Interface ICar

End Interface

C H A P T E R 5 M O R E - A D V A N C E D O B J E C T O R I E N T A T I O N

145

A WORD ON NAMING

We tend to name classes and interfaces a little differently. A class name always uses a capital letter at the start of each word in its name—for example, FactoryEmployee, GameGraphic, Car, and so on.

Just like a class, interfaces use a capital letter as the first letter of each word in the interface name, but traditionally begin with a capital I—for example, IFactoryEmployee, IGameGraphic, ICar.

This difference makes it easy when you’re glancing through your code to see just what is an interface and what is a class. It’s also the standard used to name classes and interfaces within the .NET class library, so it makes a lot of sense for us to adopt the same standards.

Interfaces never contain any code or variables, just methods. Even then the methods are declared with no body, just as when you declared an abstract method earlier.

Add a few methods to this interface:

Public Interface ICar

Sub TurnOn()

Sub TurnOff()

Sub Drive()

End Interface

You’ll notice of course that there are no Public or Private modifiers on the methods in the interface. Everything in an interface is public because the very nature of a VB interface is to define a public interface supported by a class.

Let’s create a class now that implements this interface, just to see the special help Visual Basic 2005 Express gives us when working with interfaces.

Underneath the interface, type in this:

Public Class OffRoader

Implements ICar

146

C H A P T E R 5 M O R E - A D V A N C E D O B J E C T O R I E N T A T I O N

As soon as you press the Enter key, VB Express automatically drops a bunch of code into your class, as you can see in Figure 5-10.

Figure 5-10. When you implement an interface with a class, VB automatically generates the Interface stubs for you.

The keyword Implements tells Visual Basic that this class implements the named interface. Every single method in the interface is now in your class. Also, notice how every single method in the class from the interface also uses the Implements keyword. This lets VB know that the method in question is a part of this class’s implementation of the interface, not simply a generic class method.

If a class implements an interface like this, it must provide code for absolutely every method within the interface. Interfaces, then, are like a contract. By implementing an interface in a class, the person writing the class is committing to a contract that says he will make sure the class does everything the interface says that it should.

Every method within the class has the same name as the method in the interface. That’s what implementing the methods implicitly is all about. Now, Visual Basic will let you inherit from only one base class, but a class can implement as many interfaces as you like. For example, a Jeep class could implement OffRoader and Car interfaces, showing the world that it does everything both interfaces say it should. This becomes a problem, though, when a class implements two or more interfaces that have the same method

C H A P T E R 5 M O R E - A D V A N C E D O B J E C T O R I E N T A T I O N

147

names in them. For example, an OffRoader interface may also have a Drive() method, just like the class. This is why every interface method in the class explicitly identifies the interface and method that it’s implementing. Also, there’s nothing stopping you from implementing more than one interface within a class. In fact, you can even implement a number of interfaces that support the same method name, because in code you explicitly identify which interface a method implementation belongs to.

Public Class OffRoader

Implements ICar

Public Sub Drive() Implements ICar.Drive

End Sub

Public Sub TurnOff() Implements ICar.TurnOff

End Sub

Public Sub TurnOn() Implements ICar.TurnOn

End Sub

End Class

You have even more flexibility, in that if you wish you can implement a method from an interface and change its name too, like this:

Public Sub DoSomething() Implements ICar.TurnOn

Finally, bear in mind that at runtime an interface acts pretty much like an abstract class; although you can’t create an object directly from an interface (that would be silly, because an interface has no code), you can treat objects as interfaces. For example:

Dim mycar As ICar = New OffRoader() mycar.Drive()

Even though you’re not going to get this project working, don’t throw it away; you’ll come back to it in a second.

Let’s move on to a new feature of Visual Basic 2005, the partial class.

Partial Classes

Imagine that you had a very complex class that also implemented a very complex interface. As you add code to the class, it would soon become quite confusing as you try to figure out just which parts are the private methods of the class, which are the public methods, and which are required methods that implement the interface. It’s a problem that’s

148

C H A P T E R 5 M O R E - A D V A N C E D O B J E C T O R I E N T A T I O N

plagued Java and C++ programmers for years, and one of the things that helps make code written in those languages so daunting to newcomers.

However, Visual Basic 2005 has partial classes. Simply put, you don’t need to dump all the code of a single class into a single file. You can break the code into multiple physical files on your computer—for example, one file to handle the interfaces that the class implements, another to hold private methods, and another to hold the class’s own unique public methods. When you start a Windows Forms project in Visual Basic 2005 Express, the IDE automatically makes every form you create a partial class. This means that as you drop controls onto the form, the IDE can populate a hidden partial class file with information about the controls on the form, leaving the main code file that you work with free of anything other than the code you write.

Let’s take a look at how this works.

Try It Out: Partial Classes

Load up the interfaces project you were just working on.

The first thing you need to do to create a partial class is to simply tell VB Express that your OffRoader class is partial. Change the class definition by removing all the code inside it and adding the word Partial to its declaration:

Public Interface ICar

Sub TurnOn()

Sub TurnOff()

Sub Drive()

End Interface

Partial Public Class OffRoader

End Class

C H A P T E R 5 M O R E - A D V A N C E D O B J E C T O R I E N T A T I O N

149

Now add another class file to the project (right-click the project in the Solution Explorer and then follow the usual steps) and call this one OffRoader_Interfaces.vb. When the code editor appears, add the Partial keyword once again and make the OffRoader class implement your interface:

Partial Public Class OffRoader_Interfaces

Implements ICar

Public Sub Drive() Implements ICar.Drive

End Sub

Public Sub TurnOff() Implements ICar.TurnOff

End Sub

Public Sub TurnOn() Implements ICar.TurnOn

End Sub

End Class

The net result of course is that you now have the source code for your OffRoader class in two files. One file contains code that implements the interfaces that OffRoader supports. The other contains (or will, if you add more code to this) the code that makes OffRoader do its thing.

USED .NET BEFORE?

If you’ve used .NET before, you’ll probably remember the nightmare code tangle that was the result of the Windows Forms editor. To enlighten the rest of the readers, when you drag and drop controls onto a form to design the user interface of a Windows application in Visual Basic 2005 Express, what’s happening behind the scenes is that the VB Express IDE is actually generating code for you.

In previous versions of Visual Studio .NET, this code would get dropped into a code region within your form’s source and would easily get mixed up with your own source. As you added more and more controls to your form’s user interface, the source for the form would grow and grow in size and eventually become quite confusing.

In Visual Basic 2005 Express and Visual Studio 2005, partial classes are used. The IDE will generate code in a separate hidden source file, leaving your form’s source file completely empty and a lot less confusing to work with. Take a look for yourself—go ahead and create a new Windows Forms project, and then doubleclick on the form that appears. You’ll notice that the class definition for the form itself is a partial class.