Chapter 4. Structural Patterns

In the previous chapter, we looked at a number of ways to create objects in order to optimize for reuse. In this chapter, we'll take a look at structural patterns; these are patterns that are concerned with easing the design by describing simple ways in which objects can interact.

Again, we will limit ourselves to the patterns described in the GoF book. There are a number of other interesting structural patterns that have been identified since the publication of the GoF and we'll look at those in part 2 of the book.

The patterns we'll examine here are:

  • Adapter
  • Bridge
  • Composite
  • Decorator
  • Façade
  • Flyweight
  • Proxy

Once again, we'll discuss whether the patterns that were described years ago are still relevant for a different language and a different time.

Adapter

From time to time there is a need to fit a round peg in a square hole. If you've ever played with a child's shape sorting toy then you may have discovered that you can, in fact, put a round peg in a square hole. The hole is not completely filled and getting the peg in there can be difficult:

Adapter

To improve the fit of the peg an adapter can be used. This adapter fills the hole in completely resulting in a perfect fit:

Adapter

In software a similar approach is often needed. We may need to make use of a class that does not perfectly fit the required interface. The class may be missing methods or may have additional methods we would like to hide. This occurs frequently when dealing with third party code. In order to make it comply with the interface needed in your code, an adapter may be required.

The class diagram for an adapter is very simple as can be seen here:

Adapter

The interface of the implementation does not look the way we would like it to for use in our code. Normally the solution to this is to simply refactor the implementation so it looks the way we would like it to. However, there are a number of possible reasons that cannot be done. Perhaps the implementation exists inside third party code to which we have no access. It is also possible that the implementation is used elsewhere in the application where the interface is exactly as we would like it to be.

The adapter class is a thin piece of code that implements the required interface. It typically wraps a private copy of the implementation class and proxy calls through to it. The adapter pattern is frequently used to change the abstraction level of the code. Let's take a look at a quick example.

Implementation

In the land of Westeros, much of the trade and travel is done by boat. It is not only more dangerous to travel by ship than to walk or travel by horse, but also riskier due to the constant presence of storms and pirates. These ships are not the sort which might be used by Royal Caribbean to cruise around the Caribbean; they are crude things which might look more at home captained by 15th century European explorers.

While I am aware that ships exist, I have very little knowledge of how they work or how I might go about navigating one. I imagine that many people are in the same (cough!) boat as me. If we look at the interface for a Ship in Westeros, it looks intimidating:

interface Ship{
  SetRudderAngleTo(angle: number);
  SetSailConfiguration(configuration: SailConfiguration);
  SetSailAngle(sailId: number, sailAngle: number);
  GetCurrentBearing(): number;
  GetCurrentSpeedEstimate(): number;
  ShiftCrewWeightTo(weightToShift: number, locationId: number);
}

I would really like a much simpler interface that abstracts away all the fiddly little details. Ideally something like the following:

interface SimpleShip{
  TurnLeft();
  TurnRight();
  GoForward();
}

This looks like something I could probably figure out even living in a city that is over 1000 kilometers from the nearest ocean. In short, what I'm looking for is a higher-level abstraction around the Ship. In order to transform a Ship into a SimpleShip we need an adapter.

The adapter will have the interface of SimpleShip but it will perform actions on a wrapped instance of Ship. The code might look something like this:

let ShipAdapter = (function () {
  function ShipAdapter() {
    this._ship = new Ship();
  }
  ShipAdapter.prototype.TurnLeft = function () {
    this._ship.SetRudderAngleTo(-30);
    this._ship.SetSailAngle(3, 12);
  };
  ShipAdapter.prototype.TurnRight = function () {
    this._ship.SetRudderAngleTo(30);
    this._ship.SetSailAngle(5, -9);
  };
  ShipAdapter.prototype.GoForward = function () {
    //do something else to the _ship
  };
  return ShipAdapter;
})();

In reality these functions would be far more complex, but it should not matter much because we've got a nice simple interface to present to the world. The presented interface can also be set up so as to restrict access to certain methods on the underlying type. When building library code, adapters can be used to mask the internal method and only present the limited functions needed to the end user.

To use this pattern, the code might look like:

var ship = new ShipAdapter();
ship.GoForward();
ship.TurnLeft();

You would likely not want to use adapter in the name of your client class as it leaks some information about the underlying implementation. Clients should be unaware they are talking to an adapter.

The adapter itself can grow to be quite complex to adjust one interface to another. In order to avoid creating very complex adapters, care must be taken. It is certainly not inconceivable to build several adapters, one atop another. If you find an adapter becoming too large then it is a good idea to stop and examine if the adapter is following the single responsibility principle. That is to say, ensure that each class has only one thing for which it has some responsibility. A class that looks up users from a database should not also contain functionality for sending e-mails to these users. That is too much responsibility. Complex adapters can be replaced with a composite object, which will be explored later in this chapter.

From the testing perspective, adapters can be used to totally wrap third party dependencies. In this scenario they provide a place into which to hook tests. Unit tests should avoid testing libraries but they can certainly test the adapters to ensure that they are proxying through the correct calls.

The adapter is a very powerful pattern for simplifying code interfaces. Massaging interfaces to better match a requirement is useful in countless places. The pattern is certainly useful in JavaScript. Applications written in JavaScript tend to make use of a large number of small libraries. By wrapping up these libraries in adapters I'm able to limit the number of places I interact with the libraries directly; this means that the libraries can easily be replaced.

The adapter pattern can be slightly modified to provide consistent interfaces over a number of different implementations. This is usually known as the bridge pattern.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.212.124