Relevant Reading Material

Separation of Concerns

The best way to understand and implement a system is by breaking it into smaller pieces.

  1. Each piece has a reason for its existence.

  2. Each piece has a clear definition of how it can be used.

  3. Each pieces does one job and it does it well.

    • Single responsibility principle - A class should have one and one job.

    • Open Close principle - open to extension closed for modification.

    • Liskov substitution principle - subtypes honor the properties set by their supertypes.

    • Interface segregation principle - clients should never be forced to implement/depend on interfaces/methods they do not use.

    • Dependency Inversion principle - depend on abstractions not concrete implementations when you can.

Design Patterns

A collection of well thought out and well documented solutions to commonly found problems.

  • Creational Patterns - deal with constructing new instances

  • Structural Patterns - deal with relationships between objects

  • Behavioural Patterns - deal with communication patterns between objects

Singelton

Recall our list implementation with Cons and Empty classes. The Empty class had some peculiar properties

  • We could create multiple instance of Empty but they all had to be considered equal. Our implementation then

    1. equals was unconventional, even the IDE refused to auto-create an equals method since there was no state inside Empty objects

    2. hashCode was also unconventional

Can we do better? Can we design a solution that

  • There is one and only one instance of Empty that is used through out of our program.

    1. Equality then can be == since we can rely on the fact that there is always 1 and only 1 object

Singleton UML class diagram

singleton

Static Factory Method

We have been using this pattern, this refers to the static method in our code, typically found in an interface, e.g., createEmpty inside List, that allows us to create the appropriate object.

Factory Pattern

A Factory pattern allows us to hide the creational process of a set of instances for our clients.

Factory UML Class diagram

factory

A non-standard implementation

Creating names, an itemization, for our clients to use in order to refer to our shapes.
class  ShapeName {

  private static ShapeName self = new ShapeName();
  private static ShapeName circleInstance;
  private static ShapeName squareInstance;
  private static ShapeName rectangleInstance;

  static ShapeName circleName(){
    if (ShapeName.circleInstance == null) {
      ShapeName.circleInstance = self.new CircleName();
    }
    return ShapeName.circleInstance;
  }

  static ShapeName squareName(){
    if (ShapeName.squareInstance== null) {
      ShapeName.squareInstance= self.new SquareName();
    }
    return ShapeName.squareInstance;
  }

  static ShapeName rectangleName(){
    if (ShapeName.rectangleInstance == null) {
      ShapeName.rectangleInstance = self.new RectangleName();
    }
    return ShapeName.rectangleName();
  }

  private ShapeName() {}


  private class CircleName extends ShapeName {
    private CircleName(){}
  }

  private class SquareName extends ShapeName {
    private SquareName(){}
  }

  private class RectangleName extends ShapeName {
    private RectangleName(){}
  }
}
The factory for shapes checks creates the appropriate object given the shape name requested by the client.
  private static ShapeFactory instance;
  private final Posn posn;
  private final Integer radius;
  private final Integer side;
  private final Integer height;
  private final Integer width;

  public static ShapeFactory getInstance() {
    if (ShapeFactory.instance == null) {
      ShapeFactory.instance = new ShapeFactory();
    }
    return ShapeFactory.instance;
  }

  private ShapeFactory() {
    this.posn = new Posn(0, 0);
    this.radius = new Integer(10);
    this.side = new Integer(10);
    this.width = new Integer(20);
    this.height = new Integer(10);
  }

  public Shape getCircle(ShapeName name) {
    if (ShapeName.circleName().equals(name)) {
      return new Circle(posn, radius);
    } else if (ShapeName.squareName().equals(name)) {
      return new Rectangle(posn, side, side);
    } else if (ShapeName.rectangleName().equals(name)) {
      return new Rectangle(posn, width, height);
    } else {
      throw new IllegalArgumentException("Unknown product : " + name);
    }
  }
}

