Lesson Introduction

Hello! Today, we'll venture into the realm of design patterns. Specifically, we'll tackle exercises that apply a single design pattern to problem-solving. Mastering these patterns is a surefire way to extend your coding skills.

Our goal today is to fortify your understanding of when and how to apply specific Object-Oriented Programming (OOP) design patterns. These patterns include Encapsulation, Abstraction, Polymorphism, and Composition.

We'll dissect four real-life scenarios and distinguish which pattern is applicable and why.

Let's get underway!

Real-life Example 1: Database Management System (Encapsulation)

The Encapsulation pattern proves beneficial for the development of a Database Management System (DBMS). Each DBMS table represents a class, the fields represent private data members, and the functions operating on this data serve as methods.

Encapsulation ensures that data members are accessed through methods that promote data integrity and prevent inadvertent anomalies. Here's a mini-code snippet to support this concept:

#include <iostream>
#include <map>
#include <string>

class Employees {
private:
    std::map<int, std::string> employees; // private data member

public:
    void add_employee(int eid, const std::string& name) { // method to operate on private data
        employees[eid] = name;
    }

    void update_employee(int eid, const std::string& new_name) { // method to operate on private data
        if (employees.find(eid) != employees.end()) {
            employees[eid] = new_name;
        }
    }

    void display() const {
        for (const auto& employee : employees) {
            std::cout << "ID: " << employee.first << " Name: " << employee.second << std::endl;
        }
    }
};

int main() {
    Employees employees;
    employees.add_employee(1, "John");
    employees.add_employee(2, "Mark");

    employees.update_employee(2, "Jake");
    employees.display();
    // Outputs: 
    // ID: 1 Name: John
    // ID: 2 Name: Jake
    return 0;
}

In this context, Encapsulation restricts direct access to employee data, presenting a protective layer via designated methods.

Real-life Example 2: Creating a Vehicle (Abstraction)

Consider creating a Vehicle class in C++. Here, Abstraction comes into play. You expose only the necessary functionality and abstract away the internal workings of the Vehicle.

Let's see this in code:

#include <iostream>

class Vehicle {
protected:
    std::string color;
    std::string engine_type;
    bool engine_running;

public:
    Vehicle(const std::string& c, const std::string& e) : color(c), engine_type(e), engine_running(false) {}

    virtual void start_engine() = 0;
    virtual void stop_engine() = 0;
    virtual void drive() = 0;
};

class Car : public Vehicle {
public:
    Car(const std::string& c, const std::string& e) : Vehicle(c, e) {}

    void start_engine() override {
        engine_running = true;
        std::cout << "Car engine started!" << std::endl;
    }

    void stop_engine() override {
        engine_running = false;
        std::cout << "Car engine stopped!" << std::endl;
    }

    void drive() override {
        if (engine_running) {
            std::cout << "Car is driving!" << std::endl;
        } else {
            std::cout << "Start the engine first!" << std::endl;
        }
    }
};

int main() {
    Car car("red", "gasoline");
    car.start_engine();
    car.drive();

    return 0;
}

Here, the Vehicle abstract class exposes relevant and necessary functions such as start_engine(), stop_engine(), and drive(), and the Car class implements this abstract class, providing concrete implementations. However, it hides or abstracts away internal state management (engine_running). This is a basic instance of Abstraction, which simplifies the interaction with the class and hides underlying complexity.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal