Introduction
In this series of blog posts, I will share with you my experience with programming and any good coding guides and advices I have met so far. I have not come with the ideas and guides in these posts myself; I have taken them from different proven sources. One big source for such guides is the book of Steve McConnell – Code Complete: A Practical Handbook of Software Construction, Second Edition. I highly recommend you getting the book and reading it at least two times. From that book you can learn how develop high-quality classes, better routines, how to use variables more efficiently and so on. You can buy it from Amazon. Therefore, I like to share with you what I find useful for me and for the projects, on which I work or have worked. I am sure you will also find them useful.
In the first blog post, I will look at the foundation of the Object-Oriented Programming (OOP) – the classes. We will see what abstract data is and how it relates to the classes. We will see the reasons to create classes and what are their advantages. Then we will look at some of the OOP’s features like Abstraction and Inheritance and how to make a good use of them. There are several code examples to make you understand the concepts better. I hope that you will enjoy this tutorial.
Usage of abstract data types for creating high-quality classes
We will start with the abstract data types. Abstract Data Type (ADT) is a collection of data and operations (routines), which work on that data. Let us take for example the abstract data type Rectangle. It has three points, angles’ values, area, perimeter and probably a color as a member data. Also known as the properties which make up a rectangle object. We have operations like drawing a rectangle, which works with the triangle’s data to visualize the rectangle on a given surface. All these operations and data make the abstract data type rectangle.
Pros of using ADT
Let us mention some of the more important benefits of using abstract data types.
Benefit #1
Firstly, I will mention the information hiding as one of the most useful features of the ADT. It allows us to hide the details about the implementation of the class from the users of that class. The user will not have to bother about how the class implements a given functionality. He or she will go ahead and directly use that functionality. This allows us to change the class data without affecting the user’s program. It is possible by having public interface, which the user will use and all the implementation details will stay hidden behind the class’s private parts.
Benefit #2
Second, adding or removing data from the class or updating the routines will not affect the user’s code. It will use the public interface, which will remain the same. The user code will call the public methods and will not care what happens behind them. There is strong guarantee that those public methods will always do the same thing, no matter how they achieve it.
Benefit #3
Next, there is one more perk from the information hiding. It is the ability to improve the class’s performance. As you know, high-quality classes shall perform better. If some method is performing badly, we can go ahead and update that method. This will not brake the user’s code. In addition, we will do the change for the improvement in only one place instead of many. This will ease the code maintenance.
Benefit #4
Without the ADT and the classes, we can be in a situation, where a function, which we want to call, needs many arguments. Therefore, we have to provide each one of them separately. This will require a lot of code and the probability to do something wrong will increase. Instead, we can pack all those arguments into one or several classes and pass them as objects of the respective class. This will require less typing and will be less error-prone.
Benefit #5
Lastly, I want to mention the ability to self-document our code. Having abstract data types, which model real-life objects will make reading and understanding the code a lot easier. Instead of let us say having variables like engine, clutch, transmission, doors, windows, body, etc. and dealing with them around the code, we can pack all of them in one class Car. That way if somebody else is reading our code he or she will see that we are dealing with a car, not with some random car parts around the code. Keep in mind that the high-quality classes should be self-documenting for the ease of use.
Code examples
Let us look at some code examples, regarding the above-mentioned benefits.
void sendCanMessage(char dlc, std::vector<char> values, std::vector<char> crc) { // Send the message over CAN using the provided values }; void sendCanMessage(xq::CANMessage message) { // Send the message over CAN using the provided values }
Look at the two routines. The first one has three arguments, which might not be so clear to persons, who have not worked with CAN messages. We need to document each one of them to make them clearer. The second routine is easier to understand – we need to provide a CANMessage object. That is it. No need to write extra documentation (still better if you do so). In the first routine, we need to pass three arguments. Their count can easily go up if more CANMessage specifics are required. In the second routine, there always will be one argument. In addition, the more parameters are required the easier is to mix them up.
Let us look at the CANMessage class itself.
class CANMessage { public: void addValue(char index, char value) { m_values.at(index) = value; } void calculateCRC() { int crcValue = 0; for (int i = 0; i < m_values.size(); ++i) { crcValue += m_values.at(i); // Logic to turn the crcValue into a vector of hexadecimal numbers } } std::vector<char> getMessage() { // Logic to add the m_values and the m_crc to m_message return m_message; } private: std::vector<char> m_message; std::vector<char> m_values; std::vector<char> m_crc; char dlc; };
It provides a simple public interface, which the user can use to create a CAN message by adding values, can calculate CRC of the message or get the ready message directly. The class hides all implementation details from him or her. Here we see them because I added them for clarity, but usually in the header files, you will only see the public methods definitions. You will not care how the developer has implemented them. You will get them ready to use with the guarantee that they will work fine. In addition, if they do not work as expected, the developer can always modify them without breaking your code.
I hope you realized how useful the abstract data types are. Next, let us see some good reasons on why to create classes and make sure these are high-quality classes.
Why should we create classes (preferably high-quality classes)
Here are some of the reasons I find convenient of why should we create classes.
Reason #1
We can model and develop real-world objects. For example, we can create a class Car or Airplane. Inside those classes, you can put the data, which builds up and classifies the objects. Let us say, in the Car class we can add properties like engine, transmission and body. We will keep all these properties in one place. In the same structure, we can add the actions, which the object can execute – for example, the actions drive, turn left or turn right. We can pass these properties and actions around the code.
Reason #2
If you want to reduce the complexity of your code, you have to use classes. Especially the high-quality classes are a key to achieve that. You can hide implementation details behind the classes, so the user of your code base will not care about those details. Once you have the class, you can re-use it, making the code base smaller and reducing the maintenance cost. Any required changes will have to be done on one place in the class instead of many places in the program. You will localize the complexity in one place. Any complex algorithms can stay in the class so if something is wrong with them, you have only one place to fix.
Reason #3
Creating a class also reduce any future costs of applying changes. If we know a code piece tends to change often, we can put it inside some class and the re-use it. When we have to change it, we will just do a change in one place in the class. The rest of the program code will remain intact.
Reason #4
If we have some global data, which we access from many places we can go ahead and put that global data inside a class. Thanks to the abstraction, which we will see later, you can do changes to that global data without affecting your program.
Reason #5
As I already mentioned above, abstract data types and classes are good for data passing. If you have many variables, which you need to pass around between the existing routines, you better put those variables inside a class or several classes. That way you will have to manage only one object instead of several variables. With only one object, the chances to mix up something are low.
Reason #6
One of the biggest advantages of the classes is their reusability. You can take the class and use it in many routines, even separate programs. Once you develop some code in a class, you can use that code wherever you want. Every good developer is trying to integrate high-quality classes into his or her program. Those could be your high-quality classes.
Reason #7
The last important reason I can think of why to create a class is the ability to combine related data and operations into one place. No need to include multiple header files just so you can have the data you need. All is available in a single class. The operations which behave the same or which operate on the same data can be combined into a single class. This way your code will be more organized and easy to maintain. If you have to do changes on some data or its related operations, you will do it in one place. In addition, you will be less likely to break some of the existing code.
Using abstraction for high-quality classes
What is abstraction? Abstraction is the ability to simplify complex operations. We can hide the complex operations behind the class interface, so the users of our class will not have to bother with those complex operations. We will simplify the work with our class. The user will only call some methods. He or she will know what those methods do but will not know how they do it. Moreover, the user will not care. For him or her those methods will be something abstract. You just call them and you get the answer. That is what makes the high-quality classes – their simplicity and responsiveness.
Let us first give an example for a good and bad abstraction and then see some guidelines on how to create quality class interfaces.
Good abstraction
Take a look at the following class.
class CANMessageTransceiver { public: bool sendCanMessage(const CANMessage& message) { // Send the message over CAN } CANMessage receiveCanMessage() { // Receive a CAN message return CANMessage{}; } private: // Data and helper functions, not visible to the user };
The class’s name is CANMessageTransceiver and judging by the name, it is supposed to send and receive CAN messages. When we look at the public interface of the class (the part that is usable by the users) we see that there are two methods – sendCanMessage() and receiveCanMessage(). So the class does exactly what it name says. It doesn’t do anything extra like for example send a LIN message.
The user of this class can send and receive CAN messages using the public interface. He or she does not care how the messages are send or received. There is the guarantee that when the user calls one of these methods it will do exactly what is expected to do. The class hides all the implementation details from the user. That is why we place this class among the high-quality classes, which we will be creating.
Note: Don’t worry if these CAN and LIN messages don’t look familiar. It is something specific for the automotive and embedded world. They are some communication protocols.
Bad abstraction
Now let us look at the next class.
class CANMessageTranscieverBad { public: void openCommunicationChannel(int channelId) { // Set the channel for communication } void setCommunicationChannelBaudRate(int baudRate) { // Set the baud rate of the channel } bool isCRCValid(const CANMessage& message) { // Check if CRC of the message is valid } bool sendCanMessage(const CANMessage& message) { // Send the message over CAN } CANMessage receiveCanMessage() { // Receive a CAN message return CANMessage{}; } };
The amount of the public methods
First thing that I will notice here, which is not making this class a part of the high-quality classes family is that it seems to have too many public routines for a class, which is supposed to only send and receive messages. At least that is what its name means. As we will see later, one property of the high-quality classes is that they keep fairly small amount of public members.
Methods, which are not appropriate for this class
Next thing I will notice are the functions themselves. According to the name, the class shall send and receive messages. That is it. I as a user will require only these operations from the class. These operations are available. Nevertheless, let see the other methods and whether we need them or not.
The first two functions openCommunicationChannel() and setCommunicationChannelBaudRate() do something which is needed to prepare the communication. They do not handle the messages in any way. Would it be better if they were part of another class, for example CANCommunicationProvider or something? I think yes. That way the user of the CANMessageTransceiver will not have to worry about communication setup and so on. It will be handled somewhere else.
The next function isCRCValid() is also not needed when we are getting and sending messages. You would probably say that it is a good idea to check the CRC of the received message. However, the place for this is not here. We already have a class CANMessage. A class that is taking care of the CAN message can also take care of its CRC, because the message and the CRC go hand by hand.
I hope you get the idea why this class is not a good example of abstraction. Next, we will see some guidelines on how to use abstraction and it will become clearer to you why the above class is not an example for the high-quality classes we want to develop.
Guidelines for creating high-quality classes using abstraction
Class shall implement only one type of abstract data
Let us look at two examples. First example is a class that represent an automobile cluster and all the operation that it does. We will see some flows with it.
Example bad
class ClusterECUBad { public: void setAnalogSpeed(int value) { // Set the value of the analog speedometer } void setDigitalSpeed(int value) { // Set the value of the digital speedometer } bool sendCanMessage() { // Send the message over CAN } bool receiveCanMessage() { // Receive a CAN message } private: int m_speedValue; CANMessage m_message; // Some private data };
Usually, the automobile cluster is responsible to display the current vehicle speed. I as a user will expect that. Looking at the public interface, I see that there are two methods, which set and display the analog and digital speed. However, there are two more methods, which presumably will send and receive CANMessage from the cluster ECU to some other ECUs. The cluster as an abstract object is responsible to display the analog and digital speed. Other abstract object shall handle any messaging. Moreover, we already saw such class – CANMessageTransceiver.
The class implements two types of abstract data – displaying vehicle speed and CAN messaging. We better split them into two different classes, preferably high-quality classes.
Example good
class ClusterECU { public: void setAnalogSpeed(int value) { // Set the value of the analog speedometer } void setDigitalSpeed(int value) { // Set the value of the digital speedometer } private: CANMessageTransceiver m_messageTransceiver; };
Looking at the above class, we can see that it only deals with setting and displaying the vehicle speed. The user of the class will only use it for one purpose. One private member now handles the sending and receiving of CAN messages. This way, the user will not care how it happens. Now the class will serve one purpose. The previous class was serving multiple purposes for the user.
Provide methods in pair with their opposites
When a user uses one method, there are high chances that he or she will require the opposite of that method. For example, when there are getters of the private members, we should probably add the setters, so the user has a greater control. Of course, if we have a good reason not to provide the setters like if we want to keep the class data read-only, then we will not provide them. This goes for other methods as well. For example if you have openFile() in most of the cases you or your user will need closeFile(). The high-quality classes tend to always provide the methods in pairs with their opposites.
Break a class into multiple classes
Imagine we have a fairly big class. Some of the operations work only with one part of the class’s member data, while other work with the other part of the data. This is a good reason to split the class into two new classes. That way you will use the abstraction better. Your code will be easier to maintain because you will have a smaller code base to support. When you have to do a change, you will have to do it in a smaller code base. You will be able to find any issues easier when the code base is smaller. Try to keep your classes and their public interface smaller for easier maintenance. This will make them high-quality classes.
Enforce the correct usage of the class
We sometimes rely that the user will use our class correctly and as we intended to. However, this rarely happens. If our class public interface allows incorrect use, be sure that somebody will use it incorrectly. One example of misuse is if the class has a routine to initialize some resources before executing any other operation. However, the user calls other operation first anyway and the call fails. And they blame your for this fail. Not cool, right?
Documentation
What can we do? The easiest but not so efficient approach is to document the class. Document every public method and explicitly state which method after which to execute. And hope for the user to read the documentation.
Asserts
Next, you can add some asserts in your code. Using them, check if an initialization happened before executing any other operation. At least you will report an error to the user and the program will not fail unexpectedly.
Forcing the user
What I like best is to force the user to use the class correctly. Develop the public methods in a way, that the user will execute the correct steps before calling any method. In the example with the initialization, instead of having a separate method for the initialization, call it inside the constructor. The user always has to call the constructor first. Once he or she calls it, all initializations will be over so whatever method calls next, it will not fail due to uninitialized resources. Forcing the user to follow the correct steps will guarantee that nobody will misuse your class. Such class is part of the high-quality classes which we want to develop.
Let us look at two examples.
Example bad
class FileInterpreterBad { public: FileInterpreterBad() { } void setFilePath(std::string filePath) { m_filePath = filePath; } void processData() { // Open the file using m_filePath // Process the data } private: std::string m_filePath; };
The class FileInterpreterBad has one method to set the path to file, which we want to interpret. We expect that the user will call it before calling the processData() method. However, who guarantees that? The user is free to call the processData() method first. In that case, no file path will be available and when we try to open a file, the operation will fail.
I hope that you see the problem here. Now let us see how to do it correctly
Example good
class FileInterpreter { public: FileInterpreter(std::string filePath) : m_filePath{filePath} { } void processData() { // Open the file using m_filePath // Process the data } private: std::string m_filePath; };
Now, in this class the setting of the path to the file happens in the constructor of the class. The user will always call the constructor first and will provide it with a path to the file. This way even if he or she calls the processData() method first, the file path will be available and the method call will not fail. At least, not due to an uninitialized members.
My advice is: Try to force the initialization of member variables in the constructor. If there is a code, that you need to execute before calling other routines, do not make it a public member. Either add it in the constructor or make that routine private and call it first from your public method. No matter what you do, do not rely on the user. If forcing the function call is not an option, add asserts and checks. As a last option write good documentation, which describes the correct use of the public interface of your class. The high-quality classes are designed in way that the user cannot misuse them.
Beware of breaking the abstraction when modifying the class
Be very careful when updating your class and adding new functionality. This new functionality might require extra logic, which comes in the form of new methods. Even if the new functionality is consistent with the class abstraction, the members, which it drags with, might not be. And if they are not, it will break your abstraction. Here, a better approach would be to create a new class and put the new functionality into it.
Let us look at one example, where adding a new functionality breaks the abstraction.
Example bad
class CANMessageTransceiverEroded { public: bool sendCanMessage(const CANMessage& message) { // Send the message over CAN } CANMessage receiveCanMessage() { // Receive a CAN message return CANMessage{}; } void storeReceivedMessagesInDb() { // Store the received messages in database } void openDbConnection(const std::string& connectionString ) { // Open connection to database } void closeDbConnection() { // Close connection to database } private: // Data and helper functions, not visible to the user };
The good old CANMessageTransceiver class. We have added one new functionality to store the received messages in database – storeReceivedMessagesInDb(). However, in order to be able to store anything in database, the user needs to connect to that database. In addition, he or she should provide the connection string to that database. That forces us to add two new methods for opening and closing the database connection. These two new methods are not consistent with the class abstraction type. They break it. Now we have a class that does two different things – Handles CAN messages and handle database connections.
A better approach here would be to create a new class, which will deal with the storing of the messages in a database. Then in our CANMessageTransceiver class, we will use it as a member variable. This way the methods to open and close database connections will not confuse the user and he or she will not bother to use them. The user will only use the class to send and receive messages.
Using encapsulation to create high-quality classes
Encapsulation is one of the Object-Oriented Programming OOP pillars. It is a mechanism to restrict direct access to the class’s data by making it exclusively private for the class. Only its methods can access that data. Thus, we achieve the so-called information hiding, something very important for the high-quality classes. If we want to access the data, we can only do it through special public methods. This technique restricts any external objects to manipulate the data directly. Access to that data is available through a controlled mechanism. The class does checks for the validity of the data before providing it to the external object or taking it from that object.
For example, you can see the above class CANMessage. The data member m_message can only be obtained from the method getMessage(). This method is free to verify and validate the message before sending it to the user of the class. The user has no option to manipulate the class’s message in any way – good or bad.
Now, let us see some guidelines on how to create a good encapsulation
Minimize the access to the class
Try to use the strictest level of privacy. The class data is class specific. Do not provide access to it to somebody else. Always try to make it private. Avoid the protected specifier for the class data members. If you make the data protected this will mean that the class data is not class specific anymore. Now the base class is sharing it with its derived classes. If you need an access to that data in the derived classes, provide the respective getters and setters.
If some method tends to break the abstraction, make them private. For example, the calculateCRC() method of the CANMessage class can change the class’s m_crc value at any time by forcing its recalculation even when the message is not quite done setting its values. A better approach would be to make this method private and call it from one of the other methods. It would be better to call it inside the getMessage() method, right before returning the message to the user. That way, the user will receive the most recent and correct CRC value.
Don’t make member data public
Definitely do not do that. As I said, the private data shall only be available to the class and the class shall only be able to change it. If you make it public, you will give rights to the user of the class to change in any way. Making such direct changes can break other functionality of your code and your class might not work correctly. Better provide getter and setter methods, which will do some verification and validation of the provided data.
Try to avoid adding private implementation details in the class interface
We want the users of our class to care only for the public part of our class. However, we still provide some private details to the user. How? Well, in the header files of our classes we keep the public methods together with the private methods and data. The user is still able to see what private methods we have and what data we use. Having the user look at the private parts of our program still breaks the encapsulation. However, how do we fix this?
Let me introduce you one idea I learned from one of the C++ gurus – Scott Meyers. This technique goes by the name PIMPL – pointer to implementation. I will not go into too much detail. Follow the link for more information.
PIMPL
Let us assume we have the good old CANMessage class. You remember that it had some private data members. Anybody who uses this class can get the header file and see that the class uses four member variables. By looking at them, the user can get some idea how the class works internally (which is no good for the encapsulation). Our goal is to remove these private variables. We do this by creating a new class named CANMessageImpl and we put them there. We create getters and setters to access them. See how this class will look like.
class CANMessageImpl { public: std::vector<char> getMessage() { return m_message; } void setMessage(std::vector<char> value) { m_message = value; } std::vector<char> getValues() { return m_values; } void setValues(std::vector<char> value) { m_values = value; } std::vector<char> getCrc() { return m_crc; } void setCrc(std::vector<char> value) { m_crc = value; } char getDlc() { return m_dlc; } void setCrc(char value) { m_dlc = value; } private: std::vector<char> m_message; std::vector<char> m_values; std::vector<char> m_crc; char m_dlc; };
In our CANMessage class, we remove the private members and replace them by one pointer to the CANMessageImpl class. Everywhere in the code, we use that pointer instead of the private data members. See how our updated class will look like.
class CANMessage { public: void addValue(char index, char value) { m_canMessageImpl->getValues().at(index) = value; } void calculateCRC() { int crcValue = 0; for (int i = 0; i < m_canMessageImpl->getValues().size(); ++i) { crcValue += m_canMessageImpl->getValues().at(i); // Logic to turn the crcValue into a vector of hexadecimal numbers } } std::vector<char> getMessage() { // Logic to add the m_values and the m_crc to m_message return m_canMessageImpl->getMessage(); } private: CANMessageImpl* m_canMessageImpl; };
After we have this, we will give the users the new CANMessage class. They will not know what private data members we use. The users will only see one pointer. They will not know what that pointer uses. The users will be happy to miss the unimportant implementation details. That is our goal.
Avoid friend classes
In the forums and programming blogs, you can see many discussions about the use of friend classes. Most people say that we should avoid them, because they break the encapsulation. And I agree with them. With the friend classes, you give access to your class’s private data to some other unknown external classes. Nothing breaks encapsulation better than this.
Still, there are situation where you have to use friend classes. One good example is the overloading of the << operator. In that case, there is no way out. For the rest of the cases you can avoid the usage of friend classes.
Make a method public only if it does not break the class abstraction
Before making one method public, make sure that this change will not break the class’s abstraction. I will go ahead with one example.
class CANMessageTransceiverNonEncapsulated { public: bool sendCanMessage(const CANMessage& message) { // Send the message over CAN } CANMessage receiveCanMessage() { // Receive a CAN message storeCanMessage(); return CANMessage{}; } void storeCanMessage() { // Store the message in some medium } private: // Data and helper functions, not visible to the user };
The class you see is responsible for sending and receiving CAN messages. That is what its name suggests. Now I want to add one new method to store the received messages. I am adding the storeCanMessage() method. Now, my abstraction is no more. The reason is that my class was supposed to receive and send messages. At least that is what the user of the class expects. However, now there is one public method, which supposedly stores the messages somewhere. Something that is completely different from sending and receiving messages. I have two options here. Either make the new routine private and call it from one of the existing public routines or create a new class, which handles the storage of messages. Both options are OK.
Be careful not to break the encapsulation semantically
The class shall be so clear and self-documenting that the user shall be able to understand how to use it only by looking at the public interface. If it is not clear how the class works, the user may be tempted to look at the private implementation of the class only to get some idea how the class works. And this is very bad. The user can see for example that the method he think should call first is already called by another method. That way, the user will not call it and leave it to the other method. This way, the code becomes dependent on the private implementation rather than the public interface.
Here is one code example.
class DatabaseManager { public: void connectToDb() { // If not connected already to db, connect. } void getAllRecordsFromDb() { // If not connected to db connectToDb(); // .... // Get all records } };
The user of our class will see that getAllRecordsFromDb() already calls connectToDb() so it will not call it in his code. This makes his code dependent to our internal implementation. If we make a change in our class and remove that call from getAllRecordsFromDb(), the user program will break and it might take too much effort to find the issue and fix it.
That is why; try to make your public interface as understandable as possible. Document it good. Do anything to make the life of the user easier.
Containment
In short, I want to introduce the containment or the “has a” relationship, which is part of the OOP. We use it to define that a class contains certain object in it. If we take the Car class, we can say that it has an engine, it has a transmission, it has tires and so on and so forth.
We module the “has a” relationship by adding a data member from the respective class. It could be also a reference to that object. Therefore, in the case of our Car class, we will add one data member of type Engine, one data member of type Transmission, an array or four separate members of type Tire and so on. Now, our class will contain all these objects.
Guidelines
One important guideline here is: Try to avoid having classes with lots of data members. High-quality classes have small amount of data members which makes them easier to understand and maintain. Some experts mention that the perfect number of data members is 7±2. If necessary and possible, combine some of them into a new class and have an object of that class. For example, in our Car class instead of having objects of types Window, Door, Mirror, Trunc and so one, you can combine all of them into one new class – CarBody and contain an object of that type instead.
I mentioned the “has a” relationship because next we will see the “is a” relationship and it is very important not to mix them.
Using inheritance for developing high-quality classes
The last topic, which we will see today, is the inheritance. This is a very powerful technique. Using inheritance we can model an “is a” relationship. This relationship describes that one class is common to another, they have similar functionality or the first one is a derivative of the second one. Let us have two classes. The first one will be a Vehicle class. Let the second one be a Car class. Here, we can say that a Car object is a kind of a Vehicle object. We can introduce a third class – Truck. A Truck object is a kind of a Vehicle object. We see the “is a” relationship here. It is easy to say that the Vehicle class is a base class for the Car and Truck classes. These two classes are specializations of the Vehicle class.
I hope you get the difference between the “has a” relationship, we saw earlier and the “is a” relationship. The first one say that a Car has a transmission, while the second one says that the Car is a Vehicle. You cannot say that the Car is a transmission, nor that the Car has a Vehicle. Be careful not to make this mistake.
Applying inheritance
In order to apply inheritance we need to find the base common class for the given classes. Then you have to create that base class, which will hold the common data and routines for the derived classes. Inheriting from the base class will give you access to its protected and public methods and data. Make sure to use this visibility correctly.
Guidelines for applying inheritance
Let us see now some best practices and guidelines on how to apply inheritance.
Use public inheritance to model an “is a” relationship
If you want to model an “is a” relationship, you have to use public inheritance. When you publicly inherit a class, you will inherit all of its protected and public methods. This is the way to say that one derived class “is a” base class. It has the same public and protected method as the base class. A program using an object from the derived class, can access all the methods from the base class as if it were an object from the base class.
Here it is very important for the derived class to match the base class’s public interface. If there are some methods in the base class, which are not relevant for the derived class, then it is not a good idea to inherit from that base class. For example, the Bicycle is also some kind of vehicle and we might be tempted to derive from the Vehicle base class. But if the Vehicle has some public method like startEngine(), what is this method going to do for the Bicycle (unless of course it is an electric bicycle). Therefore, use inheritance if the derived class really “is a” kind of the base class and it can uses all of its public methods.
Be explicit if a class could be inherited from
When you create a class, which you do not want somebody else to inherit from, state it explicitly. Use special specifiers like final to state that no other class can inherit from our class. If you do not use a specifier and just add some comments, hoping that the user of your class will read them and not inherit from your class, you might be surprised. If nothing is stopping the user from inheriting from your class, be sure that at some point somebody will do.
Follow the Liskov Substitution Principle
The Liskov Substitution Principle is also part of the SOLID methodology, which we might see in a later blog post. It says, that when you inherit from a base class, make sure that the derived class really “is a” specific version of the base. At a first glance it might seems like it is but if you look closely you will see that it is not quite exactly a specific version. In order to be, the routines of the base class should mean the same thing for the inherited class.
Let us look at the following example in order to get more clear understanding. If we have the Bird base class, we can derive the Eagle class from it. The eagle is a bird. We can also decide to inherit the Hen class from it, since the hen is also a bird. Now assume that the Bird class has a public method fly(). The Eagle can fly so he can use that method. However, the Hen cannot fly so the fly() method will not work for it. Therefore, inheriting the Hen class from Bird will break the Liskov Substitution Principle.
Do not override a non-overridable public method
When you inherit from a base class, you have the chance to override the public methods in order to provide a different implementation for them. However, you might be tempted to create a method with the same name and signature in the derived class as some private method from the base class. This is allowed, but please, do not do it. It can create a confusion for the other developers, which will use and maintain your code. Make their life easier.
Be careful what you inherit
When inheriting from a base class you can inherit some methods, which are non-overridable. Thus, you will be stuck with their implementation whether you like it or not. Another issue you might face is that you can inherit some methods you do not need. Since now we inherit all the public methods from the base class, we will inherit even those, which might not be relevant to our class. Of course, this will break the Liskov Substitution Principle in the first place.
As in the example with the Bird base class and the Hen derived class, we do not need the fly() method but we will receive it anyway. In addition, if it is not a virtual one, we will remain with its implementation. Even if you provide some dummy implementation in the Hen class, if you are accessing a Hen object with a Bird pointer you will still get the Bird implementation. Therefore, you will make your hens fly.
Keep the common data and routines as high as possible in the inheritance tree
Data and methods, which are common for the derived classes and for their own derived classes, should stay as high as possible in the inheritance chain. This way all inherited classes will have access to them. Here it is very important not to break the abstraction of the base class by adding some common methods and data to it. If that would be the case, move the common data to a lower level derived class or even create a new class of necessary.
Be careful about base classes which only have one derived class
Usually, when we create one base class, our assumptions and expectations are that more than one class will inherit from it. We should model our base classes to be usable by more than one derived class. If we create a base class to be inherited only from one derived class, then why do we need the base class in first place? We can keep all the methods in one class. It is an overhead to use inheritance for only one class. Be suspicious if you see only one class inheriting from a given base class.
Of course, here you can say that we might be keeping the base class for future uses. Currently we have only one derived class but in future, there might be more. What if that future never comes? You will be stuck with unnecessary implementations. Better, keep the current work clean. When the future comes, you can always update your implementation.
Try to avoid deep inheritance trees
Deep inheritance trees are confusing and hard to debug and maintain. It is also easier to break the abstraction and encapsulation. Some authors suggest keeping a maximum level of six. I would say that even six is deep. So far, I have not seen a level of inheritance deeper than four, at least in the projects code I have worked.
Use polymorphism when possible instead of “if-else” or “switch – case” checking
Polymorphism is a powerful tool in the OOP arsenal. One interesting usage, which some authors suggest is to use it to replace “if-else” and “switch-case” statements. Honestly, the idea looks appealing. Let us see some examples. First thing that we will see, is some class, which does not use polymorphism.
Example bad
class WidgetTyped { public: void drawLabel() { // Draw a label } void drawImage() { // Draw an image } int getWidgetType() { return m_widgetType; } private: int m_widgetType; };
We have one class, which represents one widget, which we can draw on our UI (User Interface). This widget could be of any type. That is why we have a method to return its type and several methods to draw it depending on its type. If we want to use it, we should have one “if-else” statement or “switch-case”, first check the type and then call one of the provided functions. This seems like a lot of effort, doesn’t it?
Example good
Now lets us look at the following three classes.
class IWidget { virtual void draw() = 0; }; class ImageWidget : public IWidget { void draw() override { // Draw an image } }; class LabelWidget : public IWidget { void draw() override { // Draw a label } };
We have one base interface IWidget, which provides one public method draw(). The other classes inherit from that interface and implement the draw() method as they need to. Now, we can make lots of objects from these two types; store them in some data structure by using pointers to the base interface. We can traverse the whole structure and only call the draw() method. It will draw correctly each widget without any need to check the type of the currently processed widget. See how much effort does this approach save us?
With the use of the polymorphism, we can save the unnecessary checking for the widget type every time we want to draw it. This can be applied to other types of classes and methods. There are people who even suggest that it is possible to program without the use of if-else and switch-case, only by using polymorphism. I do not know if this is really possible, but why not.

Make the base class data private
The last guideline I want to mention is this – always keep your base class data private. Do not be tempted to make it protected just so the derived classes can access it directly. If access to that data is required, provide getters and setters. Making that data protected will break the encapsulation of your base class. Anybody, who inherits from your class will see and access that data and thus, modify it in unwanted ways.
Conclusion
Phew, this was a loooong post. If you have come to this conclusion, congrats, you learned some very useful things today.
As a short overview – We’ve got familiar with the concept of developing high-quality classes. We saw why we need classes and abstract data types; we saw how to use abstraction, encapsulation and inheritance to make our classes high-quality classes. I hope this was useful for you. We will see more guidelines that are useful.
The code I showed is available in GitHub and below as an archive you can download.
Next time we will see how to make high-quality routines.

Passionate developer, loving husband and caring father. As an introvert programming is my life. I work as a senior software engineer in a big international company. My hobbies include playing computer games (mostly World of Warcraft), watching TV series and hiking.
Interesting article. Learned something new today.