#1 Wzorce projektowe: Flyweight (Pyłek)

designPatternArt

Postanowiłam zacząć serię wpisów o wzorcach projektowych. Dążymy przecież do tworzenia kodu idealnego, tworząc go zastanawiamy się czy można go jakoś uprościć, zmodyfikować tak, żeby działał bardziej wydajnie oraz dało się go rozbudować w łatwy sposób. Dlaczego nie skorzystać z rozwiązań, które już istnieją? Pierwszy na tapecie flyweight, czyli mówiąc po polsku pyłek. Jest to wzorzec strukturalny, który przydaje się w aplikacjach, które korzystają z większej liczby identycznych obiektów.

W rozwiązaniu tego wzorca tworzymy tylko unikatowe obiekty. W momencie tworzenia nowego obiektu, sprawdzane jest czy egzemplarz o takich samych parametrach istnieje już w pamięci. Jeśli tak, to pobieramy go, a jeśli nie, tworzymy nowy. Obiekty te są immutable (niezmienne), czyli przygotowujemy klasę tak aby nie dało się jej zmodyfikować. Natomiast gdy zajdzie potrzeba zmiany danych wtedy tworzona jest nową instancja. Dzięki wykorzystaniu pyłku zmniejszamy ilość wykorzystywanej pamięci. Przykładem zastosowania tego wzorca jest obsługa obiektów typu String w JRE.

 

Spróbujmy teraz zaimplementować flyweight.  Stwórzmy klasę Relation.

final public class Relation {
    final private String description;

    public Relation(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

}

Następnie tworzymy klasę FlyweightFactory, w której będzie cała logika omawianego wzorca. W metodzie createRelation sprawdzamy, czy obiekt o podanym opisie już istnieje. Jeśli nie, to tworzymy ten obiekt i wyświetlamy komunikat użytkownikowi. W przeciwnym wypadku pobieramy obiekt z tym parametrem (w naszym przypadku z HashMapy).

public class FlyweightFactory {
    private Map<String, Relation> relations = Collections.synchronizedMap(new HashMap());

    public synchronized Relation createRelation(String description){
        Relation relation = (Relation)relations.get(description);
        if(relation == null) {
            relation = new Relation(description);
            relations.put(description, relation);
            System.out.println("Creating new relation: " + relation.getDescription());
        }
        return relation;
    }
}

 

Na koniec wywołujemy w pętli tworzenie obiektów w naszej fabryce pyłków.

public class Test {
    public static void main(String[] args) throws InterruptedException {
        FlyweightFactory flyweightFactory = new FlyweightFactory();
        for(int i = 0; i < 100; i++) {
            flyweightFactory.createRelation("Aga+Krzys");
        }
    }
}

 

A poniżej wynik naszej aplikacji. Widzimy, że pomimo 100 wywołań tworzenia obiektu, obiekt tworzy się tylko raz. W pozostałych 99 przypadkach pobierany jest utworzony już obiekt.

Creating new relation: Aga+Krzys
Process finished with exit code 0
  • Tomek

    Wszystko fajnie, ale:
    1. jeżeli mówisz o obiektach immutable, to takimi je pokaż w kodzie
    2. twój kod nie jest bezpieczny wielowątkowo – wypadało by uzupełnić

    Powodzenia z następnymi wzorcami.

    • Agnieszka Pieszczek

      Witaj,
      Dzięki za cenne uwagi. Celem mojego wpisu było pokazanie koncepcji samego wzorca (co mam nadzieję udało mi się przekazać). Rzeczywiście przykład nie był do końca dopracowany. Zmodyfikowałam kod z myślą, iż sam obiekt immutable jest Thread Safe natomiast używanie jego nie. Dlatego w metodzie, która korzysta z tego obiektu dodałam synchronized.

      • Lukasz

        Można dać ConcurrentHashMap i skorzystać z putIfAbsent zamiast bardzo drogo synchronizować cały blok kodu.

  • Opis nie do końca oddaje istotę wzorca flyweight – jedną z głównych cech jest właśnie to, że obiekt nie jest immutable, przez co można go wykorzystać wielokrotnie bez rezerwowania nowej przestrzeni w pamięci (opis np tutaj: https://pl.wikipedia.org/wiki/Py%C5%82ek_(wzorzec_projektowy) oraz https://www.tutorialspoint.com/design_pattern/flyweight_pattern.htm). Obiekty mogą mieć pewne cechy stałe, niezmienne pomiędzy instancjami, ale często sam typ (i związane z nim zachowania) uznawane są za taki zbiór cech wspólnych.
    Przedstawiona w opisie implementacja to najprostszy cache, a nie implementacja wzorca pyłek.

    • Agnieszka Pieszczek

      Skoro już powołujesz się na “cenne źródło wiedzy” jakim jest Wikipedia to polecam zajrzeć na angielską wersję TUTAJ,a konkretnie fragment: “Flyweight objects must be immutable.”. Twoje pierwsze zdanie jest sprzeczne z tym i każdym innym źródłem informacji na temat tego wzorca. Flyweight jest specyficznym cachem z obiektami immutable. Zachęcam do przeczytania książki Bandy Czworga.

  • Kamil Tomasz Jarmusik

    Tu chodzi o to żeby nie tworzyć obiektu jeśli jest w kolekcji, a metoda putIfAbsent wymusza utworzenie nowego obiektu.

  • Krystian Antkowiak

    to w ogole nie jest ten wzorzec…

  • Arkadiusz Plich

    A czy przypadkiem ideą tego wzorca nie jest wydzielenie do osobnej klasy pewnych statycznych i niezmiennych atrybutów i pobieranie ich w momencie np. powstawania nowego obiektu zamiast w każdym duplikować te same pola n razy?

  • James Beam

    Dziękuję za artykuł! Widziałeś nowy katalog polskich wzorców projektowych? https://refactoring.guru/pl/design-patterns