Multiple Interfaces


In Visual Basic, objects can have one or more interfaces. All objects have a primary, or native, interface, which is composed of any methods, properties, events, or member variables declared using the Public keyword. You can also have objects implement secondary interfaces in addition to their native interface by using the Implements keyword.

Object Interfaces

The native interface on any class is composed of all the methods, properties, events, and even variables that are declared as anything other than Private. Though this is nothing new, let’s quickly review what is included in the native interface to set the stage for discussing secondary interfaces. To include a method as part of your interface, you can simply declare a Public routine:

  Public Sub AMethod() End Sub 

Notice that there is no code in this routine. Any code would be implementation and is not part of the interface. Only the declaration of the method is important when discussing interfaces. This can seem confusing at first, but it is an important distinction, as the separation of the interface from its implementation is at the very core of object-oriented programming and design.

Because this method is declared as Public, it is available to any code outside the class, including other applications that may make use of the assembly. If the method has a property, then you can declare it as part of the interface by using the Property keyword:

  Public Property AProperty() As String End Property 

You can also declare events as part of the interface by using the Event keyword:

  Public Event AnEvent() 

Finally, you can include actual variables, or attributes, as part of the interface:

  Public AnInteger As Integer 

This is strongly discouraged, because it directly exposes the internal variables for use by code outside the class. Because the variable is directly accessible from other code, you give up any and all control over the way the value may be changed or code may be accessed.

Rather than make any variable Public, it is far preferable to make use of a Property method to expose the value. That way, you can implement code to ensure that your internal variable is only set to valid values and that only the appropriate code has access to the value based on your application’s logic.

Using the Native Interface

Ultimately, the native (or primary) interface for any class is defined by looking at all the methods, properties, events, and variables that are declared as anything other than Private in scope. This includes any methods, properties, events, or variables that are inherited from a base class.

You’re used to interacting with the default interface on most objects, so this should seem pretty straight-forward. Consider this simple class:

  Public Class TheClass   Public Sub DoSomething()   End Sub      Public Sub DoSomethingElse()   End Sub End Class 

This defines a class and, by extension, defines the native interface that is exposed by any objects you instantiate based on this class. The native interface defines two methods, DoSomething() and DoSomethingElse(). To make use of these methods, you simply call them:

 Dim myObject As New TheClass() myObject.DoSomething() myObject.DoSomethingElse()

This is the same thing you did in Chapter 3 and so far in this chapter. However, let’s take a look at creating and using secondary interfaces, because they are a bit different.

Secondary Interfaces

Sometimes it’s helpful for an object to have more than one interface, thus enabling you to interact with the object in different ways. Inheritance enables you to create subclasses that are specialized cases of the base class. For example, your Employee is a Person.

However, sometimes you have a group of objects that are not the same thing, but you want to be able to treat them as though they were the same. You want all these objects to act as the same thing, even though they are all different.

For instance, you may have a series of different objects in an application, product, customer, invoice, and so forth. Each of these would have default interfaces appropriate to each individual object - and each of them is a different class - so there’s no natural inheritance relationship implied between these classes. At the same time, you may need to be able to generate a printed document for each type of object, so you’d like to make them all act as a printable object.

Tip 

This chapter later discusses the is-a and act-as relationships in more detail.

To accomplish this, you can define a generic interface that enables generating such a printed document. You can call it IPrintableObject.

Tip 

By convention, this type of interface is typically prefixed with a capital I to indicate that it is a formal interface.

Each of your application objects can choose to implement the IPrintableObject interface. Every object that implements this interface must include code to provide actual implementation of the interface, which is unlike inheritance, where the code from a base class is automatically reused.

By implementing this common interface, you are able to write a routine that accepts any object that implements the IPrintableObject interface and print it - while remaining totally oblivious to the “real” datatype of the object or the methods its native interface might expose.

Before you see how to use an interface in this manner, let’s walk through the process of actually defining an interface.

Defining the Interface

You define a formal interface using the Interface keyword. This can be done in any code module in your project, but a good place to put this type of definition is in a standard module. An interface defines a set of methods (Sub, Function, or Property) and events that must be exposed by any class that chooses to implement the interface.

Add a module to the project using Project image from book Add Module and name it Interfaces.vb. Then, add the following code to the module, outside the Module code block itself:

 Public Interface IPrintableObject End Interface Module Interfaces End Module

A code module can contain a number of interface definitions, and these definitions must exist outside of any other code block. Thus, they don’t go within a Class or Module block; they are at a peer level to those constructs.

