Это третья часть моего выступления « Шаблоны проектирования в 21 веке» .
Шаблон Адаптер соединяет миры. В одном мире у нас есть интерфейс для концепции; в другом мире у нас другой интерфейс. Эти два интерфейса служат разным целям, но иногда нам нужно передавать вещи через. В хорошо написанном юниверсе мы можем использовать адаптеры, чтобы заставить объекты, следующие одному протоколу, придерживаться другого.
Существует два вида шаблона адаптера. Мы не будем говорить об этом:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
 | 
interface Fire {    <T> Burnt<T> burn(T thing);}interface Oven {    Food cook(Food food);}class WoodFire implements Fire { ... }class MakeshiftOven extends WoodFire implements Oven {    @Override public Food cook(Food food) {        Burnt<Food> noms = burn(food);        return noms.scrapeOffBurntBits();    }} | 
 Эта форма, шаблон адаптера класса , выводит меня из себя, потому что extends дает мне хиби джиби.  Почему выходит за рамки этого эссе;  не стесняйтесь спрашивать меня в любое время, и я с радостью отговорю ваши уши (и, вероятно, ваш нос) об этом. 
Вместо этого давайте поговорим об объектном шаблоне Adapter , который обычно считается гораздо более полезным и гибким во всех отношениях.
Давайте посмотрим на тот же класс, следуя этой альтернативе:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
 | 
class MakeshiftOven implements Oven {    private final Fire fire;    public MakeshiftOven(Fire fire) {        this.fire = fire;    }    @Override public Food cook(Food food) {        Burnt<Food> noms = fire.burn(food);        return noms.scrapeOffBurntBits();    }} | 
И мы будем использовать это так:
| 
 1 
2 
 | 
Oven oven = new MakeshiftOven(fire);Food bakedPie = oven.cook(pie); | 
Шаблон обычно следует этой простой структуре:
Это хорошо, правда?
Да. Вроде, как бы, что-то вроде. Мы можем сделать лучше.
  У нас уже есть ссылка на Fire , поэтому создание другого объекта, чтобы поиграть с ним, кажется немного … излишним.  И этот объект реализует Oven .  Который имеет один абстрактный метод .  Я вижу тенденцию здесь. 
Вместо этого мы можем сделать функцию, которая делает то же самое.
| 
 1 
2 
 | 
Oven oven = food -> fire.burn(food).scrapeOffBurntBits();Food bakedPie = oven.cook(pie); | 
Мы могли бы пойти еще дальше и составить ссылки на методы, но на самом деле все становится еще хуже.
| 
 1 
2 
3 
4 
5 
 | 
// Do *not* do this.Function<Food, Burnt<Food>> burn = fire::burn;Function<Food, Food> cook = burn.andThen(Burnt::scrapeOffBurntBits);Oven oven = cook::apply;Food bakedPie = oven.cook(pie); | 
Это потому, что Java не может конвертировать между функциональными интерфейсами неявно, поэтому нам нужно дать ей много подсказок о том, что представляет собой каждый этап операции. С другой стороны, лямбда-выражения неявно применимы к любому функциональному интерфейсу с правильными типами, и компилятор довольно неплохо разбирается, как это сделать.
Наша новая UML-диаграмма будет выглядеть примерно так:
  Однако часто все, что нам действительно нужно, — это ссылка на метод.  Например, возьмем интерфейс Executor . 
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
package java.util.concurrent;/** * An object that executes submitted {@link Runnable} tasks. */public interface Executor {    void execute(Runnable command);} | 
  Он потребляет объекты Runnable , и это очень полезный интерфейс. 
  Теперь предположим, что у нас есть одна из них и куча задач Runnable , которые находятся в Stream . 
| 
 1 
2 
 | 
Executor executor = ...;Stream<Runnable> tasks = ...; | 
  Как мы выполняем их все на нашем Executor ? 
Это не сработает:
| 
 1 
 | 
tasks.forEach(executor); | 
  Оказывается, метод forEach в Stream действительно принимает потребителя, но очень специфического типа: 
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
public interface Stream<T> {    ...    void forEach(Consumer<? super T> action);    ...} | 
  Consumer выглядит так: 
| 
 1 
2 
3 
4 
5 
6 
7 
 | 
@FunctionalInterfacepublic interface Consumer<T>{    void accept(T t);    ...} | 
  На первый взгляд, это не выглядит таким уж полезным.  Но обратите внимание, что Consumer — это функциональный интерфейс, поэтому мы можем использовать лямбда-выражения для их простого определения.  Это означает, что мы можем сделать это: 
| 
 1 
 | 
tasks.forEach(task -> executor.execute(task)); | 
Что можно упростить до этого:
| 
 1 
 | 
tasks.forEach(executor::execute); | 
Java 8 сделала адаптеры настолько простыми, что я не решаюсь называть их шаблонами. Концепция все еще очень важна; явно создавая адаптеры, мы можем разделить эти два мира, кроме как в определенных граничных точках. Реализации, правда? Они просто функции.
| Ссылка: | Шаблоны проектирования в 21-м веке: шаблон адаптера от нашего партнера JCG Самира Талвара в блоге Crafted Software . | 

