JavaRush/Java Blog/Random EN/Design Pattern Adapter

Design Pattern Adapter

Published in the Random EN group
members
Hello! Today we will touch on an important new topic - patterns, or in other words - design patterns . What are patterns? I think you know the expression “don’t reinvent the wheel.” In programming, as in many other areas, there are a large number of typical situations. For each of them, in the process of programming development, ready-made working solutions were created. These are design patterns. Relatively speaking, a pattern is a certain example that offers a solution to a situation like: “if your program needs to do something, how best to do it.” There are a lot of patterns, an excellent book “Studying Design Patterns” is dedicated to them, which you should definitely read. Design pattern "Adapter" - 2To put it as briefly as possible, a pattern consists of a common problem and its solution, which can already be considered a kind of standard. In today's lecture we will get acquainted with one of these patterns called “Adapter”. Its name is telling, and you have come across adapters in real life more than once. One of the most common adapters is card readers, which are equipped with many computers and laptops. Adapter design pattern - 3Imagine that we have some kind of memory card. What is the problem? The fact is that she does not know how to interact with a computer. They do not have a common interface. The computer has a USB connector, but you cannot insert a memory card into it. The card cannot be inserted into the computer, due to which we will not be able to save our photos, videos and other data. The card reader is an adapter that solves this problem. After all, it has a USB cable! Unlike the card itself, the card reader can be inserted into a computer. They have a common interface with the computer - USB. Let's see what it would look like with an example:
public interface USB {

   void connectWithUsbCable();
}
This is our USB interface with the only method being to insert the USB cable:
public class MemoryCard {

   public void insert() {
       System.out.println("Карта памяти успешно вставлена!");
   }

   public void copyData() {
       System.out.println("Данные скопированы на компьютер!");
   }
}
This is our class that implements the memory map. It already has 2 methods we need, but here’s the problem: it doesn’t implement the USB interface. The card cannot be inserted into the USB slot.
public class CardReader implements USB {

   private MemoryCard memoryCard;

   public CardReader(MemoryCard memoryCard) {
       this.memoryCard = memoryCard;
   }

   @Override
   public void connectWithUsbCable() {
       this.memoryCard.insert();
       this.memoryCard.copyData();
   }
}
And here is our adapter! What does the classCardReader do and why is it an adapter? It's simple. The class being adapted (memory map) becomes one of the fields of the adapter. This is logical, because in real life we ​​also insert a card inside the card reader, and it also becomes part of it. Unlike a memory card, the adapter has a common interface with the computer. It has a USB cable, meaning it can connect to other devices via USB. Therefore, in the program, our class CardReaderimplements the USB interface. But what happens inside this method? And there happens exactly what we need! The adapter delegates the work to our memory card. After all, the adapter itself does nothing; the card reader does not have any independent functionality. Its job is only to link the computer and the memory card so that the card can do its job and copy files! Our adapter allows it to do this by providing its own interface (method connectWithUsbCable()) for the “needs” of the memory card. Let's create some kind of client program that will simulate a person who wants to copy data from a memory card:
public class Main {

   public static void main(String[] args) {

       USB cardReader = new CardReader(new MemoryCard());
       cardReader.connectWithUsbCable();

   }
}
What did we get as a result? Console output:
Карта памяти успешно вставлена!
Данные скопированы на компьютер!
Great, our task has been successfully completed! Here are some additional links with information about the Adapter pattern:

Abstract classes Reader and Writer

Now we will return to our favorite pastime: we will learn a couple of new classes for working with input and output :) How many of them have we already learned, I wonder? Today we will talk about classes Readerand Writer. Why about them? Because this will be related to our previous section - adapters. Let's look at them in more detail. Let's start with Reader'a. Readeris an abstract class, so we won’t be able to create its objects explicitly. But in fact, you already know him! After all, the classes you know well BufferedReaderare InputStreamReaderits heirs :)
public class BufferedReader extends Reader {}

public class InputStreamReader extends Reader {}
So, a class InputStreamReaderis a classic adapter . As you probably remember, we can pass an object to its constructor InputStream. Most often we use a variable for this System.in:
public static void main(String[] args) {

   InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
What does it do InputStreamReader? Like any adapter, it converts one interface to another. In this case, the interface InputStream'a to the interface Reader'a. Initially we had a class InputStream. It works well, but it can only read individual bytes. In addition, we have an abstract class Reader. It has excellent functionality that we really need - it can read characters! We, of course, really need this opportunity. But here we are faced with a classic problem that adapters usually solve - interface incompatibility. How does it manifest itself? Let's look straight into the Oracle documentation. Here are the class methods InputStream. Design pattern "Adapter" - 4A set of methods is an interface. As you can see, read()this class has a method (even in several versions), but it can only read bytes: either individual bytes, or several bytes using a buffer. This option does not suit us - we want to read characters. The functionality we need is already implemented in the abstract classReader . This can also be seen in the documentation. Adapter design pattern - 5However, the InputStream'a' and Reader'a' interfaces are incompatible! As you can see, in all method implementations, read()both the passed parameters and the return values ​​differ. And this is where we need it InputStreamReader! He will act as an Adapter between our classes. As in the example with the card reader, which we looked at above, we pass the object of the “adapted” class “internally,” that is, to the constructor of the adapter class. In the previous example, we passed an object MemoryCardinside CardReader. Now we pass the object InputStreamto the constructor InputStreamReader! As a quality InputStreamwe use the already familiar variable System.in:
public static void main(String[] args) {

   InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
And indeed: by looking at the documentation InputStreamReaderwe will see that the “adaptation” was successful :) Now we have at our disposal methods that allow us to read characters. Adapter design pattern - 6And although initially our object System.in(a thread bound to the keyboard) did not allow this, by creating the Adapter pattern, the creators of the language solved this problem. The abstract class Reader, like most I/O classes, has a twin brother - Writer. It has the same big advantage Reader- it provides a convenient interface for working with symbols. With output streams, the problem and its solution look the same as in the case of input streams. There is a class OutputStreamthat can only write bytes; There is an abstract class Writerthat can work with symbols, and there are two incompatible interfaces. This problem is again successfully solved by the Adapter pattern. Using a class, OutputStreamWriterwe can easily “adapt” two class interfaces Writerto OutputStreameach other. And, having received a byte stream OutputStreamin the constructor, with the help OutputStreamWriterwe can, however, write characters, not bytes!
import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException {

       OutputStreamWriter streamWriter = new OutputStreamWriter(new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt"));
       streamWriter.write(32144);
       streamWriter.close();
   }
}
We wrote a character with code 32144 - 綐 into our file, thus eliminating the need to work with bytes :) That's all for today, see you in the next lectures! :)
Comments
  • Popular
  • New
  • Old
You must be signed in to leave a comment
This page doesn't have any comments yet