Skip to content

C++ Abstraction

Overview

Abstraction is one of the fundamental principles of object-oriented programming. It hides complex implementation details and only exposes necessary functional interfaces to users. C++ implements abstraction through mechanisms such as abstract classes, pure virtual functions, and interface design, helping developers build clear and maintainable code architectures.

🎭 Abstraction Concepts

The Essence of Abstraction

cpp
#include <iostream>
#include <string>
#include <memory>

// Abstraction: Hide complexity, present simple interfaces
class MediaPlayer {
public:
    virtual ~MediaPlayer() = default;
    
    // Abstract interface: Users only need to know these operations
    virtual void play() = 0;
    virtual void pause() = 0;
    virtual void stop() = 0;
    virtual void setVolume(int volume) = 0;
    virtual int getVolume() const = 0;
    virtual bool isPlaying() const = 0;
    
    // Common functionality
    void showStatus() const {
        std::cout << "Player status: " << (isPlaying() ? "Playing" : "Stopped")
                  << ", Volume: " << getVolume() << std::endl;
    }
};

// Concrete implementation: Users don't need to understand internal details
class MP3Player : public MediaPlayer {
private:
    std::string filename_;
    bool playing_;
    int volume_;
    int position_;  // Playback position
    
    // Internal complex operations (details hidden by abstraction)
    void loadAudioCodec() {
        std::cout << "Loading MP3 decoder..." << std::endl;
    }
    
    void initializeHardware() {
        std::cout << "Initializing audio hardware..." << std::endl;
    }
    
public:
    MP3Player(const std::string& filename) 
        : filename_(filename), playing_(false), volume_(50), position_(0) {
        loadAudioCodec();
        initializeHardware();
    }
    
    // Simple external interface
    void play() override {
        if (!playing_) {
            std::cout << "Starting playback: " << filename_ << std::endl;
            playing_ = true;
        }
    }
    
    void pause() override {
        if (playing_) {
            std::cout << "Pausing playback" << std::endl;
            playing_ = false;
        }
    }
    
    void stop() override {
        std::cout << "Stopping playback" << std::endl;
        playing_ = false;
        position_ = 0;
    }
    
    void setVolume(int volume) override {
        volume_ = std::max(0, std::min(100, volume));
        std::cout << "Volume set to: " << volume_ << std::endl;
    }
    
    int getVolume() const override { return volume_; }
    bool isPlaying() const override { return playing_; }
};

int main() {
    std::cout << "=== Abstraction Concept Demo ===" << std::endl;
    
    // User uses through abstract interface, no need to understand internal implementation
    std::unique_ptr<MediaPlayer> player = std::make_unique<MP3Player>("song.mp3");
    
    player->play();
    player->setVolume(75);
    player->showStatus();
    
    player->pause();
    player->showStatus();
    
    return 0;
}

🏗️ Abstract Class Design

Pure Virtual Functions and Abstract Base Classes

cpp
#include <iostream>
#include <vector>
#include <memory>

// Abstract base class: Defines interface specifications
class Database {
public:
    virtual ~Database() = default;
    
    // Pure virtual functions define abstract interface
    virtual bool connect(const std::string& connectionString) = 0;
    virtual void disconnect() = 0;
    virtual bool executeQuery(const std::string& query) = 0;
    virtual std::string getLastError() const = 0;
    
    // Template method pattern: Defines algorithm skeleton
    bool executeTransaction(const std::vector<std::string>& queries) {
        beginTransaction();
        
        for (const auto& query : queries) {
            if (!executeQuery(query)) {
                rollback();
                return false;
            }
        }
        
        return commit();
    }
    
protected:
    // Hook methods that subclasses must implement
    virtual void beginTransaction() = 0;
    virtual bool commit() = 0;
    virtual void rollback() = 0;
};

// MySQL implementation
class MySQLDatabase : public Database {
private:
    bool connected_;
    std::string lastError_;
    
public:
    MySQLDatabase() : connected_(false) {}
    
    bool connect(const std::string& connectionString) override {
        std::cout << "Connecting to MySQL: " << connectionString << std::endl;
        connected_ = true;
        return true;
    }
    
