Pull to refresh

Паттерн проектирования «Приспособленец» / «Flyweight»

Reading time6 min
Views31K
Почитать описание других паттернов.

Проблема


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

Описание


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

Приспособленец использует разделение для эффективной поддержки множества мелких объектов.

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

Приспособленец может быть одновременно использован в разных контекстах. Он словно, «приспосабливается» к контексту своего использования. У клиента, при этом, создается впечатление, что он работает с разными объектами. Хотя, на самом деле, это один и тот-же приспособленец.

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

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


Рассмотрим задачу проектирования векторного редактора. Причем, для получения максимальной гибкости редактора, необходимо спроектировать его вплоть до самых низких уровней — до элементарного примитива — точки. Очевидно, что при прямом подходе в одном рисунке может быть огромное число точек, работать с которыми и хранить их в памяти редактору будет не просто. Поэтому, спроектируем редактор используя паттерн «Приспособленец».

Пусть векторный редактор, умеет работать с ограниченным набором примитивов — Квадрат (Square), Окружность (Circle), Точка (Point) и Изображение (Picture). Все эти примитивы являются приспособленцами. Причем, лишь изображение не является разделяемым приспособленцем, ввиду своей составной природы. Стоит отметить, что изображение в данном случае, пример паттерна «Компоновщик». Остальные же примитивы должны быть разделены на основании своего внутреннего состояния. Для квадрата — размеры сторон (height, width), для окружности — радиус (radius). Точка, хоть и является разделяемым приспособленцем, не имеет внутреннего состояния, ввиду того, что вся необходимая для ее отрисовки информация предается клиентом в т.н. контексте рисования (Context).

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


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


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


/*
* Интерфейс приспособленца
*/
public interface Primitive {
  /*
   * Метод отрисовки примитива с передачей заданного контекста рисования
   */
  public void draw(Context context);
}

/*
* Окружнсоть - разделяемый приспособленец. Внутреннее состояние - радиус
*/
public class Circle implements Primitive {
  private int radius;
  
  public Circle(int radius) {
    this.radius = radius;
  }

  @Override
  public void draw(Context context) { }
}

/*
* Разделяемый приспособленец - Квадрат.
* Внутренее состояние - высота, ширина.
*/
public class Square implements Primitive {
  private int height, width;
  
  public Square(int height, int width) {
    this.height = height;
    this.width = width;
  }

  @Override
  public void draw(Context context) { }
}

/*
* Разделяемый приспособленец - точка
*/
public class Point implements Primitive {
  @Override
  public void draw(Context context) { }
}

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/*
* Не разделяемый приспособленец - изображение.
*/
public class Picture implements Primitive {
  private List<Primitive> childrens;
  
  public Picture(Primitive ...primitives) {
    this.childrens = new LinkedList<Primitive>();
    this.childrens.addAll(Arrays.asList(primitives));
  }

  @Override
  public void draw(Context context) {
    for (Primitive p: childrens) {
      p.draw(context);
    }
  }
}

import java.awt.Color;
/*
* Контекст рисования, передается клиентом примитиву для отрисовки последнего
*/
public final class Context {
  public final int x;
  public final int y;
  public final Color color;
  
  public Context(int x, int y, Color collor) {
    this.x = x;
    this.y = y;
    this.color = collor;
  }
}

import java.util.HashMap;
import java.util.Map;

/*
* Фабрика приспособленцев.
* Реализует разделение оных на основании их внутренних состояний.
*
*/
public abstract class PrimitiveFactory {
  
  private static Point onePoint;
  private static Map<Integer, Circle> circles;
  private static Map<Integer, Square> squares;
  
  static {
    circles = new HashMap<Integer, Circle>();            
    squares = new HashMap<Integer, Square>();
  }
  
  public static synchronized Picture createPicture(Primitive ... childrens) {
    return new Picture(childrens);
  }
  
  public static synchronized Circle createCircle(int radius) {
    if (circles.get(radius) == null) {
      circles.put(radius, new Circle(radius));
    }
    
    return circles.get(radius);
  }
  
  public static synchronized Square createSquare(int height, int width) {
    if (squares.get(height*10+width) == null) {
      squares.put(height*10+width, new Square(height, width));
    }
    
    return squares.get(height*10+width);
  }
  
  public static synchronized Point createPoint() {
    if (onePoint == null) {
      onePoint = new Point();
    }
    
    return onePoint;
  }
}

import java.awt.Color;

// Использование
public class Main {
  public static void main(String[] args) {
    
    Primitive[] primitives = {
        PrimitiveFactory.createPoint(),
        PrimitiveFactory.createCircle(10),
        PrimitiveFactory.createSquare(20, 30),
        PrimitiveFactory.createCircle(20),
        PrimitiveFactory.createCircle(20),
        PrimitiveFactory.createPoint(),
        PrimitiveFactory.createSquare(20, 40),        
    };
    
    Picture picture = PrimitiveFactory.createPicture(primitives);
    Context context = new Context(10, 20, Color.BLUE);
    picture.draw(context);
  }
}


* This source code was highlighted with Source Code Highlighter.


Важно понимать, что применимость данного паттерна определяется, в первую очередь тем, на сколько четко идентифицируются внутреннее и внешнее состояния объектов системы.
Tags:
Hubs:
Total votes 58: ↑37 and ↓21+16
Comments21

Articles