Adapter Design Pattern The adapter pattern is a design pattern - TopicsExpress



          

Adapter Design Pattern The adapter pattern is a design pattern that is used to allow two incompatible types to communicate. Where one class relies upon a specific interface that is not implemented by another class, the adapter acts as a translator between the two types. What is the Adapter Pattern? The adapter pattern is a Gang of Four design pattern. This is a structural pattern as it defines a manner for creating relationships between classes. The adapter design pattern is used to provide a link between two otherwise incompatible types by wrapping the adaptee with a class that supports the interface required by the client. Lets take an example where we have a personnel system and an intranet solution that displays, amongst other things, a telephone list. It may be that the personnel system includes methods that permit the retrieval of employees, including their name, job title and telephone number. The intranet solution may include a plug-in system that can be used to source this data but the plug-in must provide a specific interface that is not supported by the personnel software. In this case, we can create a new class to be an adapter. This class will provide the intranets desired interface and will hold an object of the type required by the personnel system. When the intranet makes a request, this request can be passed to the correct method of the personnel system. The response from the personnel system will then be translated to a format that can be used by the intranet. Implementing the Adapter Pattern The UML class diagram above describes an implementation of the adapter design pattern. The items in the diagram are described below: • Client. The client class is that which requires the use of an incompatible type. It expects to interact with a type that implements the ITarget interface. However, the class that we wish it to use is the incompatible Adaptee. • ITarget. This is the expected interface for the client class. Although shown in the diagram as an interface, it may be a class that the adapter inherits. If a class is used, the adapter must override its members. • Adaptee. This class contains the functionality that is required by the client. However, its interface is not compatible with that which is expected. • Adapter. This class provides the link between the incompatible Client and Adaptee classes. The adapter implements the ITarget interface and contains a private instance of the Adaptee class. When the client executes MethodA on the ITarget interface, MethodA in the adapter translates this request to a call to MethodB on the internal Adaptee instance. The following shows the basic code of the adapter design pattern implemented using C#. In this case, the object that implements ITarget is passed to the clients constructor. However, it could quite easily be provided as a parameter of a method. public class Client{ private ITarget _target; public Client(ITarget target) { _target = target; } public void MakeRequest() { _target.MethodA(); } } public interface ITarget { void MethodA();} public class Adaptee { public void MethodB() { Console.WriteLine(MethodB called); } } public class Adapter : ITarget { Adaptee _adaptee = new Adaptee(); public void MethodA() { _adaptee.MethodB(); }} Adapter Pattern Role The Adapter pattern enables a system to use classes whose interfaces dont quite match its requirements. It is especially useful for off-the-shelf code, for toolkits, and for libraries. Many examples of the Adapter pattern involve input/output because that is one domain that is constantly changing. For example, programs written in the 1980s will have very different user interfaces from those written in the 2000s. Being able to adapt those parts of the system to new hardware facilities would be much more cost effective than rewriting them. Toolkits also need adapters. Although they are designed for reuse, not all applications will want to use the interfaces that toolkits provide; some might prefer to stick to a well-known, domain-specific interface. In such cases, the adapter can accept calls from the application and transform them into calls on toolkit methods. Illustration Our illustration of the Adapter pattern is a very real one-it involves hardware instruction sets, not input/output. From 1996 to 2006, Apple Macintosh computers ran on the PowerPC processor. The operating system was Mac OS X. But in April 2006, Apple started releasing all new Apple computers-iMacs, Minis, and MacBooks-with Intel Core Duo processors. Mac OS X was rewritten to target the new processor, and users of the new computers mostly accessed existing Intel-based software via other operating systems, such as Linux and Windows. Figure 4.1, Adapter pattern illustration-migration of Mac OS X from a 1998 PowerPC-based iMac to a 2007 Intel-based iMac shows iMacs made in 1998 and 2006. Figure 4.1. Adapter pattern illustration-migration of Mac OS X from a 1998 PowerPC-based iMac to a 2007 Intel-based iMac Mac OS X was originally designed to take advantage of the AltiVec floating-point and integer SIMD instruction set that is part of the PowerPC processor. When the Intel processor replaced this processor, calls to AltiVec instructions from Mac OS X had to be retargeted to the Intel x86 SSE extensions, which provide similar functionality to AltiVec. For something as important as an operating system, the code could be rewritten to replace the calls to AltiVec with calls to SSE. However, Apple recognized that application developers might not want to do this, or might not have access to the source of old AltiVec-based code, so they recommended the use of the Accelerate framework. The Accelerate framework is a set of high-performance vector-accelerated libraries. It provides a layer of abstraction for accessing vector-based code without needing to use vector instructions or knowing the architecture of the target machine. (This is the important point for us here.) The framework automatically invokes the appropriate instruction set, be it PowerPC or Intel (in these processors various versions). Thus, the Accelerate framework is an example of the Adapter pattern. It takes an existing situation and adapts it to a new one, thus solving the problem of migrating existing code to a new environment. No alterations to the original code are required.[3] Design The Adapter patterns important contribution is that it promotes programming to interfaces. The Client works to a domain-specific standard, which is specified in the ITarget interface. An Adaptee class provides the required functionality, but with a different interface. The Adapter implements the ITarget interface and routes calls from the Client through to the Adaptee, making whatever changes to parameters and return types are necessary to meet the requirements. A Target class that implements the ITarget interface directly could exist, but this is not a necessary part of the pattern. In any case, the Client is aware only of the ITarget interface, and it relies on that for its correct operation. The adapter shown in Figure 4.2, Adapter pattern UML diagram is a class adapter because it implements an interface and inherits a class. The alternative to inheriting a class is to aggregate the Adaptee. This creates an object adapter. The design differences are primarily that overriding Adaptee behavior can be done more easily with a class adapter, whereas adding behavior to Adaptees can be done more easily with an object adapter. As we go along, I will point out different instances. Figure 4.2. Adapter pattern UML diagram The purpose of the ITarget interface is to enable objects of adaptee types to be interchangeable with any other objects that might implement the same interface. However, the adaptees might not conform to the operation names and signatures of ITarget, so an interface alone is not a sufficiently powerful mechanism. That is why we need the Adapter pattern. An Adaptee offers similar functionality to Request, but under a different name and with possibly different parameters. The Adaptee is completely independent of the other classes and is oblivious to any naming conventions or signatures that they have. Now, lets consider the roles in the pattern: ITarget The interface that the Client wants to use Adaptee An implementation that needs adapting Adapter The class that implements the ITarget interface in terms of the Adaptee Request An operation that the Client wants SpecificRequest The implementation of Requests functionality in the Adaptee Tip The pattern applies to a single computer, which would only have either the PowerPC or the Intel chip. In this case, it has the Intel chip-hence the need for the adapter. There is no Target class present, just the ITarget interface. Quiz: Match the Adapter Pattern Players with the Mac OS X Migration Illustration To test whether you understand the Adapter pattern, cover the lefthand column of the table below and see if you can identify its players among the items from the illustrative example (Figure 4.1, Adapter pattern illustration-migration of Mac OS X from a 1998 PowerPC-based iMac to a 2007 Intel-based iMac), as shown in the righthand column. Then check your answers against the lefthand column. Client Mac OS X (or any Mac application) ITarget The specification of the AltiVec instruction set Request A call to an AltiVec instruction Adapter The Accelerate framework Adaptee An Intel processor with an SSE instruction set SpecificRequest A Call to an SSE instruction Implementation It is best to illustrate the structure of the adapter with a small example, even at the theory code level. Suppose that technical readings are being collected and reported at a high level of precision, but the client can only make use of rough estimates. The signatures for the interface would be couched in terms of integers, and for the actual implementation in terms of double-precision numbers. Thus, an adapter is needed, as shown in Example 4.1, Adapter pattern theory code. Example 4.1. Adapter pattern theory code 1 using System; 2 3 // Adapter Pattern - Simple Judith Bishop Oct 2007 4 // Simplest adapter using interfaces and inheritance 5 6 // Existing way requests are implemented 7 class Adaptee { 8 // Provide full precision 9 public double SpecificRequest (double a, double b) { 10 return a/b; 11 } 12 } 13 14 // Required standard for requests 15 interface ITarget { 16 // Rough estimate required 17 string Request (int i); 18 } 19 20 // Implementing the required standard via Adaptee 21 class Adapter : Adaptee, ITarget { 22 public string Request (int i) { 23 return Rough estimate is + (int) Math.Round(SpecificRequest (i,3)); 24 } 25 } 26 27 class Client { 28 29 static void Main ( ) { 30 // Showing the Adapteee in standalone mode 31 Adaptee first = new Adaptee( ); 32 Console.Write(Before the new standard\nPrecise reading: ); 33 Console.WriteLine(first.SpecificRequest(5,3)); 34 35 // What the client really wants 36 ITarget second = new Adapter( ); 37 Console.WriteLine(\nMoving to the new standard); 38 Console.WriteLine(second.Request(5)); 39 } 40 } 41/* Output 42 Before the new standard 43 Precise reading: 1.66666666666667 44 45 Moving to the new standard 46 Rough estimate is 2 47 */ The main program in the client shows two scenarios. First, there is an example of how the Adaptee could be called directly (line 33)-its output is shown in line 43. However, the client wants to work to a different interface for requests (lines 17 and 38). The Adapter implements the ITarget interface and inherits the Adaptee (line 21). Therefore, it can accept Request calls with a string-int signature and route them to the Adaptee with a double-double-double signature (line 23). The new output is shown on line 46. A feature of adapters is that they can insert additional behavior between the ITarget interface and the Adaptee. In other words, they do not have to be invisible to the Client. In this case, the Adapter adds the words Rough estimate is to indicate that the Request has been adapted before it calls the SpecificRequest (line 23). Adapters can put in varying amounts of work to adapt an Adaptees implementation to the Targets interface. The simplest adaptation is just to reroute a method call to one of a different name, as in the preceding example. However, it may be necessary to support a completely different set of operations. For example, the Accelerate framework mentioned in the the section called Illustration section will need to do considerable work to convert AltiVec instructions to those of the Intel Core Duo processor. To summarize, we have the following options when matching adapter and adaptee interfaces: Adapter interface and adaptee interface have same signature This is the trivial case, with not much work to do. Tip Many examples of the Adapter pattern operate at this level and are not illustrative or helpful in explaining its real power. Beware of them. Adapter interface has fewer parameters than adaptee interface The adapter calls the adaptee with some dummy input. Tip This case is shown in Example 4.1, Adapter pattern theory code, where the second parameter is defaulted to 3. Adapter interface has more parameters than adaptee interface The adapter adds the missing functionality, making it half an adapter and half a component in its own right. Adapter interface has other types than adaptee interface The adapter performs some type conversion (casting). Tip This case is shown in Example 4.1, Adapter pattern theory code, where the first double parameter is created from an integer and the double return type is cast back to a string. Of course, combinations of these basic cases are also possible. Two-Way Adapters Adapters provide access to some behavior in the Adaptee (the behavior required in the ITarget interface), but Adapter objects are not interchangeable with Adaptee objects. They cannot be used where Adaptee objects can because they work on the implementation of the Adaptee, not its interface. Sometimes we need to have objects that can be transparently ITarget or Adaptee objects. This could be easily achieved if the Adapter inherited both from both classes; however, such multiple inheritance is not possible in C#, so we must look at other solutions. The two-way adapter addresses the problem of two systems where the characteristics of one system have to be used in the other, and vice versa. An Adapter class is set up to absorb the important common methods of both and to provide adaptations to both. The resulting adapter objects will be acceptable to both sides. Theoretically, this idea can be extended to more than two systems, so we can have multiway adapters, but there are some implementation limitations: without multiple inheritance, we have to insert an interface between each original class and the adapter. Our Macintosh example has a follow-up that illustrates this point nicely. With an Intel processor on board, a Mac can run the Windows operating system.[4] Windows, of course, is targeted directly for the Intel processor and will make use of its SSE instructions where necessary. In such a situation, we can view Windows and Mac OS X as two clients wanting to get access to the Adaptee (the Intel processor). The Adapter catches both types of instruction calls and translates them if required. For an instruction issued by an application, the situation on the different operating systems running on a Mac with an Intel processor can be summed up using pseudocode as follows: Mac OS X ExecuteAltiVec(instruction); Windows ExecuteSEE(instruction); Adapter void ExecuteAltiVec(instruction) { ExecuteSSE(ConvertToSSE(instruction)); } void ExecuteSSE(instruction) { Intel.ExecuteSSE(instruction); } A key point with a two-way adapter is that it can be used in place of both ITarget and the Adaptee. When called to execute AltiVec instructions, the adapter behaves as a PowerPC processor (the Target), and when called to execute SSE instructions, it behaves as an Intel processor (the Adaptee). Example: The Seabird We have already looked at some theory code and discussed an interesting application of the Adapter pattern concept. It is now time for an example. That illustrates a two-way adapter but sticks closely to the structure of Example 4.1, Adapter pattern theory code. Suppose an inventor has an embedded control system for a revolutionary water plane called the Seabird. The plane derives from both aircraft and seacraft design: specifically, the Seabird has the body of a plane but the controls and engine of a ship. Both parts are being assembled as-is. The inventor of the Seabird is adapting as much as he can so that it is possible to control the craft via the interfaces provided by both parts. In pattern terms, the Seabird will be a two-way adapter between the Aircraft and Seacraft classes. When running the experiments on the Seabird, the inventor will use an adapter and will be able to access methods and data in both classes. In other words, Seabird will behave as both an Aircraft and a Seacraft. We could get a simple adapter to behave as an Aircraft, say, and use the features of a Seacraft, but we could not do this the other way as well. With a two-way adapter, however, this is possible. The ITarget interface, IAircraft, has two properties, Airborne and Height, and one method, TakeOff. The Aircraft class implements the interface in the manner of an aircraft. The IAdaptee interface, ISeacraft (new in this version of the pattern), has two methods-Speed and IncreaseRevs-that are implemented by the Seacraft class. The Adapter inherits from the Adaptee (Seacraft) and implements the ITarget (IAircraft) in the normal way. The adapter then has to do some work to match these two different interfaces to each other. Table 4.1, Adapter pattern Seabird example-methods and properties makes it easier to see how one would approach such an adapter. Table 4.1. Adapter pattern Seabird example-methods and properties Aircraft (Target) Seacraft (Adaptee) Seabird (Adapter) Experiment (Client) Inherits Seabird, implements Aircraft Instantiates seabird Methods TakeOff-sets Airborne and Height to 200 TakeOff-involves Seacraft, IsFlying, and IncreaseRevs seabird.TakeOff-goes to Seabird IncreaseRevs-changes speed by 10 IncreaseRevs-calls Seacraft IncreaseRevs and SeacraftIsFlying and sets the height (seabird as ISeacraft) IncreaseRevs-goes to Seabird Variables Airborne-is true after takeoff Airborne-is true if Height > 50 seabird.Airborne-goes to Seabird Speed-returns the speed (seabird as Seacraft) Speed-goes to Seacraft Height-returns the height Height-returns the stored height seabird.Height-goes to Seabird The classes representing each part of the invention offer different methods: TakeOff for an aircraft and IncreaseRevs for a seacraft. In the simple adapter, only TakeOff would work. In the two-way adapter, we also capture the method from the Adaptee (IncreaseRevs) and adapt it to include information that otherwise would be supplied by the Target (the height, here). Two-way adapters also handle variables-in this case, Airborne, Speed, and Height. Those from the Aircraft (the Target) are trapped and adapted to return locally held information. The one in the Seacraft (Adaptee) is routed through. The result of all of the above, when translated into C# classes, is that the Client can conduct experiments on the Seabird as follows: 1 Console.WriteLine(\nExperiment 3: Increase the speed of the Seabird:); 2 (seabird as ISeacraft).IncreaseRevs( ); 3 (seabird as ISeacraft).IncreaseRevs( ); 4 if (seabird.Airborne) 5 Console.WriteLine(Seabird flying at height 6 + seabird.Height + 7 meters and speed +(seabird as ISeacraft).Speed + knots); 8 Console.WriteLine(Experiments successful; the Seabird flies!); The calls to seabird.Airborne and seabird.Height (lines 4 and 6) are regular adapter methods that adapt as described in Table 4.1, Adapter pattern Seabird example-methods and properties. However, the ability to treat the Seabird as a Seacraft as well (lines 2, 3, and 7) is peculiar to the two-way adapter. The full program is given in Example 4.2, Two-way Adapter pattern example code-Seabird. Example 4.2. Two-way Adapter pattern example code-Seabird using System; // Two-Way Adapter Pattern Pierre-Henri Kuate and Judith Bishop Aug 2007 // Embedded system for a Seabird flying plane // ITarget interface public interface IAircraft { bool Airborne {get;} void TakeOff( ); int Height {get;} } // Target public sealed class Aircraft : IAircraft { int height; bool airborne; public Aircraft( ) { height = 0; airborne = false; } public void TakeOff ( ) { Console.WriteLine(Aircraft engine takeoff); airborne = true; height = 200; // Meters } public bool Airborne { get {return airborne;} } public int Height { get {return height;} } } // Adaptee interface public interface ISeacraft { int Speed {get;} void IncreaseRevs( ); } // Adaptee implementation public class Seacraft : ISeacraft { int speed = 0; public virtual void IncreaseRevs( ) { speed += 10; Console.WriteLine(Seacraft engine increases revs to + speed + knots); } public int Speed { get {return speed;} } } // Adapter public class Seabird : Seacraft, IAircraft { int height = 0; // A two-way adapter hides and routes the Targets methods // Use Seacraft instructions to implement this one public void TakeOff( ) { while (!Airborne) IncreaseRevs( ); } // Routes this straight back to the Aircraft public int Height { get {return height;} } // This method is common to both Target and Adaptee public override void IncreaseRevs( ) { base.IncreaseRevs( ); if(Speed > 40) height += 100; } public bool Airborne { get {return height > 50;} } } class Experiment_MakeSeaBirdFly { static void Main ( ) { // No adapter Console.WriteLine(Experiment 1: test the aircraft engine); IAircraft aircraft = new Aircraft( ); aircraft.TakeOff( ); if (aircraft.Airborne) Console.WriteLine( The aircraft engine is fine, flying at +aircraft.Height+meters); // Classic usage of an adapter Console.WriteLine(\nExperiment 2: Use the engine in the Seabird); IAircraft seabird = new Seabird( ); seabird.TakeOff( ); // And automatically increases speed Console.WriteLine(The Seabird took off); // Two-way adapter: using seacraft instructions on an IAircraft object // (where they are not in the IAircraft interface) Console.WriteLine(\nExperiment 3: Increase the speed of the Seabird:); (seabird as ISeacraft).IncreaseRevs( ); (seabird as ISeacraft).IncreaseRevs( ); if (seabird.Airborne) Console.WriteLine(Seabird flying at height + seabird.Height + meters and speed +(seabird as ISeacraft).Speed + knots); Console.WriteLine(Experiments successful; the Seabird flies!); } }/* Output Experiment 1: test the aircraft engine Aircraft engine takeoff The aircraft engine is fine, flying at 200 meters Experiment 2: Use the engine in the Seabird Seacraft engine increases revs to 10 knots Seacraft engine increases revs to 20 knots Seacraft engine increases revs to 30 knots Seacraft engine increases revs to 40 knots Seacraft engine increases revs to 50 knots The Seabird took off Experiment 3: Increase the speed of the Seabird: Seacraft engine increases revs to 60 knots Seacraft engine increases revs to 70 knots Seabird flying at height 300 meters and speed 70 knots Experiments successful; the Seabird flies! */ Pluggable Adapters Developers who recognize that their systems will need to work with other components can increase their chances of adaptation. Identifying in advance the parts of the system that might change makes it easier to plug in adapters for a variety of new situations. Keeping down the size of an interface also increases the opportunities for new systems to be plugged in. Although not technically different from ordinary adapters, this feature of small interfaces gives them the name pluggable adapters. A distinguishing feature of pluggable adapters is that the name of a method called by the client and that existing in the ITarget interface can be different. The adapter must be able to handle the name change. In the previous adapter variations, this was true for all Adaptee methods, but the client had to use the names in the ITarget interface. Suppose the client wants to use its own names, or that there is more than one client and they have different terminologies. To achieve these name changes in a very dynamic way, we can use delegates (see later sidebar). Now, consider Example 4.3, Pluggable Adapter pattern theory code, which shows how to write pluggable adapters with delegates. Example 4.3. Pluggable Adapter pattern theory code 1 using System; 2 3 // Adapter Pattern - Pluggable Judith Bishop Oct 2007 4 // Adapter can accept any number of pluggable adaptees and targets 5 // and route the requests via a delegate, in some cases using the 6 // anonymous delegate construct 7 8 // Existing way requests are implemented 9 class Adaptee { 10 public double Precise (double a, double b) { 11 return a/b; 12 } 13 } 14 15 // New standard for requests 16 class Target { 17 public string Estimate (int i) { 18 return Estimate is + (int) Math.Round(i/3.0); 19 } 20 } 21 22 // Implementing new requests via old 23 class Adapter : Adaptee { 24 public Func Request; 25 26 // Different constructors for the expected targets/adaptees 27 28 // Adapter-Adaptee 29 public Adapter (Adaptee adaptee) { 30 // Set the delegate to the new standard 31 Request = delegate(int i) { 32 return Estimate based on precision is + 33 (int) Math.Round(Precise (i,3)); 34 }; 35 } 36 37 // Adapter-Target 38 public Adapter (Target target) { 39 // Set the delegate to the existing standard 40 Request = target.Estimate; 41 } 42 } 43 44 class Client { 45 46 static void Main ( ) { 47 48 Adapter adapter1 = new Adapter (new Adaptee( )); 49 Console.WriteLine(adapter1.Request(5)); 50 51 Adapter adapter2 = new Adapter (new Target( )); 52 Console.WriteLine(adapter2.Request(5)); 53 54 } 55 } 56/* Output 57 Estimate based on precision is 2 58 Estimate is 2 59 */ The delegate is contained in the Adapter and is instantiated on line 24, from one of the standard generic delegates. On lines 33 and 40, it is assigned to the methods Precise and Estimate, which are in the Adaptee and Target, respectively. Lines 31-34 show the use of an anonymous function to augment the results from the Adaptee. Notice that the Client (the Main method) refers only to its chosen method name, Request (see sidebar). The pluggable adapter sorts out which object is being plugged in at the time. Once a service has been plugged in and its methods have been assigned to the delegate objects, the association lasts until another set of methods is assigned. What characterizes a pluggable adapter is that it will have constructors for each of the types that it adapts. In each of them, it does the delegate assignments (one, or more than one if there are further methods for rerouting). C# Feature-Delegates A method that is specified in an interface is implemented with the same name in the base class. However, such close coupling is not always appropriate. The delegate construct can be used to break the coupling for this purpose. A delegate is a type that defines a method signature. A delegate instance is then able to accept a method of that signature, regardless of its method name or the type that encapsulates it. The delegate syntax in C# evolved considerably from Versions 1.0 to 2.0 to 3.0. We shall concentrate on the 3.0 version, which is the simplest to code. The language has predefined standard generic delegate types, as follows: delegate R Func( ); delegate R Func(A1 a1); delegate R Func(A1 a1, A2 a2); // ... and up to many arguments where R is the return type and the As and as represent the argument types and names, respectively. There is also another set of generic delegates introduced with the name Action that represent methods that do not return a value. Thus, declaring a delegate instance is now straightforward. For example, we can define a Request delegate that takes an integer parameter and returns a string: public Func Request; Next, we can assign an actual method to Request, as in: Request = Target.Estimate; The delegate can then be invoked just as any other method would be: string s = Request(5); This statement would invoke the Estimate method in the Target class, returning a string. cf. C# Language Specification Version 3.0, September 2007, Section 10.8 Example: CoolBook Our last Adapter pattern example picks up on an earlier example that we explored with the Proxy and Bridge patterns: SpaceBook. Recall that Example 2.4, Proxy pattern example code-SpaceBook introduced the SpaceBook class and its authentication frontend, MySpaceBook. Then, Example 2.6, Bridge pattern example code-OpenBook showed how we could create a Bridge to an alternative version of MySpaceBook called MyOpenBook, which did not have authentication. Now, we are going to consider going GUI. The input and output of SpaceBook (wall writing, pokes, etc.) will be done via Windows forms. There will be a separate form for each user, and users will be able to write on each others pages as before. However, now the input will be interactive, as well as being simulated by method calls in the program. Thus, we will have a prototype of a much more realistic system. C# Feature-Anonymous Functions Anonymous functions simplify the creation of one-time behavior for delegates. They are useful when additional behavior is to be added before or after a method is invoked. For example: Request = delegate(int i) { return Estimate based on precision is + (int) Math.Round(Precise (i,3)); }; Here, the method to be invoked is Precise. The parameters are different from the ones in the delegate, as is the return value. The anonymous function can wrap up the changes and assign a complete solution to the delegate for later invocation. cf. C# Language Specification Version 3.0, September 2007, Section 6.5 Creating a GUI and handling its events is a specialized function, and it is best to isolate it as much as possible from the ordinary logic of the system. In setting up CoolBook, we wrote a minimal GUI system called Interact. All Interact does is set up a window with a TextBox and a Button called Poke, and pass any click events on the Button to a delegate (see sidebar). Separately from this, we wrote MyCoolBook, which mimics the functionality of MyOpenBook and, for reasons of simplicity at this stage, maintains its own community of users. Given the following client program, the output will be as shown in Figure 4.3, Adapter pattern-output from CoolBook. static void Main( ) { MyCoolBook judith = new MyCoolBook(Judith); judith.Add(Hello world); MyCoolBook tom = new MyCoolBook(Tom); tom.Poke(Judith); tom.Add(Hey, We are on CoolBook); judith.Poke(Tom); Console.ReadLine( ); } The second Tom : Poked you was created interactively by Tom typing in Judith on his wall, selecting it, and clicking the Poke button. Judith then wrote on her own wall, and was getting ready to poke Tom when the snapshot was taken. MyCoolBook builds on top of Interact and acts as the adapter class. As can be seen in the Client, MyOpenBook and MySpaceBook have been completely plugged out and replaced by MyCoolBook. We can just change the instantiations back, and everything will revert to the old system. This is what a pluggable adapter achieves. Consider the insides of the adapter in Example 4.4, Pluggable Adapter pattern example code-MyCoolBook. It inherits from MyOpenBook and, through inheritance, it makes use of the MySpaceBook object stored there, as well as the Name property. It reimplements the three important methods-Poke and the two Add methods-and has two methods that connect it to Interact via a form object called visuals. Figure 4.3. Adapter pattern-output from CoolBook Example 4.4. Pluggable Adapter pattern example code-MyCoolBook // Adapter public class MyCoolBook : MyOpenBook { static SortedList community = new SortedList(100); Interact visuals; // Constructor starts the GUI public MyCoolBook(string name) : base(name) { // Create the Interact GUI on the relevant thread, and start it new Thread(delegate( ) { visuals = new Interact(CoolBook Beta); visuals.InputEvent += new InputEventHandler(OnInput); visuals.FormClosed += new FormClosedEventHandler(OnFormClosed); Application.Run(visuals); }).Start( ); community[name] = this; while (visuals == null) { Application.DoEvents( ); Thread.Sleep(100); } Add(Welcome to CoolBook + Name); } // Closing the GUI private void OnFormClosed(object sender, FormClosedEventArgs e) { community.Remove(Name); } // A handler for input events, which then calls Add to // write on the GUI private void OnInput(object sender, EventArgs e, string s) { Add(\r\n); Add(s, Poked you); } // This method can be called directly or from the other // Add and Poke methods. It adapts the calls by routing them // to the Interact GUI. public new void Add(string message) { visuals.Output(message); } public new void Poke(string who) { Add(\r\n); if (community.ContainsKey(who)) community[who].Add(Name, Poked you); else Add(Friend + who + is not part of the community); } public new void Add(string friend, string message) { if (community.ContainsKey(friend)) community[friend].Add(Name + : + message); else Add(Friend + friend + is not part of the community); } } Of the three reimplemented methods, only the behavior of the first Add is specific to MyCoolBook. The other two methods are very much like those in MyOpenBook. However, the problem is that given the closed nature of SpaceBook, MyCoolBook cannot access the dictionary there and has to keep its own community. This sometimes happens with adapters. If it were possible to rewrite parts of the Target in a more collaborative way, the adapter could do less work. This idea is addressed in the upcoming the section called Exercises section. The code for the full CoolBook system is shown in the Appendix. One point to note is the anonymous function that is passed to the Thread class in the CoolBook constructor. This is a very quick way of creating an object in a new thread. The last statement is Application.Run( ), which starts drawing the form and opens up a message pump for the interactive input/output on it. Finally, Start is called on the thread. C# Feature-Events Delegates are used extensively in Windows GUI event-driven programming, where they reflect the need to call back into the users code when some event happens. Mostly, existing code of this type will use an older syntax. Also, because the new Func delegates must have return types, void delegates must use the original syntax too. Consider a simple example of wanting to inform one object that input has occurred in another object (this is part of Example 4.4, Pluggable Adapter pattern example code-MyCoolBook). We first declare a delegate visible to both classes: public delegate void InputEventHandler(object sender, EventArgs e, string s); Then, in the class where the event is handled, we create an instance of the delegate and add it to the event object of the class that will receive the event. When creating the delegate, we indicate the method that it will call (in this case, OnInput): visuals.InputEvent += new InputEventHandler(OnInput); void OnInput(object sender, EventArgs e, string s) { // Do something } The signature of OnInput must match that of InputEventHandler, which it does. Now, in the class where event occurs, we declare the event: public event InputEventHandler InputEvent; and in some method we invoke it: public void Input(object source, EventArgs e) { InputEvent(this, EventArgs.Empty, who); } The action of invoking the InputEvent delegate causes the method currently assigned to it (here, OnInput) to be invoked. Thus, the callback from one class to the other is achieved. More than one method can be associated with a delegate; when such a delegate is invoked, all its methods are called. Thus, if another object needed to know about input in the preceding example, it could add its own handler method on to InputEvent using +=. Event handlers can be removed using -=. cf. C# Language Specification Version 3.0, September 2007, Section 10.8 Use The Adapter pattern is found wherever there is code to be wrapped up and redirected to a different implementation. In 2002, Nigel Horspool and I developed a system called Views that enabled an XML specification of a Windows GUI to run on the cross-platform version of Windows called Rotor. The Views engine ran with the GUI program and adapted Views method calls to Windows calls. That benefited the clients (students) because they could use a simpler interface to GUI programming. A subsequent advantage was that Vista, the successor to Windows XP, used the same approach. At the time, it was a long way around to get Windows forms, but the adaptation paid off later. In 2004, the backend of the Views engine was ported by Basil Worrall to QT4, a graphics toolkit running on Linux. Immediately, all applications that were using Views for GUI programming became independent of Windows and could run with the Mono .NET Framework on Linux. The Views engine was therefore a pluggable adapter. (Our paper describing the approach is referenced in the Bibliography at the end of the book.) Use the Adapter pattern when... You have: • A domain-specific interface. • A class to connect to with a mismatching interface. You want to: • Create a reusable class to cooperate with yet-to-be-built classes. • Change the names of methods as called and as implemented. • Support different sets of methods for different purposes. Choose the Adapter you need... Class adapter Simple and versatile, invisible to the client. Object adapter Extensible to subclasses of the adapter. Two-way adapter Enables different clients to view an object differently. Pluggable adapter Presence of adapter is transparent; it can be put in and taken out Several adapters can be active. Exercises 1. Consider the Seabird program. Would it be possible to instantiate an Aircraft object instead of a Seacraft object and change the methods inside Seabird accordingly? If so, make the changes. If not, explain how the present program would need to be altered to enable this and then make the changes. 2. Add a SuperPoke button to CoolBook, enabling one user to send a message to another. 3. Having two different communities for SpaceBook and CoolBook is clearly a disadvantage. Assume you can make minor changes to the SpaceBook, MySpaceBook, and MyOpenBook classes, and see whether you can remove the community collection from MyCoolBook, routing all accesses back through MyOpenBook to SpaceBook. Façade Pattern ________________________________________ Role The role of the Façade pattern is to provide different high-level views of subsystems whose details are hidden from users. In general, the operations that might be desirable from a users perspective could be made up of different selections of parts of the subsystems. Illustration Simple interfaces to complex subsystems abound in real life. They can be created to make frequent use of a system faster, or to differentiate between novices and power users. A good illustration is Amazons 1-Click® system (Figure 4.4, Façade pattern illustration-1-Click®), which simplifies the process of ordering items for well-known customers. Normally, after selecting an item to purchase, an Amazon customer has to enter delivery and bank account details before the order is accepted. If these details are stored and the customer verifies her identity in some way, 1-Click takes that customer straight to the checkout. The customers stored bank account details and selected delivery address are used for the purchase, thus considerably speeding up (and simplifying) the ordering process. Thus, the 1-Click option forms a façade to the fuller system underneath. Figure 4.4. Façade pattern illustration-1-Click® Design Hiding detail is a key programming concept. What makes the Façade pattern different from, say, the Decorator or Adapter patterns is that the interface it builds up can be entirely new. It is not coupled to existing requirements, nor must it conform to existing interfaces. There can also be several façades built up around an existing set of subsystems. The term subsystem is used here deliberately; we are talking at a higher level than classes. See the UML diagram in Figure 4.5, Façade pattern UML diagram; it considers the subsystems to be grouped together, so they can interact in agreed ways to form the top-level operations. Figure 4.5. Façade pattern UML diagram The roles are: Namespace 1 A library of subsystems Subsystem A class offering detailed operations Façade A class offering a few high-level operations as selections from the subsystems Namespace 2 Where the client resides Client Calls the high-level operations in the Façade in Namespace 1 As shown in the UML diagram, the clients code does not make reference to the classes of the names of the subsystems; it only gets access to their operations via the Façade. Implementation The Façade pattern is simple to implement. It uses the C# concept of namespaces. Classes in namespaces have the facility to define accessibility as internal or public. If accessibility is defined as internal, the member is visible only in the assembly in which the namespace is compiled. In a very large system, the clients GUI will be in a different namespace from the library, so we can enforce the Façade. (Alternative implementations of the Façade pattern will be discussed shortly.) QUIZ: Match the Façade Pattern Players with the 1-Click Illustration To test whether you understand the Façade pattern, cover the lefthand column of the table below and see if you can identify its players among the items from the illustrative example (Figure 4.4, Façade pattern illustration-1-Click®), as shown in the righthand column. Then check your answers against the lefthand column. Namespace 1 Amazon server Subsystems Registering, checkout, address entering, account details verification, etc. Facade Window offering choices for purchasing Operation 1-Click Namespace 2 Client GUI Client Customer In Example 4.5, Façade pattern theory code, the theory code comes from two files: Façade-Main.cs and Façade-Library.cs. Both have to be compiled with special directives so that library in Façade-Library.cs is recognized as a lib file and the client in Façade-Main.cs can reference it. These commands are: // Compile the library csc /t:library /out:FacadeLib.dll Facade-Library.cs // Compile the main program csc /r:FacadeLib.dll Facade-Main.cs Tip This process of compiling and using libraries is facilitated in environments such as Visual Studio. The example mirrors the diagram in Figure 4.5, Façade pattern UML diagram. Three subsystems, implemented as classes, are inserted into the library. Facade is a static class that instantiates the three subsystems under the façade as objects called a, b, and c. Operations 1 and 2 then select combinations of methods from a, b, and c. For example, Operation1 will call two methods in a, one in b, and none in c. Thus, the Facade is a means of providing an interface to operations that should, on their own, remain hidden. The client starts with a using statement that indicates it wants access to the public members in the FacadeLib namespace. Then it calls the Facades two high-level operations. The output is shown below in Example 4.5, Façade pattern theory code. Example 4.5. Façade pattern theory code using System; // Facade Pattern Judith Bishop Dec 2006 // Sets up a library of three systems, accessed through a // Facade of two operations // Compile with // csc /t:library /out:FacadeLib.dll Facade-Library.cs namespace Library { internal class SubsystemA { internal string A1( ) { return Subsystem A, Method A1\n; } internal string A2( ) { return Subsystem A, Method A2\n; } } internal class SubsystemB { internal string B1( ) { return Subsystem B, Method B1\n; } } internal class SubsystemC { internal string C1( ) { return Subsystem C, Method C1\n; } } } public static class Facade { static SubsystemA a = new SubsystemA( ); static SubsystemB b = new SubsystemB( ); static SubsystemC c = new SubsystemC( ); public static void Operation1( ) { Console.WriteLine(Operation 1\n + a.A1( ) + a.A2( ) + b.B1( )); } public static void Operation2( ) { Console.WriteLine(Operation 2\n + b.B1( ) + c.C1( )); } } // ============= Different compilation using System; using FacadeLib; // Compile with csc /r:FacadeLib.dll Facade-Main.cs class Client { static void Main ( ) { Facade.Operation1( ); Facade.Operation2( ); } }/* Output Operation 1 Subsystem A, Method A1 Subsystem A, Method A2 Subsystem B, Method B1 Operation 2 Subsystem B, Method B1 Subsystem C, Method C1 */ Everything in the façade has to be public so that the Client, which is compiled into a different assembly, can access it. The classes all have the default internal visibility, limiting access to them to the assembly in which they were compiled (excluding the Client). As a test, if we try to let the Client instantiate any of the subsystems directly, we will get an error like the following: SubsystemC x = new SubsystemC( ); x.C1( );Facade2Main.cs(12,3): error CS0122: FacadeLib.SubsystemC is inaccessible due to its protection level Façade Alternatives Some alternative implementations of the Façade pattern are: Transparent façades The façade described in the preceding example is opaque, in that the subsystems cannot be accessed except via the Facade object. This requirement might be too stringent. Suppose some users want to get at the individual operations of particular subsystems. We can change all the internal modifiers to public, which will make the façade optional, or transparent. That is, as well as being able to go through the Facade, the client will be able to instantiate SubsystemA directly, for example, and then call A1. Static façades In most cases, there will only be one instance of a façade in the client for a set of subsystems, so its operations could more appropriately be called on the users side as members of the class itself, as in: public void ClientMain ( ) { Facade.Operation1( ); Facade.Operation2( ); } This implies that Facade is a static class. No instantiation is necessary; the user interfaces with the Facade class directly. In fact, the Singleton pattern (Chapter 5, Creational Patterns: Prototype, Factory Method, and Singleton) would be the preferred way of achieving this effect. Example: Novice Photo Library Consider the Composite pattern example in Chapter 3, Structural Patterns: Composite and Flyweight that showed how photos could be loaded into directories of arbitrary configurations. The instructions for using the six commands relied on the current place (where I am), which was a powerful, but perhaps confusing, concept. For novices, it might be a good idea to abstract from the power of the Photo Library and just let them load sets of photos, all at the same level, and immediately display them (as Flickr does). The commands could simply be: Upload setname photoname1 photoname2 ... ending with a blank line or some other indicator. These instructions would translate into the following existing ones: Find album AddSet setname AddPhoto photoname1 AddPhoto photoname2 ... Display Instead of going in and altering the code to have a new command, we can have a completely separate Façade that makes the calls as described above. The more complex and rich commands might be available to expert users, but not to novices. (See the preceding discussion on opaque and transparent façades.) Use Façades can be useful in different circumstances. There are many instances where a computer system is built up out of a set of largely independent subsystems. One well-known case is a compiler: it consists of clearly identifiable subsystems called a lexical analyzer, a syntax analyzer, semantic analyzers, a code generator, and several optimizers. In modern compilers, each subsystem has many subtasks. The different tasks and subtasks are called in a sequence, but sometimes they are not all needed. For example, if an error occurs in one of the analysis tasks, the compiler might not go onto a later phase. (The .NET compilers follow this approach.) Hiding this detail behind a façade enables a program to call tasks within subsystems in a logical order, passing the necessary data structures between them. Use the Façade pattern when... A system has several identifiable subsystems and: • The abstractions and implementations of a subsystem are tightly coupled. • The system evolves and gets more complex, but early adopters might want to retain their simple views. • You want to provide alternative novice, intermediate, and power user interfaces. • There is a need for an entry point to each level of layered software. But consider using instead: • The Abstract Factory pattern for designs where subsystems create objects on behalf of the client. Choose the Façade you need... Opaque Subsystem operations can only be called through the Façade. Transparent Subsystem operations can be called directly as well as through the Façade. Singleton Only one instance of the Façade is meaningful. Exercises 1. Program the suggested extension for novices to the Photo Library program. 2. Consider large systems that you use on the Internet, and come up with more examples of Façades. 3. If you have access to source code for a compiler, find the part where the subsystems are called and examine how the data structures are passed between them. Pattern Comparison ________________________________________ The Adapter pattern has much in common with the patterns discussed in Chapter 2, Structural Patterns: Decorator, Proxy, and Bridge. The differences are in the intents of the patterns. A bridge, for example, separates an interface and its implementation so that they can vary independently, whereas an adapter changes the interface of an existing object. The Adapter pattern is more useful for one-off changes, whereas the Bridge pattern anticipates that change might happen continuously. A decorator enhances an object without changing its interface and should be transparent to the application. An adapter is not transparent, as it is the named implementation of the interface the client sees. The Proxy pattern does not change any interfaces; it defines substitute objects for other objects. From a certain point of view, the Façade pattern is also adapting requests: it transforms high-level requests into a sequence of lower-level requests. The Façades intent is to hide complexity, and the Façade subsystems are not intended to be accessible by the client. To complete the picture, we can classify the Adapter and Façade patterns according to the mechanisms shown in Table 4.2, Comparison of Adapter and Façade patterns. Table 4.2. Comparison of Adapter and Façade patterns Mechanism Adapter Façade Original Adaptee SubsystemA, B, and C Interface ITarget Façade New Adapter Operation1 and 2 Client Aggregates ITarget Accesses Façade Client activates New New Original changed by No change No change New classes/subsystems Adapter provides adaptations to their methods Façade supplies high-level operations Operation routed From new to original From new to original
Posted on: Fri, 15 Nov 2013 11:08:56 +0000

Trending Topics



in-height:30px;"> SIX STEPS TOWARD JOY By Ashayana Deane, in Angelic
Black Friday Cyber Monday Velo Transit Womens The District 30
Start Living Your Dreams Now! Time waits for no one… As you
finally got around to roasting that danish pork roast!! first,
Dear Friends, Few days back I was reading a book “Retired but
n.054 MOSCOW, RUSSIA - English Nanny/Governess *TOP salary -

Recently Viewed Topics




© 2015