What is the Difference Between Encapsulation and Information Hiding in OOP?

Learn the Difference Between Encapsulation and Information Hiding in object-oriented programming.

Picture of Nsikak Imoh, author of Macsika Blog
A plain background with the text What is the Difference Between Encapsulation and Information Hiding in OOP?
A plain background with the text What is the Difference Between Encapsulation and Information Hiding in OOP?

Table of Content

As a programmer, you may have encountered the words encapsulation and information hiding.

These two concepts are pretty common in Object-Oriented Programming (OOP) especially when we are working with one or more classes.

In this post, you will learn about what they are in programming, why encapsulation is not the same as information hiding, and how we can use it to improve your design system when coding.

What is Encapsulation?

There are two ways to define encapsulation in object-oriented programming.

First Meaning of Encapsulation in OOP

The first definition is the grouping of a concept in a way that each group represents the essential features of another concept.

For example, a document showing the culture of a company encapsulates a company's missions, visions, values, and practices.

Example of Encapsulation in Object-Oriented Programming (OOP)

In object-oriented programming, we can have a Customer class that represents all the data that we consider to be essential for representing a customer.

Let's look at an example here using python:


class Customer:
   	id: str
   	full_name: str
   	email: str
Customer class example.

The example above is a very simple Customer class.

Now, there is no way you would at the moment say that this encapsulates what a customer represents.

It has an id, a name, and an email address field.

You might want to add lots of different fields to this customer class.

For example, we want to know the zip code, city, and country as well.


class Customer:
    id: str
    full_name: str
    email: str
    zip_code: str
    city: str
    country: str
Customer class example with more fields.

You can add as many fields as possible until you reach a point where this list is going to be complete.

It is going to encapsulate everything that you want a customer to represent.

Second Meaning of Encapsulation in OOP

The second meaning of encapsulation is that it defines boundaries around things.

For example, putting animals in cages in a zoo or being kept behind prison bars for a crime you committed means you have been encapsulated by the government.

So, encapsulation in this sense is about restricting access to some things in one form or the other.

In OOP, you can implement this by making instance variables and methods to be private or protected.

This is well described in programming languages like C++ and Java.

However, the Python programming language is a bit special in handling this situation.

This is because Python does not explicitly allow you to restrict access.

Although, conventionally, most Python developers like myself, use a single underscore to conceptually indicate that a method or function is protected while double underscores represent private.

Second Example of Encapsulation in Object-Oriented Programming (OOP)

For example:


class Customer
    def _protected_method(self) -> str:
      ...
    def __private_method(self) -> str:
      ...
Customer class with protected and private methods.

What is Information Hiding?

Information Hiding in OOP means that you keep certain aspects of your code from the outside.

It serves as a kind of black box that other functions, modules, and classes use without having to know anything in particular about how it works internally.

Access to it is usually provided through an interface or API.

Information hiding is similar to encapsulation, but it is not the same thing.

Example of Information Hiding

For example, assuming you create a software that uses a payment system like Plaid or Stripe to process payment at various places in your code, but you don't want that code to be dependent on the payment system itself.

So what do you do?

You hide the information by creating a payment processing module that contains functions for starting a payment, processing refunds, calculating tax percentages, etc.


class Stripe
    def make_payment(self):
    	...
    	
    def process_refunds(self):
    	...
    	
    def calculate_tax_percentage(self):
    	...
Information hiding using Stripe code example.

Inside the module, you can also have the implementation details such as, choosing stripe-specific API calls to make, authenticating the stripe interface, extracting the data and transforming it into a format that your application can use, and so on.

All the other parts of your code will use the stripe payment processing module that you have built.

However, they do not need to know anything about the implementation details that information is hiding.

In that sense, the stripe payment processor module acts as a facade, like a facade design pattern, which is one of the design patterns in the gang of four books.

Example of Information Hiding in Object-Oriented Programming (OOP)

Let's look at another example of implementation hiding in OOP using Python.

Here is an ordering example for an eCommerce:


  from enum import Enum


  class PaymentStatus(Enum):
      CANCELLED = "cancelled"
      PENDING = "pending"
      PAID = "paid"
  
  class StatusError(Exception):
      pass
  
  class OrderItem:
      name: str
      price: int
      quantity: int
  
      @property
      def total_price(self) -> int:
          return self.price * self.quantity
  
  class Order:
      items: list[OrderItem] = []
      _payment_status: PaymentStatus = PaymentStatus.PENDING
  
      def add_item(self, item: OrderItem):
          self.items.append(item)
  
      def is_paid(self) -> bool:
          return self._payment_status == PaymentStatus.PAID
  
      def is_cancelled(self) -> bool:
          return self._payment_status == PaymentStatus.CANCELLED
  
      def cancel(self) -> None:
          if self._payment_status == PaymentStatus.PAID:
              raise StatusError("This order is already paid.")
          self._payment_status = PaymentStatus.CANCELLED
  
      def pay(self) -> None:
          if self._payment_status == PaymentStatus.PAID:
              raise StatusError("Order is already paid.")
          self._payment_status = PaymentStatus.PAID
  
      def set_payment_status(self, status: PaymentStatus) -> None:
          if self._payment_status == PaymentStatus.PAID:
              raise StatusError(
                  "This item is already paid!"
              )
          self._payment_status = status
  
      @property
      def total_price(self) -> int:
          return sum(item.total_price for item in self.items)
