CHAPTER 5 Adapter, Bridge, and Façade Objectives The objectives of this chapter are to identify the following: Complete the exercise in class design. Introduce the adapter, bridge, and façade patterns. Create basic UML diagrams for these design patterns. Software Development Methods 1
Class Exercise After getting into groups of 3-4, please draw a class diagram, including relationships and multiplicity, of the following scenario: A university library system wants a system that meets the following criteria: library holdings include books, periodicals, and videos. books and videos circulate, but periodicals don t. patrons can check out materials that circulate. patrons may be undergraduate students, graduate students, or faculty. patrons may perform searches on items by author or title. undergraduates may check out 10 items, graduate students may check out 30 items, and faculty may check out an unlimited number of items. the system can report all of the items checked out by a particular patron. the system can report on all items that are overdue. Your diagram should include all relevant classes including those for supporting whatever design patterns you choose. Bridge Pattern Use the Bridge pattern to: Avoid binding an abstraction to its implementation. We ll see what this means when we look more closely at and extend the SortAlgorithm class from last week. Allow both abstractions and implementations to be extensible by subclassing. Make changes to an implementation that doesn t impact the client. This is just another way of saying that we have programmed to an interface and not to an implementation. Share an implementation across multiple objects and hide this fact from the client. 2 Software Development Methods
Bridges have several participants: An Abstraction class which declares an interface an maintains a reference to an Implementor. A Refined Abstractor which extends the Abstraction interface. An Implementor defines the interface for implementation classes. This interface need have no relationship or similarity to the Abstraction interface. A Concrete Implementor that implements the Implementor interface. The results we gain from implementing a Bridge pattern are: Decoupling of an interface and an implementation. This allows an abstraction to avoid binding itself permanently to a specific implementation. An implementation might be derived and used at run-time rather than compile time which increases the maintainability and potential reuse of our classes. Improved extensibility. The abstraction and its implementation may be extended separately. Hidden implementations. The client need not know of what the implementation does behind the scenes. The client communicates with the abstraction and only the abstraction worries about its implementation. To illustrate this design pattern, consider some of the sorting algorithms that were discussed last week, specifically the Quick Sort algorithm. Recall that with Quick Sort you must choose a pivot element and perform sorting on each side of the pivot. It turns out that there are multiple ways of choosing that pivot element. As a general rule: Choose a simple pivot scheme when the array is small. Why? Choose a more complex pivot when the array is larger. Why? QuickSort Class Recall that last week we used the InsertionSort class. This week we ll focus on the QuickSort class. One implementation might appear as: class QuickSort : public SortAlgorithm { public: void sort(array & a, int lo, int hi); The Java equivalent might appear as: class QuickSort extends SortAlgorithm { public void sort(array a, int lo, int hi) {... Software Development Methods 3
If we were now to go ahead and code the QuickSort algorithm we might get something like: void QuickSort::sort(Array & a, int lo, int hi) { if (hi <= low) { int i = part(a, lo, hi); sort(a, lo, hi - 1); sort(a, lo + 1, hi) We will also need some kind of partition algorithm. This can easily be implemented as another class method: int QuickSort::part(Array & a, int l, int r) {... Notice the potential problem here. We have embedded the implementation of the partition logic as a method in the class. This means we have only one possible way to partition our data set. But since what we want is to be able to use different implementations of the partition depending on the characteristics of our data, we need to make some changes. We could: Subclass the QuickSort class and override the partition method. This would work but might lead to a series of different classes which differ only in their partitioning scheme. In this case, subclassing might be a case of using a sledgehammer to swat a fly. Use the Bridge pattern to replace the concrete implementation of the partition method with an object. Needless to say, this is the option we re going to use. Revised SortAlgorithm Class We first need to tweak the SortAlgorithm class a bit: 4 Software Development Methods
template <class Item> public class SortAlgorithm { protected: const Implementor * option; public: SortAlgorithm(const Implementor * op = 0); void setoption(const Implementor * op); bool lessthan(const Item & a, const Item & b) const { if (option) return option->lessthan(a, b); else return (a < b); The Java equivalent might appear as: public class SortAlgorithm { protected final Implementor option; public SortAlgorithm(Implementor op) { option = op; public void setoption(implementor op) { option = op; public boolean lessthan(object a, Object b) { if (option!= null) return (option.lessthan(a, b)); else return (a < b); Implementor Class The next class we ll build will be the implementor which is used as the base class to our implementation. Notice that this is similar to a Strategy pattern save that with a strategy, the entire strategy class is extended whereas with a Bridge pattern, the abstraction and its implementor may be extended separately. Our abstract implementor might appear as: template <class Item> class Implementor { public: virtual bool lessthan(const Item &, const Item &) const = 0; The Java equivalent might appear as: Software Development Methods 5
public abstract class Implementor { public boolean lessthan(object a, Object b) {... Concrete Implementor Now we can actually write the code for the lessthan method of the Implementor. A C++ version might appear as: template <class Item> class Pivot : public Implementor { protected: bool sortasc, ignorecase; public: Pivot(bool so = true, bool ic = true) : sortasc(so), ignorecase(ic) {... bool lessthan(const Item &, const Item &) const; The Java implementation might appear as: public class Pivot extends Implementor { protected boolean sortasc; protected boolean ignorecase; public Pivot(bool so, bool ic) { sortasc = so; ignorecase = ic; bool lessthan(object a, Object b) {... And now we can provide an implementation to the lessthan method which will be used by our sort algorithm to make its determinations: template <class Item> bool lessthan (const Item & a, const Item & b) const { if (sortasc) return (a < b); else return (b < a); 6 Software Development Methods
The corresponding Java code might appear as: boolean lessthan (Object a, Object b) { if (sortasc) return (a < b); else return (b < a); Now we can see how some client code might make use of these classes: int main(int argc, char * argv[]) { Sort * sort = new QuickSort(); Implementor * option = new Pivot(); ArrayList input; cin >> input; sort->setoption(option); sort->sort(input); cout << input; Thus when the sort algorithm s sort method is called, it will begin comparing elements using the implementation defined by the associated option implementor. Conceivably this means that the sort mechanism and option could be read from the command line and created a run time; you could sort the same data set different ways without forcing a recompilation of your program. It would also be feasible to make each Pivot or SortAlgorithm a Singleton since you may never need more than one such object at a time. Adapter Pattern Sometimes toolkits or other classes that are purchased off-the-shelf or that come with other products are not compatible with your own internal classes due to differences in the interface. Your options in this case are somewhat limited. You d like to take advantage of the libraries provided by your vendor, but you don t want your own classes to become dependent upon that vendor s specifications (e.g. what happens if you switch platforms). This is where the Adapter pattern can be useful. Use the Adapter pattern to: Use an existing class whose interface doesn t match what you need. Create a reusable class that cooperates with unrelated or unforeseen classes that may not have a compatible interface. Software Development Methods 7
Adapters have several participants: A Target which defines the domain-specific interface that the Client uses. A Client which collaborates with objects conforming to the Target interface. An Adaptee which defines an existing interface that needs to be adapted. An Adapter which adapts the Adaptee to the Target interface. Façade We have already seen an example of the façade pattern. A façade is simply a way to provide a simple interface to a complicated process. When you use the compile option of your compiler, it is a façade that hides a myriad of details behind the scenes. Use the façade pattern to: Provide a simple interface to a complex subsystem. Layer the various subsystems. Decouple clients from subsystems and subsystems from one another. This is a common idea behind APIs and layered architectures. Façades have two participants: A Façade which knows which subsystem classes are required to satisfy a given request and is able to delegate client requests to the appropriate subsystem. The Subsystem Classes which implement the bulk of the functionality. These classes can handle requests issued to them from the façade but yet have no knowledge of the existence of such a façade. Design Pattern Consequences The overall consequences of using the various design patterns may be summarized as follows: Clients only need to know about a general interface with which they will be interacting; they remain unaware of the concrete classes working behind the scenes. Flexibility is increased because there are no direct dependencies between a class interface and its implementation. 8 Software Development Methods