Адаптер (Adapter)

Адаптер берет один тип, а производит интерфейс некоторого другого типа. Когда у вас есть это, а вам нужно то, Адаптер решит эту проблему. Единственное требование состоит в том, чтобы получить то, и есть несколько способов выполнить такую адаптацию.

//: adapter:SimpleAdapter.java
// "Object Adapter"из диаграммы GoF
package adapter;

import junit.framework.*;

class Target {
  
public void request() {
   }
}

class Adaptee {
  
public void specificRequest() {
     
System.out.println("Adaptee: SpecificRequest");
  
}
}

class Adapter extends Target {
  
private Adaptee adaptee;
  
  
public Adapter(Adaptee a) {
     
adaptee = a;
  
}
  
  
public void request() {
     
adaptee.specificRequest();
  
}
}

public class SimpleAdapter extends TestCase {
  
Adaptee a = new Adaptee();
   Target t =
new Adapter(a);
  
  
public void test() {
     
t.request();
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(SimpleAdapter.class);
  
}
}
// /:~
//: adapter:AdapterVariations.java
// Вариации на тему шаблона Адаптера.
package adapter;

import junit.framework.*;

class WhatIHave {
  
public void g() {
   }
  
  
public void h() {
   }
}

interface WhatIWant {
  
void f();
}

class SurrogateAdapter implements WhatIWant {
  
WhatIHave whatIHave;
  
  
public SurrogateAdapter(WhatIHave wih) {
     
whatIHave = wih;
  
}
  
  
public void f() {
     
// Implement behavior using
      // methods in WhatIHave:
     
whatIHave.g();
      whatIHave.h
();
  
}
}

class WhatIUse {
  
public void op(WhatIWant wiw) {
     
wiw.f();
  
}
}

// Approach 2: build adapter use into op():
class WhatIUse2 extends WhatIUse {
  
public void op(WhatIHave wih) {
     
new SurrogateAdapter(wih).f();
  
}
}

// Approach 3: build adapter into WhatIHave:
class WhatIHave2 extends WhatIHave implements WhatIWant {
  
public void f() {
     
g();
      h
();
  
}
}

// Approach 4: use an inner class:
class WhatIHave3 extends WhatIHave {
  
private class InnerAdapter implements WhatIWant {
     
public void f() {
        
g();
         h
();
     
}
   }
  
  
public WhatIWant whatIWant() {
     
return new InnerAdapter();
  
}
}

public class AdapterVariations extends TestCase {
  
WhatIUse whatIUse = new WhatIUse();
   WhatIHave whatIHave =
new WhatIHave();
   WhatIWant adapt =
new SurrogateAdapter(whatIHave);
   WhatIUse2 whatIUse2 =
new WhatIUse2();
   WhatIHave2 whatIHave2 =
new WhatIHave2();
   WhatIHave3 whatIHave3 =
new WhatIHave3();
  
  
public void test() {
     
whatIUse.op(adapt);
     
// Approach 2:
     
whatIUse2.op(whatIHave);
     
// Approach 3:
     
whatIUse.op(whatIHave2);
     
// Approach 4:
     
whatIUse.op(whatIHave3.whatIWant());
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(AdapterVariations.class);
  
}
}
// /:~

Я обошелся достаточно свободно с термином "прокси", поскольку в книге Design Patterns предполагается, что прокси должен иметь идентичный интерфейс с объектом, для которого создается суррогат. Однако, если вы соедините два слова вместе: "прокси адаптер", это будет более осмысленно.

Мост (Bridge)

Во время исследования Моста, я обнаружил, что он появляется для большинства неудачно описанных шаблонов в GoF. Я пришел к этому выводу после прочтения главы Алана Шеловэя (Alan Shalloway) о Мостах в его книге "Design Patterns Explained" - он указывает на то, что описание осталось им не освещенным в GoF.

На конференции я поговорил с двумя людьми, которые уже писали и говорили о шаблонах проектирования, включая Мост. В двух независимых дискуссиях я получил полностью различную перспективу структуры Моста.

Вооружившись отсутствием информации, я снова погрузился в GoF и обнаружил, что ни одна из этих перспектив не согласуется с этой книгой. Также я нашел, что книга делает неполную попытку описания Моста, исключая одно место - не в описании основной структуры диаграммы шаблона, которая не очень-то полезна, а в диаграмме структуры, описывающей их специфический пример. Только если вы присмотритесь к тому, что называют Мостом, то начнете понимать смысл.