    void disconnect() override {
        if (connected_) {
            std::cout << "Disconnecting MySQL" << std::endl;
            connected_ = false;
        }
    }
    
    bool executeQuery(const std::string& query) override {
        if (!connected_) {
            lastError_ = "Database not connected";
            return false;
        }
        
        std::cout << "Executing MySQL query: " << query << std::endl;
        return true;
    }
    
    std::string getLastError() const override {
        return lastError_;
    }
    
protected:
    void beginTransaction() override {
        std::cout << "MySQL: BEGIN TRANSACTION" << std::endl;
    }
    
    bool commit() override {
        std::cout << "MySQL: COMMIT" << std::endl;
        return true;
    }
    
    void rollback() override {
        std::cout << "MySQL: ROLLBACK" << std::endl;
    }
};

// PostgreSQL implementation
class PostgreSQLDatabase : public Database {
private:
    bool connected_;
    std::string lastError_;
    
public:
    PostgreSQLDatabase() : connected_(false) {}
    
    bool connect(const std::string& connectionString) override {
        std::cout << "Connecting to PostgreSQL: " << connectionString << std::endl;
        connected_ = true;
        return true;
    }
    
    void disconnect() override {
        if (connected_) {
            std::cout << "Disconnecting PostgreSQL" << std::endl;
            connected_ = false;
        }
    }
    
    bool executeQuery(const std::string& query) override {
        if (!connected_) {
            lastError_ = "Database not connected";
            return false;
        }
        
        std::cout << "Executing PostgreSQL query: " << query << std::endl;
        return true;
    }
    
    std::string getLastError() const override {
        return lastError_;
    }
    
protected:
    void beginTransaction() override {
        std::cout << "PostgreSQL: START TRANSACTION" << std::endl;
    }
    
    bool commit() override {
        std::cout << "PostgreSQL: COMMIT" << std::endl;
        return true;
    }
    
    void rollback() override {
        std::cout << "PostgreSQL: ROLLBACK" << std::endl;
    }
};

int main() {
    std::cout << "=== Abstract Class Design ===" << std::endl;
    
    // Use different database implementations through abstract interface
    std::vector<std::unique_ptr<Database>> databases;
    databases.push_back(std::make_unique<MySQLDatabase>());
    databases.push_back(std::make_unique<PostgreSQLDatabase>());
    
    for (auto& db : databases) {
        db->connect("localhost:5432/mydb");
        
        // Execute transaction
        std::vector<std::string> queries = {
            "INSERT INTO users VALUES (1, 'Alice')",
            "INSERT INTO users VALUES (2, 'Bob')"
        };
        
        if (db->executeTransaction(queries)) {
            std::cout << "Transaction executed successfully" << std::endl;
        }
        
        db->disconnect();
        std::cout << std::endl;
    }
    
    return 0;
}

🎯 Interface Design Patterns

Interface Segregation Principle

cpp
#include <iostream>

// Design violating interface segregation principle (bad example)
class BadWorker {
public:
    virtual ~BadWorker() = default;
    virtual void work() = 0;
    virtual void eat() = 0;
    virtual void sleep() = 0;
    virtual void code() = 0;        // Not all workers can code
    virtual void manageMeetings() = 0;  // Not all workers manage meetings
};

// Design following interface segregation principle
class Worker {
public:
    virtual ~Worker() = default;
    virtual void work() = 0;
};

class Human {
public:
    virtual ~Human() = default;
    virtual void eat() = 0;
    virtual void sleep() = 0;
};

class Programmer {
public:
    virtual ~Programmer() = default;
    virtual void code() = 0;
};

class Manager {
public:
    virtual ~Manager() = default;
    virtual void manageMeetings() = 0;
};

// Concrete implementation classes: Only implement needed interfaces
class Developer : public Worker, public Human, public Programmer {
private:
    std::string name_;
    
public:
    Developer(const std::string& name) : name_(name) {}
    
    void work() override {
        std::cout << name_ << " is working" << std::endl;
    }
    
    void eat() override {
        std::cout << name_ << " is eating" << std::endl;
    }
    
    void sleep() override {
        std::cout << name_ << " is sleeping" << std::endl;
    }
    
