Pyłek

Wzorce projektowe

  |   7 min czytania

Zastosowanie

Pyłek (ang. Flyweight) (wzorzec strukturalny) minimalizuje wykorzystanie pamięci w procesie tworzenia skomplikowanych obiektów poprzez współdzielenie istniejących referencji do obiektów lub ich elementów. Zamiast tworzyć całkowicie od nowa kolejny egzemplarz wykorzystuje się już istniejące elementy składowe obiektu, które rzadko się zmieniają, a ich stworzenie konsumuje dużo pamięci. Wzorzec ten może przypominać rozbudowany Singleton, jednakże główna różnica polega na tym, że Pyłek zawiera referencję do wielu niezmiennych obiektów różniących się stanem.

Ograniczenia

Ze względu na przeszukiwanie kolekcji istniejących już egzemplarzy oraz wykonanie niezbędnych operacji kontekstu wzrasta zużycie procesora. W przypadku wielu dodatkowych klasy zwiększa się poziom skomplikowania kodu.

Użycie

Wzorca Pyłek stosuje się tam, gdzie zachodzi potrzeba minimalizacji zużycia pamięci RAM w trakcie tworzenia kosztownych obiektów występujących w wielu egzemplarzach, które mogą być sterowane w ten sam sposób.

Implementacja

Klasy, których instancje występują w wielu egzemplarzach implementują lub rozszerzają klasę abstrakcyjną Flyweight. Klasa FlyweightFactory na podstawie przechowywanych niepowtarzających się obiektów, generuje instancje klasy Flyweight.

Pyłek diagram

Poniższy listing przedstawia implementację wzorca Pyłek dla obiektów należących do klasy ConcreteFlyweight.

public class FlyweightFactor {

    //if there are more types of flyweight classes than use Factory method

    public static Map<String, Flyweight> flyweightTypes = new HashMap<>();

    public static Flyweight getFlyweight(String type, int value) {
        Flyweight flyweight = flyweightTypes.get(type);
        if(result == null) {
            result = new ConcreteFlyweight(name, value);
            flyweightTypes.put(name, result);
        }
        else {
            result.setValue(value);
        }
        return result;
    }
}

public class ConcreteFlyweight implements Flyweight {

    private String type;
    private int value;

    public ConcreteFlyweight(String type, int value) {
        //creation of object is consuming resources
        this.type = type;
        this.value = value;
    }

    @Override
    public void operation() {
        //do specific operation for ConcreteFlyweight
    }

    public void setValue(int value) {
        this.value = value;
    }

    //other setters and getters
}

//implement more concrete flyweight's classes if needed

//interface can by replaced by abstract class if more common classes exist
interface Flyweight {

    void operation();
}

Klient generuje 100 obiektów typu Flyweight o losowych wartościach. Tworzone obiekty występują w 3 wariantach w związku z czym FlyweightFactory przechowuje referencje tylko do 3 obiektów i na ich bazie tworzy pozostałe.

//3 types of items can be created so reuse those items instead of creation new one
String types[] = { "Type1", "Type2", "Type3" };

//generate a lot of items for example in loop with random values
for(int i=0; i<100; i++) {
    //get random type and value
    String type = randomType(types);
    value = randomValue(0, 100);
    //get flyweight item from factory and do operation
    Flyweight flyweight = FlyweightFactory.getFlyweight(type, value);
    flyweight.operation();
}
//factory created only 3 items: Type1, Type2, Type3 and rest of 97 have been reused

Przykład

Gra strategiczna Farmer w losowy sposób generuje roślinność na mapie (typ oraz położenie). W związku ze skończoną liczbą wariantów tworzonych roślin i wysokim zużyciem zasobów generowania obiektów graficznych, moduł tworzenia planszy wymaga optymalizacj zużycia pamięci RAM. W tym celu wykorzystuje wzorzec Pyłek.

public class DetailsFactory {

    private static Map<Type, Details> types  = new HashMap<>();

    public static Details getPlantDetails(Type type) {
        Details item = types.get(type);
        if(item == null) {
            item = new Details(type);
            types.put(type, item);
        }
        return item;
    }
}

public class Tree extends Plant {

    @Override
    public void draw() {
        //draw concrete type of the tree in the point in specific way
    }
}

public class Flower extends Plant {

    @Override
    public void draw() {
        //draw concrete type of the flower in the point in specific way
    }
}

public class Details {

    private Type type;
    private Texture texture;
    private String data;

    public Details(Type type) {
        this.type = type;
        loadTexture();
        loadData();
        this.texture = texture;
        this.data = data;
    }

    private void loadTexture() {
        //load texture based on the type
    }

    private void loadData() {
        //load additional data based on the type
    }

    //some methods
}

public abstract class Plant {
    
    private Point point;
    private Details details; //consume a lot of memory

    public Plant(Point point, Details details) {
        this.point = point;
        this.details = details;
    }

    public abstract void draw();
}

Klient w pseudolosowy sposób wyznacza rodzaj rośliny oraz punkt w przestrzeni mapy w którym zostanie ona wygenerowana.

//user start new game so the board is creating
//a lot of trees are in the board, so create all types of the tree and reuse them

for(int i=0; i<1000; i++) {
    //random type of the plant and get it from cache factory
    Type type = randomType();
    Details details = DetailsFactory.getTreeType(type);

    //random position of the tree on the board
    Position position = randomPosition();

    //create plant based on the type and position
    //Factory method can be used, but for simplify just if-else
    Plant plant;
    if(type == Type.OAK || type == Type.PINE || Type.CHESTNUT) {
        plant = new Tree(point, data);
    }
    else if(type == Type.ROSE || Type.TULIP || Type.HEATHER) {
        plant = new Flower(point, data);
    }

    //put the plant on the map
    plant.draw();
}

Biblioteki

Metody klas osłonowych (ze standardowej biblioteki Java) takie jak np.: Integer.valueOf(int), Boolean.valueOf(boolean) itp. są przykładem realizacji wzorca Pyłek.