Monday, March 27, 2023

Examples Of Adapter Design Pattern in JDK

 Here are some examples of Java API that make use of Adapter design pattern:

  1. java.util.Arrays#asList(Object[]): This method returns a List that is backed by an array. Here, the Array is the adaptee, and the List is the target interface.

  2. java.io.InputStreamReader(InputStream): This constructor reads bytes from the InputStream and converts them into characters using a specified character encoding. The InputStream is the adaptee, and the Reader interface is the target.

  3. javax.xml.bind.annotation.adapters.XmlAdapter: This is an abstract class that can be extended to create a custom adapter to marshal and unmarshal XML elements. Here, the XML data is the adaptee, and the Java objects are the target interface.

  4. java.awt.event.MouseAdapter: This is a class that extends the MouseAdapter abstract class and provides default implementations for all of the methods in the MouseListener interface. This adapter can be used to avoid having to implement all of the methods in the interface when only a few of them are needed.

  5. java.util.zip.ZipOutputStream: This class provides a way to write compressed data to a file in the ZIP file format. The ZipOutputStream class adapts the FileOutputStream to write data in the ZIP format.

  6. java.sql.ResultSet: This interface provides methods to access data from a database. The ResultSet interface can be adapted using the ResultSetMetaData interface to get information about the columns in the ResultSet.

     

    Detailed Explanation of  java.util.Arrays#asList(Object[]).

    String[] array = {"apple", "banana", "orange"};
    List<String> list = Arrays.asList(array);

    In this code, we have an array of strings (array) and we want to convert it into a list. We can use the Arrays.asList() method to do this. This method takes an array as its argument and returns a list that is backed by the array.

    Here, the array is the adaptee, and the List interface is the target. The Arrays.asList() method adapts the array to the List interface by creating a List object that uses the array as its backing store. This allows us to use the List interface to manipulate the array as if it were a list.

    For example, we can use the list object to add or remove elements from the array:

    list.add("grape");
    list.remove("apple");

     

    In this code, we add the "grape" element to the list and remove the "apple" element from the list. These changes are reflected in the original array, since the list is backed by the array.

    However, there are some limitations to using Arrays.asList(). For example, the resulting list is fixed-size, so we cannot add or remove elements from the list if the underlying array cannot be resized. Additionally, changes made to the list (such as adding or removing elements) may not be reflected in the original array if the array is modified in some other way.

     Detailed Explanation of java.io.InputStreamReader(InputStream):

    The InputStreamReader class is an adapter that adapts the InputStream interface (which reads bytes) to the Reader interface (which reads characters).

    The InputStream interface is the adaptee in this pattern. It provides methods to read bytes from a stream of input data. However, in many cases, we want to read characters instead of bytes. The Reader interface provides methods to read characters from an input source. Therefore, we need an adapter to convert the bytes read by the InputStream into characters that can be read by the Reader.

    The InputStreamReader class does this by implementing the Reader interface and delegating the read() and read(char[], int, int) methods to the read() method of the InputStream interface. The read() method of the InputStream interface returns a byte, so the InputStreamReader class converts the byte to a character using a specified character encoding (such as UTF-8) before returning it to the caller.

    Here's an example code snippet that shows how InputStreamReader adapts an InputStream to a Reader:

    // create an input stream that reads bytes from a file
    InputStream inputStream = new FileInputStream("input.txt");

    // create an InputStreamReader that adapts the input stream to a Reader
    Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);

    // read characters from the Reader
    int character;
    while ((character = reader.read()) != -1) {
        // do something with the character
    }
     

    In this example, the InputStream object inputStream reads bytes from a file. The InputStreamReader object reader adapts the inputStream to a Reader that can read characters. The while loop reads characters from the reader one by one and does something with each character. The InputStreamReader adapter pattern allows us to read characters from an input stream that would otherwise only be able to read bytes.

     

     

 

Sunday, March 26, 2023

Adapter Design Pattern in Java

 

The Adapter pattern is a design pattern in Java that allows two incompatible interfaces to work together. It is used when two classes with incompatible interfaces need to work together, or when an existing class needs to work with new functionality that cannot be easily integrated.

The Adapter pattern works by creating a new class that acts as a bridge between the two incompatible interfaces. This new class, known as the adapter, implements the interface of the client code and wraps the interface of the legacy code, translating the calls between the two interfaces.

 Here is an example of how the Adapter pattern can be implemented in Java:

Suppose we have a legacy class that provides an interface to print a message to the console:

public class LegacyPrinter {
    public void print(String message) {
        System.out.println(message);
    }
}

