Плавающий вес (Flyweight): слишком много объектов


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

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

В качестве очень простого примера рассмотрим объект DataPoint, который хранит int, float, и id, содержащий номер объекта. Предположим, вам нужно создать миллион таких объектов, а затем манипулировать ими подобным образом:

//: flyweight:ManyObjects.java
class DataPoint {
  
private static int count = 0;
  
private int id = count++;
  
private int i;
  
private float f;
  
  
public int getI() {
     
return i;
  
}
  
  
public void setI(int i) {
     
this.i = i;
  
}
  
  
public float getF() {
     
return f;
  
}
  
  
public void setF(float f) {
     
this.f = f;
  
}
  
  
public String toString() {
     
return "id: " + id + ", i = " + i + ", f = " + f;
  
}
}

public class ManyObjects {
  
static final int size = 1000000;
  
  
public static void main(String[] args) {
     
DataPoint[] array = new DataPoint[size];
     
for (int i = 0; i < array.length; i++)
        
array[i] = new DataPoint();
     
for (int i = 0; i < array.length; i++) {
        
DataPoint dp = array[i];
         dp.setI
(dp.getI() + 1);
         dp.setF
(47.0f);
     
}
     
System.out.println(array[size - 1]);
  
}
}
// /:~

В зависимости от вашего компьютера, эта программа может выполняться несколько секунд. Более сложные объекты и более запутанные операции могут стать причиной увеличения накладных расходов и этот метод станет неприемлемым. Для решения проблемы DataPoint можно снизить количество объектов с миллиона до одного объекта, введя хранитель расширенных данных в DataPoint:

//: flyweight:FlyWeightObjects.java
class ExternalizedData {
  
static final int size = 5000000;
  
static int[] id = new int[size];
  
static int[] i = new int[size];
  
static float[] f = new float[size];
  
static {
     
for (int i = 0; i < size; i++)
        
id[i] = i;
  
}
}

class FlyPoint {
  
private FlyPoint() {
   }
  
  
public static int getI(int obnum) {
     
return ExternalizedData.i[obnum];
  
}
  
  
public static void setI(int obnum, int i) {
     
ExternalizedData.i[obnum] = i;
  
}
  
  
public static float getF(int obnum) {
     
return ExternalizedData.f[obnum];
  
}
  
  
public static void setF(int obnum, float f) {
     
ExternalizedData.f[obnum] = f;
  
}
  
  
public static String str(int obnum) {
     
return "id: " + ExternalizedData.id[obnum] + ", i = "
           
+ ExternalizedData.i[obnum] + ", f = "
           
+ ExternalizedData.f[obnum];
  
}
}

public class FlyWeightObjects {
  
public static void main(String[] args) {
     
for (int i = 0; i < ExternalizedData.size; i++) {
        
FlyPoint.setI(i, FlyPoint.getI(i) + 1);
         FlyPoint.setF
(i, 47.0f);
     
}
     
System.out.println(FlyPoint.str(ExternalizedData.size - 1));
  
}
}
// /:~

Так как все данные теперь хранятся в ExternalizedData, каждый вызов метода FlyPoint должен включать индекс для ExternalizedData. Чтобы быть последовательным, и чтобы напомнить читателю схожесть с явным указателем this при вызове метода, "this индекс" передается в метод в качестве первого аргумента.

Естественно, здесь стоит повторить относительно преждевременной оптимизации. "Сначала заставьте это работать, затем сделайте это быстрее - если это заработало". Также, профайлер - это инструмент, который используется для обнаружения узких мест, а не для гадания.