CLOSE
Updated on 24 Jun, 202625 mins read 15 views

In the previous chapters, we studied:

Objects
Classes
State
Behavior
Constructors
Object Lifecycle
OOP Pillars
Relationships

Among the four pillars of OOP, we introduced encapsulation as:

Bundling data and behavior together while controlling access to internal state.

Many developers stop there.

They think:

private:

means:

Encapsulation Achieved

Unfortunately, this is one of the biggest misconceptions in software engineering.

Consider this class:

class BankAccount
{
private:
    double balance;

public:

    void setBalance(double value)
    {
        balance = value;
    }
};

The field is private.

But can we do this?

account.setBalance(-1000000);

Yes.

The object's internal state can still become invalid.

So despite using private, we have not truly encapsulated anything.

The deeper purpose of encapsulation is:

Protecting the integrity of an object.

Historical Context

In early procedural systems:

Data
Functions

were often completely exposed.

Example:

struct Account
{
    double balance;
};

Any code anywhere could modify:

account.balance

Large systems became difficult to maintain because:

Every module knew everything.

Every module modified everything.

Every module depended on everything.

The Real Goal of Encapsulation

Many textbooks define encapsulation as:

Data + Behavior

While technically correct, that definition missed the most important point.

The true goal is: Protect Invariants

What Is an Invariants?

An invariant is:

A rule that must always remain true for an object.

Examples:

Bank Account
	Balance >= 0
User
	Email must be valid
Order
	Total Price >= 0
Library Book
	Available Copies >= 0

If invariants beak:

Object becomes invalid.

Invalid objects create system-wide bugs.

Example: Invalid State

Bad Design:

class User
{
public:
    std::string email;
};

Usage:

User user;
user.email = "";

Object now contains invalid state.

Nothing prevents it.

Encapsulation Solution

class User
{
private:
    std::string email;

public:

    bool updateEmail(const std::string& value)
    {
        if(value.empty())
            return false;

        email = value;

        return true;
    }
};

Now:

Object protects itself.

Encapsulation Creates Trust

Imagine:

User user;

Question:

Can another developer accidently corrupt it?

If encapsulation is good:

Maybe

Professional software depends on trust.

Objects should guarantee their own correctness.

Information Hiding

Now we move beyond encapsulation.

Definition:

Information Hiding means:

Expose only what users need to know and hide anything else.

Why Information Hiding Exists

Consider a car.

Driver sees:

Steering Wheel
Brake
Accelerator

Driver does NOT see:

Fuel Injection Algorithms
Transmission Control Logic
ECU Software

The car hides implementation complexity

Software should behave the same way.

Example

Bad API:

class EmailService
{
public:

    void openSocket();

    void performHandshake();

    void authenticate();

    void sendBytes();
};

Users must understand internals.

Better:

class EmailService
{
public:

    void sendEmail();
};

Simple.

Everything else hidden.

Encapsulation vs Information Hiding

Many engineers confuse them.

They are related but different.

Encapsulation

Focus:

Protect State

Question:

Who can access data?

Information Hiding

Focus:

Hide Complexity

Question:

What should users know?

Comparison Table

EncapsulationInformation Hiding
Protects object integrityHides implementation
Controls accessControls knowledge
Focus on correctnessFocus on simplicity
Prevents corruptionReduces complexity

Example

class CoffeeMachine
{
public:

    void makeCoffee();

private:

    void heatWater();

    void grindBeans();

    void brew();
};

Encapsulation:

Private methods protected

Information Hiding:

User only sees makeCoffee().

Both working together.

Access Modifiers

C++ provides mechanisms for encapsulation.

Public

Visible everywhere.

public:

Use for:

Stable APIs
Business Operations

Private

Visible only inside class.

private:

Use for:

State
Implementation Detaisl

Protected

Visible to derived classes.

protected:

Use carefully.

Often overused.

Visual Model

+----------------------+
|      Public API      |
+----------------------+

       Hidden

+----------------------+
| Private State        |
| Private Logic        |
+----------------------+

Public interface.

Private implementation.

Designing Good Public APIs

One of the most important software engineering skills.

Bad:

class Order
{
public:

    std::vector<Item> items;

    double total;

    bool paid;
};

Everything exposed.

Good:

class Order
{
public:

    void addItem();

    void removeItem();

    double getTotal() const;

    void pay();

private:

    std::vector<Item> items;

    double total;

    bool paid;
};

Controlled access.

Tell, Don't Ask Principle

A powerful encapsulation principle.

Bad:

if(order.getStatus() == PENDING)
{
    order.setStatus(SHIPPED);
}

External code controls internals.

Better:

order.ship()

Order manages itself.

Mental Model:

Tell object what to do.

Do not ask object for data
and perform its work externally.

Data-Centric vs Behavior-Centric Design

Data-Centric

class User
{
public:

    string name;
    string email;
};

Behavios lives elsewhere.

Behavior-Centric

class User
{
public:

    void changeEmail();

    bool login();

    void logout();
};

Behavior lives inside object.

This is more object-oriented.

Information Leakage

One of the most common design problems.

Example:

class BankAccount
{
public:

    double getBalance()
    {
        return balance;
    }

private:

    double balance;
};

This is acceptable.

But:

class BankAccount
{
public:

    std::vector<Transaction>& getTransactions();
};

Danger.

External code can modify internals.

Information hiding broken.

Safer Version

const std::vector<Transaction>& getTransaction() const;

Read-only access.

Much safer.

The Getter/Setter Trap

Beginners often write:

getName()

setName()

getAge()

setAge()

getEmail()

setEmail()

for every field.

Result:

class User
{
private:
    string name;
    int age;
};

becomes:

class User
{
public:
    string getName();
    void setName();

    int getAge();
    void setAge();
};

Effectively:

Everything still public.

Encapsulation illusion.

Better Design

Ask:

What behavior belongs here?

Example:

user.changeEmail();
user.updateAddress();
user.deactivate();

Behavior-focused APIs.

Law of Least Knowledge

Also known as:

Law of Demeter

Rule:

An object should know as little as possible about other objects.

Bad

customer.getOrder()
        .getPayment()
        .getCard()
        .getExpiryDate();

Known as:

Train Wreck

Too many dependencies.

Better

customer.getPaymentExpiry();

Complexity hidden internally.

Encapsulation and Coupling

Good encapsulation reduces coupling.

Without encapsulation:

Many classes depend on internal details.

Changing implementation becomes dangerous.

With encapsulation:

Only public interface matters.

Example

Version 1:

class Cache
{
private:
    std::unordered_map<int,int> data;
};

Later:

class Cache
{
private:
    std::map<int,int> data;
};

Clients unaffected.

Why?

Because implementation was hidden.

Buy Me A Coffee

Leave a comment

Your email address will not be published. Required fields are marked *

Your experience on this site will be improved by allowing cookies Cookie Policy