Skip to content

C++ Encapsulation

Overview

Encapsulation is one of the basic principles of object-oriented programming. It binds data (attributes) and methods that operate on data (behaviors) together, hiding the internal implementation details of objects. Encapsulation protects data integrity through access control mechanisms and provides clean external interfaces.

🔒 Basic Encapsulation Concepts

Data Hiding and Access Control

cpp
#include <iostream>
#include <string>
#include <stdexcept>

class BankAccount {
private:
    // Private data members: External direct access is not allowed
    std::string accountNumber_;
    std::string ownerName_;
    double balance_;
    std::string pin_;
    bool locked_;
    
    // Private helper methods
    bool validatePin(const std::string& inputPin) const {
        return pin_ == inputPin;
    }
    
    void logTransaction(const std::string& operation, double amount) const {
        std::cout << "[Log] " << accountNumber_ << ": " << operation 
                  << " " << amount << ", Balance: " << balance_ << std::endl;
    }
    
public:
    // Constructor: Ensure valid object creation state
    BankAccount(const std::string& accountNumber, const std::string& ownerName, 
                const std::string& pin, double initialBalance = 0.0)
        : accountNumber_(accountNumber), ownerName_(ownerName), 
          pin_(pin), balance_(initialBalance), locked_(false) {
        
        if (initialBalance < 0) {
            throw std::invalid_argument("Initial balance cannot be negative");
        }
        
        std::cout << "Account created successfully: " << accountNumber_ << std::endl;
    }
    
    // Public interface: Control access to data
    bool deposit(double amount, const std::string& pin) {
        if (!validatePin(pin)) {
            std::cout << "PIN code error" << std::endl;
            return false;
        }
        
        if (locked_) {
            std::cout << "Account is locked" << std::endl;
            return false;
        }
        
        if (amount <= 0) {
            std::cout << "Deposit amount must be greater than 0" << std::endl;
            return false;
        }
        
        balance_ += amount;
        logTransaction("Deposit", amount);
        return true;
    }
    
    bool withdraw(double amount, const std::string& pin) {
        if (!validatePin(pin)) {
            std::cout << "PIN code error" << std::endl;
            return false;
        }
        
        if (locked_) {
            std::cout << "Account is locked" << std::endl;
            return false;
        }
        
        if (amount <= 0) {
            std::cout << "Withdrawal amount must be greater than 0" << std::endl;
            return false;
        }
        
        if (amount > balance_) {
            std::cout << "Insufficient balance" << std::endl;
            return false;
        }
        
        balance_ -= amount;
        logTransaction("Withdrawal", amount);
        return true;
    }
    
    // Read-only accessors
    double getBalance(const std::string& pin) const {
        if (!validatePin(pin)) {
            std::cout << "PIN code error" << std::endl;
            return -1;  // Error identifier
        }
        
        return balance_;
    }
    
    std::string getAccountNumber() const {
        return accountNumber_;  // Account number can be public
    }
    
    std::string getOwnerName() const {
        return ownerName_;
    }
    
    // Security operations
    void lockAccount() {
        locked_ = true;
        std::cout << "Account locked" << std::endl;
    }
    
    void unlockAccount(const std::string& pin) {
        if (validatePin(pin)) {
            locked_ = false;
            std::cout << "Account unlocked" << std::endl;
        } else {
            std::cout << "PIN code error, cannot unlock" << std::endl;
        }
    }
    
    bool isLocked() const {
        return locked_;
    }
};

int main() {
    std::cout << "=== Basic Encapsulation ===" << std::endl;
    
    try {
        BankAccount account("123456789", "Zhang San", "1234", 1000.0);
        
        // Safe operations through public interface
        std::cout << "Account holder: " << account.getOwnerName() << std::endl;
        std::cout << "Balance: " << account.getBalance("1234") << std::endl;
        
        // Normal operations
        account.deposit(500, "1234");
        account.withdraw(200, "1234");
        
        // Error operations are prevented
        account.withdraw(2000, "1234");  // Insufficient balance
        account.deposit(100, "5678");    // PIN error
        
        // Security mechanisms
        account.lockAccount();
        account.deposit(100, "1234");    // Account locked
        account.unlockAccount("1234");
        
        // Cannot directly access private members
        // std::cout << account.balance_;     // Compilation error
        // account.pin_ = "0000";             // Compilation error
        
    } catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << std::endl;
    }
    
    return 0;
}

