Pull to refresh

Паттерн проектирования «Заместитель» / «Proxy»

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

Проблема


Необходимо контролировать доступ к объекту, не изменяя при этом поведение клиента.

Описание


При проектировании сложных систем, достаточно часто возникает необходимость обеспечить контролируемый доступ к определенным объектам системы. Мотивацией для этого служит ряд приобретаемых преимуществ. Таких как, ленивая инициализация по требованию для «громоздких» объектов, подсчет количества ссылок на объект и т.д. и т.п. Однако, не всегда потребность в контролируемом доступе к объекту базируется только на преимуществах. Как правило, сложность процессов реального мира, ограничения вычислительных ресурсов просто не оставляют проектировщику выбора, нежели как воспользоваться паттерном «Заместитель» («Сурогат»).

Идея паттерна «Заместитель» заключается в предоставлении клиенту другого объекта (заместителя), взамен объекту с контролируемым доступом. При этом, объект-заместитель, реализует тот-же интерфейс, что и оригинальный объект, в результате чего, поведение клиента не требует изменений. Иными словами, клиент взаимодействует с заместителем ровно как с оригинальным объектом посредством единого интерфейса. Клиент, так же, не делает предположений о том работает ли он с реальным объектом или его заместителем. Контролирование доступа к объекту, при этом, достигается за счет использования ссылки на него в заместителе, благодаря которой заместитель переадресовывает внешние вызовы контролируемому объекту, возможно сопровождая их дополнительными операциями.

Такой подход позволяет неявным для клиента образом контролировать доступ к объекту.

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


Рассмотрим задачу реализации игры «Сапер». Будем полагать, что для ячеек (Cell), которые бывают заминированными (Mine) и пустыми (Empty), существуют некоторые громоздкие графические изображения. Для заминированной ячейки — мина, для пустой ячейки изображение с количеством мин в соседних клетках. При этом, само изображение хранится в каждой ячейке и инстанциируется в момент ее создания. Игрок же, видит изображение ячейки только после ее открытия (операция open()). Поэтому, было бы разумным инстанциировать ячейки в тот момент, когда игрок пытается их открыть, чтобы сократить расходы общей памяти для хранения изображений. Однако, такой подход тут применить нельзя. Дело в том, что до операции open() у каждой ячейки вызываются операции getTop(), getLeft() для получения координат ячейки. Но если ячейка еще не будет создана, о каких ее координатах может идти речь?

Использование паттерна прокси решает данную проблему. Вместо оригинальных объектов клиент будет использовать их земестители (MineProxy, EmptyProxy). При этом становится возможной ленивая инициализация ячеек, ввиду того, что оригинальный объект создается лишь при вызове операции open() у прокси а на запросы о получении координат (getTop(), getLeft()) прокси отвечает самостоятельно, по крайней мере до момента создания оригинального объекта.

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




Реализация на Java


/**
* Абстрактный класс ячейки минного поля
*/
public abstract class Cell {
  public static final int OPENED = 0;
  public static final int CLOSED = 1;
  
  protected int status;

  protected int left, top;

  public Cell(int left, int top) {
    super();
    
    this.left = left;
    this.top = top;
    this.status = Cell.CLOSED;
  }
  
  /**
   * Открыть данную ячейку. Будем считать, что в этой операции происходит некоторая
   * ресурсоемкая операция. Например, загрузка изображения, для отображения содержимого ячейки.
   */
  public void open() {  
    this.status = Cell.OPENED;
  }

  public int getLeft() {
    return left;
  }
  
  public int getTop() {
    return top;
  }
  
  public int getStatus() {
    return status;
  }

  /**
   * Единственная абстрактная операция, возвращаяет количество очков за открытие данной ячейки.
   */
  public abstract int getPoints();  
}

/**
* Уточнение ячейки минного поля, в качестве пустой ячейки
*/
public class Empty extends Cell {
  
  public Empty(int left, int top) {
    super(left, top);
    
    // загружаем тяжелое изображение пустой ячейки.
  }

  @Override
  public int getPoints() {
    return 10;  // 10 очков за открытую пустую ячейку
  }
}

/**
* Уточнение ячейки, как ячейки с миной.
*/
public class Mine extends Cell {
  
  public Mine(int left, int top) {
    super(left, top);
    
    // загружаем тяжелое изображение ячейки c миной
  }

  @Override
  public int getPoints() {
    return 100;   // 100 очков за открытую мину
  }
}

/**
* Прокси для пустой ячейки
*/
public class EmptyProxy extends Cell {
  private Empty proxy; // ссылка на пустую ячейку

  public EmptyProxy(int left, int top) {
    super(left, top);
    this.proxy = null;
  }

  /**
   * Ленивая инициализация пустой ячейки
   */
  @Override
  public void open() {
    if (proxy == null) {
      proxy = new Empty(left, top);
    }
    
    proxy.open();
  }

  @Override
  public int getLeft() {
    if (proxy == null) {
      return left;
    } else {
      return proxy.getLeft();
    }
  
  }
  
  @Override
  public int getTop() {
    if (proxy == null) {
      return top;
    } else {
      return proxy.getTop();
    }
  }

  @Override
  public int getStatus() {
    if (proxy == null) {
      return status;
    } else {
      return proxy.getStatus();
    }
  }

  @Override
  public int getPoints() {
    if (proxy == null) {
      return 10;
    } else {
      return proxy.getPoints();
    }
  }
}

/**
* Прокси для ячейки с миной
*/
public class MineProxy extends Cell {
  private Mine proxy;

  public MineProxy(int left, int top) {
    super(left, top);
    
    this.proxy = null;
  }
  
  /**
   * Ленивая инициализация ячейки с миной
   */
  @Override
  public void open() {
    if (proxy == null) {
      proxy = new Mine(left, top);
    }
    
    proxy.open();
  }

  @Override
  public int getLeft() {
    if (proxy == null) {
      return left;
    } else {
      return proxy.getLeft();
    }
  
  }
  
  @Override
  public int getTop() {
    if (proxy == null) {
      return top;
    } else {
      return proxy.getTop();
    }
  }

  @Override
  public int getStatus() {
    if (proxy == null) {
      return status;
    } else {
      return proxy.getStatus();
    }
  }

  @Override
  public int getPoints() {
    if (proxy == null) {
      return 100;
    } else {
      return proxy.getPoints();
    }
  }
}

/**
* Использование
*/
public class Main {
  public static void main(String[] args) {
    Cell cells[][] = new Cell[10][10];
    
    for (int i=0; i<10; i++) {
      for (int j=0; j<10; j++) {
        
        if (i+j % 2 == 0) {
          cells[i][j] = new MineProxy(i, j);
        } else {
          cells[i][j] = new EmptyProxy(i, j);
        }
      }
    }
    
    for (int i=0; i<10; i++) {
      for (int j=0; j<10; j++) {
        cells[i][j].open();
      }
    }
  }
}

* This source code was highlighted with Source Code Highlighter.
Tags:
Hubs:
Total votes 52: ↑39 and ↓13 +26
Views 40K
Comments Comments 11