3
\$\begingroup\$

In a space game I'm writing in C++11, I have a class Spaceship. Now I want to create 3 more spaceship types, how should I do?

  • Attack type: have a weapon
  • Research type: have a sensor
  • Religious type: have both a weapon and a sensor.

I think I'll write three classes AttackSpaceship, ResearchSpaceship, ReligiousSpaceship, which all inherits from Spaceship, but I think there is another, better solution for this case.

\$\endgroup\$
5
\$\begingroup\$

Prefer composition over inheritance as it is more malleable / easy to modify later, but do not use a compose-always approach.

class Weapon {
    public:
        virtual ~Weapon() = default;

        virtual void action() = 0;
};

class AttackWeapon : public Weapon {
    public:
        void action() override {
            // ...
        }
};

class ReligiousWeapon : public Weapon {
    public:
        void action() override {
            // ...
        }
};

class Sensor {
    public:
        virtual ~Sensor() = default;

        virtual void check() = 0;
};

class ResearchSensor : public Sensor {
    public:
        void check() override {
            // ....
        }
};

class ReligiousSensor : public Sensor {
    public:
        void check() override {
            // ...
        }
};

class Spaceship {
    public:
        enum class Type {
            Research,
            Attack,
            Religious
        };

        Spaceship(Type type) {
            switch(type) {
                case Type::Research:
                    m_sensor.reset(new ResearchSensor());
                    break;
                case Type::Attack:
                    m_weapon.reset(new AttackWeapon());
                    break;
                case Type::Religious:
                    m_sensor.reset(new ReligiousSensor());
                    m_weapon.reset(new ReligiousWeapon());
                    break;
            }
        }

        void update() {
            if(m_weapon/* && Keyboard::isKeyPressed(Keyboard::Key::A)*/) {
                m_weapon->action();
            }

            if(m_sensor) {
                m_sensor->check();
            }
        }

    private:
        std::unique_ptr<Weapon> m_weapon;
        std::unique_ptr<Sensor> m_sensor;
};
\$\endgroup\$
2
  • 2
    \$\begingroup\$ The only bummer about your solution is the dynamic memory allocations. An alternative could be template parameters or maybe do like professional games do and make the behavior of the different types be data driven, but all use the same type at runtime. \$\endgroup\$
    – Alan Wolfe
    May 2 '15 at 17:05
  • 1
    \$\begingroup\$ @Alan Wolfe makes a good point, but it depends on your design and distribution goals, as well as your revenue model. Data-driven behavior generally makes DLC easier to support and debug, but it can be more complicated to implement code for it because you have to cover so many potential exceptions and loopholes. On the other hand, type driven behavior is usually more straightforward to program (this answer is a good example of that), but you have to update your software if you ever want to change anything, making DLC distribution and code reuse for sequels, etc. more of a pain. \$\endgroup\$ May 3 '15 at 8:41
1
\$\begingroup\$

I think it is perfectly fine use inheritance if your only plan on having 3 or so subclasses. AttackSpaceship, ResearchSpaceship, ReligiousSpaceship would be fine, but what if six months from now you decide to add 3 more types of ships? What a hassle it would be having to create another class for each new type of ship. Other worse extensibility problems might also come up if you need to extend the hierarchy. For instance, suppose a new type of ship (say a CivilianFreighterShip) could use some of the functionality of AttackSpaceship and some functionality of ResearchSpaceship, but most of the functionality provided by those classes would be misplaces in it (i.e. it shouldn't have attack capabilities but it has a few sensors and shields). Then your options would be to either duplicating code and functionality or sprinkling a bunch of if tests on the parent classes to enable/disable features so that your CivilianFreighterShip can inherit from them.

So if you aim on making your code more flexible and extensible, which other approaches to take? You could make your space ships configurable. If you think about it, a space ship that can attack doesn't have to be an AttackSpaceship class, it just has to have weapons attached to its hull, right? So why not have a single SpaceShip class having an array of Weapons? The is a relationship so common in OOP languages is very overused in places a simple component would be a much simpler an flexible solution.

I should reiterate that if you have a static hierarchy that is not meant to grow, then plain inheritance is fine and might even be simpler and cleaner that components and configuration parameters, however, if the goal if flexibility and extensibility, then a component-based approach will produce much better and saner code.

References you should read before making your decision:

Do a search for "components based architecture" and you should find a lot more.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.