🎯 Getter and Setter Methods

Accessor and Mutator Design

cpp
#include <iostream>
#include <string>
#include <algorithm>

class Employee {
private:
    std::string name_;
    int age_;
    double salary_;
    std::string department_;
    int employeeId_;
    static int nextId_;
    
public:
    Employee(const std::string& name, int age, double salary, const std::string& department)
        : name_(name), age_(age), salary_(salary), department_(department), employeeId_(++nextId_) {
        validateEmployee();
    }
    
    // Getter methods: Provide read-only access
    std::string getName() const { return name_; }
    int getAge() const { return age_; }
    double getSalary() const { return salary_; }
    std::string getDepartment() const { return department_; }
    int getEmployeeId() const { return employeeId_; }
    
    // Setter methods: Provide controlled modification
    void setName(const std::string& name) {
        if (name.empty()) {
            throw std::invalid_argument("Name cannot be empty");
        }
        name_ = name;
        std::cout << "Name updated to: " << name_ << std::endl;
    }
    
    void setAge(int age) {
        if (age < 18 || age > 65) {
            throw std::invalid_argument("Age must be between 18-65");
        }
        age_ = age;
        std::cout << "Age updated to: " << age_ << std::endl;
    }
    
    void setSalary(double salary) {
        if (salary < 0) {
            throw std::invalid_argument("Salary cannot be negative");
        }
        
        double oldSalary = salary_;
        salary_ = salary;
        
        std::cout << "Salary updated from " << oldSalary << " to " << salary_ << std::endl;
        
        // Log salary changes
        if (salary > oldSalary) {
            std::cout << "Congratulations! Salary increased by " << (salary - oldSalary) << std::endl;
        }
    }
    
    void setDepartment(const std::string& department) {
        if (department.empty()) {
            throw std::invalid_argument("Department cannot be empty");
        }
        
        std::string oldDept = department_;
        department_ = department;
        std::cout << "Department moved from " << oldDept << " to " << department_ << std::endl;
    }
    
    // Read-only property: Employee ID cannot be modified
    // Do not provide setEmployeeId method
};

    // Computed properties
    double getAnnualSalary() const {
        return salary_ * 12;
    }
    
    std::string getDisplayName() const {
        return name_ + " (#" + std::to_string(employeeId_) + ")";
    }
    
    void displayInfo() const {
        std::cout << "Employee information:" << std::endl;
        std::cout << "  ID: " << employeeId_ << std::endl;
        std::cout << "  Name: " << name_ << std::endl;
        std::cout << "  Age: " << age_ << std::endl;
        std::cout << "  Department: " << department_ << std::endl;
        std::cout << "  Monthly Salary: " << salary_ << std::endl;
        std::cout << "  Annual Salary: " << getAnnualSalary() << std::endl;
    }
    
private:
    void validateEmployee() {
        if (name_.empty()) {
            throw std::invalid_argument("Name cannot be empty");
        }
        if (age_ < 18 || age_ > 65) {
            throw std::invalid_argument("Age must be between 18-65");
        }
        if (salary_ < 0) {
            throw std::invalid_argument("Salary cannot be negative");
        }
        if (department_.empty()) {
            throw std::invalid_argument("Department cannot be empty");
        }
    }
};

int Employee::nextId_ = 0;

