The main pattern we will consider in this chapter is the Adapter pattern. It is a versatile pattern that joins together types that were not designed to work with each other. It is one of those patterns that comes in useful when dealing with legacy code—i.e., code that was written a while ago and to which one might not have access. There are different kinds of adapters, including class, object, two-way, and pluggable. We'll explore the differences here. The second pattern we will look at in this chapter—the Façade pattern—is a simple one that rounds out the structural group. The aim of this pattern is to provide a simplified interface to a set of complex systems.
The Adapter pattern enables a system to use classes whose interfaces don't 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.
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 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]
The Adapter pattern's 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 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
Adaptee
s can be done more easily
with an object adapter. As we go along, I will point out different
instances.
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, let's consider the roles in the pattern:
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.
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.
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 Adaptee
's implementation to the Target
's 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 "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, 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, where the first
double
parameter is created from an integer and thedouble
return type is cast back to astring
.
Of course, combinations of these basic cases are also possible.
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:
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
).
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.
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
makes it easier to see how one would approach such an adapter.
Table 4-1. Adapter pattern Seabird example—methods and properties
Seacraft (Adaptee) | Seabird (Adapter) | Experiment (Client) | |
---|---|---|---|
Inherits | Instantiates | ||
Methods | |||
|
|
| |
|
| ( | |
Variables | |||
|
|
| |
| ( | ||
|
|
|
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. 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.
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 Target's 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!
*/
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, 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 <int,string> 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).
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 introduced
the SpaceBook
class and its
authentication frontend, MySpaceBook
. Then, Example 2-6 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 other's
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.
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.
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. 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
.
Example 4-4. Pluggable Adapter pattern example code—MyCoolBook
// Adapter public class MyCoolBook : MyOpenBook { static SortedList<string, MyCoolBook> community = new SortedList<string, MyCoolBook>(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
"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.
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:
|
You want to:
|
Choose the Adapter you need...
|
Consider the Seabird program. Would it be possible to instantiate an
Aircraft
object instead of aSeacraft
object and change the methods insideSeabird
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.Add a "SuperPoke" button to CoolBook, enabling one user to send a message to another.
Having two different communities for SpaceBook and CoolBook is clearly a disadvantage. Assume you can make minor changes to the
SpaceBook
,MySpaceBook
, andMyOpenBook
classes, and see whether you can remove the community collection fromMyCoolBook
, routing all accesses back throughMyOpenBook
toSpaceBook
.
[3] * For more about this migration, read the "Introduction to AltiVec/SSE Migration Guide" at http://developer.apple.com.
[4] * Windows runs on a Mac with the help of the Parallels or BootCamp virtual machines.
Get C# 3.0 Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.