Adapter

Design patterns

  |   6 min read

Purpose

The Adapter (structural pattern) allows two classes with incompatible interfaces to work by transforming the interface of one class into an intelligible form for the other. Adapter otherwise called Wrapper is a kind of adapter between two entities. There are two variants of the classic form, Class Adapter and Object Adapter. The choice of the Adapter type can be arbitrary or dictated by the constraints depending on the conditions. In the situation when the adaptable class can not be extended, the Object Adapter is used, whereas if the adapted class is abstract Class Adapter is used. Due to the danger of using the Class Adapter, in most situations the Object Adapter is used. In addition it is referred to as the Bidirectional Adapter, in which the translation takes place in both directions, i.e. the client is also an adapted class. Such a mechanism can be used, for example to convert types.

Limitations

When creating the Adapter should be kept in mind that with the increase of incompatibility, performance decreases, which may be a problem in some applications. Object Adapter can not overload adaptive class methods, while Class Adapter does not allow adaptation of subclasses of the adapted class. Class Adapter strictly binds the client with the adapted class, which in the case of system growth or change of requirements, reduces the code reusability.

Usage

It is mainly used where the use of an existing class to communicate with another class is impossible due to the interface’s incompatibility. In addition, Adapter is used in situations where the created class will work with classes with undefined interfaces.

Implementation

Class Adapter inherits from the adaptive Adaptee class and implements the client Client class. The Object Adapter implements the client Client class and contains an instance of the adapted Adaptee class.

Adapter diagram

The Client and Adaptee class are incompatible with the following listing.

public interface Client {

  void operation1();
  void operation2();
}

public class Adaptee {

  public void method1() {
    //some work
  }

  public void method2() {
    //some work
  }

  public void method3() {
    //some work
  }
}

To enable communication, enter Adapter between them. The following listing presents implementations of the template in two variants.

public class AdapterClass extends Adaptee implements Client {

  @Override
  public void operation1() {
    method1();
  }

  @Override
  public void operation2() {
    method2();
  }
}

public class AdapterObject implements Client {

  private Adaptee adaptee;

  public Client() {
    this.adaptee = new Adaptee();
    //some parameters can be passed to initialize Adaptee
  }

  @Override
  public void operation1() {
    adaptee.method1();
  }

  @Override
  public void operation2() {
    adaptee.method2();
  }
}

The adapters prepared in this way allow calling methods of the adapted Adaptee class from the Client class, which is presented in the following listing.

Client clientClass = new AdapterClass();
clientClass.operation1(); //runs method1() from Adaptee

Client clientObject = new AdapterObject();
clientObject.operation2(); //runs method2() from Adaptee

Example

Messenger application allows to send text messages and files between users using the internal library of the network protocol. In addition, the application allows you to change the status. The following listing illustrates implementations of the Messenger application with its internal MessageProtocol protocol.

public class Messenger {

  private IMessageProtocol protocol;

  public Messenger(IMessageProtocol protocol) {
    this.protocol = protocol;
  }

  public void sendMessage(String text) {
    protocol.sendText(text);
  }

  public void sendMessage(File file) {
    protocol.sendFile(file);
  }

  public void changeStatus(String text) {
    //set and show status
  }
}

public interface IMessageProtocol {

  void sendText(String text);
  void sendFile(File file);
}

public class MessageProtocol implements IMessageProtocol {

  @Override
  public void sendText(String text) {
    //converting and sending text
  }

  @Override
  public void sendFile(File file) {
    //converting and sending file
  }
}

At some point in the project life cycle, replacing the MessageProtocol network protocol with a more efficient ExternalMessageProtocol external library is needed. In addition, each operation is to send data for analytics. Due to the size of the project, it is not possible to change the type of the protocol instance and method calls in all instances of the Messenger class object. The solution to this problem is to use the Adapter, implementation of which is shown below.

public class MessageProtocolAdapter implements IMessageProtocol {

  private ExternalMessageProtocol protocol = new ExternalMessageProtocol();

  public MessageProtocolAdapter() {
    this.protocol = new ExternalMessageProtocol();
  }

  @Override
  public void sendText(String text) {
    Analytics.report("Message");
    protocol.sendString(text);
  }

  @Override
  public void sendFile(File file) {
    Analytics.report("File");
    protocol.sendBytes(protocol.getBytes(file));
  }
}

To use the new solution, simply pass the Adapter reference to the application as follows.

Messenger newMessenger = new Messenger(new MessageProtocolAdapter());
newMessenger.sendMessage("Message from external library"); //send optimized message and analytics

Messenger oldMessenger = new Messenger(new MessageProtocol());
oldMessenger.sendMessage("Message from internal library"); //send only message

Libraries

Adapter is a standard feature of the Android library. Its purpose is to provide data to the view of the AdaperView collection, as well as to create views View for each element from the data set.