Encapsulating the model.
What we have looked at up to now is really only the state of the model.
That is, how its structure and values are represented;
in this case by a 2D array and an enum type respectively.
We have also introduced an initial encapsulation through the TiledMap3 class developped in the previous chapter.
It is now time to focus on the design of the model
by reviewing again the requirements identified in
the case study introduction.
These requirements stated that a client application should be able to use the model to ...
- create a map of any size;
- retrieve the size of the map;
- model different terrain contexts;
- initialize the map to some specific terrain type from the context;
- get and set the terrain type for a particular (x,y) tile;
- render the map to some output device;
- allow a border to be set to a terrain type;
- set subareas of the map to a specific terrain type, perhaps randomly;
Reviewing these requirements we can identify that we have met items A to F
although we should now review our solution and consider any refinements
that can make our solution more robust, flexible and maintainable.
Although we have implemented requirement F,
this is only for the console device.
This requirement will be further explored in the chapter on
rendering with a strategy pattern
for other output devices.
In relation to requirement C, the terrain context is application dependant
in terms of its values, what we require to do here is design a model that is as flexible as possible in terms of including different terrain contexts.
All we can know at this stage is that we require an enum called TerrainType.
Therefore code the enum declaration in a seperate .java file
that can be replaced to reflect the terrain context requirements of an actual scenario.
The behaviour of the tiled map model should therefore only refer to TerrainType
and not require to know anything about its values.
-
Create a seperate 'TerrainType.java' file
and define the TerrainType enumeration to represent
the terrain context for
outdoor landscape
with
grass, tree, bush, earth, rock, river, water, fence, cliff and ravine
tile types.
Remember to adopt good programming practice in terms of commenting, naming, layout, etc.
The tiled map class can be designed by first identifying the behaviour without having to worry about its implementation
(i.e. define WHAT we can do without defining HOW we are going to do it).
this is accomplished by first defining an interface
that defines public methods with no iplementation.
-
Create the file 'ITiledMap.java'
and define the ITiledMap interface
for the exposed behaviour of the tiled map model.
At this stage only include the behaviour we have met already for requirements A to F.
-
Add the setBorder(...) method to the interface that will allow us to meet requirement G;
that is, the ability to define a border on the map set to a supplied terrain type.
Allow optionally to define the width of this border.
So we now have the TerrainType enum
and ITiledMap interface components
that both design & define a representation for the different tiles available on our map
together with what we can do with a map.
From this design phase, we now require to move on to implementation;
i.e. programming how this design works.
The state (2D array) should now be encapsulated into a concrete class together with the implementation of the behaviour defined by the interface.
This concrete class will also require constructor method(s) to be defined
since they are not part of an interface.
-
Create the 'TiledMap.java' file
and define the TiledMap class
that implements the ITiledMap interface.
- Define the 2D array as a private field.
-
Include overloaded constructors for defining
- a tiled map of a defined size (leave the array elements initialised as null) and
- a tiled map of a defined size with the terrain type to be used to initialise it.
-
Implement the interface methods as before.
-
Remember you now have the additional setBorder() behaviour to implement;
the width parameter for this method should ve validated and appropriate response made
such as having the method return a boolean indicating success or failure.
-
In a seperate file construct a main application class
to test and exercise the model.
You ight do this by drawing out some outdoor landscape map on paper
and writing appropriate code that will represent this map using the model.
-
Compile, execute and review the application.
These activiteies illustrate that when designing and implementing �
- consider how the entity you are developing will be used in terms of its publicly exposed behaviour, this can often be expressed via an interface;
- analyse how a client will instantiate objects of your entity class, this identifies the required constructors; and
- evaluate the required client software code in terms of calling members, supplying parameters and retrieving results.
Adding more behaviour.
Let us now consider meeting requirement H which requires the model to expose behaviour
to allow a specific terrain type to be set in some rectangular subarea within the map, perhaps randomly.
The random generation will be considered later,
let us first consider setting an area to a single terrain type value.
this behaviour will be exposed through the setArea(...) method taking the subarea definition and terrain type as parameters.
-
Consider how to define the subarea;
this might be in terms of ...
- coordinates of one corner with the width and height of the subarea;
- coordinates of the centre of the subarea with the width and height of the subarea;
- coordinates of diagonally opposite corners;
- or some other definition.
Decide on one of these to use as the mechanism for subarea definition.
-
Consider what happens if an invalid subarea definition
(i.e. partly or wholly outside the map)
is supplied as parameter to the method.
Possible approaches to this are to ...
- return without setting any part of the subarea;
- set the part of the subarea (if any) that is within the map, ignoring the rest.
Also consider what indication the method should provide to the client code that an invalid subarea has been supplied.
this could be accomplished by ...
- providing no indication;
- returning a bool;
- throwing an exception.
Consider the effect of these approaches on what the client application can and cannot do
and hence decide on the approach to take in your model.
-
Amend the ITiledMap interface
to include the setArea(...) method as you have identified above.
-
Amend the tiledMap class to provide an implementation of the setArea(...) method.
-
Amend your exerciser application to include tests for the correct operation of
the setArea(...) method,
including its handling of an invalid subarea definition.
Requirement H also indicated that the model should have
the ability to randomly generate a subarea of map.
This will require our code to create random numbers;
in Java this is handled by the java.util.Random class.
To review how to generate random numbers in Java you can review the following references:
the term "randomly generate" is ambiguous;
it might apply to
- the subarea definition;
- the terrain tile type to use;
- the position of the tiles; and/or
- the number of tiles to generate.
So to introduce random area setting behaviour we will first have to decide
on which of the above we will incorporate into our model.
-
Design appropriate subarea random generation behaviour by adding methods (or one overloaded method) to the interface.
-
Amend the concrete class to implement the revised interface.
-
Add appropriate exercise code in your main application.
-
As usual, compile, execute and review your application and the tiled map model.
Reviewing this section it is quite surprising that such a simple requirement
as setting a subarea of the map could lead to so many design decisions needing to be made.
Never under-estimate the complexity of what are apparently simple problem areas!