Reputation: 1505
Take a class Player
, which (currently) has a property of type Role
:
public class Player
{
public string Name { get; set; }
public int Health { get; protected set; }
public Role Role { get; set; }
}
Where Role
holds a few distinct values:
public class Role
{
public double DamageModifier { get { return _damageModifier; } }
public double DefenseModifier { get { return _defenseModifier; } }
public double HealthModifier { get { return _healthModifier; } }
private double _damageModifier;
private double _defenseModifier;
private double _healthModifier;
public Role(double damageMod, double defenseMod, double healthMod)
{
_damageModifier = damageMod;
_defenseModifier = defenseMod;
_healthModifier = healthMod;
}
}
And there are a few "classes" that derive from Role:
public class Archer : Role
{
private const double _damageModifier = 1.2;
private const double _defenseModifier = 0.9;
private const double _healthModifier = 0.8;
public Archer() : base(_damageModifier, _defenseModifier, _healthModifier) { }
}
public class Mage : Role
{
private const double _damageModifier = 1.4;
private const double _defenseModifier = 0.7;
private const double _healthModifier = 0.9;
public Mage() : base(_damageModifier, _defenseModifier, _healthModifier) { }
}
public class Warrior : Role
{
private const double _damageModifier = 1.0;
private const double _defenseModifier = 1.2;
private const double _healthModifier = 1.0;
public Warrior() : base(_damageModifier, _defenseModifier, _healthModifier) { }
}
The end result of all this is really just that the Player.Role
property is a placeholder for what will become an Archer
, Mage
, or Warrior
. However, this specific subtype is decided at run-time (or more specifically, at character creation).
The issue I'm having arose from trying to serialize
the player object to JSON
data. When JSON
serializes the Role
property, it knows that it's a Role
object and is able to get the corresponding xxModifier
values from the instance, but I lose the fact that the Role
property was a class derived from Role
.
I'm obviously still learning, but this sort of issue kind of tells me that I've messed up in designing this class and it needs to be fixed. I could refactor the constructor so that Role
holds a reference to the child class, but that seems wrong too.
EDIT Based on comments:
My intention for the Archer/Mage/Warrior
classes is that each class will have certain methods (abilities) available to it, in addition to the xxModifier
properties that currently exist, such that a Warrior
will do (comparatively) less damage but have higher defense and health, and the Role
class currently serves as a way to give the Player
object a place to hold his Role (RPG class). This specific implementation is my goal, and what's listed here is what I've come up with so far.
Role
class even necessary in this case? Player
object is a Archer/Mage/Warrior
?Upvotes: 3
Views: 94
Reputation: 151654
You have two different problems here: specification and implementation.
In the specification, so in your game back-end, or hardcoded, or in your database or whatever, you want to be able to specify the attributes for various character classes for your player. This is fine.
That each specification holds different values does however not warrant a different implementation. You use subclassing to add behavior. You're not adding behavior, just altering existing behavior (I guess your non-shown damage calculation) slightly, based on the values of the player's role.
Furthermore, I now suspect you have in code something like this:
void NewPlayerButtonClicked(string characterClass)
{
var player = new Player();
if (characterClass == "Mage")
{
player.Role = new Mage();
}
// ...
}
That in and of itself is a design smell.
You just need a Role class to hold the values:
public class Role
{
public string ClassName { get; set; }
public decimal DamageModifier { get; set; }
public decimal DefenseModifier { get; set; }
public decimal HealthModifier { get; set; }
}
And in your player handling code you access the role's properties:
public decimal GetAttackDamage(Player attacker, Player defender, AttackInfo attack)
{
// do calculations with attacker.Role.N and defender.Role.N
}
Now I guess you're asking this because you want to populate the fields while creating a character, or loading a game state. You can do that for example by storing your specification in JSON:
{
"characterClasses" :
[
{
"className" : "Mage",
"damageModifier" : 1.4,
"defenseModifier" : 0.7,
"healthModifier" : 0.9,
},
{
...
}
]
}
Then if you save the player's character class, loading the data becomes very trivial:
player.Role = rolesLoadedFromJson.First(r => r.ClassName == player.CharacterClass);
But on all of this many books have been written, and the question is far too abstract (and the subject too broad) to give an in-depth answer on all aspects of object-oriented design.
Upvotes: 2
Reputation: 45155
To the immediate question of how to serialize and deserialize to JSON while retaining type, using something like JSON.Net, this is pretty trivial to do:
var p = new Player() { Role = new Mage() };
var json = JsonConvert.SerializeObject(p, new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Objects });
Now you JSON will look something like this:
{
"$type": "MyApp.Program+Player, MyApp",
"Name": null,
"
Health": 0,
"Role": {
"$type": "MyApp.Program+Mage, MyApp",
"DamageModifier": 1.4,
"DefenseModifier": 0.7,
"HealthModifier": 0.9
}
}
The additional $type
properties are added so when you deserialize:
var d = (Player)JsonConvert.DeserializeObject(json, typeof(Player), new JsonSerializerSettings() { TypeNameHandling = TypeNameHandling.Objects });
var isMage = d.Role.GetType() == typeof(Mage); // true
The question of whether or not your design is a good idea or not (as discussed in the comments) is a little broader.
Upvotes: 5