Hello! Сегодня мы затронем важную новую тему — паттерны, or по-другому — шаблоны проектирования. What же такое паттерны? Думаю, тебе известно выражение «не надо изобретать велосипед». В программировании, How и во многих других сферах, есть большое количество типовых ситуаций. Для каждой из них в процессе развития программирования создавались готовые работающие решения. Это и есть шаблоны проектирования. Условно говоря, паттерн — это некий пример, который предлагает решение ситуации вида: «если в вашей программе нужно сделать то-то, How это лучше всего сделать». Паттернов очень много, им посвящена отличная книга «Изучаем шаблоны проектирования», с которой обязательно нужно ознакомиться. Если говорить максимально кратко, паттерн состоит из распространенной проблемы и ее решения, которое уже можно считать неким standard. В сегодняшней лекции мы познакомимся с одним из таких паттернов под названием «Адаптер». Название у него говорящее, и ты не раз встречался с адаптерами в реальной жизни. Один из самых распространенных адаптеров — кардридеры, которыми снабжены множество компьютеров и ноутбуков. Представь, что у нас есть Howая-то карта памяти. В чем состоит проблема? В том, что она не умеет взаимодействовать с компьютером. У них нет общего интерфейса. У компьютера есть разъем USB, но карту памяти в него не вставить. Карту невозможно вставить в компьютер, из-за чего мы не сможем сохранить наши фотографии, видео и другие данные. Кардридер является адаптером, решающим данную проблему. Ведь у него есть USB-кабель! В отличие от самой карты, кардридер можно вставить в компьютер. У них с компьютером есть общий интерфейс — USB. Давай посмотрим, How это будет выглядеть на примере:
public interface USB {
void connectWithUsbCable();
}
Это наш интерфейс USB с единственным методом — вставить USB-кабель:
public class MemoryCard {
public void insert() {
System.out.println("Карта памяти успешно вставлена!");
}
public void copyData() {
System.out.println("Данные скопированы на компьютер!");
}
}
Это наш класс, реализующий карту памяти. В нем уже есть 2 нужных нам метода, но вот беда: интерфейс USB он не реализует. Карту нельзя вставить в USB-разъем.
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();
}
}
А вот и наш адаптер! What же делает класс CardReader
и почему, собственно, он является адаптером? Все просто. Адаптируемый класс (карта памяти) становится одним из полей адаптера. Это логично, ведь в реальной жизни мы тоже вставляем карту внутрь кардридера, и она тоже становится его частью. В отличие от карты памяти, у адаптера есть общий интерфейс с компьютером. У него есть USB-кабель, то есть он может соединяться с другими устройствами по USB. Поэтому в программе наш класс CardReader
реализует интерфейс USB. Но что же происходит внутри этого метода? А там происходит ровно то, что нам нужно! Адаптер делегирует выполнение работы нашей карте памяти. Ведь сам-то адаптер ничего не делает, Howого-то самостоятельного функционала у кардридера нет. Его задача — только связать компьютер и карту памяти, чтобы карта могла сделать свою работу и скопировать файлы! Наш адаптер позволяет ей сделать это, предоставив свой интерфейс (метод connectWithUsbCable()
) для «нужд» карты памяти. Давай создадим Howую-то программу-клиент, которая будет имитировать человека, желающего скопировать данные с карты памяти:
public class Main {
public static void main(String[] args) {
USB cardReader = new CardReader(new MemoryCard());
cardReader.connectWithUsbCable();
}
}
What же у нас в результате получилось? Вывод в консоль:
Карта памяти успешно вставлена!
Данные скопированы на компьютер!
Отлично, наша задача успешно выполнена! Вот несколько дополнительных ссылок с информацией о паттерне Адаптер:
- Видео Adapter Pattern – Design Patterns;
- Паттерн проектирования «Адаптер» / «Adapter»;
- Шаблоны проектирования простым языком.
Абстрактные классы Reader и Writer
Теперь мы вернемся к нашему любимому занятию: выучим парочку новых классов для работы со вводом и выводом :) Сколько мы их уже выучor, интересно? Сегодня речь пойдет о классахReader
и Writer
. Почему именно о них? Потому что это будет в тему нашему предыдущему разделу — адаптерам. Давай рассмотрим их подробнее. Начнем с Reader
’a. Reader
— это абстрактный класс, поэтому явно создавать его an objectы у нас не получится. Но на самом деле ты с ним уже знаком! Ведь хорошо знакомые тебе классы BufferedReader
и InputStreamReader
являются его наследниками :)
public class BufferedReader extends Reader {
…
}
public class InputStreamReader extends Reader {
…
}
Так вот, класс InputStreamReader
— это классический адаптер. Как ты, наверное, помнишь, мы можем передать в его конструктор an object InputStream
. Чаще всего мы для этого используем переменную System.in
:
public static void main(String[] args) {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
What же делает InputStreamReader
? Как и всякий адаптер, он преобразует один интерфейс к другому. В данном случае — интерфейс InputStream
’a к интерфейсу Reader
’a. Изначально у нас был класс InputStream
. Он неплохо работает, но с его помощью можно читать только отдельные byteы. Кроме того, у нас есть абстрактный класс Reader
. У него есть отличный и очень нужный нам функционал — он умеет читать символы! Нам такая возможность, конечно, очень нужна. Но здесь мы сталкиваемся с классической проблемой, которую обычно решают адаптеры — несовместимость интерфейсов. В чем же она проявляется? Давай заглянем прямо в documentацию Oracle. Вот методы класса InputStream
. Совокупность методов — это и есть интерфейс. Как видишь, метод read()
у этого класса есть (даже в нескольких вариантах), но читать он может только byteы: or отдельные byteы, or несколько byte с использованием буфера. Нам такой вариант не подходит — мы хотим читать символы. Нужный нам функционал уже реализован в абстрактном классе Reader
. Это тоже можно увидеть в documentации. Однако интерфейсы InputStream
'a и Reader
'a несовместимы! Как видишь, во всех реализациях метода read()
у них отличаются и передаваемые параметры, и возвращаемые значения. И именно здесь нам понадобится InputStreamReader
! Он выступит Адаптером между нашими классами. Как и в примере с кардридером, который мы рассмотрели выше, мы передаем an object «адаптируемого» класса «внутрь», то есть в конструктор класса-адаптера. В прошлом примере мы передавали an object MemoryCard
внутрь CardReader
. А теперь передаем an object InputStream
в конструктор InputStreamReader
! В качестве InputStream
мы используем уже ставшую привычной переменную System.in
:
public static void main(String[] args) {
InputStreamReader inputStreamReader = new InputStreamReader(System.in);
}
И действительно: заглянув в documentацию InputStreamReader
'a мы увидим, что «адаптация» прошла успешно :) Теперь в нашем распоряжении есть методы, которые позволяют нам читать символы. И хотя изначально наш an object System.in
(поток, привязанный к клавиатуре) не позволял этого делать, создав паттерн Адаптер создатели языка решor эту проблему. У абстрактного класса Reader
, How и у большинства I/O-классов, есть брат-близнец — Writer
. Он имеет тот же большой плюс, что и Reader
— предоставляет удобный интерфейс для работы с символами. С выходными потоками проблема и ее решение выглядят так же, How и в случае со входными. Есть класс OutputStream
, который умеет записывать только byteы; есть абстрактный класс Writer
, который умеет работать с символами, и есть два несовместимых интерфейса. Эту проблему вновь успешно решает паттерн Адаптер. При помощи класса OutputStreamWriter
мы легко «адаптируем» два интерфейса классов Writer
и OutputStream
друг другу. И, получив byteовый поток OutputStream
в конструктор, с помощью OutputStreamWriter
мы, тем не менее, можем записывать символы, а не byteы!
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();
}
}
Мы записали в наш файл символ с codeом 32144 — 綐, таким образом избавившись от необходимости работать с byteами :) На этом на сегодня все, до встречи на следующих лекциях! :)
GO TO FULL VERSION