1. Trang chủ
  2. » Công Nghệ Thông Tin

Visual Basic .NET The Complete Reference phần 6 pps

67 220 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 67
Dung lượng 299,16 KB

Nội dung

Interfaces can and should be used for developing classes in situations where inheritance of implementation from base or parent classes is not required. Class hierarchies, is−a relationships, and interface declaration and implementation serve very different design requirements. • Interfaces can and should be used in situations that call for interface implementation. A good example is the Structure (Value Types) that cannot inherit implementation via standard class inheritance (see Chapter 9). • Interfaces should not be used as a substitute for the lack of multiple inheritance. That's not the premise for their inclusion in the .NET Framework, or any other object−oriented framework. Multiple inheritance does not have its place in the .NET Framework; interfaces do and have nothing to do with multiple inheritance. This is further discussed in this chapter. Also see "Inheritance and Multiple Inheritance" in Chapter 9. • Interfaces are great for structure and formality in a classgreat tools for class providers to force conformance by class consumers. If your classes seem like badly packed, jumbled up, holiday suitcases (the whole family's togs tossed into one bag), then it's in need of the structuring that interfaces provide. • Finally, and most importantly, interfaces allow you to delegate. This is probably their most advanced and useful purpose. By being able to collaborate with an interface that is a bridge to an autonomous class that implements the interface, the client to the interface has a way of targeting the implementation. By being totally decoupled from the class that references the interface and the class that implements it, the client class can use an interface to delegate functionality to classes specially designed to play certain roles for them. In other words, rather than working under is−a relationships, classes collaborate on the basis of being able to play the role of something else. And the interface, a form of delegate or agent, is the middle person in the relationship. This concept is discussed in detail in the next few chapters and is the basis for .NET event handling. So don't worry now if you did not understand a word of what I have just said. • Abstract Class or Explicit Interface When do you use an interface and when do you use an abstract class? Understanding the difference between the two is important and can be confusing because both declare abstract members and they cannot be instantiated. To recap from the last chapter: An abstract class is a class that cannot be instantiated, but must be inherited from. In this regard, all abstract classes are declared with the MustInherit modifier. The main difference in terms of implementation is that an abstract class may be fully or partially implemented in its declaration space, while the formal interface class cannot be implemented in its declaration space in any way, shape, or form. Abstract classes form the basis for inheritance hierarchies and, as such, they are usually partially implemented. The abstract class thus serves as the basis for encapsulating common functionality for inherited classes. Object is the ultimate abstract class. Classes can inherit from only one base class, and while you can provide polymorphism by deriving from the base class, all classes that need access to that polymorphism must inherit, directly or indirectly, from your abstract class. This can be a limiting factor in the design and layout of your application. A positive aspect of abstract classes is that, through inheritance and the implementation of the members, all deriving classes gain access to the same functionality. Interfaces cannot provide this facility, and the consumer of the interface must manually provide the access to the implementing or referencing objects. While I strongly believe that interface implementation and class inheritance capabilities have distinct rolesand I intend to expose the differences later in this chapterhere are some recommendations for choosing between the two constructs (assuming polymorphism rather than classification is the objective): Abstract Class or Explicit Interface 321 Abstract classes provide a simple and easy way to version all deriving classes. By updating the base class, all the inheriting classes get automatically updated. This is not possible with interfaces, as you will see in this chapter. • If you need to create functionality that will be used by a wide range of unrelated objects, then the interface is your choice. As discussed in Chapter 9 (and often repeated in this chapter), abstract classes provide the base class for classes of objects that are closely related. Interfaces are best suited for providing common functionality to unrelated classes and are not a substitute for the lack of multiple inheritance. If you fully understand inheritance, as discussed in the previous chapter, then this distinction will be clear to you. • As mentioned earlier, interfaces are great for targeting functionality that resides in other classes and that can be used in a wide variety of places, even in abstract classes in which the implementation can be inherited, and the polymorphism thus propagated. Note Chapter 9 provides extensive coverage of abstract classes, for further reference. • An Introduction to Interface Design and Implementation The interface can be explicitly declared with the Interface class as demonstrated in the following code. (Don't worry about copying any of this code; just follow along and try to understand what is taking place. Later in this chapter, we'll go through creating and implementing an interface step by step.) 'The IMyDays Interface declaration Public Interface IMyDays 'The method definition or signature declared in the interface Function SetDay(ByVal day As Integer) As String End Interface Note As shown earlier the implementation of IMyDays interface makes use of the DaysEnum enumeration that we constructed in Chapter 8. Great code reuse, huh! No implementation is allowed in the preceding class. As you can see, you are not allowed to provide the terminating End keyword for the method, such as End Function. The interface is then implicitly implemented in another class (often called the concrete class) in the following fashion: Class MyDayManipulator Implements ImyDay Sub New() End Sub Function SetDay(ByVal Day As Integer) As String Implements IMyDays.SetDay Select Case Day Case 1 Return "Monday" Case 2 Return "Tuesday" Case 3 Return "Wednesday" Case 4 Return "Thursday" Case 5 Return "Friday" Case 6 Return "Saturday" Case 7 An Introduction to Interface Design and Implementation 322 Return "Sunday" Case Else Return "No such day matches the number" End Select End Function End Class What you are seeing here is a loose coupling of classes, which can be better visualized with the graphic in Figure 10−3 (which, as you can see, is very similar to Figure 10−2). Figure 10−3: Loosely coupled modules separating interface from implementation Note Members of an interface are public by design, and access modifiers are invalid in the interface class. You can now access the implemented method by simply sending a message to the MyDayManipulator object, as follows: Class MyDay Dim mydayman As New MyDayManipulator Function GetDay(ByVal Day as Integer) As String GetDay = mydayman.SetDay(Days.Tuesday) 'GetDay is now given the string value of day 3 and you _ 'can now do something with it End Sub End Class Many classes in the .NET Framework implement classes in this fashion. Examples include implementation of the IClonable interface, implemented in many classes, IComparer, and IEnumerator. Depending on your design requirements, the preceding style of referencing the interface can be a little inelegant and rigid because you are referencing, from a logical point of view, the object rather than the interface. However, you can first create a reference variable of the actual interface and then "plug" it onto any object that implements the interface. .NET lets you do that, and thus you can reference the interface anywhere in your code and reuse any object that provides the implementation, where appropriate. The illustration demonstrates this idea. An Introduction to Interface Design and Implementation 323 Code to reference the interface rather than the implementing object can be written as follows: Imports Vb7cr.Interfaces Public Class InterfaceTester Shared Sub Main() 'instantiate the object that implements the interface Dim daymanipulator As New MyDayManipulator() 'create the reference variable of the interface Dim Day As IMyDay = daymanipulator 'reference the interface rather than the object Console.WriteLine(Day.SetDay(Days.Friday)) Console.ReadLine() End Sub End Class The InterfaceTester code writes Friday to the console. Many classes in the .NET Framework implement interfaces. You can then use the implementing classes by referencing variables of the interface as just described. This is known as interface indirection and it is discussed further later in this chapter and in the chapters ahead. Accessing and Using the Implementation How do you thus use the explicit interface? There are several implementation access styles that point the way in terms of fundamental design. We looked at these styles earlier, but they are worth repeating. The first style to discuss, because it is the most often used, directs that the class that needs access to the implementation of an interface explicitly implement the interface (in other words, the class that implements the interface is also the class that requires direct access to the implementation). A good example of this is the implementation of the IClonable interface in many classes, even in the base class of a hierarchy. While abstraction and polymorphism are still served here, because the interface exists separately from the implementation, this style may clutter the container class, especially if the class implements more than one interface, which it can. For example, if your class Trajectory inherits from class Astro and then implements IWormHoleTransform, IEngageWarp, IHyperJump, IBendSpace, and so on, life in the class can become very crowded. This interface crowding is illustrated in Figure 10−4. Accessing and Using the Implementation 324 Figure 10−4: Implementing the interface in the class that needs it Interface crowding can also produce problems of method signature clashing. In other words, ambiguities arise because you might attempt to implement two interfaces that declare the same method signature. So, one of the methods cannot be implemented. You can't simply expect to overload all method signatures either, because some interfaces might forbid it. The second style suggests you implement the interface in a separate class file and provide access to the implementation via an interface bridge. This is also known as interface indirection. This is demonstrated in Figure 10−5. Figure 10−5: Implementing the interface in another class for access by indirection Granted, you might still get ambiguities because you could easily reference more than one class that declares the identical method, but the signature clashing is more easily overcome through fully qualifying the method through the class's namespace (see Chapters 4 and 9). To access the implementation, simply create a reference to the interface and assign it to an implementing object. This is an elegant means of accessing implementation that I like a lot. You can also pass interfaces as arguments to method parameters. An example of this style is presented later in this chapter, in the section "Implementing IComparable." Compound Interfaces Using interface inheritance, multiple interfaces can be merged to form a single compound interface class. In other words, you can create a base interface class and then create a child interface that derives from it. A third−generation interface can also inherit from the second generation and thus inherit the interfaces from both its parent and grandparent, forming what is called a compound interface or an interface hierarchy. The resulting interface can then be implemented in either of the two kinds of implementation styles discussed earlier. Here is an example of the compound interface: Public Interface ICompare Function Compare(ByRef ObjA As Object,_ ByRef ObjB As Object) As Boolean End Interface Public Interface IEncrypt : Inherits ICompare Function Encrypt(ByVal Value As String) As Object End Interface Public Interface IIterator : Inherits IEncrypt Function GetNext() As Object End Interface The interface IIterator in the preceding example has inherited the definitions of both ICompare and IEncrypt. Compound Interfaces 325 Interfaces thus support multiple inheritance, meaning one interface can inherit multiple interfaces to form one compound interface. Have a look at the following example, which does the same thing as the previous codeit's just a cleaner approach to coupling the interfaces: Public Interface IIterator : Inherits IEncrypt, ICompare Function GetNext() As Object End Interface With such flexibility, it is unavoidable to end up inheriting an interface more than once. In the preceding code, IIterator actually inherits the ICompare interface twice, once through IEncrypt, which originally inherited ICompare, and a second time by implicitly inheriting ICompare. However, the compiler lets this go because it only sees one ICompare in the hierarchy, no matter how many references there are to it. The illustration shows how Visual Studio automatically enumerates and merges the inherited interface members, but it does not duplicate the definitions inherited twice. If you implement an interface, you also have your side of the deal to fulfill. The contract requires you or your client to implement the entire interface, and every aspect of the interface as it is defined. So, compounding the interfaces or inheriting multiple interfaces also has its downside, as demonstrated in the following code, which requires you to implement the methods of all interfaces inherited by the interface you are implementing: Public Class Iterator : Implements IIterator Private Function Compare(ByRef ObjA As Object, ByRef ObjB As Object) _ As Boolean Implements IIterator.Compare End Function Function Encrypt(ByVal Value As String) As Object _ Implements IIterator.Encrypt End Function Function GetNext(ByRef ObjA As Object, _ ByRef ObjB As Object) As Boolean _ Implements IIterator.GetNext End Function End Class What is clear in this code is that if you implement the compound interface, you must implement the entire compound interface, even if you only need to implement one method specified. If you do not need the whole shebang, rather implement a single interface that contains only the definitions you need. Another technique is to use an adapter classa proxy interface that adapts another interface for accesswhich is discussed in Chapter 14. Or, you can place all the interfacescompound, inheriting, or otherwiseinto an abstract class, and then inherit that abstract class. The abstract class lets you implement only what you need to, although you lose the only implementation inheritance "lifeline" you have in the process, which would be a waste and considered bad design. Compound Interfaces 326 Designing and Defining Interfaces Designing an interface is known as interface factoring. This factoring is the process of deciding what properties, events, and methods are to be included in a certain interface. When you design or at least construct an interface, it is important to know what the interface is intended to hide and the abstraction behind that interface. You should thus make sure that each interface abstracts a single tightly focused implementation. When you start cluttering up the interface with unrelated methods and properties, you obscure the abstraction domains and place unnecessary burden on the implementor. Thus, if you are going to design an interface for a collection of encryption methods, it makes no sense to "toss" the definition for comparing objects, or disposing of objects, into the same interface just because you don't feel like creating another interface project. While you should take care to assemble definitions of a common purpose in one interface and not confuse the interface, at the same time, you should not split up an interface into too many related components. For example, splitting a collection of ten financial methods for an accounting interface into two interfaces, one for debits and one for credits, would be silly. They are better packed into one clean interface. It is best to start small and get the interfaces up and running quickly. The interface can then evolve, because you can add more definitions and interfaces as needed. Tip Convention calls for giving the interface an initial capped letter I. This is not necessary but it helps you and your consumers to distinguish interfaces from the classes that implement them. The initial−capped I is the convention used by Microsoft for interfaces published in the .NET Framework (for example, ICloneable, IComparer, and so on). Interfaces, Once Published, Must Not Change As you know from the previous code examples demonstrated, for interfaces to succeed, they cannot change. In other words, the abstract definition encapsulated by the interface class must always remain the same. If the definition of a method, property, or event must change, you must create a new interface definition or member, leaving the old one in place for backward compatibilityfor consumers that depend on it and that have implemented it, directly or indirectly. Once your interface has been published, there is no telling who has implemented it and where. Changing the interface while other classes have already implemented it breaks software. Implementation thus depends on the interface to remain constant. It is thus often said that an interface must obey an unseen "contract" it has with the consumer or implementor, a contract that must be maintained because the consumer has implemented the interface on the trust that it will not change. This is known as interface invariance. Note You can't simply add more methods to an existing interface and hope your interface consumers will not notice. Remember, the consumer or contractee must implement the entire interface. If you add a method, the consumer will be forced to implement that method the next time he or she compiles. Interface Invariance Interface invariance protects existing software that has been written to use the interface. So, when you clearly need to add new definitions or alter an existing definition, a new interface should be created instead. The best way of publishing the interface is to give it the same name and add a version number onto the end of the name Designing and Defining Interfaces 327 (and then properly document the changes and additions). So, the IEncrypt interface mentioned earlier might be published as IEncrypt2 or something similar that clearly identifies it as a new version of an interface that already exists. Constructing the Interface You declare or define the interface within the Interface and End Interface keywords as demonstrated with the following formal syntax: InterfaceDeclaration ::= [ Attributes ] [ InterfaceModifier+ ] Interface Identifier LineTerminator [ InterfaceBases+ ] [ InterfaceMemberDeclaration+ ] End Interface LineTerminator InterfaceModifier ::= AccessModifier | Shadows A simple code equivalent is written like this: Public Interface IFace End Interface You can also add the optional Inherits statement after the interface identifier to list one or more inherited interfaces, as demonstrated earlier in this chapter. The Inherits keyword can be on a new line after the interface identifier, or you can place it directly after the identifier in front of the new−line colon symbol, as follows: Public Interface IFace Inherits IGullible '. . . End Interface or Public Interface IFace : Inherits IGullible '. . . End Interface The Inherits statements must precede all other elements in the declaration except comments. All remaining declaration statements in the interface definition keywords can then be Event, Sub, Function, and Property. Remember, you cannot add End Sub, End Function, or End Property terminating statements to the interface. Interface statements are public by default and you cannot declare them as being anything else (it would be illogical to hide the interface). However, you can modify the implementation of the interfaces by using any valid member modifier, such as Private, Friend, or Protected Friend (see Chapters 4, 7, 8, and 9), which can carry out any secret design or information hiding needed in the class. There is only one exception: The keyword Shared defines a static class method and is therefore illegal on an interface method. You can also overload the methods and properties on an interface with the Overloads modifier, but using any of the polymorphism Overrides, MustOverride, or Overridable modifiers is illegal. Table 10−1 provides a list of the legal and illegal modifiers for implemented interface members. Constructing the Interface 328 Table 10−1: Legal and Illegal Access and Polymorphism Modifiers in Interfaces and on the Implemented Members Access and Polymorphism Modifiers Legal/Illegal Public Illegal in the interfaces itself Protected, Friend, Protected Friend Legal only for implementations Shared Illegal in interfaces and on implemented members Overrides, MustOverride, Overridable Illegal in interfaces, legal on implemented members Overloads Legal in interfaces and on implemented members Getting Started with the Interface Definition The steps to take to create an interface are straightforward, but there are a few angles to tackle this task. First, you could create the interface definition in the same way you create the abstract class, by just providing the abstract methods and not going into any implementation at all for now. This approach would also allow you to first model the interface in UML with tools like Visio or Rational Rose, which would allow you to write the definitions out to Visual Studio code units. Another approach would be to implement and provide the methods of a standard class and then later decide if it makes sense to extract an interface using the formal Interface class. The latter approach is often how interfaces come into existent, but that does not mean you need to forgo the benefits of modeling the classes in UML first. You will likely first model a standard class and then fully implement it. Then you will decide whether or not the class would benefit from the creation of a formal interface to it (or the provision of adapter interfaces). This is, in fact, how the IIterator interface introduced earlier in this chapter came into existence. I first fully implemented it in the linked list class (discussed in Chapter 13). But it is better served as an interface that can be implemented apart from the list class. I thus made it available for many other forms of collections, data structures such as lists, and so on. It is also worth noting that IIterator is a formal behavioral design pattern documented in the book Design Patterns, mentioned at the beginning of this chapter. The book describes the intent of the pattern as a means to "provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation." My motivation for implementing IIterator external to a LinkedList class was to prevent the class from becoming bloated with the various traversal and list traversal functionality that was going into the IIterator. I noticed that my simple LinkedList class was in fact becoming bogged down with iterator−specific methods that were detracting from the clean implementation of the LinkedList and its data elements encapsulated within (see Chapter 13). The IIterator interface thus allowed me to extract the construct from the LinkedList class and allow it to be used with objects that have other traversal and iteration needs. (By the way, this is also the motivation behind Java's iterator interfacea very clean adaptation of the formal IIterator pattern.) Basically, our objective is this: Define an interface for accessing a collection of objects in a linked list. The iterator has the job of accessing the collection, iterating from one object to the next, forward (in the case of singly linked lists) and backward (in the case of doubly linked lists), and so on. The following list represents the formal requirements of the IIterator object: Getting Started with the Interface Definition 329 The IIterator object must be able to gain access to the list to iterate over.• The IIterator object must be able to obtain a reference to the first item (node) in the list.• The IIterator object must be able to keep track of, or record, the current element.• The IIterator object must be able to keep track of previous elements iterated over.• The IIterator object must know when it has reached the end of the list, the last object.• The IIterator object must be able to communicate to the list object information the list object requires to do its work, such as adding, inserting, removing, and changing data. • Figure 10−6 illustrates the relationships between the LinkedList object, the IIterator interface, and an IIterator implementation (any IIterator implementation). Figure 10−6: The class that instantiates a LinkedList object also instantiates a reference to an IIterator interface in order to access the functionality of the IIterator for the list in question An important objective of creating the IIterator interface is to allow list objects to work with their own IIterator objects. A LinkedList object will therefore reference an IIterator interface and work with a suitable implementation, or create its own IIterator object by implementing the IIterator interface. The following code is an example of the IIterator interface: Public Interface IIterator : Inherits IComparable Function GetNext(ByRef ObjA As Object, _ByRef ObjB As Object) As Boolean Function HasNext() As Boolean_ 'is there a node after the current position Property Current() As Object Sub AddClone() Sub Add(ByRef Obj As Object) Sub Remove() End Interface The next two examples show the two ways a class can reference the interface and instantiate an IIterator object for its use: Imports Vb7cr.Iterator Public Class TreeWalker Dim iterImp As Iterator Public Sub GetObject(ByRef Obj As Object) MyTree.Node = Iter.GetNext(Obj) End Sub End Class Or better yet: Getting Started with the Interface Definition 330 [...]... you find them by expanding the class tree down to the Bases and Interfaces node in the Object Browser tree Selecting the interface in the browser brings up a summary of the interface in the Details pane of the browser Interfaces are also listed higher up the namespaces You can also explore the interfaces that ship with the Framework with the Class Viewer tool, WinCV And they are all listed in the documentation... the reference variable to the exception's object instantiated elsewhere in your application Then, in the method that raises the exception, catch the object using the default exception handler With the exception object "in your mitt," pass it by reference to the exception−handling method in the exceptions class or object delegated to handle the exception Once the reference has been received by the method... by the method in a single default catch handler and then pass the reference to another method that can better handle the exception object To use the baseball analogy again, it's like throwing the ball to the catcher at home plate and then letting the catcher decide where to throw next The pattern is simple as shown in the UML diagram here Create a static class or instance with methods that receive the. .. that can handle the type of exception Each exception object is a reference type, and in the preceding code, Except and E are the reference variables The variable reference model at work here is the same object reference model described in the previous chapter, and you can work with or on any exception object as you would with any other instance What the illustration shows is this: At the point an error... Gets the type of the current object Sets a link to a help file associated with the exception Returns information about the exception caused by the previous exception object Returns any error message that describes the exception Gets or sets the name of the application that caused the exception Takes a snapshot of the call stack and places the information in a string you can access Returns the name of the. .. immediately apparent from the Visual Basic documentation The Double Play: Rethrowing an Existing Exception Imagine, as we did earlier, that the exception is a baseball runner trying hard to ruin your game The defensive players covering the bases are the exception handlers, "trying" hard to "catch" the runner After the first throw from the pitcher, an exception handler (the catcher) picks up the ball and "throws"... (normally) return to the point in the code where the exception was first raised This is how Visual Basic has always worked, even with the old On Error construct The exception kicks you out of the block of code that erupted, nixes the block on you, and forces you to deal with the mess or make a hasty exit If you do handle the exception, control returns to the line immediately after the TryCatch code,... application to another, you can use it to specifically rethrow or reraise the exception, even transfer it out of the original TryCatchFinally block to another method You can also do whatever you need to do to fix the problem that caused the original hiccup and then return to the original method to try again (passing the ball back to the pitcher to have another shot) The exception−handling code and the code... where the programmer can deal with the error That's where the similarity ends The classic Visual Basic error handling, while still supported in Visual Basic NET, is a form of unstructured error handling that did not serve VB programmers well Visual Basic unstructured error handling is not a holistic approach to guarding code and properly dealing with an error Visual Basic 6 and earlier code often suffers... End Sub The preceding method raises an IndexOutOfRangeException exception when it tries to access an element that is out of the upper bound of the mdArray After the exception is handled, the execution continues after the End Try statement and the OnEvent call continues with 1 as the value for the code argument In other words, code did not get changed What if you need to execute the code in the Try . the object rather than the interface. However, you can first create a reference variable of the actual interface and then "plug" it onto any object that implements the interface. .NET. implement the interface (in other words, the class that implements the interface is also the class that requires direct access to the implementation). A good example of this is the implementation of the. 13 provides the complete implementation of the IIterator interface in the class IIterator, and demonstrates its employment with the LinkedList class. The chapter also examines the .NET Frameworks IEnumerator

Ngày đăng: 14/08/2014, 01:20