Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 67 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
67
Dung lượng
271,33 KB
Nội dung
The RemoveAt The IList.RemoveAt method is a variation of the IList.Remove method that can take an Integer to represent the location in the list to remove the node from. This method simply chases up an iterator to land on the node to remove. It makes the target node the CurrentNode and then one of either RemoveFirst, RemoveLast, or RemoveInBetween. Consider the following definition for RemoveAt: Method Name: Remove The method removes a node from the list container at the specifed location.• Method Signature Public Sub RemoveAt(ByVal index As Integer) Implements IList.RemoveAt • Parameters The method takes an argument of type Object representing the data in the node that will be removed • Precondition The method checks the location of the first node representing the data to be removed• Postcondition No postcondition• Exceptions This method throws two exceptions. It will throw an exception of type ArgumentOutOfRangeException when the index specified does not exist in the list (it is thus outside the bounds of the structurethat is, below zero and higher than Count). The Catch handler also writes the exception's message to the exceptInfo field, which is scoped to BaseNodeCollection. It will also throw an exception of type NodeNotFoundException if the index is 1, indicative of a search turning up negative and returning 1. • We can implement RemoveAt as follows: Public Sub RemoveAt(ByVal index As Integer)_ Implements IList.RemoveAt If (index < 0 Or index >= Count) Then Throw New ArgumentOutOfRangeException() End If If (index = Me.Count − 1) Then RemoveFirst() Return 'what if the target is at the end of the list ElseIf index = 0 Then RemoveLast() Return Else 'what if the target is somewhere between first and last Dim myIterator As System.Collections.IEnumerator = Me.GetEnumerator() Dim intI As Integer While intI < index myIterator.MoveNext() intI += 1 End While RemoveInBetween() End If Catch ArgExcept As ArgumentOutOfRangeException exceptinfo = ArgExcept.Message Catch NodeExcept As NodeNotFoundException exceptinfo = NodeExcept.Message End Try End Sub RemoveAt is typically used as follows: List.RemoveAt(0) Implementing the Container 455 Implementing the Iterator IEnumerator's implementation provides the base functionality needed for an iteratora device that moves from one object to the next in a collection. IEnumerable is the proxy interface given the taskthrough exposing of a single member, a methodof returning an iterator (enumerator) for the target collection. The following list describes the interfaces' members and the utility derived from their implementation: Reset Moves the iterator back to its starting position, just before the first node. Calling MoveNext places the iterator at the first position • Current A property that returns the current node (the one the iterator is positioned at). We can use this property to assign CurrentNode • MoveNext Moves the iterator to the next node in the list• While you can implement and work with IEnumerator aloneforgoing implementation of IEnumerableimplementing the GetEnumerator method in your collection is a convenient way to access an IEnumerator without having to permanently couple it to any particular collection object as you can see in the forthcoming sections. Incidentally, the enumerator interfaces are also used to create an iterator that can "walk" a collection of XMLNode objects. The Iterator class can be composed in BaseNodeCollection but there is not much point to doing so. Unlike the Node class, which is part and parcel of a list or tree container, the iterator (or enumerator) is independent enough to stand on its own in a separate class that is implemented at the same level as the BaseNodeCollection class. This will allow you to target the iterator class at other collections because the methods of the Iterator class are simple enough to use with a variety of collection objects that employ Node objects as the elements of their collections. The following represents the base Iterator class. Imports Nodals.BaseNodeCollection Imports Vb7cr Public Class Iterator Implements IEnumerator Private Position As Node Private Previous As Node Private workList As BaseNodeCollection Private iteratorInfo As String Public Sub New(ByRef list As BaseNodeCollection) MyBase.New() workList = list Reset() End Sub Public Sub Reset() Implements IEnumerator.Reset workList.CurrentPosition = Nothing End Sub Function MoveNext() As Boolean Implements IEnumerator.MoveNext Try If workList.Last Is Nothing Then Throw New NodeNotFoundException() End If If (workList.CurrentPosition Is Nothing) Then workList.CurrentPosition = workList.Last Implementing the Iterator 456 Return True Else If (workList.CurrentPosition.NodeNext Is Nothing) Then Reset() Return False End If workList.PreviousNode = workList.CurrentPosition workList.CurrentPosition =_ workList.CurrentPosition.NodeNext Return True End If Catch NExcept As NodeNotFoundException iteratorInfo = "No nodes exist in this container." End Try End Function Public ReadOnly Property Current() _ As Object Implements IEnumerator.Current Get Return workList.CurrentPosition End Get End Property End Class The MoveNext method is the workhorse of this class. With its reference to an instance of BaseNodeCollection, which it receives upon instantiation via its New constructor, it traverses the list by shuffling the nodes into different positionsthe previous node is assigned to the current position and the current node is assigned to the next position and so on. The Reset method is implemented very simply. It just causes the iterator to lose its place in the list. The next time you make a call to MoveNext, the iterator is forced to start from the beginning again. The IEnumerator interface specifies that IEnumerator objects typically scroll in one direction. The iterator shown here starts at the head of the list and proceeds to the tail, going from the last node that was added to the list to the first node that was addedas if the list of nodes is a stack. The current version of the iterator does not support backward scrolling. Reset is also called in the constructor so that the iterator is automatically reset whenever New is called. The last member implemented here is the Current property. It simply returns the Node object assigned to the CurrentPosition. Note that CurrentPosition and CurrentNode both refer to the same thing, only CurrentPosition is the BaseNodeCollection property that accesses the data from the internal and private CurrentNode variable. Note The formal Iterator pattern specifies a CurrentItem method as well as a Next method that is the equivalent of MoveNext. It also supports indexing, which can be easily implemented but is not really a necessity. The following code demonstrates the iterator at work. The method PrintNodesDemo1 makes an iterator using the BaseNodeCollection's GetEnumerator method, while PrintNodeDemo2 does the same thing using the For Each . . . Next construct. Module LinkedListDemo Dim List As New BaseNodeCollection() Sub Main() Implementing the Iterator 457 List.Add("I") List.Add("just") List.Add("love") List.Add("OOP") List.Add("with") List.Add("VB.NET") PrintNodesDemo1() PrintNodesDemo2() End Sub Public Sub PrintNodesDemo1() Dim myIterator As System.Collections.IEnumerator = _ List.GetEnumerator() While myIterator.MoveNext() Console.WriteLine(myIterator.Current.Data.ToString) End While End Sub Public Sub PrintNodesDemo2() Dim element As BaseNodeCollection.Node For Each element In List Console.WriteLine(element.Data) Next End Sub End Module The printout to the console for both cases shown in the code is as follows: I just love OOP with VB.NET Note The code for the BaseNodeCollection, Iterator, and Node classes can be found in the Nodals project in the Vb7cr solution. Observations This chapter extended our discussion of data structures and provided us with some interesting code. But most of all, it showed what's possible with a pure object−oriented language like Visual Basic .NET. We saw many scenarios creating linked lists wherein objects and their interfaces are aggregated into container classes after they have been first defined as composite classes. We also looked at how Visual Basic can adoptand then run withmany of the formal patterns that have emerged to assist OO design and development over the years. Some of these patterns could be represented with classic VB. However, it is the native support for interfaces, polymorphism, encapsulation, and inheritance that makes all of the patterns adopted by languages such as Java and C++ more than applicable to Visual Basic .NET and the .NET Framework. We are going to take this further in the next chapter, where we'll look at patterns for adapting interfaces, delegations, and delegates, as well as some advanced uses of interfaces. Observations 458 Chapter 14: Advanced Interface Patterns: Adapters, Delegates, and Events Overview I have already devoted a significant portion of this book to the subject of implementation inheritance (genericity), composition, and bridging. In the last four chapters, I examined interface implementation extensively. Now I will concentrate on advanced interface patterns, which underpin the .NET Delegate construct and the .NET event model. A number of years ago, I found that Adapter classes and interfaces, Wrapper interfaces, and Delegates were some of the hardest concepts to understand (for all OO students, especially Java and Visual J++ programmers). The .NET Delegate construct is vitally important to .NET programming, in particular, and OO, in general; yet, interfaces and Delegates cause more concern for .NET programmers than any other facet of this extensive framework. Therefore, it is critical for us to acquire a thorough understanding of this material. Many who don't understand how Delegates work have incorrectly attributed a magical status to them. In truth, Delegates are a very simple construct that derives from well−conceived patterns that the OO industry has provided for more than half a decade. No Harry Potter analogy needs to be interjected into discussions of them, as you will see in this chapter. Once you master Delegates, they will come to represent your most powerful toolalongside interfaces and inheritancefor programming polymorphism. You know that interfaces and Delegates are now fully implemented in the Visual Basic language. But, if you can't use their sophisticated utilityregardless of your programming knowledgeyou will not make it to the software−development majors. This chapter is probably the most important one to understand entirelyfor beginning and experienced programmers alike. I hope you will ponder this information until you have completely absorbed it. Interfaces have nothing to do with the implementation inheritance pattern, per se (the principal subject of Chapter 9), but they do have everything to do with polymorphism. .NET interfaces can be completely de−coupled from any of their implementations. Thus, the implementation can be varied on the back end, while the interfaces can be adapted on the front endwithout the two ends being any the wiser. I talked about this de− coupling in earlier chapters, but I did not discuss adapting the interfaces of concrete classes. I will discuss this subject in the current chapter. If you understood the concepts behind interfaces presented in the previous chapters, especially Chapter 10, and realized why the interface architecture of the .NET Framework is so important, then you'll quickly grasp the idea behind interface adaptation, delegation and the Delegate class, and events. Delegates, in fact, are the implementation of a fundamental design pattern that provides the highest form of class de−coupling and class cohesion in a framework. They allow highly sophisticated designs to be applied to .NET applications, and they underpin not only the .NET event model discussed here, but the entire framework itself. This chapter may be somewhat controversial and is written in a style that evokes some emotiona technique that I hope will not only inspire you to read through complex concepts, but also help you to retain them. I am also seeking to promote debate and further your thinking regarding design, code, and choice of constructs to suit your purpose. However, to grasp how Delegates work, it is essential to have an unshakable foundation in interfaces, in general, and interface adaptation, in particular, as well as in the concept of wrapping. Thus, the subject of 459 delegation is allied to the subject of adaptationthe technique whereby you adapt an interface so that another object can use it. Wrapping is the part where an additional class may be needed to translate messages, marshal calls, or convert data−types between clients and servers. The formal pattern names are "Adapter," "Wrapper," and "Delegation." The following short list places these terms in their relevant contexts: Receiver or Server The object or class that contains the implementation and a domain−specific interface. It is the final destination (the implementation of a method) of the call message of a Sender or Client (unless an overriding method intervenes). The Receiver is shown in the following illustration, on the receiving end of the method call (from whence it came does not matter). • Sender or Client The object or class that has an interest in the services or implementation of the Reciever or Server. The Sender is represented here. • Adapter A concrete class or an interface that adapts the interface of a Receiver. Often it may be necessary to do more that adapt interfaces, and an Adapter may have to provide additional functionality to allow access to the Receiver. Often referred to as a Wrapper, it may need to contain code that marshals calls and converts data−types. Thus, it accomplishes much more than simply adapting interfaces. Inner classes, or derivatives of a Receiver, can also play the role of an Adapter or Wrapper (as shown in Figure 14−1). They may also redirect calls to overriding or varying implementation. Figure 14−1: The Adapter class or object adapts the interface of a Receiver • Adaptee The interface to an Adapter, which is exposed to clients. An Adapter (or, less likely, a Receiver) may provide "pluggable" support by "implementing" an Adaptee interface (as shown in Figure 14−2). The implementation of interfaces such as ICollection and IEnumerable, discussed in • Chapter 14: Advanced Interface Patterns: Adapters, Delegates, and Events 460 the previous chapters, are examples of "pluggable" support. Figure 14−2: The Adaptee is the interface to an Adapter Delegate A sophisticated name for a sophisticated Adaptee, which is also a complex and specialized construct in the .NET Framework. However, on the surface it is really nothing more than a specialized interface pointing to a single method at the Receiver. The interrupted method calls from Sender to Delegate and from Delegate to Receiveras illustrated in the illustrationindicate the method call may arrive at the interface indirectly (perhaps from an event in the case of the Delegate interface, and from an Adapter in the case of the Receiver). Note We will add the event definitions to our list later, once we have sorted out these issues. • The next two sections probe the Adapter and Wrapper patterns. You will learn along the way that Adapter interfaces and classes also provide a viable event model, which many programmers vociferously defend. In fact, adapters underpin the Java− Swing event modelone that can certainly be applied to .NET programming and especially to Visual Basic .NET. Adapters and Wrappers The Adapter patternwhich is also known as the Wrapper patternconverts the interface of a class or an object, however coupled, into an interface that clients expect or can use. Adapter and Wrapper classes may make use of Adaptee interfaces to let otherwise incompatible classes and objectsincluding those written in different languages work together or interoperate. For the benefit of clients, you can adapt the interfaces of classes and objects written in the same language. It is the easiest level of adaptation you can implement. You can also adapt interfaces written in other languages that are part of a framework. It is very common in the .NET Framework to allow implementation to be "pluggable" into any languagea practice I will discuss shortly. This intermediate level is a little harder, but it will be something you might find yourself doing often. Finally, you can adapt interfaces of classes that are neither written in the same language nor part of the same framework. These include the classes of independent software−development kits, those of libraries and components, and any class or object that was not otherwise intended for the class or consumer−objects for which they are being adapted. This third level of adaptation is the most complex and most difficult, requiring conversion of data−types and tricky call−marshaling. It necessitates the mettle of an experienced "wrapper" programmer who understands implicitly the source and target languages and the development environments. Adapters and Wrappers 461 A good example of the last level of adaptation is the wrapping of COM interfaces for access by .NET code. This is the interoperation support that provides access to the COM home world, which is still very much developer Prime in Microsoft's part of the galaxy. COM and .NET are as different from each other as coffee is from couscous. COM codeCOM objects and componentsare written in unmanaged languages, such as VB 6, Delphi, Visual J++ (not Java), and (primarily) C++, and their interfaces are registered with the operating system. The .NET components, however, are written in the managed .NET languages like Visual Basic .NET, Visual C# .NET, or Visual J# .NET (pidgin Java for the .NET Framework). As you know, .NET interfaces are not registered with the operating system, and they are executed by the CLR, as directed by metadata. Essentially, COM objects run in one reality while .NET objects run in another. The two realities are parallel in the Windows universe, so you need to connect them via a "wormhole." On each side of the wormhole, you need to adapt the star−gate or jump− gate interfaces, so that the other side can come through in one piece. On the COM side, you'll provide .NET−callable interfaces that .NET clients can understand; on the .NET side, you have COM−callable interfaces for COM clients. With all the investment in COM code still very much at large, it was incumbent upon Microsoft to create adapter and/or wrapper layers for its valuable COM objects. After all, COM is still Microsoft's bread and butter, as Corolla is for Toyota. Regardless of the fanfare surrounding .NET, there are literally millions of COM objects afloat, which means we can't discount COM for a very long time. Consider the ".NET" server technology the company sells. It is primarily composed of COM bits. A favorite example is Index Server, whose COM object is typically programmed against classic ASP pages and VBScript; however, the rapid adoption of Visual Basic .NET and ASP.NET requires that Index Server's objects be exposed to ASP.NET codewhich is programmed in Visual Basic .NET (or any .NET language). This requires wrapper classes specifically aimed at the Index Server's COM interfaces. The next section demonstrates accessing Index Server from ASP.NET to illustrate the seamless integration and interoperation of .NET and the COM world. Another good example is Commerce Server. By and large Commerce Server is nothing more than a comprehensive collection of COM objects. So without adaptation and wrapping of its COM interfaces, it is practically off limits to .NET. Adapter interfaces or Wrappers thus allow Microsoft's flagship e−commerce product to be accessible to its .NET brainchildren. I'll discuss this interop next. Interface Adaptation in ActionCOM− .NET Interop "COM−.NET interop" is accomplished via the .NET Framework's callable wrappers for achieving interoperability between COM servers and .NET clients, and COM clients and .NET servers. The wrapper class that provides access to COM is called a Runtime Callable Wrapper (RCW), and it allows your .NET objects to interoperate with COM servers as if they were .NET managed types. Adaptation is needed because .NET objects cannot call COM directly; they don't know how. The classes "wrap" the COM objects, which are activated (instantiated) in the standard way and programmed against in the manner I've outlined in the past chapters. To expose a .NET object to a COM client, the CLR conversely provides a COM Callable Wrapper (CCW) that adapts the interface of a managed object. This adaptation in the other direction is necessary because COM objects do not know how to reference .NET objects directly. The COM clients and .NET clients thus use the wrappers as a proxy into each other's worlds as shown in Figure 14−3. Interface Adaptation in ActionCOM− .NET Interop 462 Figure 14−3: Bridging the .NET (managed) reality to COM The primary function of the wrappers is to marshal calls between .NET and COM. Manual adaptation, as mentioned earlier, is not an easy task, even for the most accomplished programmer. In the mid−1990s, it took a solo programmer with experience many weeks to adapt ADO (Active Data Objects) interfaces for Borland's Delphi. The CLR adapts COM's ADO in about ten seconds (of course Microsoft took a lot longer to get this level of automatic adaptation down pat). The CLR makes the adaptations for you by automatically generating the wrapper interfaces. It creates a single RCW for each COM object. And by being totally de−coupled, the interface can be referenced by a .NET class or objectirrespective of the number of references that may exist on the COM object. The code on the following page uses the adaptation of Index Server's COM API, which goes by the unusual name of Cisso (meaning unknown). Essentially, the wrapper allows any number of .NET clients to instantiate the "Interop.Cisso" adapter interface. Your applications are unaware that the object behind the interface is really a COM object. This ASP.NET code accesses the Index Server COM components and ADO components with very little effort (and brings the legacy database objects into the modern word of .NET data access): Public Function SearchWithCisso(ByRef searchString As String, _ ByRef rankBase As Integer, _ ByRef catalog As String, _ ByRef sortorder As String, _ ByRef columns As String) As DataSet Try Dim myDA As OleDbDataAdapter = New OleDbDataAdapter() Dim myDS As DataSet = New DataSet() 'This call passes the user's search string to a method 'that prepares it for submission to Index Server cissoQuery.Query = PrepareSearchString(searchString) cissoQuery.Catalog = catalog cissoQuery.SortBy = sortorder cissoQuery.Columns = columns cissoQuery.MaxRecords = rankBase cissoQueryRS = cissoQuery.CreateRecordset("nonsequential") adoRS = cissoQueryRS myDA.Fill(myDS, adoRS, "Results") Return myDS Interface Adaptation in ActionCOM− .NET Interop 463 'no need to close the recordset as required by ADO 'because the GC does this for us 'cissoQueryRS.Close() 'adoRS.Close() Catch Except As Exception Console.WriteLine(Except.Message) End Try End Function Conversely, when you adapt a .NET server interface for a COM client, the runtime creates a single managed CCW for it. Any number of COM clients can reference this interface. As Figure 14−4 shows, multiple COM clients can hold a reference to the CCW that exposes the adapted (essentially new) interface. Figure 14−4 portrays how both COM and .NET clients can reference the same managed object simultaneously. Figure 14−4: Bridging the COM (unmanaged) reality to .NET The primary purpose of these adapter interfaces (interfaced with IAdaptee in the figures) is to marshal calls between managed and unmanaged code. CCWs also control the identity and lifetime of the managed objects they wrap. While .NET objects are allocated on the garbage−collected heap, which enables the runtime to move them around in memory as necessary, the CLR allocates memory for the CCW from a non−collected heapas standard value−types do. This makes it possible for COM clients to reference the wrapper interfaces as they do standard COM objects. Essentially, the COM clients can count the references they have on the CCW objects directly. Thus, when the COM client's count on the CCW reaches zero, it releases its reference on the wrapper, which de−references the managed object. The CLR disposes of the reference while the GC collects the object as part of its normal garbage−collection cycle. From the .NET client's perspective in accessing a COM object, the CLR creates an assembly infused with metadata collected from the COM object's type library. The CLR thus instantiates the COM object being called and produces a wrapper interface for that object. The wrapper maintains a cache of interface pointers on its COM object and releases its reference to it when the RCW is no longer needed. At this point, the runtime invokes standard garbage collection on the wrapper. These adapter constructs (the RCW and the CCW) marshal various things: the data between managed and unmanaged code, as well as method arguments and method return values. They also translate the data between the two worlds whenever differing representations are passed through their interfaces. For example, when a .NET client passes a String in an argument to the CCW, the wrapper converts the String to a BSTR type (a Interface Adaptation in ActionCOM− .NET Interop 464 [...]... care about the classes or the objects they referencethe Adapter or Receiver They can vary their calls to any object at runtime, which satisfies a desire we expressed earlier What matters is that the signature of the Adapter's method matches the signature of the method definition prescribed in the Delegate As in the interface−implementation relationship, the Delegate definition must match the Adapter's... Class What does the Adapter pattern achieve? • The Sender and the Receiver remain completely disinterested in each other's existence It's not a matter of loose coupling; there is no coupling at all because the Sender has no way of accessing the private data and methods of the Receiver In the above examples the TrajectoryConsole makes a reference to the Adapter, which it delegates to If the Adapter implements... invoke on the specified class instance with the specified case−sensitivity The Type parameter represents the type of Delegate to create, the Object parameter represents the Receiver class instance on which method is invoked, the String parameter represents the name of the instance method that the Delegate references, and the Boolean parameter represents True or False, indicating whether to ignore the case... references in the list, and each method is called once for each reference The Delegate also invokes the methods in the order in which they appear in the list, and the Delegate will attempt the invocation each time it is activated To evaluate the invocation list at any time, you can call the GetInvocationList method on the Delegate This method call returns an array which will either contain one method reference. .. regarding the coordinates of the asteroid and can pass this information to the Delegate The Delegate then invokes the method in the weapons systems represented by the Weapons class and fires a laser at the approaching asteroid To cater to this algorithm, the application could expose an AsteroidEnter event or the applications could simply invoke the Delegate object The class that encapsulates the events... of the Receiver and make this derived/composite class the Adapter instead This also works around the issue of having to share (make static) the method in the Receiver, which may not always be convenient or desirable 4 68 The Adapter Pattern in NET The following code now throws inheritance into the mix It shows the Sender object calling the method via the Adaptee's interface but it no longer needs to reference. .. doubles the rate of BubbleSort sorts The more you partition the array the more you speed up the sort The big problem with this algorithm is that the recursion is very tricky This is not a complex method but the more recursive calls we make the harder it is to factor the recursion A complex method demanding recursion can take a long time to get right Also remember the If Then conditional, which acts as the. .. Merge(mergearray) End Sub End Class The PartitionSort method almost concurrently sorts the two parts of the single array using the two Delegates The first Delegate sorts the first half and the second Delegate sorts the second half Lastly, the independent call to the Merge method combines the partitions into one array The QuickSort method has a lot more potential for implementing Delegates First, the QuickSort with... QSortDeld1) The reason behind calling on the CType method is that the method that does the combining must be cast up to the same type of the Delegates being combined If you don't make the CType call a type−mismatch exception is thrown The entire invocation list is ordered like an array, and each element of the list contains exactly one of the methods invoked by the Delegate There can be duplicate method references... interfaces and the Adapters for the Sender to choose how the call should be handled It can pass the reference from the Receiver to a quasi−constructor or initializer in the Adaptee or Adapter and the Adapter can make the decision (via a formal parameter list) The Adapter might also choose to implement additional methods And in the case of newer or alternate versions of the Adapter methods the Adaptee . and their interfaces are registered with the operating system. The .NET components, however, are written in the managed .NET languages like Visual Basic .NET, Visual C# .NET, or Visual J# .NET. the rapid adoption of Visual Basic .NET and ASP .NET requires that Index Server's objects be exposed to ASP .NET codewhich is programmed in Visual Basic .NET (or any .NET language). This requires. Essentially, the COM clients can count the references they have on the CCW objects directly. Thus, when the COM client's count on the CCW reaches zero, it releases its reference on the wrapper,