JavaRush /Blog Java /Random-PL /Wzorce projektowe: Singleton

Wzorce projektowe: Singleton

Opublikowano w grupie Random-PL
Cześć! Dziś przyjrzymy się bliżej różnym wzorcom projektowym, a zaczniemy od wzorca Singleton, zwanego także „singletonem”. Wzorce projektowe: Singleton - 1Przypomnijmy: co w ogóle wiemy o wzorcach projektowych? Wzorce projektowe to najlepsze praktyki, które można zastosować w celu rozwiązania wielu znanych problemów. Wzorce projektowe na ogół nie są powiązane z żadnym językiem programowania. Potraktuj je jako zbiór rekomendacji, dzięki którym unikniesz błędów i nie wymyślisz koła na nowo.

Co to jest singleton?

Singleton to jeden z najprostszych wzorców projektowych, które można zastosować w klasie. Ludzie czasami mówią „ta klasa to singleton”, co oznacza, że ​​ta klasa implementuje wzorzec projektowy singletonu. Czasami konieczne jest napisanie klasy, dla której można utworzyć tylko jeden obiekt. Na przykład klasa odpowiedzialna za logowanie lub łączenie się z bazą danych. Wzorzec projektowy Singleton opisuje, w jaki sposób możemy wykonać takie zadanie. Singleton to wzorzec projektowy, który spełnia dwie funkcje:
  1. Zapewnia gwarancję, że klasa będzie miała tylko jedną instancję klasy.

  2. Zapewnia globalny punkt dostępu do instancji tej klasy.

Istnieją zatem dwie cechy charakterystyczne dla niemal każdej implementacji wzorca singletona:
  1. Prywatny konstruktor. Ogranicza możliwość tworzenia obiektów klas poza samą klasą.

  2. Publiczna metoda statyczna, która zwraca instancję klasy. Ta metoda nazywa się getInstance. Jest to globalny punkt dostępu do instancji klasy.

Opcje wdrożenia

Wzorzec projektowy singleton jest używany na różne sposoby. Każda opcja jest dobra i zła na swój sposób. Tutaj jak zawsze: nie ma ideału, ale trzeba do niego dążyć. Ale przede wszystkim zdefiniujmy, co jest dobre, a co złe i jakie metryki wpływają na ocenę wdrożenia wzorca projektowego. Zacznijmy od pozytywów. Oto kryteria, które nadają realizacji soczystość i atrakcyjność:
  • Leniwa inicjalizacja: gdy klasa jest ładowana, podczas gdy aplikacja działa dokładnie wtedy, gdy jest potrzebna.

  • Prostota i przejrzystość kodu: metryka jest oczywiście subiektywna, ale ważna.

  • Bezpieczeństwo wątków: działa poprawnie w środowisku wielowątkowym.

  • Wysoka wydajność w środowisku wielowątkowym: wątki blokują się minimalnie lub wcale podczas udostępniania zasobu.

Teraz wady. Wymieniamy kryteria, które pokazują wdrożenie w złym świetle:
  • Inicjalizacja nieleniwa: kiedy ładowana jest klasa w momencie uruchamiania aplikacji, niezależnie od tego, czy jest to potrzebne, czy nie (paradoks, w świecie IT lepiej być leniwym)

  • Złożoność i słaba czytelność kodu. Wskaźnik jest również subiektywny. Zakładamy, że jeśli z oczu leci krew, realizacja jest taka sobie.

  • Brak bezpieczeństwa nici. Inaczej mówiąc, „zagrożenie gwintem”. Błędne działanie w środowisku wielowątkowym.

  • Słaba wydajność w środowisku wielowątkowym: wątki blokują się wzajemnie przez cały czas lub często podczas udostępniania zasobu.

Kod

Teraz jesteśmy gotowi rozważyć różne opcje wdrożenia, wymieniając zalety i wady:

Proste rozwiązanie

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
Najprostsza implementacja. Plusy:
  • Prostota i przejrzystość kodu

  • Bezpieczeństwo nici

  • Wysoka wydajność w środowisku wielowątkowym

Wady:
  • Nie leniwa inicjalizacja.
Próbując naprawić ostatnią wadę, otrzymujemy implementację numer dwa:

Leniwa inicjalizacja

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Plusy:
  • Leniwa inicjalizacja.

Wady:
  • Nie jest bezpieczny dla wątków

Realizacja jest ciekawa. Możemy inicjować leniwie, ale straciliśmy bezpieczeństwo wątku. Nie ma problemu: w trzecim wdrożeniu wszystko synchronizujemy.

