Home CPSC 330

Design Patterns 2

Overview

Today we will discuss design patterns that solve structural problems. We will look at design patterns that connect classes together effectively.

The Adapter Pattern

The adapter pattern, also called the wrapper pattern, adapts one interface into another. It allows two classes to work together that normally would not.

An example of when this might be employed is if we have a graphics library with this interface:


class Graphics {
  private static void drawLine(int x0, int y0, int x1, int y1);
  private static void drawCircle(int x, int y, int radius);
  private static void drawRectangle(int x, int y, int w, int h);
}

But suppose we also have a geometry library with classes like this:


class Point {
  public Point(double x, doubl y);
  public double getX();
  public gouble getY();
}

class Line {
  public Line(Point start, Point end);
  public Point getStart();
  public Point getEnd();
}

class Circle {
  public Circle(Point center, double radius);
  public Point getCenter();
  public double getRadius();
}

class Rectangle {
  public Rectangle(Point upper_left, Point lower_right);
  public Point geUpperLeft();
  public Point getLowerRight();
}

If we want to use the graphics library to draw objects in the geometry library, it will be awkward:


Graphics.drawLine((int)line.getStart().getX(), (int)line.getStart.getY(),
                  (int)line.getEnd().getX(), (int)line.getEnd.getY());

Graphics.drawCircle((int)circle.getCenter().getX(), (int)circle.getCenter().getY(), (int)circle.getRadius());

Graphics.drawRectangle((int)rect.getUpperLeft().getX(), (int)rect.getUpperLeft().getY(),
    (int)(rect.getLowerRight().getX() -  rect.getUpperLeft().getX()),
    (int)(rect.getLowerRight().getY() -  rect.getUpperLeft().getY()));

We can use the adapter pattern to allow these two non-compatible interface to work together more nicely:


class GraphicsAdapter {
  public static void drawLine(Line l) {
    Graphics.drawLine((int)l.getStart().getX(), (int)l.getStart.getY(),
                  (int)l.getEnd().getX(), (int)l.getEnd.getY());
  }
  public static void drawCircle(Circle c) {
    Graphics.drawCircle((int)c.getCenter().getX(),
      (int)c.getCenter().getY(), (int)c.getRadius());
  }
  public static void drawRectangle(Rectangle r) {
    Graphics.drawRectangle((int)r.getUpperLeft().getX(), (int)r.getUpperLeft().getY(),
      (int)(r.getLowerRight().getX() -  r.getUpperLeft().getX()),
      (int)(r.getLowerRight().getY() -  r.getUpperLeft().getY()));
  }
}

Now we can easily draw the shapes in the geometry library:


GraphicsAdapter.drawLine(line);
GraphicsAdapter.drawCircle(circle);
GraphicsAdapter.drawRectangle(rect);
The adapter class is responsible for transforming one type of object into another.


The Composite Pattern

The aim of the composite pattern is to allow us to treat groups of objects as if they were a single object. For example, if we have a Warrior object in our 3D video game, he might have some component objects:


interface GameObject {
  public void draw();
  public void move();
  public void rotate();
}

class Armor implements GameObject {
  public void draw(){ }
  public void move(){ }
  public void rotate(){ }
}

class Weapon implements GameObject {
  public void draw(){ }
  public void move(){ }
  public void rotate(){ }
}

class Warrior implements GameObject {
  private Armor armor;
  private Weapon weapon;

  public void draw(){ }
  public void move(){ }
  public void rotate(){ }
}

The warrior objects each have a weapon and armor object. There are many operations we would apply to the Warrior object - such as moving, rotating or drawing, that we would want to apply to all of the component object as well.

With the composite pattern, we create a class for combining GameObjects:


class GameObjectComposite implements GameObject {
  private ArrayList<GameObject> objects = new ArrayList<GameObject>();

  public void add(GameObject obj) {
    objects.add(obj);
  }

  public void draw() {
    for(GameObject obj : objects) {
      obj.draw();
    }
  }
  public void move() {
    for(GameObject obj : objects) {
      obj.move();
    }
  }
  public void rotate() {
    for(GameObject obj : objects) {
      obj.rotate();
    }
  }
}

Now we can inherit the Warrior class from the GameObjectComposite class and we will automatically draw all sub-objects:


class Warrior extends GameObjectComposite {
  private Armor armor;
  private Weapon weapon;

  public Warrior() {
    armor = new Armor();
    add(armor);
    
    weapon = new Weapon();
    add(weapon);
  }

  public void draw() {
    // draw the warrior

    // draw everything else
    super.draw();
  }
  public void move() {
    // move the warrior

    // move everything else
    super.draw();
  }

  public void rotate() {
    // rotate the warrior

    // rotate everything else
    super.draw();
  }
}

The Facade Pattern

The goal of the facade pattern is to fit a new interface on an existing body of code. This is helpful when the existing code has a poorly-designed or hard to use interface.

For example, the Socket class (and related classes) in the Java standard library are used for networking. The API is modeled after the Berkeley socket API developed in the early '80s to be familiar to C programmers. It is also somewhat low-level since it can only be used to send raw bytes, not higher level objects such as ints or Strings.

It may be useful to develop a facade over this interface to allow us to send and receive data more simply.

The goal of a facade is to provide just the needed functionality in a simple way:


class SocketFacade {
  public SocketFacade(String host, int port);

  public void close();
  public void send(String data);
  public bool dataReady();
  public String read();

  private Socket sock;
}

The implementation would use the normal, more complex Java Socket API. A benefit of the facade pattern is that our code will only be coupled with the SocketFacade class, allowing us to more easaily replace the Java Socket API with something else if we needed to.


The Flyweight Pattern

The goal of the flyweight pattern is to minimize memory usage by objects that can share information. For example, if we have the following base class for representing enemies in our game:


abstract class Enemy {
  // the 3D coordinates for drawing the enemy
  protected Model model;

  // draw the enemy to the screen using the model
  public void draw() {
    // ...
  }

  abstract public void attack();
  abstract public void defend();
}

Then we can make a derived class for a Warrior enemy:


class Warrior extends Enemy {
  public Enemy() {
    model = new Model("warrior.md3");
  }

  public void attack() {
    // solider attack
  }
  public void defend() {
    // solider defense
  }
}

The problem here is that if we make several Warrior objects, they will each load their own models, even though they are identical. This will result in a lot of wasted memory.

The flyweight pattern addresses this issue by separating an object into its shared and non-shared parts:


class Warrior extends Enemy {
  // now we take the model as a parameter
  public Enemy(Model m) {
    model = m;
  }

  public void attack() {
    // solider attack
  }
  public void defend() {
    // solider defense
  }
}

// we use a factory class to produce Warriors
class WarriorFactory {
  // the actual model is owned by the WarriorFactory
  private static Model warrior_model = new Model("warrior.md3");

  public static void createWarrior() {
    return new Warrior(warrior_model);
  }
}

Now all of the Warrior objects share the same model object, saving lots of memory.

Copyright © 2018 Ian Finlayson | Licensed under a Creative Commons Attribution 4.0 International License.