Przejdź do głównej zawartości

[OOP] Na czym polega wzorzec projektowy Pyłek?


Wzorzec projektowy Pyłek służy do zmniejszania zużycia pamięci przesz aplikację poprzez współdzielenie przez wiele obiektów wspólnych zasobów.

Pyłek należy do strukturalnych wzorców projektowych. Więcej o rodzajach wzorców można poczytać tutaj [under construction].

Rozważmy przykład prostej gry 2D. W grze tej pojawiają się postacie elfów i krasnoludów. Każdy obiekt postaci zawiera w sobie położenie na mapie, ilość punktów zdrowia oraz teksturę z obrazkiem. Zobaczmy kod:

    abstract class Sprite
    {
        protected abstract string TextureLocation { get; }
        protected Texture _texture;

        public Sprite()
        {
            _texture = Texture.Load(TextureLocation);
        }

        public int X { get; set; }
        public int Y { get; set; }

        public int HealthPoints { get; set; }
    }

    class Elf : Sprite
    {
        protected override string TextureLocation => "textures/elf";
    }

    class Dwarf : Sprite
    {
        protected override string TextureLocation => "textures/dwarf";
    }

    //ciężki obiekt tekstury
    class Texture
    {
        internal static Texture Load(string textureLocation)
        {
            //wyobraźmy sobie, że tu ładujemy teksturę z pamięci
            return new Texture();
        }
    }

Przyjrzyjmy się bliżej obiektowi tekstury. Klasy Elf i Krasnolud zawierają w sobie lokację pliku tekstury. W momencie tworzenia obiektu klas Elf lub Dwarf tekstura jest ładowana z dysku i przypisywana do nowo utworzonego obiektu. Możemy założyć, że każdy obiekt tekstury jest stosunkowo duży. Wyobraźmy sobie, że zaraz po włączeniu gry tworzymy 100 postaci elfów i 100 postaci krasnoludów:

            var sprites = new List<Sprite>();

            for(int i =0; i < 100; ++i)
            {
                var elf = new Elf();
                sprites.Add(elf);

                var dwarf = new Dwarf();
                sprites.Add(dwarf);
            }

W tej niewinnej pętli dzieje się bardzo zła rzecz. Mianowicie ładujemy każdą teksturę z dysku 100 razy. Po przejściu pętli mamy 200 obiektów tekstur w pamięci aplikacji. To bardzo dużo. Zastosujmy wzorzec Pyłek, by zoptymalizować zużycie pamięci. Refactoringiem, który nasuwa się sam, jest wykorzystanie faktu, że każdy elf i każdy krasnolud ma taką samą teksturę. Nie musimy więc ładować każdej z nich 100 razy - wystarczy raz. Każdy obiekt postaci będzie miał swoje własne współrzędne X i Y oraz poziom punktów zdrowia, ale będą się dzielić obiektem tekstury.

Zacznijmy od utworzenia klasy TextureLoader, która wczytywać będzie tekstury. Najpierw przenieśmy do niej istniejący kod:

    class TextureLoader
    {
        public Texture Load(string textureLocation)
        {

            //wyobraźmy sobie, że tu ładujemy teksturę z pamięci
            return new Texture();
        }
    }

    class Texture
    {
    }

Teraz zmieńmy tę klasę w ten sposób, by każdą teksturę ładowała co najwyżej raz:

    class TextureLoader
    {
        private Dictionary<string, Texture> _loadedTextures = new Dictionary<string, Texture>();

        public Texture Load(string textureLocation)
        {
            if(!_loadedTextures.ContainsKey(textureLocation))
            {
                //wyobraźmy sobie, że tu ładujemy teksturę z pamięci
                _loadedTextures[textureLocation] = new Texture(); 
            }

            return _loadedTextures[textureLocation];
        }
    }

Zobaczmy, co się zmieniło. Metoda Load nie będzie wczytywać tekstury z pamięci, jeśli została już ona wczytana. Wczytane tekstury zapamiętywane są w prywatnym słowniku _loadedTextures. Z metody zwracany jest wpis z tego słownika. Oznacza to, że każdy obiekt klasy Elf będzie zawierał w sobie referencję do tego samego obiektu tekstury. Dzięki temu w pamięci będziemy mieć dwa obiekty tekstur zamiast dwustu.

Wzorzec Pyłek pozwolił nam na stukrotne zmniejszenie liczby przechowywanych w pamięci obiektów tekstur.

W poście tym korzystałam z:

  • http://devman.pl/pl/techniki/wzorce-projektowe-pylekflyweight/
  • https://www.tutorialspoint.com/design_pattern/flyweight_pattern.htm


Komentarze

  1. Te nazwy TextureLoader i Load sugerują, że dalej dzieje się coś obciążającego zasoby. Może lepiej by je było zmienić na TextureSource i Get?

    Poza tym częściej spotykam się z implementacją, w ramach której poszczególne „pyłki” posiadają referencję do wspólnego dla nich ciężkiego obiektu, a nie uzyskują go z innej klasy na podstawie jakichś lżejszych danych (w tym przypadku na podstawie napisu). Oczywiście przykład tutaj zamieszczony robi to co trzeba i oszczędza zasoby, ale osobie wdrażającej ten wzorzec polecam zastanowić się nad bardziej klasyczną implementacją.

    Paweł Ś.

    OdpowiedzUsuń

Prześlij komentarz