Skip to content

Flyweight

Let's assume that the application we are working on is installed directly on the client's server. After some time, the client informs us that over time the application stops working due to insufficient RAM available. After analyzing the problem, it turns out that there were many (veeery many) objects of the same type on the stack just before the moment when the JVM stopped working.

Construction

In order to solve the problem and reduce the application memory, we can use the flyweight structural pattern. Having a large number of objects of the same type, we can try to isolate the common part (or several parts) of these objects.

Then we modify each of the original objects as follows:

  • rather than having a separate copy of the "common" fields, it has a reference to the object that represents that common part. flyweight

Example

The pattern construction does not impose the method of creating the object. The example uses a simple factory. The following example creates 1000 objects of type Car. Each of these objects has several fields (unique for each car, such as VIN), including the engine field of theEngine type, which was identified as a common repeating part of many cars. The flyweight pattern was used for this field. We create only 4 Engine objects for 1000Car objects.

public enum EngineType {
  DIESEL, GASOLINE, ELECTRIC
}
@Data
public class Engine {

  public static int instances = 0;

  private String identifier;
  private Double volume;
  private EngineType engineType;

  public Engine(final String identifier, final Double volume, final EngineType engineType) {
    instances++;
    this.identifier = identifier;
    this.volume = volume;
    this.engineType = engineType;
  }
}
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Car {
  private String producer;
  private String VIN;
  private String version;
  private String modelName;
  private Engine engine;
}
import java.util.List;

public class CarFactory {

  private static final List<Engine> ENGINES = List.of(
      new Engine("polo", 1.6D, EngineType.DIESEL),
      new Engine("poloGTI", 2.0D, EngineType.GASOLINE),
      new Engine("golf", 1.5, EngineType.GASOLINE),
      new Engine("e", 0D, EngineType.ELECTRIC)
  );

  private static Engine findEngineByKey(final String key) {
    return ENGINES.stream()
        .filter(engine -> engine.getIdentifier().equals(key))
        .findFirst()
        .orElseThrow();
  }

  public Car createVWPolo(final String vin, final String version) {
    return new Car("VW", vin, version, "Polo", findEngineByKey("polo"));
  }

  public Car createVWPoloGTI(final String vin, final String version) {
    return new Car("VW", vin, version, "poloGTI", findEngineByKey("poloGTI"));
  }

  public Car createVWGolf(final String vin, final String version) {
    return new Car("VW", vin, version, "Golf", findEngineByKey("golf"));
  }

  public Car createSkodaCityGo(final String vin, final String version) {
    return new Car("Skoda", vin, version, "CityGO", findEngineByKey("e"));
  }
}
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;

public class FlyweightUsage {

  private static final int ITERATIONS = 1000;
  private static final int AVAILABLE_CAR_TYPES = 4;
  private static final List<String> VERSIONS = List.of("BASIC", "PREMIUM", "COMFORT");

  public static void main(String[] args) {
    final CarFactory carFactory = new CarFactory();

    final List<Car> producedCars = new ArrayList<>(ITERATIONS);

    for (int idx = 0; idx < ITERATIONS; idx++) {
      final int carType = new Random().nextInt(AVAILABLE_CAR_TYPES);
      switch (carType) {
        case 0:
          producedCars.add(carFactory.createSkodaCityGo(UUID.randomUUID().toString(), getVersion()));
          break;
        case 1:
          producedCars.add(carFactory.createVWGolf(UUID.randomUUID().toString(), getVersion()));
          break;
        case 2:
          producedCars.add(carFactory.createVWPolo(UUID.randomUUID().toString(), getVersion()));
          break;
        case 3:
          producedCars.add(carFactory.createVWPoloGTI(UUID.randomUUID().toString(), getVersion()));
          break;
      }
    }

    System.out.println("I created " + producedCars.size() + " cars, but only " + Engine.instances + " references to Engine object");
    // it will print: 'I created 1000 cars, but only 4 references to Engine object'
  }

  private static String getVersion() {
    return VERSIONS.get(new Random().nextInt(VERSIONS.size()));
  }
}

When using flyweight, we must remember that by changing the value of a common object's field, we are changing it in many objects.