Proxy design pattern in python

This is one of the patterns in the structural design pattern category, which is used in the place of another object and performs the same functionality but through a different method. The meaning of proxy is on behalf of or representing, so the meaning of proxy simplifies its work. It is used as a placeholder for different objects, or it works as a substitute for another object that allows performing specific operations after getting permission from the original object.

Some resources are expensive or impossible to duplicate. They can be a large block of objects or some particular resource, so here proxy class will work as an interface. The proxy class wraps the real object, and then whenever the client requests access, the proxy class will act as an interface to the real object.

Proxy Design Pattern In Python

As the client is unaware of the implementation, working proxy will be the same as working with real objects. According to GoF, a Proxy Pattern provides the control for accessing the original object. Protects the original object from the outside world. We can perform many operations like hiding the information of the original object, on-demand loading, etc. A proxy pattern is also known as a Surrogate or Placeholder. With the proxy pattern, you want the interface to remain constant, with some actions taking place in the background. Conversely, the adapter pattern is targeted at changing the interface. If you are already familiar with web proxy, then thinking about proxy design patterns becomes easy.

When to use proxy design pattern :

  • If our main object is complex, then we need to hide this complexity from the user. Here we use a proxy design pattern for wrapping the complexity and simplifying the object for the user.

There are three parts to proxy pattern :

  • The client: The client wants to access some particular object.
  • The object: This is the original object that the client wants to access.
  • The proxy: The access to the object is controlled by proxy.

Types of proxy patterns :

  • Remote proxy: If we want to access or represent the object remotely and when we want to abstract the location of an object, we can use a remote proxy. The remote object appears to be a local resource to the client. When you call a method on the proxy, it transfers the call to the remote object. Once the result is returned, the proxy makes this result available to the local object.Accessing a real object has some consequences, and accessing it remotely assures us that no marshaling and unmarshalling data is involved.
  • Virtual proxy: If we want the result frequently and the real object is time-consuming, then we will use the virtual proxy. Sometimes objects may be expensive to create and may not be needed until later in the program's execution. A proxy can be used when it is helpful to delay object creation. This proxy provides us with some default answers. The target object can then be created only once it is needed. The default answers will be replaced by original answers when the real object is operated.
  • Protection Proxy: This is used for mainly protection purposes.Many programs have different user roles with different levels of access. Sometimes when the real object does not provide access, we can use this proxy to get the results. By placing a proxy between the target object and the client object, you can restrict access to information and methods on the target object.
  • Smart proxy is for security assurance that the real object is not changed. It provides an additional layer of security to assure the authentic result every time.

Real-world analogy

We use proxy design patterns in various fields of our daily life activities. Let's understand it just by some real-life problems and solutions. Suppose you want to buy a bike and its cost is one lakh twenty thousand, you put all money in your bag and then go to the shop and buy the bike. But tell me, is it a reliable way of doing so? Because carrying such huge cash with you is not safe, someone can steal your bag, and you will lose all your money at once. If you forget to zip the bag and even the zip can be defective, the money can be lost too. So carrying this much cash is not safe at all. You can carry a debit card or credit card with you. In the shop, you will pay money to the shopkeeper electrically. The money will be debited from your account and directly credited to the shopkeeper's account, and you can take your bike. This method is reliable because you and the shopkeeper have fewer chances of losing money. Here, the debit card is working as the proxy between you and the shopkeeper. Originally you are paying money to the shopkeeper, but you are not paying it in cash mode, so the debit card is helping to transfer money. The proxy method works the same, it is used as a placeholder for different objects or as a substitute for another object that allows performing specific operations after getting permission from the original object.

Like debit cards, various things work as a real-life proxy.

Example.py code:

class Patient_Data_Manager:
    """ Real Subject"""
    def __init__(self):
        self.__patients = {}
        
    def _add_patients(self, patient_id, data):
        self.__patients[patient_id] = data
   
    def _get_patient(self, patient_id):
        Problem, Date = self.__patients[patient_id]
        return f"Name: {patient_id}, Problem: {Problem}, Date: {Date}"
class Access_Patient_Data(Patient_Data_Manager):
    """implimenting Proxy deisgn pattern Subject"""
    def __init__(self, fm):
        self.fm = fm
    
    def add_patients(self, patient_id, data, _password):
        if _password == '123456':
            self.fm._add_patients(patient_id, data)
        else:
            print("Wrong password.")
            
    def get_patient(self, patient_id, _password):
        if _password == '123456':
            return self.fm._get_patient(patient_id)
        else:
            print("Unauthorized Access.")
obj = Access_Patient_Data(Patient_Data_Manager())
obj.add_patients('Dr. Sourav Kumar Das', ['heart attack', '2022-12-11'], '123456')


print(obj.get_patient('Dr. Sourav Kumar Das', '123456'))
obj2 = Access_Patient_Data(Patient_Data_Manager())
obj2.add_patients('sonika', ['corona', '2022-11-11'], '123456')
print(obj2.get_patient('sonika', '123456'))
obj3 = Access_Patient_Data(Patient_Data_Manager())
obj3.add_patients('monika', ['healthy', '2022-11-11'], '12456')
print(obj3.get_patient('monika', '12356'))

Output .text

Name: Dr. Sourav Kumar Das, Problem: heart attack, Date: 2022-12-11
Name: Sonika, Problem: corona, Date: 2022-11-11
Wrong password.
Unauthorized Access.
None

Explanation of code:

This is for information management of the hospital system, so patient data must be confidential and should now spread out of the hospital management system. So in the above example, we have a real object, which will be cheating the ids of the patient, and we have created one proxy which will be responsible for allowing or not allowing access to the object. Firstly it will check the password. Only access to the information will be granted if the password and the id are correct. Else, it will show the error message.

Creation of proxy :

class Access_Patient_Data(Patient_Data_Manager):
    def __init__(self, fm):
        self.fm = fm
    
    def add_patients(self, patient_id, data, _password):
        if _password == '123456':
            self.fm._add_patients(patient_id, data)
        else:
            print("Wrong password.")
            
    def get_patient(self, patient_id, _password):
        if _password == '123456':
            return self.fm._get_patient(patient_id)
        else:
            print("Unauthorized Access.")

Here Access_Patient_Data is the name of our proxy class. First, we will receive the client's self, patient_id, data, and _password. We need to check whether the client is authentic, so first, we will validate the password. If the password provided by the client is the same as the password of the proxy class, then only the client can access the data. Else they will get an error message. This is how this proxy class is working.

Calling client ( correct password)

Calling client ( correct password) 
obj2 = Access_Patient_Data(Patient_Data_Manager())
obj2.add_patients('sonika', ['corona', '2022-11-11'], '123456')
print(obj2.get_patient('sonika', '123456'))

Here we call the methods, and you can see the original object is passed as an argument to the proxy. If the password is correct, then only the information will be displayed, and here, the password is the same as the password of the proxy class, so it provides us with the desirable output( check-in output.txt).

Calling client ( incorrect password) :

obj3 = Access_Patient_Data(Patient_Data_Manager())

obj3.add_patients('monika', ['healthy', '2022-11-11'], '12456')

print(obj3.get_patient('monika', '12356'))

Here all the working code is similar to the above the only difference is that the password is incorrect, so it gives us output as follows-

Wrong password.
Unauthorized Access.
None

This is how the whole structured code is functioning. I hope now you have gained a basic understanding of proxy design patterns.

The advantage of using a proxy design pattern :

  • It boosts the overall performance of the software.
  • It has control of the original object.

The disadvantage of using a proxy design pattern :

  • It might increase the complexity of the code.
  • The response might face some delays.