You are right in that objects consist of attributes, states, and behavior, if you define attributes to mean non-changing characteristics of an instance. As a matter of fact, it is important to make this distinction, because there exist objects which contain only attributes, (in your sense,) and no state; they are called immutable and they are very useful in programming.
This three-part definition is indeed represented in programming languages, for example using the final
keyword in Java or the readonly
keyword in C# to denote instance data which may not change throughout the lifetime of the instance.
I have to add, though, that non-changing instance data are usually not called attributes. We tend to speak of them as 'final' or 'readonly' or 'constant data' depending on which language we are using. The proper term for them would be 'invariants', but then this word is not frequently used in this sense; it is more often used for other things.
You don't really explain why your group thought it would be a good idea to (hopefully) generate 151 classes, as "to reduce coupling" doesn't really make sense yet as you had no design or code and therefore no coupling.
Each class will be able to store data such as an ID, its name, the type of Pokemon it is (ie: rock/water/fire/etc), and the moves it can use and learn.
Change 'class' to 'instance' in that sentence and you get why you only need to create one Pokemon class: they all share the same properties. A derived class should add or implement properties (and / or methods) unique to that derivation.
If you define the properties of ElementType
, Move
and PokemonBase
as such:
enum ElementType
{
Fire = 1,
Water = 2,
Grass = 4,
Rock = 8,
}
class Move
{
int ID;
string Name;
ElementType ElementType;
int BaseDamage;
}
class PokemonBase
{
int ID;
string Name;
ElementType ElementTypes;
List<Move> Moves;
decimal HitPointsPerLevel;
decimal AttackPointsPerLevel;
}
Then your various instances only differ in what is in those properties. Your class and code should not care about that. A Pokemon that has the type Fire can be stored in exactly the same class as a Pokemon of type Water: they only carry a different value of their ElementTypes
property.
You can now fill a list of Pokemon instances with their properties and moves, for example by loading a text file or reading from a database, in a format you like.
You can solve all those problems when loading the Pokemon. In the data source you can store additional properties, which in the case of "the moves it can use and learn" can be a relational table (or flat text equivalent) where you join Pokemon.ID
, Move.ID
and Level
to each other (if memory serves me right, they learn a new move on reaching preset levels), storing it in a list or dictionary property in your Pokemon class.
Or perhaps while designing you thought you needed a different class to implement the fighting system differently for each Pokémon. You don't have to, as by combining the various properties you can give each instance its unique properties.
The problem of course starts when you realize that you actually have two types of Pokemon: a 'blueprint' for all Pokemon that can possibly occur in the game, and an actual Pokemon that a player carries, which has a Level, HitPoints, and so on, all properties depending on or calculated through the Level or based on the fact that the isntance is "in the game". You can implement those properties in a derived class:
class Pokemon : PokemonBase
{
int Level;
int MaxHitPoints;
int HitPoints;
// Stats, for example
int Attack { get { return Level * base.AttackPointsPerLevel } };
int Defense;
// This method directly applies the attack results to the incoming Pokemon, by reference.
int Attack(Pokemon other, Move attack)
{
int damageDone = (this.Attack * attack.BaseDamage) - defender.Defense;
// TODO: http://bulbapedia.bulbagarden.net/wiki/Type_chart
decimal modifier = CalculateEffectiveness(other, attack);
damageDone *= modifier;
other.HitPoints -= damageDone;
return damageDone;
}
}
And all Pokemon
instances can use it, without having to derive anything. The key is modeling your class and method so, that the actions you want to undertake on each of them can be applied to all combinations of types. You can also for exampla add a List<DamageOverTimeAttack>
to a Pokemon
to implement attacks that should stay active for a few turns, handling it in the Attack()
method.
Now when a player encounters a new Pokemon, you might want create that from a Factory:
class PokemonFactory
{
List<PokemonBase> _allPokemon;
Pokemon GetPokemon(id, level)
{
blueprint = _allPokemon.Find(id);
return new Pokemon
{
ID = blueprint.ID,
Name = blueprint.Name,
...
Level = level,
...
};
}
}
Best Answer
Yes there is. It is called the entity component system. It is used mostly in gaming industry to avoid deep hierarchies and share "components" (attributes or behavior or both) between "entity" objects dynamically, preferring composition over inheritance.