Map rendering using a strategy pattern.

Continuing the tiled map case study, we now look again at requirement F that states that a client should be able to render a map on some output device. Currently we have rendered the map on a console, but how might we render the tiled map on some other display technology? we will identify some issues with our existing rendering approach and redesign the rendering using a strategy pattern to resolve these issues.

Issues with the rendering approach.

Let us look again at how we might render the tiled map on some display technology; by "display technology" we mean the actual device or media that presents the map to the user in some visual, aural or tactile manner using some relevant representation for different terrain tile types. For example, we might

You have already implemented a concrete render method within the TiledMap class but this method is specific to the console display technology and particular character representation. Consider what we would have to change if we want to

There may be many other different display technologies and/or terrain type representations that a client may wish to use in the future. Currently this would require re-coding of the source code base for our model; something we wish to avoid as this requires the client to have access to the source code, be able to understand it, amend code, introduce new errors, compromise robustness and all sorts of other nasties.

Part of our development responsibilities is to identify the requirements for future flexibility and build this into our design so that a client can introduce new funtionality in the future with minimal modifications to the existing design and code base.

Rendering with a strategy pattern.

A strategy design pattern is one of the many design patterns that are used in software development. This will provide a flexible mechanism for including a rendering engine and, in addition, will allow us to change the rendering engine dynamically (i.e. while the application is running). The rendering engine is the render method but in the context of supporting a specific display technology.

Review the strategy design pattern presentation (opens in new window) in order to

In this chapter we are going to see this design pattern in action by building it from a "bottom-up" perspective . Following is a step-by-step description of how the strategy pattern for a tiled map rendering engine might be implemented:

  1. Define the MapRenderer interface exposing the single method render (TiledMap aMap).
    Do this in a separate '' file.
  2. In the TiledMap class, add the Renderer field of type MapRenderer.
    In due course, this field will refer to a concrete class implementing MapRenderer for a specific rendering engine.
  3. The TiledMap constructor(s) must be amended to initialise this field.
    1. In the existing constructor(s) initialize the field to null.
    2. Add additional overloaded constructor(s) based on the existing parameters but adding a MapRenderer parameter to initialise the field.
  4. Include a setter for the mRenderer field; this allows us to change the rendering engine dynamically via client code.
    1. Add this setter method to the ITiledMap interface.
    2. Add the method implementation to the tiledMap class.
  5. Replace the implementation of the render() method of the tiledMap class to
    • do nothing if the mRenderer field is null;
    • otherwise, call mRenderer.render (this)
  6. The responsibility for rendering the map has now been delegated to the composed mRenderer object. We should now have something like this ...
       class TiledMap {
          ... ... ...
          private MapRenderer mRenderer;
          public void setRenderer (MapRenderer aRenderer)
          {   mRenderer = aRenderer; }
          ... ... ...
          public void render ()
             if (mRenderer == null) return;
             mRenderer.render (this);
          } // end render method.
       } // end TiledMap class.

  7. So where is the actual rendering done?
    Define a concrete class ConsoleRenderer that implements the MapRenderer interface.
       class ConsoleRenderer implements MapRenderer
          public void render (TiledMap aMap)
             // variation on the console rendering code you have written previously
          } // end render method.
       } // end ConsoleRenderer class.

    the implementation of the render(...) method in this class will be similar to that in previous code but it will not be able to directly access the TiledMap fields since it is not a member of this class. It will require instead to use the public API of TiledMap on the this parameter.
  8. Client code can now use this model in the following way:
       final TiledMap map = new tiledMap (100, 600, TerrainType.GRASS, new ConsoleRenderer());
       ... ... ...
       ... ... ...
       map.setRenderer (new OtherRenderer(...) );

    Assume in the above code that a OtherRenderer class has been defined.
    Draw a diagram of the variables, objects and fields created when the above code segment is executed.
  9. We now have a flexible map model that allows us to "plug in" different rendering engines.
    Illustrate this by creating a client that will display a map on the console in terms of whether or not the tile areas are passable; '.' will indicate a passable tile and 'X' an impassable tile.
    Remember in an earlier chapter we should have added an isPassable() property to TerrainType.

    You can do this by
    1. defining a rendering engine class that implements the MapRenderer interface for the required display technology & terrain tile representation;
    2. instantiating an object of this rendering class; and
    3. composing our map with this rendering object.

    Note the above requires no changes to the existing source code, only to new client code.

... and finally ...

In order to clarify the use of the strategy design pattern and the different rendering mechanisms, consider what you would have to change from your current implementation (both map model code and client code) if the following scenario arose.

  1. Reflect on what you would have to do as a "thought experiment"; considering what source code you would have to amend and what new files and/or code you will require to create.

The following steps are optional.
They outline the development of an implementation.

  1. replace the Terraintype enumeration with one that corresponds to the scenario requirements.
  2. create a new rendering engine class for the terrain tile representation defined above in some audio context.
    this is the most substantive element in this development; it will require you to use some technology that will allow you to play and control audio.
    Following is an example of using the java.applet.AudioClip class that you might find useful:.
    1. Download the '5_audio.jar' archive file.
    2. Extract the archive by using the command
       jar xf 5_audio.jar 
      from the command prompt.
    3. Execute the program using
       java AudioClipExample 
      from the command prompt.
    4. Examine '' for useful code snippets.
    Note that seperate audio files are used for different sounds and you will have to create these files for the sounds you need.
    you will have to decide on how sounds relate to terrain tile types.
  3. create a client application that will, amongst other things,
    1. instantiate a TiledMap object, setting it up with an appropriate map for a computer lab layout;
    2. instantiate the new rendering class;
    3. attach the new rendering engine object to the map object; and
    4. call the render() method of the map.
  4. As always, compile, test and reflect on your code.
  5. Note that no part of the existing source code base for our tiled map model requires to be modified. Even TerrainType is not modified, it is replaced. So all coding is new; TerrainType, the rendering engine, and the client application code.

    Reflect on what this means for software maintainability, robustness and flexibility?