1. Co to jest wzorzec Flyweight

1.1 Definicja i Koncept

Wzorzec Flyweight to wzorzec projektowy strukturalny, którego głównym celem jest minimalizacja liczby współdzielonych obiektów, co pozwala zaoszczędzić pamięć i poprawić wydajność. Wzorzec Flyweight zmniejsza tworzenie i zużycie obiektów poprzez współdzielenie tych samych lub podobnych obiektów, osiągając optymalizację wydajności.

1.2 Różnice w porównaniu z innymi wzorcami projektowymi

W porównaniu z innymi wzorcami projektowymi, wzorzec Flyweight skupia się głównie na współdzieleniu i ponownym użyciu obiektów. Dzieli obiekty na współdzielone stany wewnętrzne i niewspółdzielone stany zewnętrzne. Poprzez współdzielenie stanów wewnętrznych zmniejsza tworzenie i zużycie pamięci obiektów, poprawiając wydajność systemu.

2. Charakterystyka i Zalety wzorca Flyweight

Główne cechy i zalety wzorca Flyweight obejmują:

  • Zminimalizowane użycie pamięci: Redukuje zużycie pamięci poprzez współdzielenie tych samych lub podobnych obiektów.
  • Poprawiona wydajność: Zmniejsza tworzenie i niszczenie obiektów, przyspieszając działanie systemu.
  • Wsparcie dla dużej liczby drobnoziarnistych obiektów: Może tworzyć dużą liczbę drobnoziarnistych obiektów, nie zajmując przy tym zbyt dużo miejsca w pamięci.
  • Uproszczona struktura systemu: Poprzez oddzielenie stanów wewnętrznych i zewnętrznych obiektów, upraszcza strukturę i złożoność systemu.

3. Przykłady praktycznych zastosowań wzorca Flyweight

Wzorzec Flyweight można zastosować w następujących scenariuszach:

  • Obiekty cząsteczkowe w grach: Właściwości każdego obiektu cząsteczki można podzielić na stany wewnętrzne i zewnętrzne, a obiekty cząsteczek o tych samych właściwościach mogą być współdzielone.
  • Obiekty połączeń w serwerach sieciowych: Właściwości obiektów połączeń można podzielić na stany wewnętrzne i zewnętrzne, a istniejące obiekty połączeń mogą być ponownie wykorzystane przed ich odzyskaniem.

4. Implementacja wzorca Flyweight w języku Golang

4.1 Diagram klas UML

Diagram klas UML wzorca Flyweight w języku Golang wygląda następująco:

Wzorzec Flyweight w języku Golang

4.2 Wprowadzenie przykładu

W tym przykładzie stworzymy edytor graficzny oparty na wzorcu Flyweight, zawierający koła różnych kolorów i redukując zużycie pamięci poprzez współdzielenie obiektów okręgów o tym samym kolorze.

4.3 Kroki implementacji

4.3.1 Utwórz interfejs Flyweight i klasę ConcreteFlyweight

Najpierw musimy utworzyć interfejs Flyweight w celu zdefiniowania operacji obiektów współdzielonych. Następnie można utworzyć klasę ConcreteFlyweight, aby zaimplementować interfejs Flyweight i zawierać stany wewnętrzne.

// Flyweight definiuje interfejs obiektów flyweight
type Flyweight interface {
	Operation(extrinsicState string)
}

// ConcreteFlyweight reprezentuje konkretne obiekty flyweight, implementując interfejs Flyweight
type ConcreteFlyweight struct {
	intrinsicState string
}

// Operation implementuje metodę operacji współdzielonego obiektu
func (f *ConcreteFlyweight) Operation(extrinsicState string) {
	fmt.Printf("Konkretny obiekt flyweight, stan wewnętrzny: %s, stan zewnętrzny: %s\n", f.intrinsicState, extrinsicState)
}

4.3.2 Utwórz klasę FlyweightFactory