int main() {
    std::cout << "=== Getter/Setter Example ===" << std::endl;
    
    try {
        Employee emp("Li Si", 28, 8000, "Technology Department");
        emp.displayInfo();
        
        std::cout << "\n--- Update Employee Information ---" << std::endl;
        emp.setAge(30);
        emp.setSalary(10000);
        emp.setDepartment("Product Department");
        
        std::cout << "\n--- Computed Properties ---" << std::endl;
        std::cout << "Display Name: " << emp.getDisplayName() << std::endl;
        std::cout << "Annual Salary: " << emp.getAnnualSalary() << std::endl;
        
        // Try invalid operations
        std::cout << "\n--- Validation Error Handling ---" << std::endl;
        try {
            emp.setAge(70);  // Age out of range
        } catch (const std::exception& e) {
            std::cout << "Caught exception: " << e.what() << std::endl;
        }
        
        try {
            emp.setSalary(-1000);  // Negative salary
        } catch (const std::exception& e) {
            std::cout << "Caught exception: " << e.what() << std::endl;
        }
        
    } catch (const std::exception& e) {
        std::cout << "Employee creation failed: " << e.what() << std::endl;
    }
    
    return 0;
}

🏛️ Class Cohesive Design

High Cohesion Class Design

cpp
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

// High cohesion: All members revolve around "student grades" core concept
class StudentGrades {
private:
    std::string studentName_;
    std::string studentId_;
    std::vector<double> grades_;
    
    // Private helper methods: Support core functionality
    bool isValidGrade(double grade) const {
        return grade >= 0.0 && grade <= 100.0;
    }
    
    void sortGrades() {
        std::sort(grades_.begin(), grades_.end(), std::greater<double>());
    }
    
public:
    StudentGrades(const std::string& name, const std::string& id) 
        : studentName_(name), studentId_(id) {}
    
    // Core functionality: Grade management
    bool addGrade(double grade) {
        if (!isValidGrade(grade)) {
            std::cout << "Invalid grade: " << grade << " (must be between 0-100)" << std::endl;
            return false;
        }
        
        grades_.push_back(grade);
        std::cout << "Added grade: " << grade << std::endl;
        return true;
    }
    
    bool removeLowestGrade() {
        if (grades_.empty()) {
            std::cout << "No grades to remove" << std::endl;
            return false;
        }
        
        auto min_it = std::min_element(grades_.begin(), grades_.end());
        double removed = *min_it;
        grades_.erase(min_it);
        std::cout << "Removed lowest grade: " << removed << std::endl;
        return true;
    }
    
    // Core calculation functions
    double getAverageGrade() const {
        if (grades_.empty()) return 0.0;
        
        double sum = 0.0;
        for (double grade : grades_) {
            sum += grade;
        }
        return sum / grades_.size();
    }
    
    double getHighestGrade() const {
        if (grades_.empty()) return 0.0;
        return *std::max_element(grades_.begin(), grades_.end());
    }
    
    double getLowestGrade() const {
        if (grades_.empty()) return 0.0;
        return *std::min_element(grades_.begin(), grades_.end());
    }
    
    char getLetterGrade() const {
        double avg = getAverageGrade();
        if (avg >= 90) return 'A';
        if (avg >= 80) return 'B';
        if (avg >= 70) return 'C';
        if (avg >= 60) return 'D';
        return 'F';
    }
    
    // Accessors
    std::string getStudentName() const { return studentName_; }
    std::string getStudentId() const { return studentId_; }
    size_t getGradeCount() const { return grades_.size(); }
    
    // Display functionality
    void displayGrades() const {
        std::cout << "\nStudent: " << studentName_ << " (ID: " << studentId_ << ")" << std::endl;
        std::cout << "Grade list: ";
        
        if (grades_.empty()) {
            std::cout << "No grades" << std::endl;
            return;
        }
        
        for (size_t i = 0; i < grades_.size(); ++i) {
            std::cout << grades_[i];
            if (i < grades_.size() - 1) std::cout << ", ";
        }
        
        std::cout << std::endl;
        std::cout << "Average: " << getAverageGrade() << std::endl;
        std::cout << "Highest: " << getHighestGrade() << std::endl;
        std::cout << "Lowest: " << getLowestGrade() << std::endl;
        std::cout << "Grade: " << getLetterGrade() << std::endl;
    }
    
