Proxy
Learning Objectives
Implement virtual (lazy), protection, remote and caching proxies.
Distinguish Proxy from Decorator and Adapter.
Prerequisites: Modules 33, 36.
Intuition and Definition
Intent: provide a surrogate or placeholder for another object to control access to it.
creation — loads the real object lazily on first use permissions — checks the caller is allowed location — represents an object in another process/machine repeated work — remembers results extra bookkeeping on access (reference counting — shared_ptr)
Participants: Subject interface, RealSubject, Proxy (same interface, holds or creates the real subject).
Why It Matters
Proxies add control without the client noticing: an image that loads only when shown, an ECU function callable only after security access, a service that lives on a server.
Syntax and Language Notes
// JavaScript has a built-in Proxy object for intercepting property access
const audited = new Proxy(target, { get: (t, key) => { console.log("read", String(key)); return (t as any)[key]; } });Complete Example
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
struct Calibration { // Subject
virtual ~Calibration() = default;
virtual double offset(const std::string& sensor) = 0;
virtual void setOffset(const std::string& sensor, double v) = 0;
};
class FlashCalibration : public Calibration { // RealSubject: expensive to load
public:
FlashCalibration() { std::cout << " (loading 64 KiB calibration table from flash)\n"; }
double offset(const std::string& s) override { return s == "coolant" ? 1.5 : 0.0; }
void setOffset(const std::string& s, double v) override { std::cout << " flash write " << s << "=" << v << "\n"; }
};
class LazyCalibration : public Calibration { // virtual proxy
std::unique_ptr<FlashCalibration> real_;
FlashCalibration& real() { if (!real_) real_ = std::make_unique<FlashCalibration>(); return *real_; }
public:
double offset(const std::string& s) override { return real().offset(s); }
void setOffset(const std::string& s, double v) override { real().setOffset(s, v); }
};
class ProtectedCalibration : public Calibration { // protectio
…proxy created, nothing loaded yet
(loading 64 KiB calibration table from flash)
coolant offset: 1.5
oil offset: 0
write refused: security access required
flash write coolant=2Step-by-Step Walkthrough
Creating the proxies loads nothing; the first offset() call triggers the load (virtual proxy).
The second read reuses the loaded table.
Writes go through the protection proxy, which refuses them until security access is granted.
Clients only see Calibration — they can't tell how many proxies sit in front of the real object.
classDiagram
class Calibration {
<<interface>>
+offset(sensor) double
+setOffset(sensor, v)
}
Calibration <|.. FlashCalibration
Calibration <|.. LazyCalibration
Calibration <|.. ProtectedCalibration
LazyCalibration --> FlashCalibration : creates on demand
ProtectedCalibration --> Calibration : guardsCommon Mistakes
Remote proxies that pretend to be local — network calls can fail and are slow; expose that in the interface (timeouts, errors) — Module 76.
Lazy proxies without thread safety — two threads may both create the real object.
Security checks only in the proxy while the real subject remains reachable directly.
Trade-offs and Alternatives
Proxy vs Decorator: same structure; a proxy *controls access* to one subject (often managing its lifecycle), a decorator *adds behaviour* and is meant to be stacked. Proxy vs Adapter: a proxy keeps the interface; an adapter changes it.
Real-World Use
std::shared_ptr (smart reference), ORM lazy-loading (Hibernate proxies), gRPC/Web service client stubs (remote proxies), image placeholders in UIs, permission wrappers, java.util.Collections.unmodifiableList (protection proxy).
Practice
Q1. (Easy) Which proxy defers creation?
Answer: The virtual proxy.
Q2. (Medium) Why are protection proxies insufficient on their own?
Answer: If the real subject is reachable another way, the check is bypassed; enforce authorization at the resource too (defence in depth).
Q3. (Advanced) How would you make LazyCalibration::real() thread-safe?
Answer: Use std::call_once with a std::once_flag, or a mutex around the check-and-create, so exactly one thread creates the real object and others see the fully constructed instance.
Interview Question
Q1. "Proxy vs Decorator — same diagram, so what's the difference?"
Answer: Intent and lifecycle. A proxy stands in for one specific subject and controls access to it (lazy creation, remote calls, permissions); it often creates or owns the subject and the client doesn't choose to stack it. A decorator is composed by the client to add responsibilities and is designed to be stacked in any order on any component. A protection proxy refusing writes changes *whether* an operation happens; a logging decorator changes *what else* happens.
Quick Quiz
Q1. Name four proxy kinds.
Answer: Virtual, protection, remote, caching (also smart reference).
Q2. Does a proxy change the interface?
Answer: No.
Q3. Which standard smart pointer is a proxy?
Answer: std::shared_ptr (smart reference).
Summary
Proxy controls access to a subject behind the same interface: lazy, protection, remote, caching.
Distinguish from Decorator (adds behaviour) and Adapter (changes interface).
Related: Modules 33, 36, 68, 76.