Interfaces must be declared using either Public or Friend scope. Declaring a Private or Protected interface results in a syntax error.

Within the Interface block of code, you can define the methods, properties, and events that make up your particular interface. Because the scope of the interface is defined by the Interface declaration itself, you can’t specify scopes for individual methods and events; they are all scoped like the interface itself.

For instance, add the following code:

 Public Interface IPrintableObject    Function Label(ByVal index As Integer) As String    Function Value(ByVal index As Integer) As String    ReadOnly Property Count() As Integer End Interface

This defines a new datatype, somewhat like creating a class or structure, which you can use when declaring variables. For instance, you can now declare a variable of type IPrintableObject:

 Private printable As IPrintableObject

You can also have your classes implement this interface, which requires each class to provide implementation code for each of the three methods defined on the interface.

Before you implement the interface in a class, let’s see how you can make use of the interface to write a generic routine that can print any object that implements IPrintableObject.

Using the Interface

Interfaces define the methods and events (including parameters and datatypes) that an object is required to implement if you choose to support the interface. This means that, given just the interface definition, you can easily write code that can interact with any object that implements the interface, even though you don’t know what the native datatypes of those objects will be.

To see how you can write such code, let’s create a simple routine in your form that can display data to the output window in the IDE from any object that implements IPrintableObject. Bring up the code window for your form and add the following routine:

  Public Sub PrintObject(obj As IPrintableObject)   Dim index As Integer   For index = 0 To obj.Count     Debug.Write(obj.Label(index) & ": ")     Debug.WriteLine(obj.Value(index))   Next End Sub 

Notice that you’re accepting a parameter of type IPrintableObject. This is how secondary interfaces are used, by treating an object of one type as though it were actually of the interface type. As long as the object passed to this routine implements the IPrintableObject interface, your code will work fine.

Within the PrintObject routine, you’re assuming that the object will implement three elements - Count, Label, and Value - as part of the IPrintableObject interface. Secondary interfaces can include methods, properties, and events, much like a default interface, but the interface itself is defined and implemented using some special syntax.

Now that you have a generic printing routine, you need a way to call it. Bring up the designer for Form1, add a button, and name it btnPrint. Double-click the button and put this code behind it:

 Private Sub btnPrint_Click(ByVal sender As System.Object, _     ByVal e As System.EventArgs) Handles btnPrint.Click   Dim obj As New Employee("Andy")   obj.EmployeeNumber = 123   obj.BirthDate = #1/1/1980#   obj.HireDate = #1/1/1996#   PrintObject(obj) End Sub

This code simply initializes an Employee object and calls the PrintObject() routine. Of course, this code produces runtime exceptions, because PrintObject is expecting a parameter that implements IPrintableObject, and Employee implements no such interface. Let’s move on and implement that interface in Employee so that you can see how it works.

Implementing the Interface

Any class (other than an abstract base class) can implement an interface by using the Implements keyword. For instance, you can implement the IPrintableObject interface in Employee by adding the following line:

 Public Class Employee   Inherits Person   Implements IPrintableObject 

This causes the interface to be exposed by any object created as an instance of Employee. Adding this line of code and pressing the Enter key triggers the IDE to add skeleton methods for the interface to your class. All you need to do is provide implementations for the methods.

Tip 

To implement an interface, you must implement all the methods and properties defined by that interface.

Before actually implementing the interface, however, let’s create an array to contain the labels for the data fields so that you can return them via the IPrintableObject interface. Add the following code to the Employee class:

 Public Class Employee   Inherits Person   Implements IPrintableObject      Private mLabels() As String = {"ID", "Age", "HireDate"}   Private mHireDate As Date   Private mSalary As Double

To implement the interface, you need to create methods and properties with the same parameter and return datatypes as those defined in the interface. The actual name of each method or property doesn’t matter because you are using the Implements keyword to link your internal method names to the external method names defined by the interface. As long as the method signatures match, you are all set.

This applies to scope as well. Although the interface and its methods and properties are publicly available, you don’t have to declare your actual methods and properties as Public. In many cases, you can implement them as Private, so they don’t become part of the native interface and are only exposed via the secondary interface.

However, if you do have a Public method with a method signature, you can use it to implement a method from the interface. This has the interesting side effect that this method provides implementation for both a method on the object’s native interface and one on the secondary interface.

In this case, you’ll use a Private method, so it is only providing implementation for the IPrintableObject interface. You can implement the Label method by adding the following code to Employee:

 Private Function Label(ByVal index As Integer) As String _       Implements IPrintableObject.Label     Return mLabels(index) End Function

