Design patterns - introduction

From Juneday education
Jump to: navigation, search

Description

This is just an introduction. The Strategy pattern is used as an example of a design pattern, but we will return to this pattern in a later chapter with a deeper look at it.

The introductory lecture talks about what design patterns are and where they come from. It tries to explain that design patterns are simply catalogs of solutions to common design problems, which have been discovered by experienced software developers and architects.

Originally the patterns came from physical architecture and later on, people started to find patterns in software code which were solutions to general problems much like the original architecture patterns.

This first lecture explains that in software design patterns, the patterns can be grouped in categories. We will look at some patterns later on from the following categories:

  • Creational patterns - how objects are created
  • Structural patterns - how classes and objects are organized and composed
  • Behavioral patterns - how objects and modules behave

The first small example, the Strategy pattern, is a behavioral pattern which shows how behavior which differs between similar objects can be encapsulated in a separate object described by an interface. This behavioral object can be replaced and exchanged in runtime.

In the Java API (the class library which comes with the Java installation) uses strategy for the sort methods in Arrays and Collections, which use the fact that the behavior of comparing two similar objects can be encapsulated in a Comparator object (described by the java.util.Comparator<T> interface. This allows for a decoupling of the comparison behavior from both the objects being compared and from the sort method. With a decoupled comparator object, the sort method can remain oblivious of how the actual comparisons used in the sorting algorithm takes place, which in turn means that we can use the same sort method on the same list of objects many times, but with a totally different comparison behavior.

An obvious example of how powerful this is, would be a product listing in a web store. The web application would hold a list of Product objects which can be sorted according to their natural order, say "product name". The customer might want to re-order the list, instead ordered on "price" (lowest price first). So the customer clicks on the "price" heading and the web application resorts the list, now using a comparator objects which can compare two Products on their price. The list is the same each time, a list of Product references. And the sort method is the same. The calls to the sort method, however, differs depending on what key should be used for sorting. The system may have comparators capable of comparing two products on Name, Price, Item number, Customer review or some other key.

Chapter videos

English chapter videos

Exercises

The example from the lecture was an application of the Strategy pattern where a text-based adventure game had characters whose weapon behavior was encapsulated in an interface WeaponBehavior. The point of the example was to show a way to apply the Strategy Pattern to let all characters in the game have an abstraction for a weapon which they could use.

The abstraction was implemented with the use of a simple interface WeaponBehavior which declared only one public method, int useWeapon(), which is a behavior for using some weapon and get the weapon's injury as an int representing the damage (as health points) as the result of using the weapon.

This allowed us to design the abstract base class Character in a way so that all concrete characters (described in concrete subclasses to Character) whould "have a" weapon (of type WeaponBehavior). Now, all types of characters can have a behavior (a method) void fight(Character opponent) which could make use of the weapon (if the character has any) to injure the opponent.

Making WeaponBehavior an interface allows us to treat basically any object as a weapon (it the object's class implements WeaponBehavior and the only method int useWeapon()).

This is a flexible and future proof design, because it lets us add new classes which represent something which can also be used as a weapon in the game, regardless of the class hierarchy (using inheritance) of the new class. We can write a new class Staff which is a walking rod used by perhaps wizards, and let Staff inherit WalkingAid (or something) and also implement WeaponBehavior so that it can also be used as a weapon in a fight!

All characters have a changeWeapon(WeaponBehavior weapon) method, which also lets us change the weapon of a character in runtime (while the game is running). This allows a character to drop its current weapon and pick up a new one.

Here's the UML design of the public interface of the classes used in the example:

+----------------------------------------+     +----------------------------+
|         Character (abstr)              |     |      WeaponBehavior (I)    |
+----------------------------------------+     +----------------------------+
| +Character(String)                     |     | +useWeapon() : int (abstr) |
| +Character(String, WeaponBehavior)     |     +----------------------------+
+----------------------------------------+      ^              ^           ^
| +name(): String                        |      .              .             .   <implements>
| +changeWeapon(WeaponBehavior) : void   |      .              .               .
| +weapon() : WeaponBehavior             |   +--------------+  +-------------+  +-------------+
| +takeDamage(int) : void                |   | Club         |  |  Sword      |  | Shotgun     |
| +fight(Character) : void (abstract)    |   +--------------+  +-------------+  +-------------+
| +health() : int                        |   | +useWeapon() |  | +useWeapon()|  | +useWeapon()|
| +toString() : String (@override)       |   | +toString()  |  | +toString() |  | +toString() |
+----------------------------------------+   +--------------+  +-------------+  +-------------+
                ^                             
               / \
              /   \  <extends>
             /     \
            /       \
+---------------+    +--------------+
|    Troll      |    |    Knight    |
+---------------+    +--------------+
| +fight():void |    | +fight():void|
+---------------+    +--------------+

The Character class is abstract, and the only abstract method in the class is fight(Character). WeaponBehavior is an interface with one abstract method, int useWeapon(). There are two concrete classes extending Character, Troll and Knight. Troll and Knight override the method fight(Character) (because it's abstract and they are concrete classes).

The idea is that a Knight implements fight by saying somthing knightly (to std out) and uses its weapon and reports the result by printing itself and the opponent. (The inherited toString() from Character prints out the name, health and weapon)

We'll provide the Character class and the WeaponBehavior interface for you below.

// Save in Character.java
public abstract class Character{
  private WeaponBehavior weapon;
  private String name;
  private int health = 100;
  public Character(String name){
    this.name = name;
  }
  public Character(String name, WeaponBehavior weapon){
    this(name);
    this.weapon = weapon;
  }
  public String name(){ return name; }
  public void changeWeapon(WeaponBehavior weapon){
    this.weapon = weapon;
  }
  public WeaponBehavior weapon(){ return weapon; }

  public void takeDamage(int damage){
    // unsafe for now
    health -= damage;
  }
  public abstract void fight(Character opponent);
  public int health(){ return health; }
  public String toString(){
    return name 
      + " with health: " + health 
      + ((weapon!=null)?" carrying " + weapon :"");
  }
}

// Save in WeaponBehavior.java
public interface WeaponBehavior{
  // Outputs a descriptive text and
  // returns the weapons dammage
  public int useWeapon();
}

Your task is to implement the Troll and Knight classes (they both extend Character) as well as the Sword, Club and Shotgun classes (they all implement WeaponBehavior). The damage from a Club is 20, from a Sword 15 and from a Shotgun 45. And example implementation of Knight's overridden version of fight could be:

  @Override
  public void fight(Character opponent){
    if(weapon()==null){
      System.out.println("Hitting and kicking (in lack of weapon)!");
      opponent.takeDamage(5); // damage from an unarmed Knight is 5
    }else{
      System.out.println("-None shall pass!");
      System.out.println(name() + " nobly uses its " + weapon() + " against the simple " + opponent.name());
      opponent.takeDamage(weapon().useWeapon());
    }
    System.out.println(" Score after attack: " + this + " - " + opponent);
  }

In the above, you'll see the basic example logic for the fight implementation. If the Knight is unarmed, it outputs a descriptive text and inflicts a damage of 5 to the opponent. Otherwise it uses its weapon (whatever it may be) and outputs some knightly words and inflicts the damage which is the result from using the weapon on its opponent. The last things that happens in the fight implementation is that the score is output to standard out. The score is simply printing this and opponent (the plus operator triggers toString() on the characters involved - see Character to understand what toString() represents).

When you have finished the Knight, Troll, Club, Sword and Shotgun classes, you may write a test program simulating a game round or use the below:

public class Game{
  public static void main(String[] args){
    Troll troll = new Troll("Lillfjant", new Club());
    Knight sirJames = new Knight("Sir James", new Sword());
    System.out.println("<event>" + sirJames 
                       + " runs into the troll " 
                       + troll);
    System.out.println("<event>The troll attacks");
    troll.fight(sirJames);
    System.out.println();
    System.out.println("<event>The knight returns the favor");
    sirJames.fight(troll);
    System.out.println();
    System.out.println("<event>The knight realizes that the sword isn't enough");
    System.out.println("<event>The knight finds a shotgun, drops the sword and picks the shotgun up");
    sirJames.changeWeapon(new Shotgun());
    System.out.println("<event>The troll attacks again");
    troll.fight(sirJames);
    System.out.println();
    System.out.println("<event>The knight fires the shotgun");
    sirJames.fight(troll);
    System.out.println();
    System.out.println("<event>The knight shoots again");
    sirJames.fight(troll);
    System.out.println();
    //System.out.println("Game over");
    //System.out.println("Score: " + sirJames + " - " + troll);
    Knight blackKnight = new Knight("Fistfighting Black Knight");
    System.out.println("<event>The evil, but unarmed Black Knight shows up and attacks Sir James");
    blackKnight.fight(sirJames);
    System.out.println();
    System.out.println("<event>Sir James gets tired of this bad day and fires three rounds against Black Knight");
    sirJames.fight(blackKnight);
    sirJames.fight(blackKnight);
    sirJames.fight(blackKnight);
  }
}

As a bonus, you may put the classes in appropriate packages (and corresponding directories!). Use your imagination to come up with a reasonable package structure. Example: org.adventure.character (characters) org.adventure.weapon (weapons and the interface) org.adventure.application (main class)

Concluding remarks

This example is very simplified and could of course be elaborated further. For instance, the weapons could be completed with some more methods and state like a name and sound etc. That would allow for making fight even more of an abstraction. Remember, the goal is to make the classes as little aware of each other as possible. We accomplished this to some degree. The character classes only know what a weapon is in very general terms (it's something which can be used as a weapon, and it has a String representation).

Note that there is no knowledge about what particular type of weapon a character has. That is totally decided in runtime by the Game class (which could be seen as a kind of game engine). The code in the character classes only program against the interface WeaponBehavior. They are totally unaware even of what concrete weapons exist. A character can have its weapon changed at runtime by the game engine, using the changeWeapon method. The classes don't care, they only know that an instance either has a weapon of type WeaponBehavior or it has no weapon.

TODO: You could implement a special weapon "Unarmed" or (Default) which could take the damage and description as parameters to the constructor. This way, an unarmed Knight could be armed with a Default weapon with the description "Fights with hands and feet" and the damage 5 for instance. Try it if you feel like it.

Suggested solutions

Expand using link to the right to see the full content.

public class Knight extends Character{
  public Knight(String name){ super(name); }
  public Knight(String name, WeaponBehavior weapon){ super(name, weapon); }

  public void fight(Character opponent){
    if(weapon()==null){
      System.out.println("Hitting and kicking (in lack of weapons)!");
      opponent.takeDamage(5);
    }else{
      System.out.println("-None shall pass!");
      System.out.println(name() + " nobly uses its " + weapon() + " against the simple " + opponent.name());
      opponent.takeDamage(weapon().useWeapon());
    }
    System.out.println(" Score after attack: " + this + " - " + opponent);
  }

}

public class Troll extends Character{
  public Troll(String name){ super(name); }
  public Troll(String name, WeaponBehavior weapon){ super(name, weapon); }

  public void fight(Character opponent){
    if(weapon()==null){
      System.out.println("Jag ska meja ned hela tomtearmén, utan vapen");
      opponent.takeDamage(10);
    }else{
      System.out.println("-Bränn, bränn, bränn!");
      System.out.println(name() + " uses " + weapon() + " against " + opponent.name());
      opponent.takeDamage(weapon().useWeapon());
    }
    System.out.println(" Score after attack: " + this + " - " + opponent);
  }
}

public class Club implements WeaponBehavior{
  public int useWeapon(){
    System.out.println("The club makes a swooshing sound");
    return 20;
  }
  public String toString(){ return "Ugly wooden club"; }
}

public class Sword implements WeaponBehavior{
  public int useWeapon(){
    System.out.println("The sword shines and cuts through the air before it hits its target");
    return 15;
  }
  public String toString(){ return "Excalibur"; } // excalibur should be a singleton!
}

public class Shotgun implements WeaponBehavior{
  public int useWeapon(){
    System.out.println("Schklikt, klikt Ka-POW goes the shotgun");
    return 45;
  }
  public String toString(){ return "Shotgun"; }
}

Chapter links

Source code

Books this chapter is a part of

Further reading

Book TOC | previous chapter (tbd) | next chapter