Full example of information hiding.

There are a couple of classes in this file.

We have a PaymentStatus class which is an Enum, so it's either canceled, pending, or paid.

There is a StatusError, which inherits an Exception class to create a custom exception class.

And we have an OrderItem class that represents a single order with a name, a price, and a quantity. This class also has a property to compute the total price.

Then we have the Order class, which has a list of the items to be ordered.

It also has a _payment_status variable that maintains the current status of the payment of this particular order and a bunch of methods to do something with the order.

The code above is an example where we are applying encapsulation in that we are providing a boundary using the _payment_status variable, which is marked as a protected variable.

We could also make it private by adding two underscores in front of it __payment_status.

Since we are using Python, this means that in principle, you are not supposed to use this outside of the Order class or any class that inherits from the Order class — even though you can.

We also implement information hiding about what a payment status is.

If you look at the methods, we have is_paid(), is_cancelled(), cancel(), and pay(), the methods do not get any parameters.

They just return a boolean or null.

So, when you use the Order class in Python, in principle, you do not need to know anything about how payment status is implemented.

Let's say we wanted to change the _payment_status variable to an integer value or a string.

We would, of course, have to adapt the bodies of each of the methods, but the user of the Order class would not have to know anything about that.

Well, unless they were accessing this variable directly, which they're not supposed to because it's conceptually protected, but not inaccessible in Python.

In languages like Java and C++, accessing a protected or private method or variable directly will throw an exception.

So, this encapsulation has a boundary, which is what you see here.

It is a protected member and there is information hiding because the user of the Order class does not need to know anything about how a payment status in the order is represented.

Example of Information Hiding in methods

Information hiding in object-oriented programming does not always have to be applied to instant variables of classes.

It can be a lot of things such as methods. Take a look at the code below.



def send_email(to: str, subject: str, body: str) -> None:
    print(f"Sending email to {to}.")
    print(f"Subject: {subject}")
    print("Body:")
    print(body)


class Customer:
    id: str
    name: str
    email_address: str

    def __post_init__(self):
        self._send_welcome_email()

    def _send_welcome_email(self):
        subject = "Welcome!"
        body = (
            f"Hi, {self.name}, we're so glad you joined us."
        )
        send_email(self.email_address, subject, body)
Example of information hiding in methods.

For example, here is the previous Customer class we used earlier slightly adapted to accommodate a protected method.

What we want to achieve here is, if we create a new customer in the system, that customer should automatically receive a welcome email.

However, at the moment, this function does nothing but print the email to the screen.

To do that, we have added a method _send_welcome_email() that defines a subject, a body, and then calls a send_email() function.

Notice that the _send_welcome_email() method itself is protected because we are not supposed to call it explicitly when using the Customer class.

We added a __post_init__() dunder method, which calls the _send_welcome_email() method that in turn sends the welcome emails, after the Object has been initialized, and these values have been set.

So, this example shows how to implement information hiding in OOP using Python.

But, in this case, we are hiding a method, not an instance variable.

What is the Difference Between Encapsulation and Information Hiding

In a simple definition, encapsulation and information hiding help increase cohesion and reduce coupling.

Encapsulation provides boundaries and groups things together. It is very closely related to cohesion. By grouping more things and providing clear boundaries, you are increasing the cohesion of your software.

Information hiding, on the other hand, helps to reduce coupling. It removes dependencies by introducing abstraction layers between the different parts of your code.

Despite their differences, what is fascinating is how encapsulation and information hiding relates to software design principles.

Code Example of the Difference Between Encapsulation and Information Hiding in OOP

Let us look at one more example to show the difference between encapsulation and information hiding.


from enum import Enum
from typing import Any


class PaymentStatus(Enum):
    CANCELLED = "cancelled"
    PENDING = "pending"
    PAID = "paid"


class PaymentStatusError(Exception):
    pass
Encapsulation example.

Here, we have a modified version of the eCommerce store ordering system.

So we have the PaymentStatus and PaymentStatusError classes.

No Encapsulation and No Information Hiding Example


class OrderNoEncapsulationNoInformationHiding:
    """Anyone can get the payment status directly via the instance variable.
    There are no boundaries whatsoever."""

    payment_status: PaymentStatus = PaymentStatus.PENDING
No Encapsulation and No Information Hiding Example.

The first class after that is an order class called OrderNoEncapsulationNoInformationHiding class that has no encapsulation or information hiding.

This is the most basic case.

