In this chapter, we will further extend our knowledge of OOP by looking at the slightly more advanced concepts of inheritance and polymorphism. We will then be able to use this new knowledge to implement the star characters of our game, Thomas and Bob. Here is what we will cover in this chapter in a little more detail:
PlayableCharacter
classThomas
and Bob
classesWe have seen how we can use other people's hard work by instantiating/creating objects from the classes of the SFML library. But this whole OOP thing goes even further than that.
What if there is a class that has loads of useful functionality in it, but is not quite what we want? In this situation we can inherit from the other class. Just like it sounds, inheritance means we can harness all the features and benefits of other people's classes, including the encapsulation, while further refining or extending the code specifically to our situation. In this project, we will inherit from and extend some SFML classes. We will also do so with our own classes.
Let's look at some code that uses inheritance,
With all this in mind, let's look at an example class and see how we can extend it, just to see the syntax and as a first step.
First, we define a class to inherit from. This is no different from how we created any of our other classes. Take a look at this hypothetical Soldier
class declaration:
class Soldier { private: // How much damage can the soldier take int m_Health; int m_Armour; int m_Range; int m_ShotPower; Public: void setHealth(int h); void setArmour(int a); void setRange(int r); void setShotPower(int p); };
In the previous code, we define a Soldier
class. It has four private variables, m_Health
, m_Armour
, m_Range
, and m_ShotPower
. It has four public functions setHealth
, setArmour
, setRange
, and setShotPower
. We don't need to see the definition of the functions, they will simply initialize the appropriate variable that their name makes obvious.
We can also imagine that a fully implemented Soldier
class would be much more in-depth than this. It would probably have functions such as shoot
, goProne
, and others. If we implemented a Soldier
class in an SFML project, it would likely have a Sprite
object, as well as an update
and a getPostion
function.
The simple scenario presented here is suitable for learning about inheritance. Now let's look at something new, actually inheriting from the Soldier
class. Look at this code, especially the highlighted part:
class Sniper : public Soldier
{
public:
// A constructor specific to Sniper
Sniper::Sniper();
};
By adding the : public Soldier
code to the Sniper
class declaration, Sniper
inherits from Soldier
. But what does this mean, exactly? Sniper
is a Soldier
. It has all the variables and functions of Soldier
. Inheritance is more than this, however.
Notice also that in the previous code, we declare a Sniper
constructor. This constructor is unique to Sniper
. We have not only inherited from Soldier
, we have extended
Soldier
. All the functionality (definitions) of the Soldier
class are handled by the Soldier
class, but the definition of the Sniper
constructor must be handled by the Sniper
class.
This is what the hypothetical Sniper
constructor definition might look like:
// In Sniper.cpp Sniper::Sniper() { setHealth(10); setArmour(10); setRange(1000); setShotPower(100); }
We could go ahead and write a bunch of other classes that are an extension of the Soldier
class, perhaps Commando
and Infantryman
. Each would have the exact same variables and functions, but each could also have a unique constructor that initializes those variables appropriate to the type of Soldier
. Commando
might have very high m_Health
and m_ShotPower
but really puny m_Range
. Infantryman
might be in between Commando
and Sniper
, with mediocre values for each variable.
The terminology we might like to learn here is that the class that is extended from is the super-class, and the class that inherits from the super-class is the sub-class. We can also say parent and child class.
You might find yourself asking this question about inheritance: Why? The reason is something like this: we can write common code once; in the parent class, we can update that common code and all classes that inherit from it are also updated. Furthermore, a sub-class only gets to use public and protected instance variables and functions. So, designed properly, this also further enhances the goals of encapsulation.
Did you say protected? Yes. There is an access specifier for class variables and functions called protected. You can think of protected variables as being somewhere between public and private. Here is a quick summary of access specifiers, along with more details about the protected specifier:
Public
variables and functions can be accessed and used by anyone.Private
variables and functions can only be accessed/used by the internal code of the class. This is good for encapsulation, and when we need to access/change private variables, we can provide public getter
and setter
functions (such as getSprite
and so on). If we extend a class that has private
variables and functions, that child class cannot directly access the private data of its parent.Protected
variables and functions are almost the same as private. They cannot be accessed/used directly by an instance of the class. However, they can be used directly by any class that extends the class they are declared in. So it is like they are private, except to child classes.To fully understand what protected variables and functions are and how they can be useful, let's look at another topic first and then we can see them in action.
18.218.93.169