Practical Aspects of the Adapter Pattern.
Software development is improved every day by new concepts, methodologies, and high quality libraries and frameworks. But even with all these improvements, we cannot prevent change in software development. You may think that your system is designed perfectly to cater to all of its requirements, but there will always be a change request that ruins your perfect design. We have to be prepared for all possible changes as developers.
The Adapter pattern is a design pattern which is commonly used to manage changes in development. Throughout this article we'll be looking at the usage and benefits of the patterns using real world applications.
What is the Adapter Pattern?
The Adapter design pattern simplifies concerns by adapting to changes in existing functionalities as well as building new functionalities. In short, we can define an adapter as an interface which helps integrate incompatible components.
Assume we have a mobile phone which is used to access an email account to send emails. The phone and email application act as separate components which get connected through the Internet.
Now assume that we travel to a place where an Internet connection is not available for the phone. How do we access email in this situation? We need an adapter which connects our mobile phone to an email application. Let's take a look at the features expected from such an adapter:
- Enable an Internet connection between mobile phone and email application.
- Access email application API to send an email.
Considering the requirements, we can choose IFTTT as the adapter. IFTTT is a service that supports automating tasks with popular API's. Thus we can use IFTTT as the adapter as shown in the illustration below.
In this solution, we send a SMS to the IFTTT service containing the email text. There is no need for an Internet connection to send an SMS to an international number. Then the IFTTT service gets the message contents and initializes the recipe (recipes need to be configured prior) to send an email using the email application's API.
IFTTT has access to the email API as well as an Internet connection to the email application, completing both requirements expected from the adapter. IFTTT acts as the adapter to integrate our phone and email application which was in an incompatible state due to the unavailability of the Internet.
I think by now you should have a clear understanding about the functionality of the Adapter pattern in the real world. Before we move further into the implementation, though, let's take a look at the definition of the pattern as given by Wikipedia:
In computer programming, the adapter pattern is a design pattern that translates one interface for a class into a compatible interface.An adapter allows classes to work together that normally could not because of incompatible interfaces, by providing its interface to clients while using the original interface.
Understanding Adapter Pattern Implementation
It would be ideal to make use of a practical scenario to understand the process and components of the Adapter pattern, so assume that we have a common interface for email subscriptions for our website. The following code contains the implementation of an email subscription interface.
<?php interface EmailSubscribe { public function subscribe($email); public function unsubscribe($email); public function sendUpdates(); }
Developers and email service providers can implement this interface to provide email subscription classes for each of the email providers such as Feedburner, Mailchimp, etc. The following code contains a sample implementation for one service and the initialization of the sendUpdates()
method.
<?php class FeedburnerEmail implements EmailSubscribe { public function subscribe($email) { } public function unsubscribe($email) { } public function sendUpdates() { // Get Available Subscribers // Get Website Updates // Send Emails } } $feedburner_email = new FeedburnerEmail(); $feedburner_email->sendUpdates();
Now assume Feedburner decides to change its library with the latest version.
<?php class FeedburnerEmailVersion2 { public function subscribe($email) { } public function unsubscribe($email) { } public function getSubscribers() { // Return Subscribers } public function sendEmails($subscribers) { // Get Website Updates // Send Emails echo "emails sent today"; } } $feedburner_email = new FeedburnerEmailVersion2(); $subscribers = $feedburner_email->getSubscribers(); $feedburner_email->sendEmails($subscribers);
According to the preceding code, new methods are added and existing functionality is modified in the new version of the library. The initialization code has changed and the latest version of Feedburner has become incompatible with the EmailSubscribe
interface.
We cannot implement the common interface, and therefore we need an adapter to make the library compatible with the original interface to keep consistency in our code. Since the current version is not implementing the interface, we have no choice other than to create the adapter based on the interface.
<?php class FeedburnerAdapter implements EmailSubscribe { public function subscribe($email) { } public function unsubscribe($email) { } public function sendUpdates() { $feedburner = new FeedburnerEmailVersion2(); $subscribers = $feedburner->getSubscribers(); $feedburner->sendEmails($subscribers); } } $feedburner_email = new FeedburnerAdapter(); $feedburner_email->sendUpdates();
The FeedburnerAdapter
adapter initializes the Feedburner email library inside it's sendUpdates()
method and reconstructs the previous implementation by calling new methods in the latest version of the library. Now our application and the Feedburner library communicate through the standard interface of FeedburnerAdapter
. Our application does not know that the implementation has changed and an adapter is working in place of the original library class. Developers can call the standard set of methods without making any change to their original code.
Now it's time for understanding theoretical aspects of the Adapter pattern using its class diagram.
Usually we have a Client
, Target
, and Adaptee
in our application, and the Adaptee
class implements the Target
interface. In situations where Client
and Adaptee
become incompatible, we create a new class called Adapter
and place it in between Target
and Adaptee
to make the components compatible with each other.
The diagram above contains the original design of the Adapter pattern to use in best-case scenarios. Interfaces are not used widely in PHP projects but this doesn't mean that you cannot use the Adapter pattern. As long as some component integrates incompatible interfaces, it can be considered an adapter.
In my last article on Opauth, we discussed about using a strategy class in the Opauth library. It also acts as an adapter, even though it doesn't implement any interfaces. The strategy adapter made the open authentication libraries compatible with the core Opauth library.
Who Develops the Adapter Class?
When we're in need of an adapter, either we can create it as developers or we can ask the vendor to provide the adapter. It depends on the situation and type of the project we're working on. If we are developing applications by using common third party libraries, then we will be responsible for creating adapters to suit the requirements. On the other hand, we might be developing a large scale application and expect the vendors to develop libraries specially for our application. In such scenarios, if the vendors changes their library then they should provide the Adapter as well.
Adapter Pattern – The Wrong Way
Many experienced developers think that the Adapter pattern is used to fix poorly designed systems. Depending on the situation, we might have to agree with that. But let's consider a slightly modified version of the email subscription example we discussed previously.
Assume that two teams have been assigned to develop Feedburner and Mailchimp classes separately based on the original interface we used earlier.
<?php class FeedburnerEmail implements EmailSubscribe { public function subscribe($email) { } public function unsubscribe($email) { } public function getSubscribers() { // Returns list of subscribers } public function sendUpdates() { $this->getSubscribers(); // Get Website Updates // Send Emails } }
<?php class MailchimpEmail implements EmailSubscribe { public $subscribers; public function subscribe($email) { } public function unsubscribe($email) { } public function getSubscribers() { $this->subscribers = "List of subscribers"; } public function sendUpdates() { $subscribers = $this->subscribers; // Get Website Updates // Send Emails } }
Even though both classes are compatible with the target interface, there is an incompatibility between the client and these two classes. Consider the initialization code to understand this better:
<?php $email = FeedburnerEmail(); $email->sendUpdates(); $email = MailchimpEmail(); $email->getSubscribers(); $email->sendUpdates();
The code from Team 2 doesn't match with the client initialization code and hence becomes incompatible. We need an adapter to fix the issue for the Mailchimp class. This is considered a bad use of Adapter pattern since we could have planned the interface properly to avoid such incompatibility issues.
Adapter Pattern – The Right Way
Adapters are mostly used in situations where we work with third-party libraries or create a new functionality which is considerably different from the original requirements. So, let's consider the following scenario for effective use of adapters.
Email subscriptions is working perfectly on our website. Due to the success of subscriptions, management is planning to implement Twitter subscriptions for the site. Currently, when a user subscribes through email, he or she will get email notifications about updates in website content. With the new requirement, basically the user subscribes by authenticating their Twitter account for our website. Whenever site is updated, new tweets will be created in their tweet stream about the update.
The following code contains the Twitter library for this implementation.
<?php class TwitterService { public function authenticate($username) {} public function deauthenticate($username) {} public function tweet($message,$user) { // Update wall with new tweet } public function getUpdates() { // Return Updates } public function getFollowers() { // Return followers } }
There is no way we can make TwitterService
compatible with a target interface or client with its original implementation. But we can see that logic of the class is similar to EmailSubscription
. Therefore, we can effectively use an adapter class in this situation to make TwitterService
compatible with the client without changing client code.
Let's look at the implementation of the TwitterAdapter
class.
<?php class TwitterAdapter implements EmailSubscribe { public function subscribe($username) { } public function unsubscribe($username) { } public function sendUpdates() { $tw_service = new TwitterService(); $updates = $tw_service->getUpdates(); $subscribers = $tw_service->getFollowers(); $tw_service->tweet($updates,$subscribers); } } $twitter_subscribe = new TwitterAdapter(); $twitter_subscribe->sendUpdates();
The TwitterAdapter
class implements our target interface with original email subscription related functionalities. Internally it creates an object of TwitterService
and makes the tweet function compatible with sendUpdates()
by calling the necessary functions and returning the output expected by the client.
The initialization code seems similar to the previous code. Therefore, the client class doesn't know that Twitter service sends a tweet on updates instead of an email. The client class keeps calling the sendUpdate()
method for all the services and the respective updating techniques will be executed through adapters.
Summary
Throughout this article we've looked at the Adapter pattern and tried to to understand the effective uses of it through some practical examples. We learned that there are both good and bad uses of the Adapter pattern, and now it's up to you to decide when to go with adapters.
Let me know about the practical scenarios which you faced in application development and how you provided a solution through adapters in the comments below.
Image via Fotolia
Source: www.bing.com
Images credite d to www.bing.com and motherandchild.org.ua