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 = ±
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
| Technique | Purpose | Characteristics |
|---|---|---|
| Pure Virtual Functions | Define interfaces | Force implementation |
| Abstract Base Classes | Class hierarchy root | Cannot be instantiated |
| Interface Design | Behavioral contracts | Single responsibility |
| Templates | Generic abstraction | Compile-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.