Patterns.. Patterns.. Patterns! Patterns have become something that surround us everywhere. Well, not everywhere in the real world, but definitely so in the world of Programing Languages.
Today, in this post, I am going to write about the Decorator Pattern. And of course, i will implement it in source code using Java. Doing a simple search on Google tells you that one of the best examples of this pattern's implementation is the classes of the package java.io. BufferedReader, BufferedWriter, FileReader, FileWriter etc etc, are all examples of using the decorator pattern.
So, What is the decorator pattern?
The decorator pattern, as its name suggests, is a pattern that lets you decorate an 'object' by adding or removing certain features. Note that I emphasize the word 'object'. Lets see why.
I assume that the reader of this post is aware of the concept of inheritance and its implementation in Java.
Now lets illustrate the concept by an example. Most of us might have played computer games ryt? There are many games that let 2 or more players play at the same time against several computer players(called Bots). Bots are similar to computer players, but they differ in the way they play because the computer handles their gameplay. And most of the times, we find that bots have a few different features as compared to human players.For example, a bot has a skill level/difficulty level, whereas a human player may have experience points. And both might have common features 'health' etc etc.
So, if we were to model this scenario using OOPS, one possible way would be to create a base class called Player with attributes common to all Player's like 'health', and functions like 'play()' and 'reduceHealth(). Now we can have a class called 'HumanPlayer' that extends the Player class and adds features like 'experience points', overrides the play() method and the reduceHealth() method.
We can also have a class 'Bot' that extends the 'Player' class that overrides the play() and reduceHealth() functions and attributes like skillLevel.
We can have a Game class that has a list of players, and a list of Bots.
Its pretty simple till now ryt? The game begins by initializing the player list with player objects and the Bot list with Bot objects. Everyone plays and everyone is happy.
But if games were so simple, they wouldn't be so much fun now, would they? Lets try do add a new feature to our game. Lets suppose that when a human player interacts with a certain object in the game landscape, he becomes a power player such that for any kind of attack that he receives by the bots, his health is reduced by only 1 unit instead of being reduced by several units and inflicted by the bots. (Note : The game is responsible for handling the duration of the powerplay.)
Now, there are 2 approaches to implement this feature.
- The Normal Approach : Subclass the HumanPlayer class and override the reduceHealth() method in the subclass. When the player in the game ineracts with the object that gives him a power play, create a new object of this subclass, and initialize it with the values in the already existing HumanPlayer object and use this new subclassed object in your game.
Now, the problem with this approach is that when you create a subclass for a given scenario, you are adding another node in the inheritance hierarchy that lets you create a whole new bunch of objects that behave differently for a given scenario. Suppose you have 20 such scenarios, you will have to create 20 subclasses in the hierarchy just to give 20 different powers to your Human players. This doesn't make much sense when you realize that you will always be using the features of the already existing HumanPlayer in your game for each subclassed object that you create. Waste of precious memory? Ya, you guessed right. So, what do we do? This is where the decorator pattern comes to the rescue.
- The Decorator Pattern Approach : If we observe the requirements carefully, we can see that the what we actually need to do is to decorate the player, who has managed to get a power play by interacting with a game object; with a differently implementation of the reduceHealth() method. Notice that I only need to decorate a single object at runtime, the object that will use power play.
So, what we need to do is to create a new class called PowerPlayer that subclasses the Player class and overrides the implementation of the reduceHealth() for the power play mode. This class is initialized with an instance of a Player to which it delegates other responsibilities.
Note that we can also choose to subclass the HumanPlayer class. But that can create a restriction in the game when in the future, you decide to enable even bots to have a power play. In that case you would have to duplicate the reduceHealth() code for the Bot.
Maybe you never want to do such a thing. Maybe you might have to. The decision of the class that you choose to subclass is a design decision and is based upon your foresight and requirements.
Since this is just an example, and in order to keep it simple, I will subclass the HumanPlayer.
By subclassing the HumanPlayer, now what I can have in my game is a feature that allows several HumanPlayer objects to coexist but only a single HumanPlayer object to be a power player as determined by the game. Had I chosen to subclass the Player class, I could have had any player- bots or human players, to be able to possess a power play feature by 'decorating' selected player objects with the PowerPlay feature.
Here is the implementation. The only difference here is that I am going to have only one player and several bots, just to keep it simple and not confuse game logic with the logic of implementing the decorator class. The PowerPlayer is my decorator class in this example. But please note that this is only a demonstration of concepet. And it might not be feasible in all situations to apply this pattern. Nor is it an example of how one should implement this in a game. So, just read the code, and have some fun enjoying the beauty of the Decorator pattern.
Player.java
public abstract class Player{ private int health; public abstract boolean play(); public boolean reduceHealth(int value) { health-=value; System.out.println("Health reduced By : "+ value); return true; } public int getHealth() { return health; } public void setHealth(int health) { this.health = health; } }
Bot.java
public class Bot extends Player{ private int skillLevel; @Override public boolean play() { System.out.println("Bot (With Skill Level : "+ skillLevel + ") Is Ready To Play"); //Code that implements how a bot plays return true; } public int getSkillLevel() { return skillLevel; } public void setSkillLevel(int skillLevel) { this.skillLevel = skillLevel; } @Override public boolean reduceHealth(int value) { //The health of bots reduces slowly as compared to players. setHealth(getHealth()-(value-1)); System.out.println("Health reduced By : "+ value); return true; } public boolean attackPlayer(Player p,int healthReductionValue) { p.reduceHealth(healthReductionValue); return true; } }
HumanPlayer.java
public class HumanPlayer extends Player{ private int experiencePoints; public int getExperiencePoints() { return experiencePoints; } public void setExperiencePoints(int experiencePoints) { this.experiencePoints = experiencePoints; } @Override public boolean play() { System.out.println("Player (With Experience Points : "+ experiencePoints + ") Is Ready To Play"); //Code that implements how a human player plays return true; } @Override public boolean reduceHealth(int value) { setHealth(getHealth()-value); System.out.println("Health reduced By : "+ value); return true; } }
PowerPlayer.java -- The Decorator Class
public class PowerPlayer extends HumanPlayer{ private HumanPlayer player; public PowerPlayer(HumanPlayer player) { this.player = player; } @Override public boolean play() { System.out.println("Power Player"); player.play(); return true; } @Override public boolean reduceHealth(int value) { //Power Player's health reduces very slowly value=1; System.out.println("Setting Health Reduction Value To : "+ value); player.reduceHealth(value); return true; } public Player getPlayer() { return player; } }
Game.java
public class Game { private Player player1; private List <Bot> bots; public Game() { bots=new ArrayList(); } public Player getPlayer1() { return player1; } public void setPlayer1(Player player1) { this.player1 = player1; } public List <Bot> getBots() { return bots; } public void setBots(List <Bot> bots) { this.bots = bots; } public void addBots(Bot bot) { bots.add(bot); } }
Main.java
public class Main { /** * @param args the command line arguments */ public static void main(String[] args) { Game game=new Game(); HumanPlayer hp=new HumanPlayer(); Bot b1=new Bot(); Bot b2=new Bot(); //Initialize the Player's values hp.setExperiencePoints(10); hp.setHealth(100); //Initialize the bots values. //b1 is a powerful bot because it has a high skill level b1.setHealth(100); b1.setSkillLevel(10); //b2 is a weak bot because it has a low skill level b1.setHealth(100); b2.setSkillLevel(5); //Lets Start the game. game.setPlayer1(hp); game.addBots(b1); game.addBots(b2); hp.play(); b1.play(); b2.play(); System.out.println("Initial Player Health : " + game.getPlayer1().getHealth()); //Let him take a few attacks by the bots b1.attackPlayer(game.getPlayer1(), 8); b1.attackPlayer(game.getPlayer1(), 4); System.out.println("Player Health : " + game.getPlayer1().getHealth()); //Now suppose the player picks up an artifact that //allows him to become a power player //Note that this Power Play option should be available for a //fixed duration, determined by the game. e.g. 30 seconds //This is how it 'can' be implemented System.out.println("\nStarting Power Play Mode"); PowerPlayer pp=new PowerPlayer(hp); game.setPlayer1(pp); //Now let the game continue //And let the player receive some attacks by the bots b1.attackPlayer(game.getPlayer1(), 8); b1.attackPlayer(game.getPlayer1(), 20); System.out.println("Player Health : " + game.getPlayer1().getHealth()); //After 30 seconds, remove the power player settings //This is a possible implementation System.out.println("\nEnding Power Play Mode"); game.setPlayer1(pp.getPlayer()); System.out.println("Player Health : " + game.getPlayer1().getHealth()); //Now let the game continue //And let the player receive some attacks by the bots b1.attackPlayer(game.getPlayer1(), 8); b1.attackPlayer(game.getPlayer1(), 20); System.out.println("Player Health : " + game.getPlayer1().getHealth()); //Finally How you end the game is up to you! } }
Signing Off
Ryan
No comments:
Post a Comment