Важнейшей особенностью в понимании, когда вы рассматриваете Мост (Bridge), является то, что часто эта конструкция используется для помощи в написании кода. Вы можете выбрать объекты, которые вы используете для определенной ситуации во время компиляции или во время выполнения, но цель Моста состоит в предоставлении вам такой структуры кода, которая поможет легко добавлять новые виды объектов переднего плана, которые реализуются с функциональностью новых объектов заднего плана. Таким образом, и объекты переднего плана, и объекты заднего плана могут изменяться независимо друг от друга.

Классы объектов переднего плана могут иметь полностью отличающийся интерфейс от других классов, и обычно отличаются. То, что они делают, в общем - они могут реализовать свою функциональность, используя возможности любого числа различных объектов заднего плана. Объекты заднего плата также не имеют общего интерфейса. Единственной вещью, которой должны обладать объекты заднего плана является то, что они реализуют функциональность одного вида - например, группа различных способов реализации графической библиотеки или набор различных решений по хранению данных.

Мост (Bridge) на самом деле является инструментом организации кода, который позволяет вам добавлять любое число новых сервисов переднего плана, которые реализуют свои операции, делегируя их произвольному количеству опций заднего плана. Используя Мост, вы можете достигнуть совершенства без обычного взрывного увеличения комбинаций возможностей, которое может произойти в противном случае. Но держите в уме, что вектор изменений при использовании Моста обычно происходит в момент кодирования: он сохраняет ваш код организованным, когда вы решаете увеличить число опций для реализации функциональность.

Bridge pattern

Ниже приведен пример, единственным назначением которого является демонстрация структуры Моста (он реализует приведенную выше диаграмму):

//: bridge:BridgeStructure.java
// Демонстрация структуры и операций
// Шаблона Моста.
package bridge;

import junit.framework.*;

class Abstraction {
  
private Implementation implementation;
  
  
public Abstraction(Implementation imp) {
     
implementation = imp;
  
}
  
  
// Абстракция используется различными объектами
   // переднего плана для своей реализации
   // различных интерфейсов.
  
public void service1() {
     
// Реализация этой возможности использует некую
      // комбинацию реализаций заднего плана:
     
implementation.facility1();
      implementation.facility2
();
  
}
  
  
public void service2() {
     
// Реализация этой возможности использует некую
      // другую комбинацию реализаций заднего плана:
     
implementation.facility2();
      implementation.facility3
();
  
}
  
  
public void service3() {
     
// Реализация этой возможности использует некую
      // другую комбинацию реализаций заднего плана:
     
implementation.facility1();
      implementation.facility2
();
      implementation.facility4
();
  
}
  
  
// Для использования подклассов:
  
protected Implementation getImplementation() {
     
return implementation;
  
}
}

class ClientService1 extends Abstraction {
  
public ClientService1(Implementation imp) {
     
super(imp);
  
}
  
  
public void serviceA() {
     
service1();
      service2
();
  
}
  
  
public void serviceB() {
     
service3();
  
}
}

class ClientService2 extends Abstraction {
  
public ClientService2(Implementation imp) {
     
super(imp);
  
}
  
  
public void serviceC() {
     
service2();
      service3
();
  
}
  
  
public void serviceD() {
     
service1();
      service3
();
  
}
  
  
public void serviceE() {
     
getImplementation().facility3();
  
}
}

interface Implementation {
  
// Эта общая реализация обеспечивается
   // объектами заднего плана, каждый - своим собственным способом.
  
void facility1();
  
  
void facility2();
  
  
void facility3();
  
  
void facility4();
}

class Library1 {
  
public void method1() {
     
System.out.println("Library1.method1()");
  
}
  
  
public void method2() {
     
System.out.println("Library1.method2()");
  
}
}

class Library2 {
  
public void operation1() {
     
System.out.println("Library2.operation1()");
  
}
  
  
public void operation2() {
     
System.out.println("Library2.operation2()");
  
}
  
  
public void operation3() {
     
System.out.println("Library2.operation3()");
  
}
}

class Implementation1 implements Implementation {
  
// Каждая способность делегируется разным библиотекам
   // для того, чтобы получить дивиденды.
  
private Library1 delegate = new Library1();
  
  
public void facility1() {
     
System.out.println("Implementation1.facility1");
      delegate.method1
();
  
}
  
  
public void facility2() {
     
System.out.println("Implementation1.facility2");
      delegate.method2
();
  
}
  
  
public void facility3() {
     
System.out.println("Implementation1.facility3");
      delegate.method2
();
      delegate.method1
();
  
}
  
  
public void facility4() {
     
System.out.println("Implementation1.facility4");
      delegate.method1
();
  
}
}

class Implementation2 implements Implementation {
  
private Library2 delegate = new Library2();
  
  
public void facility1() {
     
System.out.println("Implementation2.facility1");
      delegate.operation1
();
  
}
  
  
public void facility2() {
     
System.out.println("Implementation2.facility2");
      delegate.operation2
();
  
}
  
  
public void facility3() {
     
System.out.println("Implementation2.facility3");
      delegate.operation3
();
  
}
  
  
public void facility4() {
     
System.out.println("Implementation2.facility4");
      delegate.operation1
();
  
}
}

public class BridgeStructure extends TestCase {
  
public void test1() {
     
// Здесь реализация определяется
      // клиентом во время создания:
     
ClientService1 cs1 = new ClientService1(new Implementation1());
      cs1.serviceA
();
      cs1.serviceB
();
  
}
  
  
public void test2() {
     
ClientService1 cs1 = new ClientService1(new Implementation2());
      cs1.serviceA
();
      cs1.serviceB
();
  
}
  
  
public void test3() {
     
ClientService2 cs2 = new ClientService2(new Implementation1());
      cs2.serviceC
();
      cs2.serviceD
();
      cs2.serviceE
();
  
}
  
  
public void test4() {
     
ClientService2 cs2 = new ClientService2(new Implementation2());
      cs2.serviceC
();
      cs2.serviceD
();
      cs2.serviceE
();
  
}
  
  
public static void main(String[] args) {
     
junit.textui.TestRunner.run(BridgeStructure.class);
  
}
}
// /:~

Базовый класс переднего плана обеспечивает операции, используемые для выполнения производными классами переднего плана в терминах методов базового классов заднего плана. Таким образом, любой производный класс заднего плана может быть использован для выполнения операции, необходимой классу переднего плана. Заметьте, что мост возникает в последовательности шагов, каждый из которых обеспечивает уровень абстракции. В данном примере Implementation определяется как интерфейс для того, чтобы подчеркнуть, что вся функциональность реализуется производными классами заднего плана, а не базовым классом заднего плана.

Производные классы заднего плана выполняют операции, определенные в базовом классе, делегируя их объектам (классов Library1 и Library2, в данном случае), которые обычно имеют радикально различающийся интерфейс, но чаще всего предоставляют одинаковую функциональность (это одно из важных требования Моста). В реальности, каждая реализация заднего плана является адаптером к различным библиотекам или инструментам, используемым для реализации определенной функциональности различными способами.

Упражнения

  1. Измените BridgeStructure.java таким образом, чтобы реализация выбиралась с использованием фабрики.
  2. Измените BridgeStructure.java таким образом, чтоби использовалась делегирование вместо наследования на переднем плане. Какие преимущества и недостатки вы видите при использовании делегирования вместо наследования?
  3. Создайте пример Моста с абстракцией, являющейся ассоциативным массивом. Это позволит вам доставать элементы, передавая в него ключевой Объект. Конструктор обеспечивает начальное множество пар ключ-значение, которые помещаются в массив. В течении того времени, как вы получаете элементы, используется массив, как только вы установите новые значения пар ключ-значение, реализация переключится на карту (Map).
  4. Используйте мост совместно с коллекцией из java.util.collections для создания классов стека и очереди, используя ArrayList. После того, как вы запустите систему, добавьте класс двунаправленной очереди. Теперь добавьте LinkedList в качестве реализации. Эти шаги продемонстрируют вам, как Мост позволяет добавлять новые классы переднего и заднего плана в ваш код с минимальными издержками.
  5. Создайте Мост, который обеспечивает соединение между различными сортами программ книгохранилища (вместе с их интерфейсами и форматами данных) и различными банками (которые предоставляют различные виды услуг и интерфейсов).