Now suppose we have a new client class that needs to print a message to the console using a different interface:

public interface Printer {
    void printMessage(String message);
}

To use the LegacyPrinter with the Printer interface, we can create an adapter class that implements the Printer interface and wraps the LegacyPrinter:

public class LegacyPrinterAdapter implements Printer {
    private LegacyPrinter legacyPrinter;

    public LegacyPrinterAdapter(LegacyPrinter legacyPrinter) {
        this.legacyPrinter = legacyPrinter;
    }

    @Override
    public void printMessage(String message) {
        legacyPrinter.print(message);
    }
}

Now the client code can use the Printer interface to print messages to the console, even though it is using the LegacyPrinter behind the scenes:

public class Client {
    public static void main(String[] args) {
        Printer printer = new LegacyPrinterAdapter(new LegacyPrinter());
        printer.printMessage("Hello, World!");
    }
}

In this example, the LegacyPrinterAdapter acts as a bridge between the Printer interface and the LegacyPrinter interface, allowing the client code to use the Printer interface to print messages to the console using the LegacyPrinter.

 Types of Adapter design patterns

1) Class Adapter pattern

Class Adapter pattern: The Class Adapter pattern uses inheritance to adapt one interface to another. In this pattern, the adapter class extends the existing class that provides the legacy interface, and also implements the new interface that the client code expects. The adapter class translates the calls from the new interface into calls to the existing class. 

In the Class Adapter pattern, we use inheritance to adapt the legacy interface to the target interface. Here's an example:

Legacy Interface:

public class LegacyRectangle {
    public void draw(int x, int y, int width, int height) {
        System.out.println("Drawing Rectangle [" + x + "," + y + "," + width + "," + height + "]");
    }
}

Target Interface:

 public interface Shape {
    void draw(int x1, int y1, int x2, int y2);
}

Adapter class:

public class RectangleAdapter extends LegacyRectangle implements Shape {

    @Override
    public void draw(int x1, int y1, int x2, int y2) {
        int x = Math.min(x1, x2);
        int y = Math.min(y1, y2);
        int width = Math.abs(x2 - x1);
        int height = Math.abs(y2 - y1);
        super.draw(x, y, width, height);
    }
}

Client Code:

public class Client {
    public static void main(String[] args) {
        Shape shape = new RectangleAdapter();
        shape.draw(10, 20, 30, 40);
    }
}

In this example, we created a RectangleAdapter class that extends the LegacyRectangle class and implements the Shape interface. The adapter class adapts the draw() method of the LegacyRectangle class to the draw() method of the Shape interface. The client code can now use the Shape interface to draw a rectangle, even though the underlying implementation uses the LegacyRectangle class.

2) Object Adapter pattern

The Object Adapter pattern uses composition to adapt one interface to another. In this pattern, the adapter class contains an instance of the existing class that provides the legacy interface, and also implements the new interface that the client code expects. The adapter class translates the calls from the new interface into calls to the existing class by delegating the calls to the contained object. 

In the Object Adapter pattern, we use composition to adapt the legacy interface to the target interface. Here's an example:

Legacy Interface:

public class LegacyLine {
    public void draw(int x1, int y1, int x2, int y2) {
        System.out.println("Drawing Line [" + x1 + "," + y1 + "] to [" + x2 + "," + y2 + "]");
    }
}

Target Interface

public interface Shape {
    void draw(int x1, int y1, int x2, int y2);
}

Adapter class:

public class LineAdapter implements Shape {

    private LegacyLine legacyLine;
    public LineAdapter(LegacyLine legacyLine) {
        this.legacyLine = legacyLine;
    }
    @Override
    public void draw(int x1, int y1, int x2, int y2) {
        legacyLine.draw(x1, y1, x2, y2);
    }
}

Client code:

public class Client {
    public static void main(String[] args) {
        LegacyLine legacyLine = new LegacyLine();
        Shape shape = new LineAdapter(legacyLine);
        shape.draw(10, 20, 30, 40);
    }
}

In this example, we created a LineAdapter class that implements the Shape interface and contains an instance of the LegacyLine class. The adapter class adapts the draw() method of the LegacyLine class to the draw() method of the Shape interface by delegating the call to the contained object. The client code can now use the Shape interface to draw a line, even though the underlying implementation uses the LegacyLine class. 

The real JDK examples are well explained in below post

Examples Of Adapter Design Pattern in JDK

 

 


 

Copyright @ 2013 Java Tutorial.

Designed by Mathew | mathai