Proxy, Facade and Adapter Pattern using Python
In this post, we will look at these few patterns that has been used in one or the other form throughout every programmers career. We will cover proxy, facade and adapter pattern, because they tend to be confusing and more importantly, doing it together will give us opportunity to compare and contrast them.
One best example that can be used as an analogy is the episode of “Batman: The Animated Series”, in which Batman is unwell or missing and Gotham needs him. Superman decides to fill in his shoes, to keep the Gotham city’s criminals at bay and give the impression that Batman is still on the prowl. You can see the fight between Batman (disguised Superman) and Bane.
<a href="https://medium.com/media/dc4316324d9544741e651ce86a329f23/href">https://medium.com/media/dc4316324d9544741e651ce86a329f23/href</a>
It is my favorite Batman show and I wanted to reference it somewhere for so long. Let’s begin with design patterns. It can be broadly said that the idea behind all of the three patterns we are covering is almost the same as shown in the above video. You have a Batman cloak (interface) that even Superman can wear and fight like Batman.
The key being “fight like Batman”, not become him. He has to stick to the same methods of fighting like batman. He doesn’t fly, doesn’t shoot lazers, although he is quite capable of doing so. He sticks to the rules of Batman, but bends them slightly by using superspeed near the end of video. That slight bending can also be though of as adapting.
Since, all the patterns are about replacing one object with another similar one to get the task done, then why are there three of them? I hear you ask. To answer that, let’s study each of them a little bit more in depth.
To begin, we must first setup the framework to use when studying these patterns.
It is my favorite Batman show and I wanted to reference it somewhere for so long. Anyways, let’s begin understanding these design patterns.
It can be broadly said that the idea behind all of the three patterns we are covering is almost the same as shown in the above video. You have a Batman cloak (interface) that even Superman can wear and fight like Batman.
The key being “fight like Batman”, not become him. He has to stick to the same methods of fighting like batman. He doesn’t fly, doesn’t shoot lazers, although he is quite capable of doing so. He sticks to the rules of Batman, but bends them slightly by using superspeed near the end of video.
Since, all these patterns are about replacing one object with another similar one to get the task done, then it begs the question, what is the difference. Let’s find out.
Proxy Pattern
First one on the list is Proxy pattern and as the name suggests, it is used to proxy an object. One of the biggest example is that of RPC or CORBA. Basically we replace a more heavy object with a light weight object, which is responsible for managing the heavy object whenever it is required to.
Proxy pattern works when there is a need for a stand in object (lighter) which will actually be a communicating with a remote object (or heavy object) as a middleman. The biggest return of such design is testability. We get the ability to test our service with a local object and when we are ready, we can replace the local object with a remote calling object.
class DataStore:
@abc.abstractmethod
def load(self, query):
pass
class FileSystem(DataStore):
def __init__(self):
# Initialize volume
def load(self, query):
# Call some heavy method that takes a lot of time
data = load_data_from_files_by(query)
return data
class CacheSystem(DataStore):
def __init__(self, actual_data_store: DataStore):
this.store = actual_data_store
this.cache = {}
def load(self, query):
if query in this.cache:
return this.cache[query]
data = this.store.load(query)
this.cache[query] = data
return data
In the above example, we can see an implementation of a simple in-memory cache. Here, we have an abstract interface with a method load, which is implemented by the implementation classes such as CacheSystem and FileSystem. The load method takes a query and returns data associated with that query.
As can be seen, file system storage uses some kind of heavy method call to load the actual data from disk, which puts a lot of strain on system resource if used again and again. To avoid such a strain, we can simply create a cache system, as shown above, to act as a proxy between file system and the user of the file system.
The proxy works by making sure that when a query is given to load data, it first checks in its own cache, whether that data was already loaded and stored there. If it is, then that data is returned and if not, then the data is retrieved from the actual storage, stored in the cache for later usage and returned to the caller. As you can see, the actual storage is passed to the cache storage during its construction.
This kind of middle work is what proxy is good for. It seamlessly adds a layer between the client and the server and usually ends up reducing the number of calls from clients to the server, without the client knowing anything about it.
The same layer also decouples the client from the server, such that if server is not up, then the proxy can also ensure that the client doesn’t break and so on. It really ends up making the system a lot robust than the systems where such layers don’t exist and are directly affected by what is going on the server.
Adapter Pattern
Adapter pattern is very similar to proxy pattern in all respect but one. It adapts the interface between client and server. Every thing else behaves the same. We will consider an example as we did for proxy pattern to clearly understand the difference between adapter and proxy.
class MicrosoftServer:
def service(self):
pass
class AppleServer:
def service_with_class(self):
pass
class GoogleServer:
def service_nicely_but_without_guarantee(self):
pass
class ServerProxy:
def __init__(self, server):
self.server = server
def service(self):
if type(self.server) == AppleServer:
return self.server.service_with_class()
elif type(self.server) == GoogleServer:
return self.sever.service_nicely_but_without_guarantee()
return self.server.service()
class Client:
def __init__(self, server):
self.server = server
def call_server(self):
service_received = self.server.service()
# enjoy the service
If you see the above example, there are three service providing companies: Microsoft, Google and Apple. Each have their own interface for their service. It is possible that the client you are writing may need to talk to one or the other service providers based on some constraints.
So the dilemma really is, we will have to write if else condition everywhere to make sure that we use the right service method exposed by each of these companies.
Imagine a situation where the client class is something that may not be in your control and you are responsible for the service providers. In that case, the service provider’s difference in interface will leak to whoever is responsible for the client class.
Therefore, to avoid a situation of leaking abstractions, where the clients will have to know which server they are talking to everytime, we can create an adapter class, which just like proxy class sits between the calling client and service providers.
This layer of adapter can standardise the service method and make sure that all clients will be free of knowing which provider exposes which service, thereby decoupling them.
This decoupling is achieved by acting like a middle man, which is similar to proxy pattern, but at the same time, you are adapting the service providers to a different standards that you share with your calling clients.
Facade Pattern
Facade pattern, as name suggests, is a simply putting a layer of facade between the client and the server. Just like proxy and adapter, it acts like a middle man and decouples the client from the server. But, in contrast to the proxy and adapter pattern, facade is just that: A facade.
The core responsibility of this pattern to simplify an interface. Ideologically, it sits somewhere between the proxy and adapter pattern. Unlike the proxy pattern, it doesn’t act like a stand in object. And unlike the adapter pattern, it doesn’t merely standardises the interface, it simplifies them. Let’s again understand with an example.
class Airplane:
def tilt_left(self): pass
def tilt_right(self): pass
def rudder_right(self): pass
def rudder_left(self): pass
def tires_in(self): pass
def tires_out(self): pass
def throttle_up(self): pass
def throttle_down(self): pass
def is_high_enough(self): pass
def is_low_enough(self): pass
class AirplaneFacade(self):
def __init__(self, airplane: Airplane):
self.plane = plane
def take_off(self):
self.plane.tilt_up()
while not self.plane.is_high_enough():
self.plane.throttle_up()
self.plane.tires_in()
def land(self):
self.plane.tilt_down()
while not self.plane.is_low_enough()
self.plane.throttle_down()
self.plane.tires_out()
def rudder_right(self):
self.plane.rudder_right()
....
In the above example, there is an Airplane class and it has some real technical and pilot level methods exposed. If we want to be able to let a new pilot fly the plane easily, without having to learn tough things before simple things, then we need to expose simpler methods that hide the tough work behind them and are taken care of by some kind of middle-man again.
That is exactly what the AirplaneFacade does. It creates a layer with additional methods, such that any new pilot can simply take off and land without worrying too much about their complexity and focus initially on high altitude flying.
This way, it becomes easy for them to train themselves with the simple flying around and then learn the tough job of taking off and landing, later on. Although, I don’t think that truly is the way pilots learn. I don’t know to be honest and I guess: take off and landing would be the first thing any pilot should learn.
Anyways, for the sake of our example, let’s think of take off and landing as non essential tough things that can be learned later (yeah, right!).
In Conclusion
As you can see above, each of these patterns are very similar to one another but are nuanced enough to be called a pattern in their own right. Proxy helps hide big stuff, Adapter hides the variance and Facade simplifies. I hope that will stick.
Originally published at https://progarsenal.com/proxy-facade-and-adapter-pattern-using-python/
Proxy, Facade and Adapter Pattern using Python was originally published in Analytics Vidhya on Medium, where people are continuing the conversation by highlighting and responding to this story.