    void code() override {
        std::cout << name_ << " is coding" << std::endl;
    }
};

class ProjectManager : public Worker, public Human, public Manager {
private:
    std::string name_;
    
public:
    ProjectManager(const std::string& name) : name_(name) {}
    
    void work() override {
        std::cout << name_ << " is managing projects" << std::endl;
    }
    
    void eat() override {
        std::cout << name_ << " is eating" << std::endl;
    }
    
    void sleep() override {
        std::cout << name_ << " is sleeping" << std::endl;
    }
    
    void manageMeetings() override {
        std::cout << name_ << " is conducting meetings" << std::endl;
    }
};

class Robot : public Worker {
private:
    std::string model_;
    
public:
    Robot(const std::string& model) : model_(model) {}
    
    void work() override {
        std::cout << "Robot " << model_ << " is working" << std::endl;
    }
    // Robots don't need to eat and sleep
};

int main() {
    std::cout << "=== Interface Segregation Principle ===" << std::endl;
    
    Developer dev("Alice");
    ProjectManager pm("Bob");
    Robot robot("R2D2");
    
    // Access objects through different interfaces
    Worker* workers[] = {&dev, &pm, &robot};
    
    std::cout << "All workers start working:" << std::endl;
    for (Worker* worker : workers) {
        worker->work();
    }
    
    std::cout << "\nProgrammer coding:" << std::endl;
    Programmer* programmer = &dev;
    programmer->code();
    
    std::cout << "\nProject manager managing meetings:" << std::endl;
    Manager* manager = &pm;
    manager->manageMeetings();
    
    return 0;
}

🏛️ Abstract Factory Pattern

Creational Abstraction

cpp
#include <iostream>
#include <memory>

// Abstract products
class Button {
public:
    virtual ~Button() = default;
    virtual void render() const = 0;
    virtual void onClick() const = 0;
};

class TextField {
public:
    virtual ~TextField() = default;
    virtual void render() const = 0;
    virtual void onInput(const std::string& text) const = 0;
};

// Windows style products
class WindowsButton : public Button {
public:
    void render() const override {
        std::cout << "Rendering Windows style button" << std::endl;
    }
    
    void onClick() const override {
        std::cout << "Windows button click effect" << std::endl;
    }
};

class WindowsTextField : public TextField {
public:
    void render() const override {
        std::cout << "Rendering Windows style text field" << std::endl;
    }
    
    void onInput(const std::string& text) const override {
        std::cout << "Windows text field input: " << text << std::endl;
    }
};

// Mac style products
class MacButton : public Button {
public:
    void render() const override {
        std::cout << "Rendering Mac style button" << std::endl;
    }
    
    void onClick() const override {
        std::cout << "Mac button click effect" << std::endl;
    }
};

class MacTextField : public TextField {
public:
    void render() const override {
        std::cout << "Rendering Mac style text field" << std::endl;
    }
    
    void onInput(const std::string& text) const override {
        std::cout << "Mac text field input: " << text << std::endl;
    }
};

// Abstract factory
class UIFactory {
public:
    virtual ~UIFactory() = default;
    virtual std::unique_ptr<Button> createButton() const = 0;
    virtual std::unique_ptr<TextField> createTextField() const = 0;
};

// Concrete factories
class WindowsFactory : public UIFactory {
public:
    std::unique_ptr<Button> createButton() const override {
        return std::make_unique<WindowsButton>();
    }
    
    std::unique_ptr<TextField> createTextField() const override {
        return std::make_unique<WindowsTextField>();
    }
};

class MacFactory : public UIFactory {
public:
    std::unique_ptr<Button> createButton() const override {
        return std::make_unique<MacButton>();
    }
    
    std::unique_ptr<TextField> createTextField() const override {
        return std::make_unique<MacTextField>();
    }
};

// Client code
class Application {
private:
    std::unique_ptr<UIFactory> factory_;
    
public:
    Application(std::unique_ptr<UIFactory> factory) : factory_(std::move(factory)) {}
    