    void displaySortedGrades() const {
        std::cout << "\nSorted grades (highest to lowest): ";
        auto sorted_grades = grades_;
        std::sort(sorted_grades.begin(), sorted_grades.end(), std::greater<double>());
        
        for (size_t i = 0; i < sorted_grades.size(); ++i) {
            std::cout << sorted_grades[i];
            if (i < sorted_grades.size() - 1) std::cout << " > ";
        }
        std::cout << std::endl;
    }
};

// Comparison: Low cohesion design (not recommended)
class BadStudent {
public:
    std::string name;
    std::vector<double> grades;
    std::string address;          // Unrelated to grades
    std::string phoneNumber;      // Unrelated to grades
    
    void addGrade(double grade) { grades.push_back(grade); }
    void sendEmail() {}           // Unrelated to student grade management
    void calculateTax() {}        // Completely unrelated functionality
    void manageLibraryBooks() {}  // Confused responsibilities
};

int main() {
    std::cout << "=== Class Cohesion Design ===" << std::endl;
    
    StudentGrades student("Wang Xiaoming", "2023001");
    
    // Add grades
    student.addGrade(85.5);
    student.addGrade(92.0);
    student.addGrade(78.5);
    student.addGrade(88.0);
    
    // Try adding invalid grades
    student.addGrade(105.0);  // Out of range
    student.addGrade(-10.0);  // Negative number
    
    // Display grade information
    student.displayGrades();
    student.displaySortedGrades();
    
    // Delete lowest grade
    std::cout << "\n--- Delete Lowest Grade ---" << std::endl;
    student.removeLowestGrade();
    student.displayGrades();
    
    std::cout << "\n=== Design Principles ===" << std::endl;
    std::cout << "✓ High cohesion: All members revolve around core responsibility" << std::endl;
    std::cout << "✓ Single responsibility: Only responsible for student grade management" << std::endl;
    std::cout << "✓ Data validation: Ensure data integrity" << std::endl;
    std::cout << "✓ Clear interface: Provide explicit operation methods" << std::endl;
    
    return 0;
}

🛡️ Invariants and Data Integrity

Class Invariant Maintenance

cpp
#include <iostream>
#include <vector>
#include <stdexcept>
#include <algorithm>

class Rectangle {
private:
    double width_;
    double height_;
    
    // Class invariants: width and height must be positive
    void maintainInvariant() const {
        if (width_ <= 0 || height_ <= 0) {
            throw std::logic_error("Rectangle width and height must be positive");
        }
    }
    
public:
    Rectangle(double width, double height) : width_(width), height_(height) {
        if (width <= 0 || height <= 0) {
            throw std::invalid_argument("Rectangle width and height must be positive");
        }
        maintainInvariant();  // Ensure object satisfies invariants when created
    }
    
    // Modify operations must maintain invariants
    void setWidth(double width) {
        if (width <= 0) {
            throw std::invalid_argument("Width must be positive");
        }
        width_ = width;
        maintainInvariant();
    }
    
    void setHeight(double height) {
        if (height <= 0) {
            throw std::invalid_argument("Height must be positive");
        }
        height_ = height;
        maintainInvariant();
    }
    
    void scale(double factor) {
        if (factor <= 0) {
            throw std::invalid_argument("Scale factor must be positive");
        }
        width_ *= factor;
        height_ *= factor;
        maintainInvariant();
    }
    
    // Accessors don't break invariants
    double getWidth() const { return width_; }
    double getHeight() const { return height_; }
    double getArea() const { return width_ * height_; }
    double getPerimeter() const { return 2 * (width_ + height_); }
    
    void display() const {
        std::cout << "Rectangle: " << width_ << " x " << height_ 
                  << ", Area: " << getArea() 
                  << ", Perimeter: " << getPerimeter() << std::endl;
    }
};

// Complex invariants example: Sorted set
class SortedSet {
private:
    std::vector<int> data_;
    
    // Invariant: Data must remain sorted and no duplicates
    void maintainInvariant() const {
        if (!std::is_sorted(data_.begin(), data_.end())) {
            throw std::logic_error("Data must maintain sorted order");
        }
        
        if (std::adjacent_find(data_.begin(), data_.end()) != data_.end()) {
            throw std::logic_error("Data cannot have duplicate elements");
        }
    }
    
public:
    SortedSet() = default;
    
