Introduction
I attempt to discuss about Proxy Design from a different approach that you may see from other online contents.
First, I dive deep into its the concepts that are basic benefits of the design pattern: lazy loading and fast compilation. Then I explain in generals the main ideas for Proxy pattern.
For implementation, I implement the proxy that both creates and deletes Real Service class, thus having composition relationship. RefactoringGuru page uses aggregate relationship.
Finally, I add function pointer to illustrate how it saves memory in call stacks by using function pointer. This is an extended benefit of lazy loading.
Lazy Loading Concept
Lazy loading is a technique used in C/C++ to defer the loading of data or libraries until they are actually needed, instead of loading them all at once when the program starts. This can be particularly useful for programs that have large data sets or depend on large libraries that may not be needed for all program execution paths.
When lazy loading is used, data or libraries are not loaded into memory until they are actually needed by the program. This can reduce the program's memory footprint, since it does not need to load everything into memory at once. It can also improve the program's startup time, since the program does not need to load everything before it can start executing.
One common use of lazy loading is in dynamic linking. When a program uses dynamic linking, it loads libraries at runtime rather than linking them statically at compile time. With lazy loading, the libraries are not loaded until they are actually needed by the program. This can be more efficient than loading all of the libraries at once, particularly if the program only uses a small subset of the available libraries.
Another example of lazy loading is in the loading of large data sets. Instead of loading the entire data set into memory at once, the program can load only the portions that are needed at a given time. This can reduce the program's memory usage and improve its performance.
In summary, lazy loading is a technique used in C/C++ to defer the loading of data or libraries until they are actually needed, rather than loading them all at once. This can reduce the program's memory footprint and improve its performance.
C++ building concepts
- Translational unit:
- In C/C++, a translation unit is the basic unit of compilation. It is a source code file, along with any header files that it includes, after preprocessing has been applied. The resulting text is known as a translation unit, which is then compiled into an object file.
- Internal linkage: Identifiers with internal linkage are only accessible within the same translational unit. Variables and functions can be given internal linkage by using the
static
keyword. - External linkage: Identifiers with external linkage can be accessed from any part of the program, including other translation units. Functions and global variables have external linkage by default, but can be made static to limit their scope to the current translation unit.
- The compilation follows the steps:
- create preprocess outputs
- building object files from translational units that includes cpp files
- link object files to an executable file
- We will apply those knowledge to test the dependency of code changes on compilation.
C:\> g++ -E *.cpp --> this command creates preprocess files.
C:\> g++ -c *.cpp --> this command creates object files
C:\> g++ -o main *.o --> this command links object files to exe file
Reference: a very good blog detailing C/C++ compilation could be found here.
Proxy Design Pattern Concepts
Proxy design pattern employs a proxy class that provides call to a real service class. The service class provides actual logic implementation. The call to the service class is only when necessary, usually for the first time only. Data is loaded from database by real service class could be saved at different address space by proxy. Access to real service class could be further restricted, and controlled by Proxy. In addition, changes in service class will not effect the proxy class.
Therefore, proxy design pattern has 3 benefits: lazy loading and caching data, access control and faster build.
- "Implementation or Service class": implements the logic and load data from database when called by Proxy. Initialized once when needed by Proxy.
- Proxy class:
- controls access to real class, could provide additional logic to allow access Implementation class
- performs lazy loading of data. Initializes Service class, calls service class to load data and saved data to "cache" for fast access while the proxy is running.
- used as substitute for real class
Proxy Implementation
Full proxy pattern implementation with proxy interface
The following program illustrate a service class, RealShop
and a proxy class, ProxyShop
.
Both inherit IShop
interface.
The client class is Customer.
The program works similar to how a customer come to a shop to buy some commodity. If the product is available at the shop, the customer would get it rightaway. Otherwise, the seller will call warehouse to bring up the product. In our case, the ProxyShop will initialize RealShop
and load the product to its cache, or saved product object to memory.
In addition, other implementation of proxy pattern makes uses of inheritance as suggested in Refactoring Guru. The main idea is
- to delay the initialization of the real data until needed.
- date is saved to memory, and used by proxy so there is no need to query data again
- the proxy class hold a reference or pointer to the actual class the implements the logic
- both proxy class and actual class implements a common interface.
- client class only calls the interface functions.
The following class diagram illustrates a proxy pattern designed program.

