I'm currently working on a group project to recreate the original Pokemon Yellow version in Java. We've only just started planning it out but they way we've drawn out the design right now is to have an abstract Pokemon class and have 151 (Count of the original Pokemon) classes that extend it, each one being a different Pokemon. 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. It seems a little much to be creating 151 classes for the Pokemon but it also seems like a good way to limit coupling. As far as design goes is this a good way to go about doing it, if not what are some better methods to go about doing it?
Game Design – Best Practices for Recreating Pokemon
designobject-oriented
Related Solutions
What you describe is known as an anemic domain model. As with many OOP design principles (like Law of Demeter etc.), it's not worth bending over backwards just to satisfy a rule.
Nothing wrong about having bags of values, as long as they don't clutter the entire landscape and don't rely on other objects to do the housekeeping they could be doing for themselves.
It would certainly be a code smell if you had a separate class just for modifying properties of Card
- if it could be reasonably expected to take care of them on its own.
But is it really a job of a Card
to know which Player
it is visible to?
And why implement Card.isVisibleTo(Player p)
, but not Player.isVisibleTo(Card c)
? Or vice versa?
Yes, you can try to come up with some sort of a rule for that as you did - like Player
being more high level than a Card
(?) - but it's not that straightforward to guess and I'll have to look in more than one place to find the method.
Over time it can lead to a rotten design compromise of implementing isVisibleTo
on both Card
and Player
class, which I believe is a no-no. Why so? Because I already imagine the shameful day when player1.isVisibleTo(card1)
will return a different value than card1.isVisibleTo(player1).
I think - it's subjective - this should be made impossible by design.
Mutual visibility of cards and players should better be governed by some sort of a context object - be it Viewport
, Deal
or Game
.
It's not equal to having global functions. After all, there may be many concurrent games. Note that the same card can be used simultaneously on many tables. Shall we create many Card
instances for each ace of spade?
I might still implement isVisibleTo
on Card
, but pass a context object to it and make Card
delegate the query. Program to interface to avoid high coupling.
As for your second example - if the document ID consists only of a BigDecimal
, why create a wrapper class for it at all?
I'd say all you need is a DocumentRepository.getDocument(BigDecimal documentID);
By the way, while absent from Java, there are struct
s in C#.
See
for reference. It's a highly object-oriented language, but noone makes a big deal out of it.
I would have your objects implement interfaces like ICharacter, IEnemy, IHorizontalMover. The relevant design guideline is known as Favor Composition over Inheritance, and it should allow your design to be more flexible. One difference is that interfaces tend to specify behavior and any given object can implement as much or as little as it needs.
If you feel that this causes code duplication that you did not have when a base class just handled mostly non-overridden methods, then you should be using forwarding methods, where your implementing classes forward method calls to a private class (or stateless static class) that provides a function common to several implementations. This keeps the codebase DRY (meaning Don't Repeat Yourself). It can feel like you are writing more boilerplate lines to achieve what inheritance takes care of for you behind the scenes, but it is generally considered worth the effort, leaving the codebase more flexible and open to change than a pure inheritance design.
Best Answer
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.
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
andPokemonBase
as such: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
andLevel
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:
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 aList<DamageOverTimeAttack>
to aPokemon
to implement attacks that should stay active for a few turns, handling it in theAttack()
method.Now when a player encounters a new Pokemon, you might want create that from a Factory: