In this chapter, you'll see how you can benefit from using structural design patterns in your code.
What Are Structural Design Patterns?
I’m sure you’ve shopped at a grocery store:
When you are all done, you bring your items to the front of the store, checkout, and pay. You interact with the cashier. Pretty simple. It is an example of a structural pattern where a large amount of complexity is hidden behind an uncomplicated interface. As a customer, you don’t want to have to deal with ordering produce, shipping it, pricing it, or putting it on the shelves. You want to shop and go home.
Structural patterns are ways of organizing classes or objects so that they are easy to use. There might be a lot happening behind the scenes, but as a user, you don’t need to know the complexities. There are two types of structural patterns — those that organize classes and those that organize objects. We’ll look at both kinds.
What is the Adapter Pattern?
The problem this pattern addresses is when you have code that expects one interface (set of methods), but the implementation provides another. How do you get into this predicament? Usually, by incorporating a third-party library, after you have coded part of your system. The library does what you want, but the classes have named functions different than what you have coded for.
You have a couple of options. One (the hard/bad one) is to go back and change all your existing code to use the new classes and methods. The other is to create an adapter. The adapter exposes the expected interface. But the implementation of each method turns around and calls the methods of the library class.
You see this all the time in the real world. For example, my laptop has an HDMI connector. But I have an older model external monitor that only has VGA. I need an adapter to convert one signal to the other.
In our card game, let's say we created a PlayableCard interface, and our PlayingCard implemented it:
interface PlayableCard {
void flip();
};
class PlayingCard implements PlayableCard {
bool faceUp;
void flip () {
faceUp = !faceUp;
}
};
But somewhere else in the company, someone has created a CoolCard class, that looks better than our implementation. We'd like to use that instead:
class CoolCard {
void turnOver() {
// cool implementation here
}
};
If we used this new card, every place that called our flip()
operation would have to be changed to turnOver()
. Not that hard in our little game, but imagine working on a bigger project! So instead, let's introduce an adapter that looks like a PlayableCard, but acts like a CoolCard:
class PlayingCardAdapter implements PlayableCard {
CoolCard thisCard;
void flip() {
thisCard.turnOver();
}
};
Now we don't have to change everywhere that we called the flip() operation! We do, however, have to create PlayingCardAdapter objects instead of PlayingCard objects. But we have a factory that makes all of that. So that's the only place in our code that needs to know about the new CoolCard concept that we added.
What is the Composite Pattern?
There are times when you want to treat a bunch of objects and single objects the same. For example, drawing a shape. If you have a single shape, say a circle, you would like to call:
Shape shape = new Circle();
shape.draw();
But you also would like to call a whole collection of shapes, the same way:
Shape shapes = new ComplicatedDiagramOfABunchOfShapes();
shapes.draw();
The composite design pattern does this. Every item, whether it is a single entity or collection exposes the same interface. The collection’s implementation runs through all the objects it owns, asking each to perform the requested method.
In the next chapter, we will add multiple views with another pattern. In preparation for that, we want the game controller to treat one view, or many as if they are the same.
Let’s do it together!
We put all of that view management into a new composite class:
public class GameViewables implements GameViewable {
List<GameViewable> views;
public GameViewables () {
views = new ArrayList<GameViewable> ();
}
public void addViewable (GameViewable view) {
views.add(view);
}
@Override
public void showPlayerName(int playerIndex, String playerName) {
for (GameViewable view : views) {
view.showPlayerName(playerIndex, playerName);
}
}
// other GameViewable overrides here
We replaced holding just one view, with a list of views. Then, any place where we asked a view to update its display, we run through the list of views, asking each one to update its display. But you'll notice we didn't have to change the names of any of the functions to do this more flexible functionality. A single view, or many, looks the same to the callers of the showPlayerName function.
What is the Decorator Pattern?
Decorator is similar to composite in that it allows a group of objects to behave as if it were just one. However, the difference is that the objects managed by decorators add new functionality that the original did not contain.
Think about ordering an ice cream sundae. The core of the sundae is a scoop of ice cream. You eat it with a spoon. You can also add toppings and sauces. In other words, decorate the sundae. But you still eat it with a spoon. So the interface stays the same, even though the sundae has become more complex.
Going back to our card game, we'll introduce an interface called IPlayer, and Player will implement it. When the player is a winner, we'd like to so something special with their name. But only if they are a winner. We don't want this special behavior all the time. So we'll add a Decorator class, WinningPlayer. Let's implement it together!
First, let's create a new implementation of a PlayableCard, called WildlyFlipping Ace:
public class WinningPlayer implements IPlayer {
IPlayer winner;
public WinningPlayer (IPlayer player) {
winner = player;
}
public String getName() {
return "***** " + winner.getName() + " *****";
}
}
Notice we still need the original Player (it holds the player's name). This new decorator takes over the getName method, adding a little emphasis to their name, but then calls the original player to do what it normally does. That last part is really important! It is not replacing functionality, but adding to (decorating) it.
Let's Recap!
Structural patterns are ways of organizing classes or objects so that they are easy to use.
The adapter pattern changes the interface of a class from non-compatible, to one that is expected.
The composite pattern allows individual and collection objects to be treated the same.
The decorator pattern allows for additional behavior to be added and removed at runtime.
In the next chapter, we will look at the messaging between objects as we examine the behavioral design patterns.