Take a look at the following artificial but nonetheless typical pattern: Public Interface IUIControl Sub PaintEnd Interface Public Class Button Implements IUIControlPublic Sub Paint Impl
Trang 1Public Class EntryPoint
Shared Sub Main()Dim b As B = New B()b.DoSomething()b.DoSomethingElse()Dim a As A = ba.DoSomething()End Sub
End Class
You can see that the previous code introduced a new method on Class B named DoSomething() The astute reader will also notice the addition of the Shadows keyword to thedeclaration If you don’t add this keyword, the compiler will complain with a warning This isthe compiler’s way of telling you that you need to be more explicit about the fact that you’rehiding a method in the base class Arguably, the compiler does this because hiding membersthis way is generally considered bad design Let’s see why The output from the previous code
ref-no matter which type reference it is called through
In order to declare DoSomething() as overridable, you need to think about the future at thepoint you define it That is, you have to think about the possibility that someone could inheritfrom your class and possibly may want to override this functionality This is just one reasonwhy inheritance can be more complicated during the design process than it initially seems
As soon as you employ inheritance, you have to start thinking about things like this
Even though Class B now hides Class A’s implementation of DoSomething(), it does notremove it It hides it when calling the method through a B reference on the object However,
in the Main method, you see that you can easily get around this by using implicit conversion
to convert the B instance reference into an A instance reference and then calling the
A.DoSomething() implementation through that So, it’s not gone—it’s just hidden You have
to do a little more work to get to it
Consider if you passed the B instance reference to a method that accepted an A instancereference, similar to the DrawShape() example The B instance reference would be implicitlyconverted to an A instance reference, and if that method called DoSomething() on that Ainstance reference passed to it, it would get to A.DoSomething() rather than B.DoSomething().That’s probably not what the caller of the method would expect
C H A P T E R 6 ■ I N H E R I TA N C E , P O LY M O R P H I S M , A N D E N C A P S U L AT I O N
106
801-6CH06.qxd 3/2/07 8:21 AM Page 106
Trang 2This is a classic example of just because the language allows you to do something doesn’tmean that doing so fosters a good design In fact, when used improperly, it really just adds
unnecessary complexity
Inheritance, Containment, and Delegation
When many people start programming in object-oriented languages, they think inheritance
is the greatest thing since sliced bread In fact, many people consider it an integral, important
part of object-oriented programming Some strongly argue that a language that doesn’t
sup-port inheritance is not an object-oriented language at all However, as time went on, some
astute designers started to notice the pitfalls of inheritance
Choosing Between Interface and Class Inheritance
When you first discover inheritance, you may have a tendency to overuse and abuse it This is
easy to do Misuse can make software designs hard to understand and maintain It can make it
difficult for those designs to adapt to future needs, thus forcing them to be thrown out and
replaced with a completely new design In general, apply a good deal of diligence to your use
of inheritance
For example, when modeling a human-resources system at company XYZ, one nạvedesigner could be inclined to introduce classes such as Payee, BenefitsRecipient, and
Developer Then, using multiple inheritance, he could build or compose a full-time developer,
represented by the class FulltimeDeveloper, by inheriting from all three, as in Figure 6-2
Figure 6-2.Example of bad inheritance
C H A P T E R 6 ■ I N H E R I TA N C E , P O LY M O R P H I S M , A N D E N C A P S U L AT I O N 107
801-6CH06.qxd 3/2/07 8:21 AM Page 107
Trang 3As you can see, this forces the designer to create a new class for contract developers,where the concrete class doesn’t inherit from BenefitsRecipient As the system grows, you willquickly see the flaw in this design when the inheritance lattice becomes complex and deep.Now the designer has two classes for types of developers, thus making the design hard tomanage Figure 6-3 shows an attempt using the same problem with a language that supportsonly single inheritance.
Figure 6-3.Example of bad single-inheritance hierarchy
If you look closely, you can see the ambiguity that is present It’s impossible that theDeveloper class can be derived from both Payee and BenefitsRecipient in an environmentwhere only single inheritance is allowed Because of that, these two hierarchies cannot livewithin the same design You could create two different variants of the Developer class—one forFulltimeDeveloper to derive from, and one for ContractDeveloper to derive from However,code reuse—the main benefit of inheritance—is gone if you have to create two versions ofessentially the same class
A better approach is to have a Developer class that contains various properties that sent these qualities of developers within the company For example, the support of a specificinterface could represent the support of a certain property An inheritance hierarchy that ismultiple levels deep is a good telltale sign that the design needs some rethinking
repre-C H A P T E R 6 ■ I N H E R I TA N C E , P O LY M O R P H I S M , A N D E N C A P S U L AT I O N
108
801-6CH06.qxd 3/2/07 8:21 AM Page 108
Trang 4To see what’s really going on here, let’s take a moment to analyze what inheritance does
for you In reality, it allows you to get a little bit of work for free by inheriting an
implementa-tion There is an important distinction between inheritance and interface implementaimplementa-tion.
Although the object-oriented languages, including VB, typically use a similar syntax for the
two, it’s important to note that classes that implement an interface don’t get any
implementa-tion at all When using inheritance, not only do you inherit the public contract of the class,
but you also inherit the layout, or the guts
A good rule of thumb is that, when your purpose is primarily to inherit a contract, chooseinterface implementation over inheritance This will guarantee that your design has the greatest
flexibility To understand more why that’s the case, let’s investigate more pitfalls of inheritance
Delegation and Composition vs Inheritance
Not only does inheritance have the ability to break encapsulation, but it also increases
cou-pling I’m sure we all agree that encapsulation is the most fundamental and important
object-oriented concept If that’s the case, then why would you want to break it? Yet, any time
you use encapsulation where the base type contains protected fields, you’re cracking the shell
of encapsulation and exposing the internals of the base class Let me explain why it may not
be desirable and what sorts of alternatives you have at your disposal to create better designs
Many describe inheritance as white-box reuse A better form of reuse is black-box reuse,meaning the internals of the object are not exposed to you You can achieve this by using con-
tainment Instead of inheriting your new class from another, you can contain an instance of
the other class in your new class, thus reusing the class of the contained type without cracking
the encapsulation The downside to this technique is that it requires a little more coding work,
but in the end, it can provide a much more adaptable design
As a simple example, consider a problem domain where a class handles some sort of tom network communications Let’s call this class NetworkCommunicator, and let’s say it looks
cus-like this:
Imports System.IO
Public Class NetworkCommunicator
Public obj As MemoryStreamPublic Sub SendData(ByVal obj As MemoryStream)'Send the data over the wire
End SubPublic Function ReceiveData() As MemoryStream'Receive data over the wire
End FunctionEnd Class
Now, let’s say that you come along later and decide it would be nice to have an EncryptedNetworkCommunicator object, where the data transmission is encrypted before
C H A P T E R 6 ■ I N H E R I TA N C E , P O LY M O R P H I S M , A N D E N C A P S U L AT I O N 109
801-6CH06.qxd 3/2/07 8:21 AM Page 109
Trang 5it is sent A common approach would be to derive EncryptedNetworkCommunicator from NetworkCommunicator Then, the implementation would look like this:
Public Class EncryptedNetworkCommunicator
Inherits NetworkCommunicatorPublic Overrides Sub SendData(ByVal obj As MemoryStream)'Encrypt the data
MyBase.SendData(obj)End Sub
Public Overrides Function ReceiveData() As MemoryStreamDim obj As MemoryStream = MyBase.ReceiveData()'Decrypt the data
Return objEnd FunctionEnd Class
There is a major drawback here Good design dictates that if you’re going to modify thefunctionality of the base class methods, you should override them To override them properly,you need to declare them as overridable in the first place This requires you to be able to tellthe future when you design the NetworkCommunicator class and mark the methods as overrid-able Yes, you can hide them using the Shadows keyword when you define the method on thederived class But if you do that, you’re breaking the tenets of the inheritance relationshipmodeling an is-a relationship Now, let’s look at the containment solution:
Public Class EncryptedNetworkCommunicator
Private contained As NetworkCommunicatorPublic Sub New()
contained = New NetworkCommunicator()End Sub
Public Sub SendData(ByVal obj As MemoryStream)'Encrypt the data
contained.SendData(obj)End Sub
Public Function ReceiveData() As MemoryStreamDim obj As MemoryStream = contained.ReceiveData()'Decrypt the data
Return objEnd FunctionEnd Class
C H A P T E R 6 ■ I N H E R I TA N C E , P O LY M O R P H I S M , A N D E N C A P S U L AT I O N
110
801-6CH06.qxd 3/2/07 8:21 AM Page 110
Trang 6As you can see, it’s only a slight more bit of work But the good thing is, you’re able toreuse the NetworkCommunicator as if it were a black box The designer of NetworkCommunicator
could have created the thing sealed, and you would still be able to reuse it Had it been sealed,
you definitely could not have inherited from it
Another downfall of using inheritance is that it is not dynamic in nature It is static by thevery fact that it is determined at compile time This can be very limiting You can remove this
limitation by using containment However, in order to do that, you have to also employ our
good friend, polymorphism By doing so, the contained type can be, say, an interface type
Then, the contained object merely has to support the contract of that interface in order for the
container to reuse it Moreover, you can change this object at run time
Consider an object that represents a container of sortable objects Let’s say that this tainer type comes with a default sort algorithm If you implement this default algorithm as a
con-contained type that you can swap at run time, then if the problem domain required it, you
could replace it with a custom sort algorithm as long as the new sort algorithm object
imple-ments the required interface that the container type expects This technique is known as the
Strategy design pattern
You can see that designs are much more flexible if you favor dynamic rather than staticconstructs This includes favoring containment over inheritance in many reuse cases This
type of reuse is also known as delegation, since the work is delegated to the contained type.
Containment also preserves encapsulation, whereas inheritance breaks encapsulation One
word of caution is in order, though As with just about anything, you can overdo containment
For smaller utility classes, it may not make sense to go to too much effort to favor
contain-ment And in some cases, you need to use inheritance to implement specialization But, in the
grand scheme of things, designs that favor containment over inheritance as a reuse
mecha-nism are more flexible Always respect the power of inheritance, including the damage it can
cause through its misuse
Encapsulation
Arguably, one of the most important concepts in object-oriented programming is that of
encapsulation Encapsulation is the discipline of tightly controlling access to internal object
data and procedures It would be impossible to consider any language that does not support
encapsulation as belonging to the set of object-oriented languages
You always want to follow this basic concept: never define fields as publicly accessible It’s
as simple as that You want the clients of your object to only speak to your object through
con-trolled means This normally means controlling communication to your object via methods
on the object In this way, you treat the internals of the object as if they are inside a black box
No internals are visible to the outside world, and all communications that possibly modify
those internals are done through controlled channels Through encapsulation, you can
engi-neer a design whereby the integrity of the object’s state is never compromised
A simple example is in order In this example, you create a dummy helper object to sent a rectangle The example itself is a tad contrived, but it’s a good one for the sake of
repre-argument because of its minimalist complexity:
Public Class MyRectangle
Public mWidth As UIntegerPublic mHeight As UIntegerEnd Class
C H A P T E R 6 ■ I N H E R I TA N C E , P O LY M O R P H I S M , A N D E N C A P S U L AT I O N 111
801-6CH06.qxd 3/2/07 8:21 AM Page 111
Trang 7You can see a crude example of a custom rectangle class Currently, you’re only interested
in the width and the height of the rectangle Of course, a useful rectangle class for a graphicsengine would contain an origin as well, but for the sake of this example, you’ll only be inter-ested in the width and height So, you declare the two fields for mWidth and mHeight as public.Maybe you do that because you’re in a hurry as you were designing this basic little class But
as you’ll soon see, just a little bit more work up front will provide much greater flexibility.Now, let’s say that time has passed, and you have merrily used your little rectangle classfor many uses Suppose you have some client code that uses your rectangle class and needs tocompute the area of the rectangle Good object-oriented principles guide you to consider thatthe best way to do this is to let the instances of MyRectangle tell the client what their area values are So, let’s create a GetArea method to do this:
Public Class MyRectangle
Public mWidth As UIntegerPublic mHeight As UIntegerPublic Function GetArea() As UIntegerReturn mWidth * mHeight
End FunctionEnd Class
As you can see, you’ve added a new member: the GetArea method When called on aninstance, the trusty MyRectangle will compute the area of the rectangle and return the result.Now, you’ve still just got a basic little rectangle class that has one helper function defined on it
to make clients’ lives a little bit easier if they need to know the area of the rectangle But let’ssuppose you have some sort of reason to precompute the value of the area, so that each timethe GetArea method is called, you don’t have to recompute it every time Maybe you want to
do this because you know, for some reason, that GetArea() will be called many times on thesame instance during its lifetime While early optimization may not be advisable, let’s forgothis concern for the sake of the example The new MyRectangle class could look something like this:
Public Class MyRectangle
Public mWidth As UIntegerPublic mHeight As UIntegerPublic mArea As UIntegerPublic Function GetArea() As UIntegerReturn mArea
End FunctionEnd Class
If you look closely, you can start to see the errors of your ways Notice that all of the fieldsare public This allows the consumer of MyRectangle instances to access the internals of your
C H A P T E R 6 ■ I N H E R I TA N C E , P O LY M O R P H I S M , A N D E N C A P S U L AT I O N
112
801-6CH06.qxd 3/2/07 8:21 AM Page 112
Trang 8rectangle directly What would be the point of providing the GetArea method if the consumer
can simply access the mArea field directly? Well, you say, maybe you should make the mArea
field private That way, clients are forced to call GetArea() to get the area of the rectangle This
is definitely a step in the right direction Let’s do it:
Public Class MyRectangle
Public mWidth As UIntegerPublic mHeight As UIntegerPrivate mArea As UIntegerPublic Function GetArea() As UInteger
If mArea = 0 ThenmArea = mWidth * mHeightEnd If
Return mAreaEnd FunctionEnd Class
You’ve made the mArea field private, forcing the consumer to call GetArea() in order toobtain the area However, in the process, you realized that you have to compute the area of the
rectangle at some point So, you decide to check the value of the mArea field before returning it,and if it’s 0, you assume that you need to compute the area before you return it This is a sim-
plistic attempt at an optimization, as you only compute the area if it is needed Suppose a
consumer of your rectangle instance never needs to know the area of the rectangle Then,
given the previous code, that consumer wouldn’t have to lose the time it takes to compute the
area Of course, in this contrived example, this optimization will most likely be negligible But
if you give it some thought, you can probably come up with an example where it may be
bene-ficial to use this evaluation technique Think about database access across a slow network
where you may need only certain fields in a table at run time Or consider the same database
access object where it’s expensive to compute the number of rows in the table
A glaring problem still exists with the rectangle class Since the mWidth and mHeight fieldsare public, what happens if consumers change one of the values after they’ve called GetArea()
on the instance? Well, then you’ll have a really bad case of inconsistent internals The integrity
of the state of your object would be compromised This is definitely not a good situation to be
in You see the error in the analysis yet again You must make the mWidth and mHeight fields of
your rectangle private as well:
Public Class MyRectangle
Private mWidth As UIntegerPrivate mHeight As UIntegerPrivate mArea As UInteger
C H A P T E R 6 ■ I N H E R I TA N C E , P O LY M O R P H I S M , A N D E N C A P S U L AT I O N 113
801-6CH06.qxd 3/2/07 8:21 AM Page 113
Trang 9Public Property Width() As UIntegerGet
Return mWidthEnd Get
Set(ByVal value As UInteger)Me.mWidth = valueComputeArea()End Set
End PropertyPublic Property Height() As UIntegerGet
Return mHeightEnd Get
Set(ByVal value As UInteger)Me.mHeight = valueComputeArea()End Set
End PropertyPrivate Sub ComputeArea()mArea = mWidth * mHeightEnd Sub
End Class
Now, in the latest incarnation of MyRectangle, you’ve become really wise After making themWidth and mHeight fields private, you realize that the consumer of the objects needs someway to get and set the values of the width and the height That’s where VB properties come in.Internally, you handle the changes to the internal state through a method body and have tightcontrol over access to the internals Along with that control, you achieve the most essentialvalue of encapsulation You can effectively manage the state of the internals so that they neverbecome inconsistent
In the last example, your object knows exactly when the mWidth and mHeight fields change.Therefore, it can take the necessary action to compute the new area If the object had used theapproach of lazy evaluation, such that it contained a cached value of the area computed dur-ing the first call of the mArea property getter, then you would know to invalidate that cachevalue as soon as either of the setters on the mWidth or mHeight properties is called
In essence, a little bit of extra work up front to foster encapsulation goes a long way astime goes on One of the greatest properties of encapsulation is, when used properly, that theobject’s internals can change to support a slightly different algorithm without affecting theconsumers In other words, the interface visible to the consumer does not change Interface-based design patterns help in this regard, too For example, in the final incarnation of theMyRectangle class, the area is computed up front as soon as either of the mWidth or mHeightproperties is set
Once your software is nearing completion, you may run a profiler and determine thatcomputing the area early is really sapping the life out of the processor as your program runs
No problem You can change the model to use a cached area value that is only computedwhen first needed, and because you followed the tenets of encapsulation, the consumers of
C H A P T E R 6 ■ I N H E R I TA N C E , P O LY M O R P H I S M , A N D E N C A P S U L AT I O N
114
801-6CH06.qxd 3/2/07 8:21 AM Page 114
Trang 10your objects don’t even need to know about it They don’t even know a change internal to the
object occurred That’s the power of encapsulation When the internal implementation of an
object can change, and the clients that use it don’t have to change, then you know
encapsula-tion is working as it should
■ Note Encapsulation helps you achieve the age-old guideline of strong cohesion of objects with weak
coupling between objects
Summary
In this chapter, we spent some time discussing inheritance, polymorphism, and
encapsula-tion Other discussion points included member accessibility and hiding We then offered someideas regarding delegation and containment, along with some pointers for choosing when to
use them Finally, the section on encapsulation demonstrated ways to tightly control access to
object state and methods
This leads well to the next chapter, where we’ll cover the important topic of based, or contract-based, programming
interface-C H A P T E R 6 ■ I N H E R I TA N C E , P O LY M O R P H I S M , A N D E N C A P S U L AT I O N 115
801-6CH06.qxd 3/2/07 8:21 AM Page 115
Trang 12An interface defines a contract between components A contract, when applied to a type,
imposes a set of requirements on that type Typically, this means a set of methods and
proper-ties that any type implementing the interface is guaranteed to provide But, contracts aren’t
the only thing interfaces provide Since Visual Basic (VB) (wisely) doesn’t support inheritance
from multiple types but does allow types to implement multiple interfaces, interfaces are the
major foundation for polymorphic programming
Interfaces Are Reference Types
An interface defines a reference type, but, unlike classes, interfaces cannot be instantiated
Classes and structures implement interfaces—that is, they define the methods and other
members that form the contract defined by the interface Variables of an interface type can
hold a reference to any object that implements the interface
Take a look at the following artificial but nonetheless typical pattern:
Public Interface IUIControl
Sub Paint()End Interface
Public Class Button
Implements IUIControlPublic Sub Paint() Implements IUIControl.Paint'Paint the Button
End SubEnd Class
Public Class ListBox
Implements IUIControlPublic Sub Paint() Implements IUIControl.Paint'Paint the Listbox
End SubEnd Class
117
C H A P T E R 7
801-6CH07.qxd 2/28/07 12:20 AM Page 117
Trang 13This example declares an interface named IUIControl that simply exposes one method,Paint This interface defines a contract, which states that any type that implements this inter-face must implement the Paint method
Since the classes ListBox and Button implement the interface, you can treat them both
as of type IUIControl You can store any instance of either Button or ListBox in a variabledeclared as IUIControl The references to objects of these class types are implicitly convertible
to the IUIControl type However, to convert an IUIControl reference back into a ListBox orButton reference requires an explicit conversion, and that coercion will fail if the objectpointed to by the IUIControl reference is not of the type specified by the conversion
■ Note It’s useful to name methods according to both the action they perform and where the action
is directed For example, suppose the IUIControl.Paintmethod takes a Graphicsobject as a
parameter telling it where to paint itself The code is more readable if the method is named
IUIControl.PaintSelfTo() This way, the method call sort of reads like a spoken language in the sense that a method call that looks like control.PaintSelfTo( myGraphicsObject )is saying,
“control, please paint yourself to myGraphicsObject.”
Defining Interfaces
Interface declarations are similar to class declarations, but interfaces cannot declare fields,and they can only declare, but cannot implement, other members For example, in the following code
Interface IUIControl
Sub Paint()End Interface
IUIControl has only one member, the method Paint, and it uses only a Sub statement todeclare it, without providing a method body or an End Sub statement
Interfaces, like classes, default to Friend accessibility in a namespace, but you can alsodeclare them Public Within classes, modules, interfaces, and structures, they default to Public, but they can also be Friend, Protected, or Private Interface members are implicitlyPublic and may not have access modifiers
■ Note By convention, interface names start with I
Let’s code a (very) trivial interface to get familiar with how you declare and use interfaces:Interface ITrivial
End Interface
C H A P T E R 7 ■ I N T E R FA C E S
118
801-6CH07.qxd 2/28/07 12:20 AM Page 118
Trang 14Class A
Implements ITrivialEnd Class
Class B
Implements ITrivialEnd Class
Public Class EntryPoint
Shared Sub Main()Dim ca As ITrivial = New ADim cb As ITrivial = New BEnd Sub
End Class
ITrivial is as trivial as an interface can get Class A and Class B both implement it, buttrivially, since it has no members to implement However, by implementing ITrivial, you can
store instances of both Class A and Class B in ITrivial variables This is an example of how
interfaces support polymorphism Now let’s code a less trivial interface and see how classes
implement interface members:
Interface INonTrivial
Sub SomeMethod()End Interface
Class A
Implements INonTrivialPublic Sub SomeMethod() Implements INonTrivial.SomeMethodConsole.WriteLine("Class A doing something.")
End SubEnd Class
Class B
Implements INonTrivialPublic Sub SomeMethod() Implements INonTrivial.SomeMethodConsole.WriteLine("Class B doing something.")
End SubEnd Class
Public Class EntryPoint
Shared Sub Main()Dim ca As INonTrivial = New Aca.SomeMethod()
Dim cb As INonTrivial = New Bcb.SomeMethod()
End SubEnd Class
C H A P T E R 7 ■ I N T E R FA C E S 119
801-6CH07.qxd 2/28/07 12:20 AM Page 119
Trang 15The following shows the output of this program:
Class A doing something
Class B doing something
In the example, each class implements INonTrivial’s SomeMethod() to display a different
string Note that each class uses an Implements statement
Implements INonTrivial
to specify that it’s implementing the INonTrivial interface, and uses an Implements clause
Public Sub SomeMethod() Implements INonTrivial.SomeMethod
to specify the interface member being implemented This works thanks to declarative mapping With declarative mapping, the contract is tested via Implements
INonTrivial.SomeMethod, and you’re not reliant on the method name
■ Note Although you can use interfaces without members, the best practice is to use attributes to indicatethat types support a specific feature that doesn’t actually require implementation On the other hand, inter-faces with only one method are common, and several important NET Base Class Library (BCL) interfaces,ICloneable,IComparable,IDisposable, andIFormattable, have only a single method as a member
What Can Be in an Interface?
Interfaces may have methods, properties, events, and nested types (interfaces, classes, andstructures) as members Interfaces may inherit from one or more other interfaces Here’s anexample of some things you can declare in an interface:
Public Interface IMyDatabase
Inherits IDisposable, ICloneable'Method with no return typeSub Insert(ByVal element As Object)'Method with a return type
Function Retrieve(ByVal element As Object) As Object'Property
Property Count() As Integer'Event
Event DBEvent()End Interface
C H A P T E R 7 ■ I N T E R FA C E S
120
801-6CH07.qxd 2/28/07 12:20 AM Page 120
Trang 16In this example, IMyDatabase declares two methods, a property, and an event, all of whichmust be implemented by any type that implements the interface It inherits IDisposable and
ICloneable So, any type that implements IMyDatabase must also implement the Dispose
method of IDisposable and the Clone method of ICloneable
Interface Inheritance
As mentioned previously, interfaces support inheritance from multiple interfaces in the
syntactic sense, such as in the following code:
Public Interface IEditBox
Sub Edit()End Interface
Public Interface IDropList
Sub DropDown()End Interface
Public Interface IUIControl
Inherits IEditBox, IDropListSub Paint()
End Interface
Public Class ComboBox
Implements IUIControlSub Edit() Implements IUIControl.Edit'Edit
End SubSub DropDown() Implements IUIControl.DropDown'Drop down
End SubSub Paint() Implements IUIControl.Paint'Paint
End SubEnd Class
In the previous example, IUIControl inherits from two interfaces, IEditBox andIDropList The ComboBox class, though it explicitly declares that it implements only
IUIControl, must also implement all the methods of the interfaces that IUIControl inherits
C H A P T E R 7 ■ I N T E R FA C E S 121
801-6CH07.qxd 2/28/07 12:20 AM Page 121
Trang 17Implementing Multiple Interfaces
Classes can also implement multiple interfaces, as in the following example:
Public Interface IUIControl
Sub Paint()End Interface
Public Interface IEditBox
Inherits IUIControlSub Edit()
End Interface
Public Interface IDropList
Inherits IUIControlSub DropDown()End Interface
Public Class ComboBox
Implements IEditBox, IDropListSub Edit() Implements IEditBox.Edit'Edit implementation
End SubSub DropDown() Implements IDropList.DropDown'Drop down implementation
End SubPublic Sub Paint() Implements IDropList.Paint'Paint implementation
End SubEnd Class
In this example, the ComboBox class implements both IEditBox and IDropList, and eachinherits Paint() from IUIControl Since ComboBox implements both of these interfaces, it mustimplement all the methods in them, plus the Paint method they inherit Note that althoughboth IEditBox and IDropList inherit Paint() from IUIControl, ComboBox only needs to imple-ment it once It also could have used either
Sub Paint() Implements IEditBox.Paint
or
Sub Paint() Implements IUIControl.Paint
instead, and since ComboBox has only one implementation of the Paint method, if you were tocast a ComboBox instance into either an IEditBox or IDropList variable, then calling Paint() oneither variable would call the same implementation
C H A P T E R 7 ■ I N T E R FA C E S
122
801-6CH07.qxd 2/28/07 12:20 AM Page 122
Trang 18Hiding Interface Members
Sometimes—albeit rarely—you need to declare a method in an interface that hides a method in
an inherited interface You use the Overloads modifier to do so For example, if IDropList needs
its own version of Paint(), you’ll have to change both IDropList and ComboBox as follows:
Public Interface IUIControl
Sub Paint()End Interface
Public Interface IEditBox
Inherits IUIControlSub Edit()
End Interface
Public Interface IDropList
Inherits IUIControlOverloads Sub Paint()Sub DropDown()End Interface
Public Class ComboBox
Implements IEditBox, IDropListSub Edit() Implements IEditBox.Edit'Edit implementation
End SubSub DropDown() Implements IDropList.DropDown'Drop down implementation
End SubPublic Sub Paint() Implements IEditBox.Paint'Paint implementation
End SubPublic Sub DropPaint() Implements IDropList.Paint'Paint DropList
End SubEnd Class
In this example, Overloads Sub Paint() has been added to IDropList, allowing it to hidethe Paint method inherited from IUIControl Next, the new Paint() is implemented via the
DropPaint method in ComboBox Note that IUIControl.Paint() must still be implemented,
since it’s part of IEditBox, and that either IUIContol or IEditBox could have been used as the
qualifier
C H A P T E R 7 ■ I N T E R FA C E S 123
801-6CH07.qxd 2/28/07 12:20 AM Page 123
Trang 19Implementing Interfaces in Structures
So far, you’ve seen how to implement interfaces in classes, because this is by far the most cal case, but it’s possible to implement them in structures as well Let’s define two structures
typi-to represent integers and doubles and provide them with an interface that guarantees you canraise them to an integer power:
Public Interface IPowerable
Function RaiseToN(ByVal n As Integer)End Interface
Public Structure AnInteger
Implements IPowerablePublic i As IntegerPublic Sub New(ByVal i As Integer)Me.i = i
End SubPublic Function RaiseToN(ByVal n As Integer) As Object _Implements IPowerable.RaiseToN
Return i ^ nEnd FunctionEnd Structure
Public Structure ADouble
Implements IPowerablePublic d As DoublePublic Sub New(ByVal d As Double)Me.d = d
End SubPublic Function RaiseToN(ByVal n As Integer) As Object _Implements IPowerable.RaiseToN
Return d ^ nEnd FunctionEnd Structure
Public Class EntryPoint
Shared Sub main()Dim i As AnInteger = New AnInteger(2)Dim d As ADouble = New ADouble(2.1)
C H A P T E R 7 ■ I N T E R FA C E S
124
801-6CH07.qxd 2/28/07 12:20 AM Page 124
Trang 20Console.WriteLine(i.i & " cubed is " & i.RaiseToN(3))Console.WriteLine(d.d & " squared is " & d.RaiseToN(2))End Sub
to RaiseToN(), one for each object instance created
Beware of Side Effects of Value Types Implementing Interfaces
As you’ve just seen, structures can implement interfaces However, structures are value types,
not reference types, so you’ll incur a boxing penalty if you cast a value type to an interface
type or vice versa Also, if you modify the value via the interface reference, you’re modifying
the boxed copy and not the original
To expand on the example, consider the primitive type Integer, which is really the valuetype System.Int32 It’s one of the most basic types in the common language runtime (CLR)
You may or may not have noticed that it implements several interfaces: IComparable,
IFormattable, IConvertible, IComparable(Of Integer), and IEquatable(Of Integer)
IConvertible has 17 methods; however, none of them are part of the public contract of
System.Int32 If you want to call one of the IConvertible methods, you must first cast your
Int32 value type to an IConvertible Of course, since interface-typed variables are of reference
type, you must box the Int32 value type
Using Generics with Interfaces
We don’t cover the topic of generics in detail until Chapter 13, because it’s much easier to
discuss it after you’ve learned more VB, but here are a couple of simple examples of using
generics with interfaces, to suggest some of their possibilities
Using a Generic Interface
Interfaces can be generic—that is, they can provide one or more type parameters that are filled
by type arguments when the interface is used Take a look at this example:
Option Strict Off
Interface IGeneric(Of T)
Sub SomeMethod(ByVal x As T)End Interface
Class A
C H A P T E R 7 ■ I N T E R FA C E S 125
801-6CH07.qxd 2/28/07 12:20 AM Page 125
Trang 21Implements IGeneric(Of Integer)Public Sub SomeMethod(ByVal x As Integer) _Implements IGeneric(Of Integer).SomeMethodConsole.WriteLine("A.SomeMethod received " + x.ToString())End Sub
End Class
Class B
Implements IGeneric(Of Double)Public Sub SomeMethod(ByVal x As Double) _Implements IGeneric(Of Double).SomeMethodConsole.WriteLine("B.SomeMethod received " + x.ToString())End Sub
End Class
Public Class EntryPoint
Shared Sub Main()Dim ca As IGeneric(Of Integer) = New A()Dim cb As IGeneric(Of Double) = New B()ca.SomeMethod(123.456)
cb.SomeMethod(123.456)End Sub
a Double to be passed to the Integer method
Using a Generic Method in an Interface
Interfaces don’t have to be generic to have generic members Take a look at this example:Option Strict On