Implementing the Proxy Pattern in C++ for Object Interception
The Proxy design pattern provides a surrogate or placeholder for another object to control access to it. This approach enables flexible object substitution while maintaining functional entegrity, similar to how a clothing retailer can switch between different brands while continuing to serve customers' clothing needs.
Using a flower delivery scenario as an example, we can demonstrate how a proxy can deliver flowers from one person to another.
1. Define the Interface
class IGiftSender {
public:
virtual ~IGiftSender() {}
virtual void DeliverGift() = 0;
};
2. Implement the Proxy Class
class GiftProxy : public IGiftSender {
public:
GiftProxy() : gift_sender_(nullptr) {}
~GiftProxy() {
if (gift_sender_) {
delete gift_sender_;
gift_sender_ = nullptr;
}
}
void SetSender(IGiftSender* sender) {
if (gift_sender_) {
delete gift_sender_;
}
gift_sender_ = sender;
}
void DeliverGift() override {
if (gift_sender_) {
std::cout << "Proxy assisting: ";
gift_sender_->DeliverGift();
}
}
private:
IGiftSender* gift_sender_;
};
The proxy class acts as an intermediary, forwarding method calls to the underlying object. Note that the proxied object must be heap-allocated since the proxy manages its lifetime.
3. Implement the Concrete Class
class FlowerDelivery : public IGiftSender {
public:
FlowerDelivery(const std::string& sender_name) : sender_name_(sender_name) {}
void DeliverGift() override {
std::cout << sender_name_ << " sent flowers to Mary\n";
}
private:
std::string sender_name_;
};
4. Usage Example
void DemonstrateProxy() {
IGiftSender* sender = new FlowerDelivery("John");
GiftProxy proxy;
proxy.SetSender(sender);
proxy.DeliverGift();
}
5. Practical Application: Database Switching
A more practical application involves data base management where different database implementations can be switched dynamically.
struct EmployeeRecord {
int id;
std::string name;
};
class IDataAccess {
public:
virtual ~IDataAccess() {}
virtual bool SaveEmployee(const EmployeeRecord& employee) = 0;
virtual EmployeeRecord RetrieveEmployee(int employee_id) = 0;
};
class DatabaseProxy : public IDataAccess {
public:
DatabaseProxy() : data_access_(nullptr) {}
~DatabaseProxy() {
if (data_access_) {
delete data_access_;
data_access_ = nullptr;
}
}
void SetDataAccess(IDataAccess* access) {
if (data_access_) {
delete data_access_;
}
data_access_ = access;
}
bool SaveEmployee(const EmployeeRecord& employee) override {
if (data_access_) {
return data_access_->SaveEmployee(employee);
}
return false;
}
EmployeeRecord RetrieveEmployee(int employee_id) override {
if (data_access_) {
return data_access_->RetrieveEmployee(employee_id);
}
return EmployeeRecord{};
}
private:
IDataAccess* data_access_;
};
class MySQLAccess : public IDataAccess {
public:
bool SaveEmployee(const EmployeeRecord& employee) override {
std::cout << "Saving " << employee.name << " to MySQL database\n";
return true;
}
EmployeeRecord RetrieveEmployee(int employee_id) override {
std::cout << "Retrieving employee " << employee_id << " from MySQL\n";
return EmployeeRecord{};
}
};
class AccessDatabase : public IDataAccess {
public:
bool SaveEmployee(const EmployeeRecord& employee) override {
std::cout << "Saving " << employee.name << " to Access database\n";
return true;
}
EmployeeRecord RetrieveEmployee(int employee_id) override {
std::cout << "Retrieving employee " << employee_id << " from Access\n";
return EmployeeRecord{};
}
};
6. Database Switching Example
void TestDatabaseSwitching() {
IDataAccess* db = new MySQLAccess();
DatabaseProxy proxy;
proxy.SetDataAccess(db);
EmployeeRecord emp{1, "Sarah"};
proxy.SaveEmployee(emp);
// Switch database implementation
db = new AccessDatabase();
proxy.SetDataAccess(db);
proxy.SaveEmployee(emp);
}
The proxy pattern enables runtime switching between different implementations, making it particularly useful in scenarios requiring dynamic object substitution.