Następnie możemy utworzyć klasę FlyweightFactory do zarządzania i współdzielenia obiektów flyweight. Ta klasa fabryki przechowuje słownik flyweights do przechowywania utworzonych obiektów flyweight.

// Klasa FlyweightFactory
type FlyweightFactory struct {
    flyweights map[string]Flyweight
}

// GetFlyweight pobiera lub tworzy obiekt flyweight z fabryki
func (f *FlyweightFactory) GetFlyweight(key string) Flyweight {
    if fw, ok := f.flyweights[key]; ok {
        return fw
    }

    flyweight := &ConcreteFlyweight{
        intrinsicState: key,
    }

    f.flyweights[key] = flyweight

    return flyweight
}

4.3.3 Przykład wywołania klienta

Na koniec możemy utworzyć klasę Client, aby pokazać, jak używać wzorca flyweight do implementacji edytora graficznego.

// Klasa Klient
type Client struct {
    flyweight Flyweight
}

// Operacja wykonuje operację
func (c *Client) Operation() {
    c.flyweight.Operation("stan zewnętrzny")
}

4.4 Względy implementacyjne i najlepsze praktyki

4.4.1 Dzielenie stanu i bezpieczeństwo wątków

Podczas korzystania z wzorca flyweight należy zwrócić uwagę na udostępnianie wewnętrznego stanu i bezpieczeństwo wątków. Ponieważ obiekty flyweight są współdzielone przez wielu klientów, konieczne jest zapewnienie spójności wewnętrznego stanu.

4.4.2 Zarządzanie puli obiektów

Aby lepiej zarządzać i ponownie użyć obiektów flyweight, można użyć pul obiektów do przechowywania stworzonych obiektów flyweight. Pule obiektów mogą zwiększyć wskaźnik ponownego użycia obiektów i zmniejszyć narzut tworzenia i usuwania obiektów.

4.4.3 Zewnętrzne zarządzanie stanem obiektu

Wzorzec flyweight separuje wewnętrzny stan i zewnętrzny stan obiektów, ale zewnętrzny stan musi być zarządzany przez klienta. Korzystając z obiektów flyweight, klient musi przekazać zewnętrzny stan do obiektu flyweight dla operacji.

Pełny przykład kodu

Poniżej znajduje się kompletny przykład kodu w języku Go:

package main

import "fmt"

// Flyweight definiuje interfejs obiektu flyweight
type Flyweight interface {
	Operation(extrinsicState string)
}

// ConcreteFlyweight reprezentuje konkretny obiekt flyweight i implementuje interfejs Flyweight
type ConcreteFlyweight struct {
	intrinsicState string
}

// Operation implementuje metodę operacji dla udostępnianych obiektów
func (f *ConcreteFlyweight) Operation(extrinsicState string) {
	fmt.Printf("Konkretny obiekt flyweight, wewnętrzny stan: %s, zewnętrzny stan: %s\n", f.intrinsicState, extrinsicState)
}

// Klasa FlyweightFactory
type FlyweightFactory struct {
	flyweights map[string]Flyweight
}

// GetFlyweight pobiera lub tworzy obiekt flyweight z fabryki
func (f *FlyweightFactory) GetFlyweight(key string) Flyweight {
	if fw, ok := f.flyweights[key]; ok {
		return fw
	}

	flyweight := &ConcreteFlyweight{
		intrinsicState: key,
	}

	f.flyweights[key] = flyweight

	return flyweight
}

// Klasa Klient
type Client struct {
	flyweight Flyweight
}

// Operacja wykonuje operację
func (c *Client) Operation() {
	c.flyweight.Operation("zewnętrzny stan")
}

func main() {
	factory := &FlyweightFactory{
		flyweights: make(map[string]Flyweight),
	}

	flyweight1 := factory.GetFlyweight("A")
	flyweight1.Operation("zewnętrzny stan 1")

	flyweight2 := factory.GetFlyweight("B")
	flyweight2.Operation("zewnętrzny stan 2")

	client := &Client{
		flyweight: factory.GetFlyweight("A"),
	}
	client.Operation()
}