I’m doing a simple project to manage data for a tabletop game, but I’m mostly using it to get experience about correct coding.
I’ve just reached a point where I have five classes that are tightly coupled, and I’m not sure whether leaving the whole thing as it is, or refactoring.
What I have is basically this:
- class
ShipTemplate: This class ( that has nothing to do with c++ templates ) has all constant members and contains basic informations about a category of Ships. - class
TemplateSet: This class contains all ShipTemplates that are currently available to build, and it has a name. It should be stand-alone, since it represents the available technology of each player at any time, so one would be able to save/load different sets at different times. - class
Ship: This class represents a complete ship, with loadouts, name and other things. It contains a const reference to a ShipTemplate, which the class is not allowed to change, to refer to its basic functionality. It could extend ShipTemplate, but I wanted to keep track of which Ships had a particular underlying ShipTemplate, and it seemed easier doing it like this. - class
Fleet: This class contains a list of Ships, it has a name and contains other information. It should contain a cost variable equal to the sum of the cost of all Ships in it. - class
Deployment: This class contains pointers to all the Ships, Fleets, and TemplateSets available to the player. It also needs to keep track of ShipTemplates that are no longer available, but that are still used by already built Ships. It should contain a cost variable equal to the sum of the cost of all Ships available to the Player. It has to manage the transfer of Ships from one Fleet to another. It has to find out which Ships are within a given Fleet, or which Ships have a given ShipTemplate.
Unfortunately every class is pretty interwined with all the others. I thought about different approaches, but I’m not sure if even one of them is the correct one.
- Use
friendstatements all over the place, so that if one class modifies something, it can correctly update all the others. - Use very long names, like
Deployment::modifyShipThrustInFleet, and allow any modification solely through the Deployment class, which will take care of everything. - Remove TemplateSets and Fleets and represent them within Deployment, so that it can update correctly cost values/pointers without breaking any “correctness” rule. This too implies that every modification to the system has to pass through Deployment.
- Insert into lower classes pointers to upper classes, so for example when changing something in a Ship it can automatically update costs of both its Deployment and Fleet.
Is there some other solution I didn’t see, or maybe a refactor into more classes that can help me achieve readable, mantainable code?
Just some thoughts.
When thinking in object oriented way, thinking about real-life objects lays out the view, what you should describe in program. And what is object in real-life? It is a “thing” which has certain features and exposes some functionality.
For example ShipTemplate is a blue-print of the ship. It should define size, layout, part types and quantities (DieselEngine, SteamEngine, AntiAircraftGun, UnderwaterMines, etc), and how they are connected with each other.
Ship on the other had is constructed according to blueprint – it should have all part instances. For example it might have two DieselEngines and three AntiAircraftGuns. And it is correct, that ship does not inherit from blueprint. Blueprint is only a description of ship, not it’s parent.
Now, each type of the object (blueprint, part, ship) has it’s own properties and functionality. For example, each engine consumes some amount of fuel and can increase speed of the ship to some value. Why not have base class for Engine, which has these features? (inheritance). The same goes for the guns (lets call it ShipWeapon). There is of course a big difference between mine-gun and anti-aircraft gun, but they are both guns, they are both mountable on the ship, they both have weight, ammo type, reload time, ammo capacity, whether gun is operating.
So these are some properties of the objects. What about functionality? Other important concept of OO design is that each object has (encapsulated) some functions which can be done with it (and it may or may not alter objects state). For example ShipWeapon should have method Fire(), which maybe should decrees amount of ammo in it. Or try to target at first with Aim(sometarget). Engine on the other hand would have Start(), Stop(), SetSpeed(speed). Note that these would work internally on the object and it’s state. Ship might have SetCourse(direction, speed), which would start it’s engines at required power and orient its rudder. Also ship might have Colide(ship). And Hit(typeofattackinggun), which would iterate through all parts of ship and damage some randomly (and set IsOperating for a gun, or turn off one of the engines, etc.)
As you can see you can go into a lot of detail while designing OO approach. Its also very good to know when to stop – just how much detail (or accuracy) you really need for you program to work.
Also, there could be a global World, which would hold all ships. And so on..
There is other part of program, the infrastructure. How you data objects (ships, worlds, players) are managed, how they know and interact with each other. For example each ship as an object can be observed by global map and each ship would notify it about movement (observer pattern). Or global world would query state of each ship at some time intervals, based on global clock. Or…
I guess what I was trying to say is to stick to main OO principles – encapsulation, inheritance, polymorphism. And there is a lot of literature out there for object-oriented design, design patterns, etc., which is useful. Wiki entry is a bit “academic” but has main definitions, which make you think 🙂 Also look at SOLID
PS. And it is usually a sign of a bad design to do everything in a single class.