Hi All. Sorry about the length of the post, I have a hard time being brief about anything but new exercise routines.
After having come out of College and learned how Object Oriented Programming worked (what polymorphism is, what an interface is, etc.) I get to the real world and begin to find as I explore more mission-critical, non-academic examples that my knowledge of actually how to program in a true OOP way is severely lacking ("Wait, an interface has a purpose?"). I can normally get by, but I'm a stickler for doing things the "right way".
As always, I trouble myself with a semi-large problem (and as usual it's a game). Along with some TDD techniques, I'm hoping to use it as my experimentation grounds for relearning proper OOP DESIGN techniques, having already the knowledge of how it all works mechanically.
I have a character in the game (a clone of "Harvest Moon", a farming simulator) controlled by the player who can hold a bunch of tools. They can move around the world and, by utilizing certain tools on certain parts of the environment, a different action happens. For example, by using the "watering pot" tool on the ground, the end result is:
1.) The watering pot loses some water.
2.) The area of ground targeted becomes "watered".
3.) The character gains some efficiency points with the specific type of tool that they're using (the watering pot).
4.) The character grows a little more tired.
As you can see, one action causes multiple items to change. Here's how I would have gone about doing it in College:
I have an object for player, plot, tool, etc. In the Player class, I have a "UseTool" method that takes as parameters the tool and the target. If everything looks good (the tool can be used against the target, and the player can use the tool, amongst other things), then the tool is used (the tool might reduce it's durability, the character might become a bit more tired, the target would be affected by the tool, etc.). This giberrish is ALL in the Player class.
Of course, I now know that...
1.) This would not only make the "Player" class be used for just as a object to store the player data, but now it does decisions based on tool usage, which breaks Single-Responsibility.
2.) Whenever I want to add a tool and it's results, I need to change the Player class, breaking OCP.
Probably amongst other things.
So now, I'm thinking to take all the action validation ("Can a player use this tool on this target?") and the action execution ("What happens when they do") and place them into an Action class. When a player tries to use a tool, some process (that I'm not worried about quite yet) will go through and pass in the player, tool used, and target into this new Action object through the constructor, and then have methods to...
1.) Check if the player can use the tool on the target.
2.) Actually go about altering the data if the action is later on carried out.
For every action that I want, I would make a new class that overrides the two methods. When a player tries to use a tool, some factory will determine from the tool what type of Action subclass would be used, and create a new object of that subclass.
Ok, now to the actual questions...
1.) How am I doing so far. Any better ideas for something like this?
2.) Realizing that in order to check if an action if valid with a tool, I might need to do calls like tool.CanWater() or target.CanBeWatered(), I could see the tool or target classes becoming filled with "Can___()" properties. One thought I had was to make an interface. Imagine for targets having...
And then have code in my action such as...
This way, I only need to put properties onto the possible target classes that need to be there. If a class (say, a bird class) can NEVER be the target of a watering action, then I simply don't put the IWaterable interface onto that unit.
Of course, this seems like I'm just reducing my problem, rather than get rid of it.
So, any tips, pointers on a situation like this?
Thanks in advanced!
After having come out of College and learned how Object Oriented Programming worked (what polymorphism is, what an interface is, etc.) I get to the real world and begin to find as I explore more mission-critical, non-academic examples that my knowledge of actually how to program in a true OOP way is severely lacking ("Wait, an interface has a purpose?"). I can normally get by, but I'm a stickler for doing things the "right way".
As always, I trouble myself with a semi-large problem (and as usual it's a game). Along with some TDD techniques, I'm hoping to use it as my experimentation grounds for relearning proper OOP DESIGN techniques, having already the knowledge of how it all works mechanically.
I have a character in the game (a clone of "Harvest Moon", a farming simulator) controlled by the player who can hold a bunch of tools. They can move around the world and, by utilizing certain tools on certain parts of the environment, a different action happens. For example, by using the "watering pot" tool on the ground, the end result is:
1.) The watering pot loses some water.
2.) The area of ground targeted becomes "watered".
3.) The character gains some efficiency points with the specific type of tool that they're using (the watering pot).
4.) The character grows a little more tired.
As you can see, one action causes multiple items to change. Here's how I would have gone about doing it in College:
I have an object for player, plot, tool, etc. In the Player class, I have a "UseTool" method that takes as parameters the tool and the target. If everything looks good (the tool can be used against the target, and the player can use the tool, amongst other things), then the tool is used (the tool might reduce it's durability, the character might become a bit more tired, the target would be affected by the tool, etc.). This giberrish is ALL in the Player class.
Of course, I now know that...
1.) This would not only make the "Player" class be used for just as a object to store the player data, but now it does decisions based on tool usage, which breaks Single-Responsibility.
2.) Whenever I want to add a tool and it's results, I need to change the Player class, breaking OCP.
Probably amongst other things.
So now, I'm thinking to take all the action validation ("Can a player use this tool on this target?") and the action execution ("What happens when they do") and place them into an Action class. When a player tries to use a tool, some process (that I'm not worried about quite yet) will go through and pass in the player, tool used, and target into this new Action object through the constructor, and then have methods to...
1.) Check if the player can use the tool on the target.
2.) Actually go about altering the data if the action is later on carried out.
Code:
public abstract class Action
{
public bool IsValid();
public void Execute();
public Action(IActor actor, ITool tool, IActionTarget target)
{
}
}
For every action that I want, I would make a new class that overrides the two methods. When a player tries to use a tool, some factory will determine from the tool what type of Action subclass would be used, and create a new object of that subclass.
Ok, now to the actual questions...
1.) How am I doing so far. Any better ideas for something like this?
2.) Realizing that in order to check if an action if valid with a tool, I might need to do calls like tool.CanWater() or target.CanBeWatered(), I could see the tool or target classes becoming filled with "Can___()" properties. One thought I had was to make an interface. Imagine for targets having...
Code:
public interface IWaterable
{
bool CanBeWatered();
}
And then have code in my action such as...
Code:
public class WateringAction : Action
{
public bool IsValid()
{
if (actionTarget is IWaterable)
{
return actionTarget.CanWater();
}
else
{
return false;
}
}
...
}
This way, I only need to put properties onto the possible target classes that need to be there. If a class (say, a bird class) can NEVER be the target of a watering action, then I simply don't put the IWaterable interface onto that unit.
Of course, this seems like I'm just reducing my problem, rather than get rid of it.
So, any tips, pointers on a situation like this?
Thanks in advanced!