    bool insert(int value) {
        // Find insertion position
        auto it = std::lower_bound(data_.begin(), data_.end(), value);
        
        // If element already exists, don't insert
        if (it != data_.end() && *it == value) {
            return false;
        }
        
        // Insert at correct position
        data_.insert(it, value);
        maintainInvariant();
        return true;
    }
    
    bool remove(int value) {
        auto it = std::lower_bound(data_.begin(), data_.end(), value);
        
        if (it != data_.end() && *it == value) {
            data_.erase(it);
            maintainInvariant();
            return true;
        }
        
        return false;
    }
    
    bool contains(int value) const {
        auto it = std::lower_bound(data_.begin(), data_.end(), value);
        return it != data_.end() && *it == value;
    }
    
    size_t size() const { return data_.size(); }
    bool empty() const { return data_.empty(); }
    
    void display() const {
        std::cout << "Sorted set: { ";
        for (size_t i = 0; i < data_.size(); ++i) {
            std::cout << data_[i];
            if (i < data_.size() - 1) std::cout << ", ";
        }
        std::cout << " }" << std::endl;
    }
    
    // Provide controlled iterator access
    std::vector<int>::const_iterator begin() const { return data_.begin(); }
    std::vector<int>::const_iterator end() const { return data_.end(); }
    
    // Don't provide non-const iterators to prevent external invariants break
};

int main() {
    std::cout << "=== Invariants and Data Integrity ===" << std::endl;
    
    // Simple invariants test
    try {
        Rectangle rect(5.0, 3.0);
        rect.display();
        
        rect.setWidth(8.0);
        rect.display();
        
        rect.scale(1.5);
        rect.display();
        
        // Try breaking invariants
        try {
            rect.setWidth(-2.0);  // Should fail
        } catch (const std::exception& e) {
            std::cout << "Caught exception: " << e.what() << std::endl;
        }
        
    } catch (const std::exception& e) {
        std::cout << "Rectangle error: " << e.what() << std::endl;
    }
    
    // Sorted set invariants test
    std::cout << "\n--- Sorted Set Invariants Test ---" << std::endl;
    SortedSet set;
    
    // Insert elements
    std::vector<int> values = {5, 2, 8, 2, 1, 9, 5};
    for (int val : values) {
        bool inserted = set.insert(val);
        std::cout << "Insert " << val << ": " << (inserted ? "success" : "already exists") << std::endl;
    }
    
    set.display();
    
    // Find element
    std::cout << "Contains 5: " << (set.contains(5) ? "Yes" : "No") << std::endl;
    std::cout << "Contains 10: " << (set.contains(10) ? "Yes" : "No") << std::endl;
    
    // Delete element
    set.remove(2);
    std::cout << "After removing 2: ";
    set.display();
    
    return 0;
}

Summary

Encapsulation is a fundamental principle of object-oriented programming, ensuring code safety and maintainability through data hiding and interface control:

Encapsulation Elements

  • Data Hiding: Private member variables
  • Interface Control: Public methods provide access
  • Data Validation: Ensure data integrity
  • Invariant Maintenance: Maintain consistent object state

Access Control Levels

LevelClass InternalDerived ClassesExternal CodeUsage
privateInternal implementation
protectedInheritance interfaces
publicExternal interface

Design Principles

  • Least Privilege Principle: Grant minimal necessary access permissions
  • Interface Stability: Change public interfaces cautiously
  • Data Validation: Validate input in setters
  • Invariant Maintenance: Ensure consistent object state
  • High Cohesion: Class members revolve around core responsibility

Best Practices

  • Prioritize private, use protected when necessary
  • Provide clear getter/setter interfaces
  • Establish invariants in constructors
  • Control state changes through methods
  • Avoid exposing internal data structures

Good encapsulation design makes code more secure, maintainable, and reusable, forming an important foundation for building robust software systems.

Content is for learning and research only.