Understanding Encapsulation in C++

To understand what encapsulation is, I will start off with a brief explanation of setters/getters and class behavior.

Setters/Getters

C++ is an Object-Oriented Programming (OOP) language. One of the principles of OOP is to encapsulate data members as private members and access them through a public access interface.

Let's take for example the class shown below:

class Person{

private:

    int age; //1. Age is a private data member

public:

    Person();

    ~Person();

    void setAge(int uAge); //2. Sets the value of the age member

    int getAge(); //3. Gets the value of the age member

};

The data member 'age' is a private data member (line 1). The value of this data member is accessed either through the method 'setAge' or 'getAge.' The method 'setAge' sets the value of 'age' (line 2). The method 'getAge' retrieves the value of 'age' (line 3).

In Object-Oriented Programming languages, a method that sets the value of a private data member is known as a 'setter.' In contrast, a method that retrieves the value of a data member is known as a 'getter.' Therefore, the 'setAge' method is referred as a setter. Whereas, the 'getAge' method is referred as a getter.

Class Behavior

In a program, a Class represents a real-world object or entity. A class has data members and methods that account for the behavior of the object.

A class behavior is a common action performed by the object. For example, an artist has a set of common behaviors; before painting a masterpiece, an artist prepares the easel, cleans the brushes and starts painting.

A program can represent an artist behaviors or actions through a Class' method. The snippet below illustrates the behaviors of an artist object in C++. See lines 1, 2 & 3.

class Artist{

private:

public:

    Artist();
    ~Artist();

    void prepareEasel(); //1. Prepares the easel

    void cleanBrushes(); //2. Cleans the brushes

    void paint(); //3. Artist's paint style  
};

Encapsulation

Encapsulation is the act of making data members private and accessing them using a public interface; through setters and getters methods.

Object-Oriented Programming principles suggest always to encapsulate data members. However, data members are not the only entities that should be encapsulated. A Class behavior should also be encapsulated; in particular, behaviors of a class that varies.

Let's analyze the Artist class shown above. The class contains these actions:

  • Prepares Easel
  • Cleans Brushes
  • Paints

Of these behaviors, painting differs among painters. For example, an artist's could paint in a modern, impressionist or abstract style. The other two actions do not change among painters. Every artist prepares an easel and cleans a brush the same way.

Therefore, we should encapsulate the 'paint' behavior since it varies from object to object.

In C++, abstract classes are used to encapsulate behaviors. The snippet below shows an abstract class, 'PaintStyle,' that encapsulates the 'paint' behavior (see line 1).

class PaintStyle{

public:

    virtual void paint()=0;  //1. Paint virtual method

    virtual ~PaintStyle(){}; //2. Virtual destructor
};

To make use of the abstract class, you need to create a subclass. The snippet below shows a subclass of 'PaintStyle.' Notice that its 'paint' behavior prints "I paint in a modern style" (line 4).

class ModernPaintStyle:public PaintStyle{

private:

public:

    ModernPaintStyle(); //1. Constructor

    ~ModernPaintStyle(); //2. Destructor

    void paint(); //3. Paint method

};

ModernPaintStyle::ModernPaintStyle(){}

ModernPaintStyle::~ModernPaintStyle(){}

void ModernPaintStyle::paint(){

    std::cout<<"I paint in a modern style"<<std::endl; //4. Prints "I paint in modern style"

}

Let's create a second subclass of 'PaintStyle.' But this time set the behavior to print "I paint in an impressionist style" (line 4).

class ImpressionistPaintStyle:public PaintStyle{

private:

public:

    ImpressionistPaintStyle(); //1. Constructor

    ~ImpressionistPaintStyle(); //2. Destructor

    void paint(); //3. Paint method

};

ImpressionistPaintStyle::ImpressionistPaintStyle(){}

ImpressionistPaintStyle::~ImpressionistPaintStyle(){}

void ImpressionistPaintStyle::paint(){

    std::cout<<"I paint in an Impressionist style"<<std::endl; //4. Prints "I paint in an Impressionist style"

}

Let's go back to the Artist class and apply the changes shown below.

The snippet shows a pointer member of type 'PaintStyle' (line 1). Line 4 illustrates the addition of the method 'setPaintStyle.' This method along with the modification of the 'paint' method (line 5) injects polymorphism into the class. That is, the Artist class can change behaviors during runtime (See lines 6 &7).

class Artist{

private:

    PaintStyle *artistStyle; //1. Pointer member to 'PaintStyle'

public:

    Artist();
    ~Artist();

    void prepareEasel(); //2. Prepares the easel

    void cleanBrushes(); //3. Cleans the brushes

    void setPaintStyle(PaintStyle *uStyle); //4. Sets the paint style

    void paint(); //5. Artist's paint style

};

Artist::Artist(){}

Artist::~Artist(){}

void Artist::setPaintStyle(PaintStyle *uStyle){

    artistStyle=uStyle; //6. Sets the style of the painter
}

void Artist::paint(){

    artistStyle->paint(); //7. Calls the paint method of the subclass

}

Polymorphism permits the artist class to paint in a modern style. The next minute it can behave like an artist that paints in an impressionist style.

The snippet below showcase these properties.

int main(){

    Artist *picasso=new Artist(); //1. Create an instance of an artist

    PaintStyle *modernStyle=new ModernPaintStyle(); //2. Create an instance of a modern painter style

    PaintStyle *impressionistStyle=new ImpressionistPaintStyle(); //3. Create an instance of an impressionist painter style

    picasso->setPaintStyle(modernStyle); //4. sets the painter style to a modern style

    picasso->paint(); //5. Calls paint method. It prints "I paint in a modern style"

    picasso->setPaintStyle(impressionistStyle); //6. Change the painter style to an impressionist style

    picasso->paint(); //7. Calls the paint method. It prints "I paint in an impressionist style"

    return 0;

}

Line 1 creates an instance of an artist object. Lines 2 & 3 creates instances of different paint styles. The painter style is set in line 4, and when the method 'paint' is executed, it prints "I paint in a modern style" (line 5).

Because of polymorphism and encapsulation, the behavior of the class can change with a simple method call as shown in line 6. When the 'paint' method is called again, it prints "I paint in an impressionist style" (line 7).

Polymorphism and encapsulation give flexibility and modularity to an application. And it enables a program to be extended and modified with few changes. For example, years from now, a new paint style can be added without ever touching the Artist class.

Hope this helps.

Harold Serrano

Computer Graphics Enthusiast. Currently developing a 3D Game Engine.