- main is the client that uses the service provided by the interface
IProxy::provideService
function. - both
ShopProxy
andRealShop
implementsIProxy
interface. ShopProxy
has an composition relation toRealShop
, meaning it has a pointer member toRealShop
and its existence is managed by ShopProxy- RealShop is created and data is provided to RealShop. In this case, the composition relation may defer creation of RealShop until it is needed.
- RealShop is deleted when Proxy is deleted.
ShopProxy::provideService
initializesRealShop
ifFactory
is not instantiated. Otherwise,RealShop
is called to provide the main logic. ThusRealShop
pointer is also called to implement theprovideService
function.
References:
- Explaining aggregation and UML diagram convention with aggregation.
- Explaining composition and UML diagram convention with composition.
- C++ implementation of Composition. Explaining that "A composition may defer creation of some parts until they are needed".
- C++ implementation of Aggregation. Explaining the life time of member class. In this case, had aggregation been used, RealShop wouldn't be destroyed if ShopProxy is destroyed. Instead, only the member pointer to RealShop is in ShopProxy is destroyed. Please note that either Composition or Aggregation could be used in Proxy pattern. ReforingGury page applies Aggregation relation in his implementation.
shop_interface.h
// needed preprocessor to prevend loop include
#ifndef ISHOP
#define ISHOP
#include "string"
class IShop{
public:
IShop(){};
virtual void provideService(std::string product) = 0;
virtual ~IShop(){};
};
#endif
shop_proxy.h
#include "shop.h"
#include "shop_interface.h"
class ShopProxy : public IShop {
public:
ShopProxy();
virtual ~ShopProxy();
virtual void provideService(std::string product) override;
private:
RealShop * realShop_ = nullptr; // safe initialization of pointer
};
shop_proxy.cpp
#include "shop_proxy.h"
#include "iostream"
void ShopProxy::provideService(std::string product) {
if ( realShop_ == nullptr ) {
std::cout<<"Proxy creates RealShop on request"<<std::endl;
realShop_ = new RealShop(); //create RealShop, call RealShop to do service
realShop_ -> provideService(product);
}
else{
std::cout<<"Proxy can provide service"<<std::endl;
// RealShop is available in Proxy's memory.
// don't have to reload, just run logic
realShop_ ->provideService(product);
}
};
ShopProxy::ShopProxy()
{
std::cout<<"**ShopProxy Constructor**"<<std::endl;
}
ShopProxy::~ShopProxy()
{
std::cout<<"**ShopProxy Destructor**"<<std::endl;
delete realShop_;
realShop_ = nullptr; // safe delete of member pointer
}
shop.h
#include "shop_interface.h"
#include "string"
class RealShop: public IShop {
public:
RealShop();
virtual ~RealShop();
virtual void provideService(std::string product) override ;
private:
void collectMaterial();
};
shop.cpp
#include "shop.h"
#include "iostream"
using namespace std;
void RealShop::provideService(string product) {
cout<<" shop is building "<<product<<" in warehouse"<<endl;
collectMaterial();
}
void RealShop::collectMaterial() {
cout<<" collect material to build product"<<endl;
}
RealShop::RealShop(){
cout<<" RealShop constructor"<<endl;
}
RealShop::~RealShop(){
cout<<" RealShop destructor"<<endl;
}
main.cpp
- Note that main.cpp has to include interface
proxy_interface.h
and the proxy class declarationshop_proxy.h
#include "shop_interface.h"
#include "shop_proxy.h"
int main()
{
std::string requestProduct1 = "REQUEST 1: a doll";
IShop * myShopProxy = new ShopProxy();
// myShopProxy creates RealShop
myShopProxy->provideService(requestProduct1);
// myShopProxy already has RealShop, and can provide service.
std::string requestProduct2 = "REQUEST 2: a toy";
myShopProxy->provideService(requestProduct2);
// delete myShopProxy will delete RealShop and data saved in memory
delete myShopProxy;
// create new proxy
std::string requestProduct3 = "REQUEST 3: a fan";
IShop * myShopProxy2 = new ShopProxy();
// myShopProxy has to create new RealShop again and reload data.
myShopProxy2->provideService(requestProduct3);
delete myShopProxy2;
}
Analysis
Lazy loading benefit
When running the program, it shows that the RealShop instance is only created once. Instead the proxy's saved RealShop pointer is called to do the work in the second time. When proxy is deleted, RealShop is deleted as well.
- How proxy pattern performs lazy loading:
ShopProxy
class acts as a lazy-loading proxy for the Shop class- The
ShopProxy
class initializes theFactory
object on demand. - Lazy loading is implemented by initializing the
realShop_
member pointer of theShopProxy
class tonullptr
in the constructor, and creating a newRealShop
instance when thecollectMaterial
function is called for the first time - The creation of the Shop
Proxy
class is less costly compared to the creation of theRealShop
. In practice, RealShop initialization could involves querying data from database. and ShopProxy could have save the data to cache, by implementing a private data structure. - With
RealShop
already created and data alread saved byShopProxy
, we don't have to initialzeRealShop
again nor loading data from remote database.
Fast compilation benefit
- What are the translational units for the above programs:
shop.cpp
: This file defines the implementation of theShop
class, and is compiled into an object file (e.g.shop.o
).shop_proxy.cpp
: This file defines the implementation of theShopProxy
class, and is compiled into an object file (e.g.shop_proxy.o
)
main.cpp
: This file defines themain
function and creates instances ofShopProx
y. It includes the headers forshop_proxy.h
which includes the header forshop.h
. This file is compiled into an object file (e.g.main.o
).- During the linking phase, the object files are linked together to create the final executable file (e.g.
my_program
).
- How proxy pattern improves compiling time.
- If an extra function is added to
RealShop
, rebuilding only shop.cpp and linking the newly built object file with already built object files WILL UPDATE the program
- If an extra function is added to
For the following program, changes to the implementation class, RealShop
, is isolated. Thus the compilation process only requires rebuilding the translation unit of RealShop
class, or shop.cpp
We made drastic change to Shop:
- Add a private function
specialService
- Add a public variable
static const bool special_ = true
; - Add a private variable
customerName_
, year_
We rebuild only RealShop.
#include "shop_interface.h"
#include "string"
class RealShop: public IShop {
public:
RealShop();
virtual ~RealShop();
virtual void provideService(std::string product) override ;
static const bool specialOrder_ = true; // newly added
private:
void collectMaterial();
void specialService(std::string customerName); // newly added
std::string customerName_;
};
#include "shop.h"
#include "iostream"
using namespace std;
void RealShop::provideService(string product) {
cout<<" shop is building "<<product<<" in warehouse"<<endl;
if (specialOrder_ == true){
specialService("Minh");
}
collectMaterial();
}
void RealShop::collectMaterial() {
cout<<" collect material to build product"<<endl;
}
RealShop::RealShop(){
cout<<" RealShop constructor"<<endl;
}
RealShop::~RealShop(){
cout<<" RealShop destructor"<<endl;
}
void RealShop::specialService(string customer){
std::cout<<"provide custom order to customer "<<customer<<std::endl;
customerName_ = customer;
}
The following shows the log printed out when we apply the changes to RealShop
class. We only rebuild the shop.cpp
with the command g++ -c shop.cpp
. Then we linked the already available object files to make executable files. The executable runs and output new changes.
- The following output shows the effect of the changes:
C:\> g++ -c *.cpp --> this command creates object files
C:\> g++ -o main *.o --> this command links object files to exe file
C:\> .\main.exe
**ShopProxy Constructor**
Proxy creates RealShop on request
RealShop constructor
shop is building a doll in warehouse
provide custom order to customer Minh --> changes are updated
collect material to build product
Proxy can provide service
shop is building a doll in warehouse
provide custom order to customer Minh --> changes are updated
collect material to build product
**ShopProxy Destructor**
RealShop destructor
**ShopProxy Constructor**
Proxy creates RealShop on request
RealShop constructor
shop is building a doll in warehouse
provide custom order to customer Minh --> changes are updated
collect material to build product
Using function pointer in proxy
In the following program, I define:
- a function pointer
provideService_proxy
as member ofShopProxy
void (RealShop::* provideService_proxy )(std::string) = nullptr ;
- in order to assign the address of
RealShop::provideService
to my function pointerprovideService_proxy
, I have to do the following:
provideService_proxy = &RealShop::provideService;
(realShop_->*provideService_proxy)(product);
- in order to call
RealShop::provideService
to perfom work, I dont have to call it via instance of RealShop and make another function, add values to callstack, I can just call it via the proxy. The proxy will go to the address ofRealShop::provideService
, that was saved toprovideService_proxy
.
(realShop_->*provideService_proxy)(product);
Code:
#include "iostream"
#include "string"
using namespace std;
// needed preprocessor to prevend loop include
#ifndef ISHOP
#define ISHOP
#include "string"
class IShop{
public:
IShop(){};
virtual void provideService(std::string product) = 0;
virtual ~IShop(){};
};
#endif
class RealShop: public IShop {
public:
RealShop(){
cout<<"RealShop constructor"<<endl;
};
virtual ~RealShop(){
cout<<"RealShop destructor"<<endl;
};
virtual void provideService(std::string product) override{
cout<<" shop is building "<<product<<endl;
} ;
};
class ShopProxy : public IShop {
public:
ShopProxy(){
cout<<"**ShopProxy constructor**"<<endl;
};
virtual ~ShopProxy(){
cout<<"**ShopProxy destructor**"<<endl;
delete realShop_;
};
virtual void provideService(std::string product) override {
if ( realShop_ == nullptr ) {
//create RealShop, call RealShop to do service
// provideService_proxy = (void (*)(std::string))init_RealService (product);
init_RealService (product);
}
else{
std::cout<<"Proxy can provide service"<<std::endl;
// RealShop is available in Proxy's memory.
// don't have to request another function call, and add to stack
// save stack memory by using function pointer.
(realShop_->*provideService_proxy)(product);
}
};
private:
// typedef void (* functPtr )(std::string product);
// void (* provideService_proxy )(std::string product) = nullptr;
void (RealShop::* provideService_proxy )(std::string) = nullptr ;
void init_RealService ( std::string product ){
std::cout<<"Proxy creates RealShop on request"<<std::endl;
realShop_ = new RealShop();
std::cout<<"init_RealService saves realShop_->provideService to function pointer"<<endl;
provideService_proxy = &RealShop::provideService;
(realShop_->*provideService_proxy)(product);
};
RealShop * realShop_ = nullptr; // safe initialization of pointer
};
int main()
{
std::string requestProduct1 = "REQUEST 1: a doll";
IShop * myShopProxy = new ShopProxy();
// myShopProxy creates RealShop
myShopProxy->provideService(requestProduct1);
// myShopProxy already has RealShop, and can provide service.
std::string requestProduct2 = "REQUEST 2: a toy";
myShopProxy->provideService(requestProduct2);
// delete myShopProxy will delete RealShop and data saved in memory
delete myShopProxy;
// create new proxy
std::string requestProduct3 = "REQUEST 3: a fan";
IShop * myShopProxy2 = new ShopProxy();
// myShopProxy has to create new RealShop again and reload data.
myShopProxy2->provideService(requestProduct3);
delete myShopProxy2;
}
Output:
The following outputs explain that:
- RealShop is instantiated only once when Proxy is first requested to run provideService.
- In the first time, the function
RealService::provideService
is called. But in the second time, the proxy's function pointerprovideService_proxy
does the job. It does the job by going back to the address of the originRealService::provideService
.
**ShopProxy constructor**
Proxy creates RealShop on request
RealShop constructor
init_RealService saves realShop_->provideService to function pointer
shop is building REQUEST 1: a doll
Proxy can provide service
shop is building REQUEST 2: a toy
**ShopProxy destructor**
RealShop destructor
**ShopProxy constructor**
Proxy creates RealShop on request
RealShop constructor
init_RealService saves realShop_->provideService to function pointer
shop is building REQUEST 3: a fan
**ShopProxy destructor**
RealShop destructor