This is just a regular Private method that returns a String value from the preinitialized array. The interesting part is the Implements clause on the method declaration:

 Private Function Label(ByVal index As Integer) As String _       Implements IPrintableObject.Label

By using the Implements keyword in this fashion, you’re indicating that this particular method is the implementation for the Label method on the IPrintableObject interface. The actual name of the private method could be anything. It is the use of the Implements clause that makes this work. The only requirement is that the parameter datatypes and the return value datatype must match those defined by the IPrintableObject interface.

This is very similar to using the Handles clause to indicate which method should handle an event. In fact, like the Handles clause, the Implements clause allows you to have a comma-separated list of interface methods that should be implemented by this one function.

You can then move on to implement the other two elements defined by the IPrintableObject interface by adding this code to Employee:

 Private Function Value(ByVal index As Integer) As String _     Implements IPrintableObject.Value   Select Case index     Case 0       Return CStr(EmployeeNumber)     Case 1       Return CStr(Age)     Case Else       Return Format(HireDate, "Short date")   End Select End Function Private ReadOnly Property Count() As Integer _     Implements IPrintableObject.Count   Get     Return UBound(mLabels)   End Get End Property

You can now run this application and click the button. The output window in the IDE will display your results, showing the ID, age, and hire date values as appropriate.

Any object could create a similar implementation behind the IPrintableObject interface, and the PrintObject() routine in your form would continue to work regardless of the native datatype of the object itself.

Reusing Common Implementation

Secondary interfaces provide a guarantee that all objects implementing a given interface have exactly the same methods and events, including the same parameters.

The Implements clause links your actual implementation to a specific method on an interface. For instance, your Value method is linked to IPrintableObject.Value using the following clause:

 Private Function Value(ByVal index As Integer) As String _     Implements IPrintableObject.Value

Sometimes, your method might be able to serve as the implementation for more than one method, either on the same interface or on different interfaces.

Add the following interface definition to Interfaces.vb:

  Public Interface IValues   Function GetValue(ByVal index As Integer) As String End Interface 

This interface defines just one method, GetValue(). Notice that it defines a single Integer parameter and a return type of String, the same as the Value method from IPrintableObject. Even though the method name and parameter variable name don’t match, what counts here is that the parameter and return value datatypes do match.

Now bring up the code window for Employee. You’ll have it implement this new interface in addition to the IPrintableObject interface:

 Public Class Employee   Inherits Person   Implements IPrintableObject   Implements IValues 

You already have a method that returns values. Rather than reimplement that method, it would be nice to just link this new GetValues() method to your existing method. You can easily do this because the Implements clause allows you to provide a comma-separated list of method names:

 Private Function Value(ByVal index As Integer) As String _     Implements IPrintableObject.Value, IValues.GetValue    Select Case Index     Case 0       Return CStr(EmployeeNumber)     Case 1       Return CStr(Age)     Case Else       Return Format(HireDate, "Short date")   End Select End Function

This is very similar to the use of the Handles keyword, covered in Chapter 3. A single method within the class, regardless of scope or name, can be used to implement any number of methods as defined by other interfaces as long as the datatypes of the parameters and return values all match.

Combining Interfaces and Inheritance

You can combine implementation of secondary interfaces and inheritance at the same time. When you inherit from a class that implements an interface, your new subclass automatically gains the interface and the implementation from the base class. If you specify that your base class methods are overridable, then the subclass can override those methods. This not only overrides the base class implementation for your native interface, but also overrides the implementation for the interface. For instance, you could declare the Value() method in the interface as follows:

  Public Overridable Function Value(ByVal index As Integer) As String _     Implements IPrintableObject.Value, IValues.GetValue 

Now it is Public, so it is available on your native interface, and it is part of both the IPrintableObject and IValues interfaces. This means that you can access the property three ways in client code:

 Dim emp As New Employee() Dim printable As IPrintableObject = emp Dim values As IValues = emp Debug.WriteLine(emp.Value(0)) Debug.WriteLine(printable.Value(0)) Debug.WriteLine(values.GetValue(0))

Note that you’re also now using the Overrides keyword in the declaration. This means that a subclass of Employee, such as OfficeEmployee, can override the Value() method. The overridden method will be the one invoked, regardless of whether you call the object directly or via an interface.

Combining the implementation of an interface in a base class along with overridable methods can provide a very flexible object design.




Professional VB 2005 with. NET 3. 0
Professional VB 2005 with .NET 3.0 (Programmer to Programmer)
ISBN: 0470124709
EAN: 2147483647
Year: 2004
Pages: 267

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