Zsynchronizowany akcesor

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Plusy:
  • Leniwa inicjalizacja.

  • Bezpieczeństwo nici

Wady:
  • Słaba wydajność w środowisku wielowątkowym

Świetnie! W trzecim wdrożeniu przywróciliśmy bezpieczeństwo wątków! To prawda, jest powolny... Teraz metoda getInstancejest zsynchronizowana i można ją wprowadzić tylko pojedynczo. Tak naprawdę nie musimy synchronizować całej metody, a jedynie jej część, w której inicjujemy nowy obiekt klasy. Nie możemy jednak po prostu owinąć synchronizedczęści odpowiedzialnej za utworzenie nowego obiektu w bloku: nie zapewni to bezpieczeństwa wątku. To trochę bardziej skomplikowane. Poniżej podano poprawną metodę synchronizacji:

Podwójnie sprawdzone zamknięcie

public class Singleton {
    private static Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
Plusy:
  • Leniwa inicjalizacja.

  • Bezpieczeństwo nici

  • Wysoka wydajność w środowisku wielowątkowym

Wady:
  • Nieobsługiwane w wersjach Java niższych niż 1.5 (słowo kluczowe volatile zostało poprawione w wersji 1.5)

Zwracam uwagę, że aby ta opcja implementacji działała poprawnie, wymagany jest jeden z dwóch warunków. Zmienna INSTANCEmusi mieć wartość final, lub volatile. Ostatnią implementacją, którą dzisiaj omówimy, jest Class Holder Singleton.

Posiadacz klasy Singleton

public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
Plusy:
  • Leniwa inicjalizacja.

  • Bezpieczeństwo nici.

  • Wysoka wydajność w środowisku wielowątkowym.

Wady:
  • Do poprawnego działania należy zagwarantować, że obiekt klasy Singletonzostanie zainicjowany bez błędów. W przeciwnym razie pierwsze wywołanie metody getInstancezakończy się błędem ExceptionInInitializerError, a wszystkie kolejne zakończą się niepowodzeniem NoClassDefFoundError.

Wykonanie jest niemal idealne. I leniwy, bezpieczny dla wątków i szybki. Ale jest niuans opisany w minusie. Tabela porównawcza różnych implementacji wzorca Singletona:
Realizacja Leniwa inicjalizacja Bezpieczeństwo nici Szybkość wielowątkowości Kiedy użyć?
Proste rozwiązanie - + Szybko Nigdy. Lub gdy leniwa inicjalizacja nie jest ważna. Ale nigdy lepiej.
Leniwa inicjalizacja + - Nie dotyczy Zawsze, gdy wielowątkowość nie jest potrzebna
Zsynchronizowany akcesor + + Powoli Nigdy. Albo gdy szybkość pracy przy wielowątkowości nie ma znaczenia. Ale nigdy lepiej
Podwójnie sprawdzone zamknięcie + + Szybko W rzadkich przypadkach, gdy trzeba obsłużyć wyjątki podczas tworzenia singletonu. (kiedy posiadacz klasy Singleton nie ma zastosowania)
Posiadacz klasy Singleton + + Szybko Zawsze wtedy, gdy potrzebna jest wielowątkowość i jest gwarancja, że ​​obiekt klasy singleton zostanie utworzony bez problemów.

Plusy i minusy wzorca Singletona

Ogólnie rzecz biorąc, singleton robi dokładnie to, czego się od niego oczekuje:
  1. Zapewnia gwarancję, że klasa będzie miała tylko jedną instancję klasy.

  2. Zapewnia globalny punkt dostępu do instancji tej klasy.

Jednak ten wzór ma wady:
  1. Singleton narusza zasadę SRP (ang. Single Responsibility Principle) – klasa Singleton oprócz swoich bezpośrednich obowiązków kontroluje także liczbę swoich kopii.

  2. Zależność zwykłej klasy lub metody od singletonu nie jest widoczna w kontrakcie publicznym klasy.

  3. Zmienne globalne są złe. Singleton ostatecznie przekształca się w jedną potężną zmienną globalną.

  4. Obecność singletonu zmniejsza ogólnie testowalność aplikacji, a w szczególności klas korzystających z singletonu.

OK, wszystko już skończone. Przyjrzeliśmy się wzorcowi projektowemu singletonu. Teraz w rozmowie na całe życie ze znajomymi programistami będziesz mógł powiedzieć nie tylko co jest w nim dobrego, ale też kilka słów o tym co jest w nim złego. Powodzenia w zdobywaniu nowej wiedzy.

Dodatkowa lektura:

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION