Pull to refresh

Паттерн проектирования «Адаптер» / «Adapter»

Perfect code *
Почитать описание других паттернов.

Пожалуй, начнем.
Для начала, поясню несколько организационных вопросов.
  • Описание того или иного паттерна, является моей сугубо личной интерпретацией теоретического и практического материала, собранного из книг и интернет-статей;
  • При построении UML-диаграмм, я буду использовать свободный редактор от компании astah, ввиду его простоты и независимости от конкретного языка или среды. При этом, диаграммы не будут отличатся изобилием картинок и цветов, но будут ясно отображать суть паттерна;
  • При реализации практических примеров, язык программирования будет выбираться совершенно случайно. Однако, я буду стараться подбирать те языковые средства, на которых данный паттерн реализуется не тривиально;
  • Каждый мой пост, будет содержать как минимум 5 секций — Проблема, Описание патерна, Практическая задача, Диаграмма классов и Реализация;
  • Если Вы, с чем-то не согласны или у Вас есть дополнения к материалу, изложенному мной — я буду рад их почитать в комментариях. Однако, помните — я тоже изучаю паттерны вместе с Вами :)


Интро


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

Не так давно, когда я был на младших курсах, на мое высказывание «Да мы уже умеем писать программы!», мой коллега сказал — «Максимум, что вы умеет писать — это алгоритмы, но не программы.» Эти его слова я вспоминаю до сих пор с улыбкой на лице. Он был совершенно прав, все чему нас научили за 3 года (я тогда был на третьем курсе) — реализация базовых алгоритмов из Кормена/Кнута. Мы действительно могли писать эти алгоритмы, могли писать функции, процедуры реализующие их. Даже могли написать класс на C++ или Java в котором описать все логику работы с данным классом объектов. Но когда таких классов становилось два или даже три :) начинались проблемы. И при любой попытке написания какой-либо «программы» начиналось изобретение велосипеда (я думаю, что каждый кто читает этот пост, сам изобрел несколько таких велосипедов). Тогда, я начал подозревать, что должно быть что-то, какая теоретическая база, аппарат, механизм, называйте это как хотите, которая в принципе расскажет мне — «как писать программы».

Оказалось, такая база есть — это паттерны проектирования. По большому счету, паттерн — это типичное решение общих проблем проектирования. Паттерны решают ряд основных проблем, связанных с программированием/проектированием. Это — изобретение велосипеда, проблема повторного использования кода, проблема сопровождения кода.

Безусловно, шаблоны проектирования если не решают, то упрощают решение данных проблем. Так, разработчик или проектировщик, зная хотя бы базовый набор паттернов, использует их в замену придумываемым велосипедам. Повторное использование кода становится более прозрачное и оправданное. А сопровождать код, написанный с использованием паттернов как минимум понятно и не затруднительно.

Теперь, мне кажется можно перейти к рассмотрению конкретного паттерна — «Адаптера» («Adapter»).

Проблема


Обеспечить взаимодействие объектов с различными интерфейсами. Адаптировать, а не переписывать существующий код к требуемому интерфейсу.

Описание


Паттерн «Адаптер», на самом деле, является одним из немногих, который программисты применяют на практике, сами того не осознавая. Адаптер можно найти, пожалуй в любой современной программой системе — будь это простое приложение или, например, Java API.

Взглянем более детально на проблему, для понимая того как должен выглядеть Адаптер. Проблема, опять-таки, заключается в повторном использовании кода. Иными словами, есть клиент, который умеет работать с некоторым интерфейсом, назовем его клиентским. Есть класс, который, в принципе, делает то, что нужно клиенту но не реализует клиентский интерфейс. Безусловно, программирование нового класса довольно бессмысленная трата времени и ресурсов. Проще адаптировать уже существующий код к виду, пригодному для использования клиентом. Для этого и существует адаптер. Причем, разделяют два вида адаптеров — Object Adapter (адаптер на уровне объекта) и Class Adapter (адаптер на уровне класса). Мы рассмотри оба, но по порядку.

Практическая задача



Для примера, рассмотрим простую ситуацию. Есть некоторый класс — SequenceGenerator, генерирующий последовательности целых чисел, по определенному закону — это и есть наш клиент. Есть интерфейс — Generator, который использует клиент непосредственно для генерации каждого отдельного элемента последовательности — это наш клиентский интерфейс. К тому-же, есть класс RandomGenerator, который уже умеет генерировать случайные числа. Конечно, SequenceGenerator не может использовать RandomGenerator для генерации элементов, потому что он не соответствует клиентскому интерфейсу. Наша задача — написать адаптер (двумя способами) RandomGenerator к SequenceGenerator.

Диаграммы классов



Object Adapter




Class Adapter




Итак, имея диаграммы классов давайте поговорим об отличиях между адаптером на уровне объекта и адаптером на уровне класса. На самом деле различия видны уже из названия. В первом случае, адаптируемый объект (RandomGenerator) является полем (ссылкой) в классе адаптера (RandomGeneratorAdapter), во втором же он и является адаптером за счет использования механизма наследования. В реальных проектах, рекомендуется использовать Object Adapter, за счет его меньшей связности с адаптируемым объектом.

Реализация



Рассмотрим реализацию поставленной задачи. Object Adapter я реализовывал на С++, Class Adapter на Java.

Object Adapter


class Generator {
public:
  virtual int next() = 0;

};

class SequenceGenerator {
private:
    Generator *generator;
protected:
public:
  SequenceGenerator(Generator& generator);

  int* generate(int length);
};

SequenceGenerator::SequenceGenerator(Generator& generator) {
  this->generator = &generator;
}

int* SequenceGenerator::generate(int length) {
  int *ret = new int[length];
  
  for (int i=0; i<length; i++) {
    ret[i] = this->generator->next();
  }

  return ret;
}

class RandomGenerator {
public:
  inline int getRandomNumber() { return 4; }; // It`s really random number.

};

class RandomGeneratorAdapter : public Generator {
private:
  RandomGenerator *adaptee;
public:

  RandomGeneratorAdapter(RandomGenerator& adaptee);

  virtual int next();

};

RandomGeneratorAdapter::RandomGeneratorAdapter(RandomGenerator& adaptee) {
  this->adaptee = &adaptee;
}

int RandomGeneratorAdapter::next() {
  return this->adaptee->getRandomNumber();
}


// Использование

int main(int argc, char *argv[]) {

  RandomGenerator rgenerator;
  RandomGeneratorAdapter adapter(rgenerator);
  SequenceGenerator sgenerator(adapter);

  const int SIZE = 10;

  int *seq = sgenerator.generate(SIZE);

  for (int i=0; i<SIZE; i++) {
    cout << seq[i] << " ";
  }
  
}


* This source code was highlighted with Source Code Highlighter.


Class Adapter

Классическая реализация паттерна Class Adapter подразумевает использование множественного наследования. В Java, можно воспользоваться имплементацией интерфейсов. На мой взгляд, это даже как-то корректнее.

public interface Generator {

  public int next();

}

public class SequenceGenerator {
  
  private Generator generator;

  public SequenceGenerator(Generator generator) {
    super();
    this.generator = generator;
  }

  public int[] generate(int length) {
    int ret[] = new int[length];
    
    for (int i=0; i<length; i++) {
      ret[i] = generator.next();
    }
    
    return ret;
  }
}

public class RandomGenerator {
  
  public int getRandomNumber() {
    return 4;    
  }
}

public class RandomGeneratorAdapter extends RandomGenerator implements Generator {

  @Override
  public int next() {
    return getRandomNumber();
  }
  
}

// Использование
public class Main {

  public static void main(String[] args) {
    
    RandomGeneratorAdapter adapter = new RandomGeneratorAdapter();    
    SequenceGenerator generator = new SequenceGenerator(adapter);
    
    for (int i: generator.generate(10)) {
      System.out.print(i + " ");
    }
  }
}


* This source code was highlighted with Source Code Highlighter.


На этом все. Жду Ваших отзывов в комментариях.

Tags:
Hubs:
Total votes 150: ↑126 and ↓24 +102
Views 78K
Comments Comments 62