I’ll let you read up on the Abstract Factory Pattern

Template Method

We use the template method pattern when we have a predefined sequence of steps that we need to perform, but, the individual steps themselves can vary depending on the use cases that we need to address.

Template Method UML Class Diagram

templatemethod

Consider a video game that contains different players holding different roles

  • builder, builders are set to a construction site and then the work on the construction site until it is done.

  • warrior, warriors are in the barracks practising until they are set to join a battle, then then fight in the battle until the battle is won, then they return to their barracks or the battle is lost at which point the warrior is dead.

  • miner, miners are set to a mine and work on retrieving raw materials from the mine. e.g, gold, coal etc.

The sequence of tasks for each role shares some commonalities while at the same time each role needs to perform a specialized (different) task.

game

Iterator Pattern

You have already seen this pattern. In fact Java has it implemented for most of its collection classes and allows you to implement it for your own.

Composite Design Pattern

We have been using something similar (if not the same) for our immutable lists implementation

composite

Compare to our immutable List design

immutableList

Adapter Pattern

We use the adapter pattern to allow an existing interface to be used as another interface. Typically used when there is an existing class/interface that cannot be altered/extended and we need a new class/interface that does not exactly "fit" the existing interface. We create an new class/interface to "adapt" the existing interface and confront to the new requirements.

For a visual, similar problem in real life, think of a electric plug adaptor that we use to connect a device that we have that is designed for a different kind of plug.

Adapter UML Class Diagram

adapter

Let’s use our adapter pattern to add drawing our shapes with colour

Adapter
public abstract class Adapter {

  protected Shape original;

  public Adapter(Shape original) {
    this.original = original;
  }

  public abstract void draw(Colour colour);

}
Concrete Adapter for circle
public class CircleAdapter extends Adapter {

  public CircleAdapter(Shape original) {
    super(original);
  }

  @Override
  public void draw(Colour colour) {
    System.out.println(original.area() + " in colour " + colour.toString());
  }
}
Concrete Adapter for rectangle
public class RectangleAdapter extends Adapter {

  public RectangleAdapter(Shape original) {
    super(original);
  }

  @Override
  public void draw(Colour colour) {
    System.out.println(original.circumference() + " in colour " + colour);
  }
}
Simple client
public class Main {

  public static void main(String[] args) {
      Adapter circle = new CircleAdapter(new Circle(new Posn(1,1), new Integer(10)));
      Adapter rect = new RectangleAdapter(new Rectangle(new Posn(1,1), new Integer(10), new
          Integer(20)));

    circle.draw(new Red());
    circle.draw(new Green());
    rect.draw(new Blue());
  }
}
  • Can we hide the concrete adapter types from our client? How?

  • Can we find a better way to create Colour s that better captures their role as a single entity in our program?

  • Can you think of a way to "mimic" inheritance without using extends but using Adaptors instead?

The Interpreter Pattern

Used to capture the structure of sentences in a language and evaluate them.

Interpreter Pattern UML Class Diagram

Simple version of interpreter pattern that represents sentences of a language
Figure 1. Simple version of interpreter pattern that represents sentences of a language
  • Looks very close to composite pattern :)

Let’s build a small arithmetic calculator

Simple calculator
Figure 2. Simple calculator

What do we need to do in order to extend our calculator and allow for global variable, e.g.,

x = 3 + 4
y = x - 2
y

Should return 5.

Simple calculator
Figure 3. Simple calculator
  • Why is Define not an Op?

    1. What happens when we make it an Op?

    2. Can you think of a programming language where defining a variable is consider as an expression?

  • Why is Var a value?

    1. What if we make Var a non-value expression?

Pretty Print sentences of our language

Visitor Pattern

Double Dispatch

Recall from our assignment the Rock Paper Scissors game? Can we solve that problem without using

  • casting

  • instanceof

  • getClass()

  • equals() method that we implemented which in turn uses some of the preceding language features?