## Dlaczego Flyweight jest ważny?
W aplikacjach biznesowych często mamy do czynienia z tysiącami podobnych obiektów – produkty w sklepie internetowym, użytkownicy w systemie CRM, czy elementy interfejsu. Bez optymalizacji każdy obiekt konsumuje pamięć na dane, które mogą być współdzielone. Flyweight rozwiązuje ten problem, co przekłada się bezpośrednio na niższe koszty serwerów i lepszą wydajność aplikacji.
Co się nauczysz:
- Jak implementować wzorzec Flyweight w Javie 8/10
- Różnica między stanem intrinsic i extrinsic
- Praktyczne zastosowania w aplikacjach biznesowych
- Optymalizację pamięci na przykładach z życia
- Kiedy stosować, a kiedy unikać tego wzorca
## Czym jest wzorzec Flyweight?
Flyweight to strukturalny wzorzec projektowy, który minimalizuje zużycie pamięci poprzez współdzielenie efektywnie danych wspólnych dla wielu obiektów. Zamiast przechowywać wszystkie dane w każdym obiekcie, wydzielamy:
Stan extrinsic (zewnętrzny) – dane unikalne dla każdego obiektu, przekazywane jako parametry
## Implementacja w Javie – Przykład Praktyczny
Załóżmy, że tworzymy system zarządzania dokumentami w firmie. Mamy tysiące dokumentów, ale tylko kilka typów (PDF, DOC, XLS) z różnymi nazwami i rozmiarami.
### Krok 1: Interfejs Flyweight
// Intrinsic state - współdzielony między wszystkimi dokumentami tego typu public interface DocumentType { void process(String fileName, int fileSize); // extrinsic state jako parametry }
### Krok 2: Konkretne implementacje Flyweight
public class PdfDocumentType implements DocumentType { private final String typeIcon; // intrinsic state private final String defaultViewer; // intrinsic state public PdfDocumentType() { this.typeIcon = "pdf-icon.png"; this.defaultViewer = "Adobe Reader"; System.out.println("Utworzono PdfDocumentType flyweight"); } @Override public void process(String fileName, int fileSize) { System.out.println(String.format( "Przetwarzanie PDF: %s (%d KB) z ikoną %s w %s", fileName, fileSize, typeIcon, defaultViewer )); } } public class WordDocumentType implements DocumentType { private final String typeIcon; private final String defaultViewer; public WordDocumentType() { this.typeIcon = "word-icon.png"; this.defaultViewer = "Microsoft Word"; System.out.println("Utworzono WordDocumentType flyweight"); } @Override public void process(String fileName, int fileSize) { System.out.println(String.format( "Przetwarzanie Word: %s (%d KB) z ikoną %s w %s", fileName, fileSize, typeIcon, defaultViewer )); } }
### Krok 3: Factory dla zarządzania Flyweight’ami
import java.util.HashMap; import java.util.Map; public class DocumentTypeFactory { private static final Mapflyweights = new HashMap<>(); public static DocumentType getDocumentType(String type) { DocumentType flyweight = flyweights.get(type); if (flyweight == null) { switch (type.toLowerCase()) { case "pdf": flyweight = new PdfDocumentType(); break; case "doc": case "docx": flyweight = new WordDocumentType(); break; default: throw new IllegalArgumentException("Nieobsługiwany typ: " + type); } flyweights.put(type, flyweight); } return flyweight; } public static int getCreatedFlyweightsCount() { return flyweights.size(); } }
### Krok 4: Kontekst wykorzystujący Flyweight
public class Document { private final String fileName; // extrinsic state private final int fileSize; // extrinsic state private final DocumentType type; // reference do flyweight public Document(String fileName, int fileSize, String fileType) { this.fileName = fileName; this.fileSize = fileSize; this.type = DocumentTypeFactory.getDocumentType(fileType); } public void process() { type.process(fileName, fileSize); } }
## Praktyczne zastosowanie
public class DocumentManagementSystem { public static void main(String[] args) { Listdocuments = Arrays.asList( new Document("raport-q4.pdf", 2048, "pdf"), new Document("prezentacja.pdf", 5120, "pdf"), new Document("umowa.docx", 512, "docx"), new Document("notatki.doc", 256, "doc"), new Document("analiza.pdf", 3072, "pdf") ); System.out.println("=== Przetwarzanie dokumentów ===" ); documents.forEach(Document::process); System.out.println(String.format( "\\nUtworzono %d flyweight'ów dla %d dokumentów", DocumentTypeFactory.getCreatedFlyweightsCount(), documents.size() )); } }
## Korzyści i wady wzorca
### Korzyści:
– **Dramatyczna redukcja pamięci** – szczególnie przy tysiącach obiektów
– **Lepsza wydajność cache** – mniej obiektów = lepsze wykorzystanie CPU cache
– **Centralne zarządzanie** – wspólne właściwości w jednym miejscu
### Wady:
– **Zwiększona złożoność kodu** – podział na stan intrinsic/extrinsic
– **Możliwe problemy z wielowątkowością** – współdzielone obiekty wymagają synchronizacji
– **Overhead na wywołania metod** – przekazywanie extrinsic state jako parametry
## Kiedy stosować Flyweight?
Stosuj gdy | Unikaj gdy |
---|---|
Tysiące podobnych obiektów | Mało obiektów (<100) |
Duże koszty przechowywania | Obiekty mają głównie stan unikalny |
Stan można podzielić na intrinsic/extrinsic | Potrzebujesz modyfikować stan intrinsic |
Aplikacja ma problemy z pamięcią | Wydajność ważniejsza niż pamięć |
## Zastosowania w prawdziwych projektach
### 1. Systemy GUI
Swing w Javie używa Flyweight dla renderowania znaków – każda litera „A” ma współdzieloną reprezentację graficzną, ale różne pozycje na ekranie.
### 2. Gry komputerowe
Cząsteczki, drzewa, budynki – wszystko co występuje w dużych ilościach z podobnymi właściwościami.
### 3. Systemy e-commerce
Produkty w tej samej kategorii dzielą metadane (ikony, opisy kategorii), różnią się tylko ceną i nazwą.
Flyweight’y powinny być immutable (niezmienne), co czyni je naturalnie thread-safe. Problemy mogą wystąpić w Factory – rozważ synchronizację lub ConcurrentHashMap.
Użyj profilerów jak JProfiler lub VisualVM. Porównaj heap usage przed i po implementacji. Spodziewaj się 70-90% redukcji dla dobrze dopasowanych przypadków.
Tak! Często łączy się z Factory (do zarządzania), Composite (drzewa obiektów) i State (różne zachowania bez zmiany flyweight’a).
Flyweight’y powinny żyć przez cały cykl aplikacji. Uważaj na memory leaks – rozważ WeakHashMap w Factory jeśli flyweight’y mogą być „zapominane”.
Testuj Factory (czy zwraca te same instancje), logikę biznesową (z mockowymi flyweight’ami) i zużycie pamięci (testy integracyjne z wieloma obiektami).
## Przydatne zasoby
– [Oracle Java Documentation – Memory Management](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/)
– [Gang of Four – Design Patterns (1994)](https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)
– [Java Memory Profiling Tools](https://www.oracle.com/java/technologies/javase/jvm-monitoring-tools.html)
– [Effective Java by Joshua Bloch – Item 6: Avoid creating unnecessary objects](https://www.amazon.com/Effective-Java-Joshua-Bloch/dp/0134685997)
🚀 Zadanie dla Ciebie
Zaimplementuj system zarządzania fontami w edytorze tekstu używając Flyweight. Każdy font (Arial, Times New Roman) to flyweight, a rozmiar i kolor to stan extrinsic. Stwórz 1000 fragmentów tekstu z różnymi fontami i zmierz zużycie pamięci przed i po optymalizacji.
Czy implementowałeś już wzorzec Flyweight w swoich projektach? Jakie były efekty optymalizacji pamięci?