We simply have an instance variable called payment_status of type PaymentStatus, that has an initial value of pending.

Anybody who uses this class can access the payment status directly and modify it without any issue, and there is also nothing in the class that indicates that you should not do this.

There are no boundaries whatsoever, so information is not hidden, you can easily access it and there are no indications that you should not.

Encapsulation Without Information Hiding Example


class OrderEncapsulationNoInformationHiding:
    """There's an interface now that you should use that provides encapsulation.
    Users of this class still need to know that the status is represented by an enum type."""

    _payment_status: PaymentStatus = PaymentStatus.PENDING

    def get_payment_status(self) -> PaymentStatus:
        return self._payment_status

    def set_payment_status(self, status: PaymentStatus) -> None:
        if self._payment_status == PaymentStatus.PAID:
            raise PaymentStatusError(
                "You can't change the status of an already paid order."
            )
        self._payment_status = status
Encapsulation Without Information Hiding Example

Then, we have another order class called OrderEncapsulationNoInformationHiding.

This order class is different from the one above because it does have encapsulation.

The instance variable _payment_status is now a protected instance variable.

We also have two methods: get_payment_status() and set_payment_status() in this class.

The internal representation of the payment status, which is an Enum, is the same as how we are reading and modifying it from the outside.

So there is no information hiding in this case.

If we want to throw away the payment status Enum, it means you have to change the internal representation.

And we are going to have to change the methods as well because they also rely on payment status.

Information Hiding Without Encapsulation Example


class OrderInformationHidingWithoutEncapsulation:
    """The status variable is public again (so there's no boundary),
    but we don't know what the type is - that information is hidden. I know, it's a bit
    of a contrived example - you wouldn't ever do this. But at least it shows that
    it's possible."""

    payment_status: Any = None

    def is_paid(self) -> bool:
        return self.payment_status == PaymentStatus.PAID

    def is_cancelled(self) -> bool:
        return self.payment_status == PaymentStatus.CANCELLED

    def cancel(self) -> None:
        if self.payment_status == PaymentStatus.PAID:
            raise PaymentStatusError("You can't cancel an already paid order.")
        self.payment_status = PaymentStatus.CANCELLED

    def pay(self) -> None:
        if self.payment_status == PaymentStatus.PAID:
            raise PaymentStatusError("Order is already paid.")
        self.payment_status = PaymentStatus.PAID
Information Hiding Without Encapsulation Example.

This is another class called the OrderInformationHidingWithoutEncapsulation

Here, we have an order class with information hiding and no encapsulation.

So, the instance variable payment_status with type Any and a value of None is public again, hence, there is no boundary.

We have the medthids is_paid(), is_cancelled(), cancel(), and pay() here that use the payment status Enum.

By looking at the class definition or by using the class, we have no idea what payment status is.

There is no boundary. But the information about what it is about and how it is represented is not available to us.

Encapsulation and Information Hiding Example


class OrderEncapsulationAndInformationHiding:
    """The status variable is set to 'private'. The only thing you're supposed to use is the is_paid
    method, you need no knowledge of how status is represented (that information is 'hidden')."""

    _payment_status: PaymentStatus = PaymentStatus.PENDING

    def is_paid(self) -> bool:
        return self._payment_status == PaymentStatus.PAID

    def is_cancelled(self) -> bool:
        return self._payment_status == PaymentStatus.CANCELLED

    def cancel(self) -> None:
        if self._payment_status == PaymentStatus.PAID:
            raise PaymentStatusError("You can't cancel an already paid order.")
        self._payment_status = PaymentStatus.CANCELLED

    def pay(self) -> None:
        if self._payment_status == PaymentStatus.PAID:
            raise PaymentStatusError("Order is already paid.")
        self._payment_status = PaymentStatus.PAID
Encapsulation and Information Hiding Example.

Here's the fourth class called OrderEncapsulationAndInformationHiding, which we have seen before.

We have the _payment_status instance variable, which is encapsulated right. It's a protected member variable and the information about the payment status is internally hidden by the methods is_paid(), is_cancelled(), cancel(), and pay().

Since we do not use the payment status Enum here, that information is hidden.

It also means that if we decide to change the payment status to something else like a string, if we are using this class from the outside, we do not have to change anything.

We simply keep calling these methods, but internally we need to change the code in this class to no longer use the enum.

But, from the outside, nothing is going to change.

Wrap Off

Now, we have seen the difference between encapsulation and implementation hiding in four variants.

We have no encapsulation with no information hiding, encapsulation with no information hiding, information hiding with no encapsulation, and finally, we have encapsulation and information hiding.

The code example on Implementation Hiding without Encapsulation is purely theoretical.

I would never advise you to write classes like that.

Anyway. I hope you enjoyed this post. If you do, consider sharing, and exploring other articles on software design and development.

Get the Complete Code of Python Code Snippets on Github.

Connect with me.

Need an engineer on your team to grease an idea, build a great product, grow a business or just sip tea and share a laugh?