    void createUI() {
        auto button = factory_->createButton();
        auto textField = factory_->createTextField();
        
        std::cout << "Creating UI components:" << std::endl;
        button->render();
        textField->render();
        
        std::cout << "\nTesting interactions:" << std::endl;
        button->onClick();
        textField->onInput("Hello World");
    }
};

int main() {
    std::cout << "=== Abstract Factory Pattern ===" << std::endl;
    
    // Windows application
    std::cout << "Windows application:" << std::endl;
    Application windowsApp(std::make_unique<WindowsFactory>());
    windowsApp.createUI();
    
    std::cout << "\nMac application:" << std::endl;
    Application macApp(std::make_unique<MacFactory>());
    macApp.createUI();
    
    return 0;
}

📋 Abstraction Design Principles

Abstraction in SOLID Principles

cpp
#include <iostream>
#include <vector>
#include <memory>

// Dependency Inversion Principle: High-level modules should not depend on low-level modules, both should depend on abstractions
class Logger {
public:
    virtual ~Logger() = default;
    virtual void log(const std::string& message) = 0;
};

class FileLogger : public Logger {
public:
    void log(const std::string& message) override {
        std::cout << "[File Log] " << message << std::endl;
    }
};

class ConsoleLogger : public Logger {
public:
    void log(const std::string& message) override {
        std::cout << "[Console] " << message << std::endl;
    }
};

// Open/Closed Principle: Open for extension, closed for modification
class NotificationService {
private:
    std::vector<std::shared_ptr<Logger>> loggers_;
    
public:
    void addLogger(std::shared_ptr<Logger> logger) {
        loggers_.push_back(logger);
    }
    
    void sendNotification(const std::string& message) {
        // Business logic
        std::cout << "Sending notification: " << message << std::endl;
        
        // Log messages (depend on abstraction, not concrete implementation)
        for (auto& logger : loggers_) {
            logger->log("Notification sent: " + message);
        }
    }
};

// Liskov Substitution Principle: Subtypes should be substitutable for their base types
class Shape {
public:
    virtual ~Shape() = default;
    virtual double getArea() const = 0;
    
    // Invariant: Area should be non-negative
    void printArea() const {
        double area = getArea();
        if (area >= 0) {
            std::cout << "Area: " << area << std::endl;
        } else {
            std::cout << "Error: Area cannot be negative" << std::endl;
        }
    }
};

class Rectangle : public Shape {
private:
    double width_, height_;
    
public:
    Rectangle(double width, double height) : width_(width), height_(height) {}
    
    double getArea() const override {
        return width_ * height_;  // Satisfies invariant
    }
};

int main() {
    std::cout << "=== Abstraction Design Principles ===" << std::endl;
    
    // Dependency inversion and open/closed principles
    NotificationService service;
    service.addLogger(std::make_shared<FileLogger>());
    service.addLogger(std::make_shared<ConsoleLogger>());
    
    service.sendNotification("System startup complete");
    
    // Liskov substitution principle
    std::cout << "\nShape calculations:" << std::endl;
    std::unique_ptr<Shape> shape = std::make_unique<Rectangle>(5, 3);
    shape->printArea();
    
    return 0;
}

Summary

Abstraction is an important principle of software design, helping us build clear and maintainable code architectures:

Abstraction Levels

  • Conceptual Abstraction: Hide complex implementations, provide simple interfaces
  • Data Abstraction: Encapsulate data and operations
  • Behavioral Abstraction: Define unified behavioral specifications
  • Creational Abstraction: Factory patterns, abstraction of object creation

Implementation Techniques

TechniquePurposeCharacteristics
Pure Virtual FunctionsDefine interfacesForce implementation
Abstract Base ClassesClass hierarchy rootCannot be instantiated
Interface DesignBehavioral contractsSingle responsibility
TemplatesGeneric abstractionCompile-time decisions

Design Principles

  • Single Responsibility: One abstraction handles one responsibility
  • Open/Closed Principle: Open for extension, closed for modification
  • Liskov Substitution: Subtypes can replace base types
  • Interface Segregation: Interfaces should be small and focused
  • Dependency Inversion: Depend on abstractions, not concretions

Abstraction makes code more modular, testable, and extensible, serving as the cornerstone of building large-scale software systems.

Content is for learning and research only.