Key Takeaways
- The Adapter design pattern is a key tool in managing changes in software development, enabling the integration of incompatible components and the introduction of new functionalities.
- The Adapter pattern can be used to maintain compatibility between different versions of a library, ensuring that changes to a library do not disrupt the existing system.
- The Adapter pattern is not meant to fix poorly designed systems, but to manage changes in third-party libraries or to introduce new functionalities that differ significantly from original requirements.
- The Adapter pattern should be used judiciously, as incorrect or excessive use can increase code complexity and potentially lead to problems if the adapter class does not correctly mimic the behavior of the target interface.
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.
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 FotoliaFrequently Asked Questions about the Adapter Pattern
What is the main purpose of the Adapter Pattern in software design?
The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by converting the interface of one class into another interface that clients expect. This pattern is particularly useful when you want to use an existing class, but its interface doesn’t match the one you need. It allows classes to work together that couldn’t otherwise because of incompatible interfaces.
Can you provide a real-world example of the Adapter Pattern?
A real-world example of the Adapter Pattern is the case of memory card readers. Different memory cards have different interfaces, but a card reader can adapt each type of memory card to a standard USB interface that can be used by a computer. The card reader acts as an adapter, allowing memory cards and computers to work together despite their incompatible interfaces.
How does the Adapter Pattern differ from the Decorator Pattern?
While both the Adapter and Decorator Patterns are structural design patterns, they serve different purposes. The Adapter Pattern is used to make two incompatible interfaces compatible, allowing them to work together. On the other hand, the Decorator Pattern is used to add new functionality to an existing object, without altering its structure. The Decorator Pattern is more about adding responsibilities to the object, while the Adapter Pattern is more about making objects fit others’ expectations.
What are the benefits of using the Adapter Pattern?
The Adapter Pattern offers several benefits. It enhances the reusability and flexibility of the software. It allows classes with incompatible interfaces to work together. It also promotes loose coupling between the software components, which makes the software more modular and easier to understand, maintain, and test.
Can the Adapter Pattern be used with other design patterns?
Yes, the Adapter Pattern can be used in conjunction with other design patterns. For example, it can be used with the Factory Pattern to create the adapter instances. It can also be used with the Decorator Pattern to add functionality to the adapter.
What are the drawbacks of the Adapter Pattern?
While the Adapter Pattern has many benefits, it also has a few drawbacks. It can increase the complexity of the code, especially when used extensively. It can also lead to problems if the adapter class isn’t correctly designed or if it doesn’t correctly mimic the behavior of the target interface.
When should I use the Adapter Pattern?
The Adapter Pattern should be used when you want to use an existing class, but its interface doesn’t match the one you need. It’s also useful when you want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don’t necessarily have compatible interfaces.
Can you provide a simple code example of the Adapter Pattern?
Yes, here’s a simple example in Java:// Existing interface
public interface OldInterface {
void oldMethod();
}
// Adapter class
public class Adapter implements NewInterface {
private OldInterface oldObject;
public Adapter(OldInterface oldObject) {
this.oldObject = oldObject;
}
public void newMethod() {
oldObject.oldMethod();
}
}
In this example, the Adapter
class adapts the OldInterface
to the NewInterface
by wrapping an instance of a class that implements the OldInterface
and providing a method that matches the NewInterface
.
How does the Adapter Pattern relate to the Single Responsibility Principle?
The Adapter Pattern adheres to the Single Responsibility Principle, which states that a class should have only one reason to change. The adapter class has the single responsibility of adapting the interface of one class to another, allowing them to work together.
Can the Adapter Pattern be used in multithreaded applications?
Yes, the Adapter Pattern can be used in multithreaded applications. However, care must be taken to ensure that the adapter class is thread-safe, especially if the adapted class is not thread-safe. This may involve adding synchronization mechanisms to the adapter class.
Rakhitha Nimesh is a software engineer and writer from Sri Lanka. He likes to develop applications and write on latest technologies. He is available for freelance writing and WordPress development. You can read his latest book on Building Impressive Presentations with Impress.js. He is a regular contributor to 1stWebDesigner, Tuts+ network and SitePoint network. Make sure to follow him on Google+.