Przejdź do treści
Strona główna » Blog » Wzorzec SingleTon

Wzorzec SingleTon

Przyszedł czas na pierwszy opisywany przeze mnie wzorzec projektowy, a mianowicie Singleton. Jest to kreacyjny wzorzec projektowy, którego założenia są bardzo proste. Nie mniej jednak używając tego wzorca dostajemy bardzo wygodne w użyciu narzędzie. Dlaczego? Zachęcam do przeczytania artykułu.

Zbudujmy Wzorzec od początku

Zacznijmy może od przedstawienia samej idei i celu używania tego wzorca. Wzorzec ten ma na celu kontrolowanie aby w trakcie działania aplikacji powstał tylko jeden obiekt danej klasy. Programista nie musi się skupiać na tym, aby samemu tworzyć mechanizmy odwołujące się do tego samego obiektu, ponieważ klasa sama pilnuje aby nie zostało utworzone więcej obiektów.

Standardowo tworząc obiekt korzystamy z konstruktora danej klasy, który nawet jeśli go sami nie utworzymy to tworzony jest domyślnie. Zazwyczaj tworzenie obiektu wygląda następująco (wzorce będę opisywał na przykładzie języka Java):

MyClass object = new Myclass();

Uniemożliwienie tworzenia wielu obiektów rozpocznijmy od zabrania możliwości tworzenia ich w ogóle. Jak to zrobić? Wystarczy, że nie będziemy w stanie użyć konstruktora czyli zmienimy modyfikator dostępu z public na private. Wówczas nasza klasa MyClass będzie wyglądała w następujący sposób:

public class MyClass {
    private static MyClass exampleObject;
    
    private MyClass() {}
}

No dobra, ale po co mi klasa, z której nie można utworzyć w ogóle żadnego obiektu…. No cóż, teraz czas aby zaimplementować w naszej klasie bardzo ważny, podstawowy mechanizm tworzenia obiektu w oparciu o statyczną metodę.

public class MyClass {
    private static MyClass exampleObject;
    
    private MyClass() {}

    public static MyClass getExampleObject(){
         if (exampleObject == null) {
            exampleObject = new MyClass();
        }
        return exampleObject;
    }
}

Instrukcja warunkowa zapewnia nam, że jeśli obiekt został utworzony to metoda zwróci nam gotowy wcześniej utworzony obiekt, a jeśli nie został on utworzony to go stworzy. Aby skorzystać z tak utworzonej klasy należy ją wywołać poprzez metodę statyczną w następujący sposób:

MyClass object = Myclass.getExampleObject();

W tym momencie można już stwierdzić że wzorzec został zaimplementowany. Aby go przetestować zaimplementujemy jeszcze dodatkowe metody w naszej klasie inkrementujące liczbę, a następnie spróbujemy utworzyć kilka obiektów naszej klasy i wywołać na nich metodę inkrementowania liczby. Nasza klasa będzie prezentować się następująco:

public class MyClass {
    private static MyClass exampleObject;
    private int number;

    private MyClass() {
        number = 0;
    }

    public static MyClass getExampleObject() {
        if (exampleObject == null) {
            exampleObject = new MyClass();
        }
        return exampleObject;
    }

    public void incrementNumber() {
        number++;
    }

    public int getNumber() {
        return number;
    }
}

A teraz skorzystajmy z utworzonej klasy w następujący sposób:

public class Main {

    public static void main(String[] args) {

        MyClass objectOne = MyClass.getExampleObject();
        System.out.println("Wartosc z obiektu 1 wynosi " + objectOne.getNumber());
        objectOne.incrementNumber();
        System.out.println("Wartosc z obiektu 1 wynosi " + objectOne.getNumber());

        MyClass objectTwo = MyClass.getExampleObject();
        System.out.println("Wartosc z obiektu 2 wynosi " + objectTwo.getNumber());
        objectTwo.incrementNumber();
        System.out.println("Wartosc z obiektu 2 wynosi " + objectTwo.getNumber());
    }
}

Program zwraca następujące wartości:

Wartosc z obiektu 1 wynosi 0
Wartosc z obiektu 1 wynosi 1
Wartosc z obiektu 2 wynosi 1
Wartosc z obiektu 2 wynosi 2

Pierwsze wywołanie obiektu 1 tworzy nam obiekt, a następnie metoda incerementNumber() wykonuje operację na liczbie. Próba utworzenia drugiego obiektu sprawia że nie dostajemy nowego obiektu z wyzerowaną wartością lecz przypisujemy do obiektu objectTwo tak naprawdę ten sam obiekt co w przypadku objectOne. Takie rozwiązanie daje możliwość łatwego używania tego samego obiektu w różnych miejscach w aplikacji co znacząco ułatwia tworzenie bardziej elastycznego oprogramowania.

Przykład Użycia

Jako przykład napisałem prostą konsolową grę, w której aplikacja generuje losową liczbę z ustalonego zakresu. Zadaniem użytkownika jest odgadnięcie tej liczby i wpisanie jej w terminal. Gra trwa póki użytkownik nie zgadnie wylosowanej liczby. Wówczas aplikacja wyświetla stosowny komunikat i podaje ile błędów zostało popełnionych zanim użytkownik poprawnie odgadł liczbę.

Pierwsza została utworzona klasa Zgadula. W niej zapisana jest cała logika aplikacji. Klasa wygląda w następujący sposób:

package com.company;

import java.util.Random;

public class Zgadula {
    private final int randomNumber;
    private boolean win;

    //podaj minimalną i maksymalną liczbę z zakresu z którego ma być wylosowana liczba.
    public Zgadula(int min, int max) {
        win = false;
        Random random = new Random();
        randomNumber = random.nextInt(max - min + 1) + min;
    }

    //zwraca wylosowaną liczbę
    public int getRandomNumber() {
        return randomNumber;
    }

    public boolean isWin() {
        return win;
    }


    //sprawdza czy podana liczba jest tą wylosowaną
    public void checkNumber(int number) {
        if (this.randomNumber == number) win = true;
        else {
            LicznikBledow licznik = LicznikBledow.getLicznik();
            licznik.addMistake();
        }
    }
}

Konstruktor tej klasy przyjmuje dwie wartości: maksymalną i minimalną z zakresu z którego ma zostać wylosowana liczba, a następnie przypisuje te wartość do zmiennej randomNumber. Dwie kolejne metody to metody pomocnicze, natomiast zwróćmy uwage na ostatnią metodę czyli checkNumber.

Metoda ta sprawdza czy podana przez użytkownika liczba jest taka sama jak liczba randomNumber. Jeśli liczba zgadza się wówczas flaga win zostaje ustalona na wartośc true. W przeciwnym razie obiekt licznik wywołuje metodę addMistake(). Zwróćmy uwagę jak zadeklarowany jest obiekt licznik. Poprzez metodę statyczną klasy LicznikBledow. Zauważmy że obiekt tworzony jest dopiero wtedy gdy, użytkownik popełni błąd. Przejdźmy zatem do właśnie tej klasy LicznikBledow, a wygląda ona nastepująco:

package com.company;

public class LicznikBledow {
    private static LicznikBledow licznik;
    private int numberOfMistakes;

    //prywatny konstruktor uniemożliwiający utworzenie nowego obiektu wprost
    private LicznikBledow() {
        numberOfMistakes = 0;
    }

    //metoda statyczna, która tworzy i kontroluje ilość obiektów i ogranicza je do 1
    public static LicznikBledow getLicznik() {
        if (licznik == null) {
            licznik = new LicznikBledow();
        }
        return licznik;
    }

    //inkrementuje ilosc bledow
    public void addMistake() {
        numberOfMistakes++;
    }

    //zwraca ilosc bledow
    public int getNumberOfMistakes() {
        return numberOfMistakes;
    }
}

Najważniejszy jest w tej klasie konstruktor, który jest prywatny oraz metoda statyczna getLicznik(), która kontroluje tworzenie i zwracanie obiektu licznik. Właśnie tutaj mamy zaimplementowany wzorzec singleton. Nie ma możliwości utworzenia więcej niż jednego licznika błędów (choć to nie do końca prawda ale o tym później). No dobrze, ale może nasunąć się pytanie po co używać tego wzorca projektowego skoro można by utworzyć zwykłą klasę LicznikBledow z publicznym konstruktorem skoro tylko raz odwołujemy się w klasie Zgadula do obiektu licznik i zadeklarować ją następująco:

package com.company;

public class LicznikZwykly {
    private int numberOfMistakes;

    //klasyczny publiczny konstruktor
    public LicznikZwykly() {
        numberOfMistakes = 0;
    }

    //inkrementuje ilosc bledow
    public void addMistake() {
        numberOfMistakes++;
    }

    //zwraca ilosc bledow
    public int getNumberOfMistakes() {
        return numberOfMistakes;
    }
}

Taki kod jest krótszy, prostszy i wydaje się bardziej przejrzysty. Odpowiedź można dostrzec gdy zaimplementujemy naszego maina do utworzenia obiektu klasy Zgadula. Zatem do dzieła:

package com.company;

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Zgadula zgadula = new Zgadula(2, 6);
        Scanner scanner = new Scanner(System.in);

        while (!zgadula.isWin()) {
            System.out.println("Podaj liczbe: ");
            zgadula.checkNumber(scanner.nextInt());
        }

        LicznikBledow licznik = LicznikBledow.getLicznik();
        System.out.println("BRAWO! Szukana liczba to " + zgadula.getRandomNumber());
        System.out.println("Popelniles " + licznik.getNumberOfMistakes() + " bledow.");
    }
}

Po stworzeniu obiektu zgadula i scanner na potrzeby wczytywania danych do konsoli zapisano pętlę, która zostaje przerwana dopiero po poprawnym odgadnięciu wylosowanej liczby. I teraz zwrócmy uwagę na linię:

LicznikBledow licznik = LicznikBledow.getLicznik();

Wygląda to jak tworzenie nowego obiektu na podstawie klasy LicznikBledow poprzez metodę statyczną, ale tak naprawdę przypisujemy do wartości licznik już wcześniej utworzony w klasie Zgadula obiekt z zapisanymi w nim informacjami odnośnie popełnionej liczby błędów podczas gry. Przykładowa tura gry wygląda wówczas w konsoli nastepująco:

Podaj liczbe: 
2
Podaj liczbe: 
3
Podaj liczbe: 
4
Podaj liczbe: 
5
BRAWO! Szukana liczba to 5
Popelniles 3 bledow.

Zauważ w jak bardzo prosty sposób odnieśliśmy się do utworzonego gdzieś wcześniej obiektu na podstawie klasy LicznikBledow. W ten sposób można implementować kolejne klasy jak np. Menu tej aplikacji, gdzie można by wyświetlać np. ilość błędów z poprzednio rozgrywanej tury gry. W nowej klasie utworzylibyśmy nowy obiekt do którego tak naprawdę znów przypisalibyśmy wcześniej utworzony obiekt poprzez metodę statyczną. Ależ to proste i wygodne, prawda?

Podsumowanie

Wzorzec singleton mądrze używany potrafi znacząco umilić pracę programiście i uprościć kod aplikacji. W pewnym momencie wspomniałem o tym, że pomimo zastosowania mechanizmu, który uniemożliwia utworzenie większej ilości obiektów może do tego dojść. Może się tak stać gdy aplikacja jest wielowątkowa, wówczas jest ryzyko, że utworzymy 2 lub więcej singletonów jednocześnie. Istnieją sposoby żeby temu zapobiec, nie mniej jednak jest to temat na osobny artykuł, który być może kiedyś powstanie.

Mam nadzieję, że przybliżyłem możliwie dokładnie idee tego prostego lecz bardzo przydatnego wzorca i być może ktoś dozna olśnienia i przekona się do jego użycia w swojej aplikacji.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

W celu świadczenia usług na najwyższym poziomie stosuję pliki cookies, które będą zamieszczane w Państwa urządzeniu (komputerze, laptopie, smartfonie). W każdym momencie mogą Państwo dokonać zmiany ustawień Państwa przeglądarki internetowej i wyłączyć opcję zapisu plików cookies. Ze szczegółowymi informacjami dotyczącymi cookies na tej stronie można się zapoznać tutaj: View more
Ustawienia ciasteczek
Akceptuj
Blokuj
Polityka prywatności
Polityka prywatności & ciasteczka
Nazwa ciasteczka Aktywny

Polityka prywatności opisuje zasady przetwarzania przezE MNIE informacji na Twój temat, w tym danych osobowych oraz ciasteczek, czyli tzw. cookies.


1. Informacje ogólne

  1. Niniejsza polityka dotyczy Serwisu www, funkcjonującego pod adresem url: www.wojciechsiwek.pl
  2. Operatorem serwisu oraz Administratorem danych osobowych jest: Wojciech Siwek
  3. Adres kontaktowy poczty elektronicznej operatora: wojciech.siwek.programista@gmail.com
  4. Operator jest Administratorem Twoich danych osobowych w odniesieniu do danych podanych dobrowolnie w Serwisie.
  5. Serwis wykorzystuje dane osobowe w następujących celach:
    • Prowadzenie systemu komentarzy
    • Obsługa zapytań przez formularz
  6. Serwis realizuje funkcje pozyskiwania informacji o użytkownikach i ich zachowaniu w następujący sposób:
    1. Poprzez dobrowolnie wprowadzone w formularzach dane, które zostają wprowadzone do systemów Operatora.
    2. Poprzez zapisywanie w urządzeniach końcowych plików cookie (tzw. „ciasteczka”).

2. Wybrane metody ochrony danych stosowane przez Operatora

  1. Miejsca logowania i wprowadzania danych osobowych są chronione w warstwie transmisji (certyfikat SSL). Dzięki temu dane osobowe i dane logowania, wprowadzone na stronie, zostają zaszyfrowane w komputerze użytkownika i mogą być odczytane jedynie na docelowym serwerze.
  2. Hasła użytkowników są przechowywane w postaci hashowanej. Funkcja hashująca działa jednokierunkowo - nie jest możliwe odwrócenie jej działania, co stanowi obecnie współczesny standard w zakresie przechowywania haseł użytkowników.
  3. Operator okresowo zmienia swoje hasła administracyjne.
  4. W celu ochrony danych Operator regularnie wykonuje kopie bezpieczeństwa.
  5. Istotnym elementem ochrony danych jest regularna aktualizacja wszelkiego oprogramowania, wykorzystywanego przez Operatora do przetwarzania danych osobowych, co w szczególności oznacza regularne aktualizacje komponentów programistycznych.

3. Hosting

  1. Serwis jest hostowany (technicznie utrzymywany) na serwera operatora: NETMARK.

4. Twoje prawa i dodatkowe informacje o sposobie wykorzystania danych

  1. W niektórych sytuacjach Administrator ma prawo przekazywać Twoje dane osobowe innym odbiorcom, jeśli będzie to niezbędne do wykonania zawartej z Tobą umowy lub do zrealizowania obowiązków ciążących na Administratorze. Dotyczy to takich grup odbiorców:
    • operatorzy pocztowi
    • operatorzy systemu komentarzy
    • upoważnieni pracownicy i współpracownicy, którzy korzystają z danych w celu realizacji celu działania strony
  2. Twoje dane osobowe przetwarzane przez Administratora nie dłużej, niż jest to konieczne do wykonania związanych z nimi czynności określonych osobnymi przepisami (np. o prowadzeniu rachunkowości). W odniesieniu do danych marketingowych dane nie będą przetwarzane dłużej niż przez 3 lata.
  3. Przysługuje Ci prawo żądania od Administratora:
    • dostępu do danych osobowych Ciebie dotyczących,
    • ich sprostowania,
    • usunięcia,
    • ograniczenia przetwarzania,
    • oraz przenoszenia danych.
  4. Przysługuje Ci prawo do złożenia sprzeciwu w zakresie przetwarzania wskazanego w pkt 3.3 c) wobec przetwarzania danych osobowych w celu wykonania prawnie uzasadnionych interesów realizowanych przez Administratora, w tym profilowania, przy czym prawo sprzeciwu nie będzie mogło być wykonane w przypadku istnienia ważnych prawnie uzasadnionych podstaw do przetwarzania, nadrzędnych wobec Ciebie interesów, praw i wolności, w szczególności ustalenia, dochodzenia lub obrony roszczeń.
  5. Na działania Administratora przysługuje skarga do Prezesa Urzędu Ochrony Danych Osobowych, ul. Stawki 2, 00-193 Warszawa.
  6. Podanie danych osobowych jest dobrowolne, lecz niezbędne do obsługi Serwisu.
  7. W stosunku do Ciebie mogą być podejmowane czynności polegające na zautomatyzowanym podejmowaniu decyzji, w tym profilowaniu w celu świadczenia usług w ramach zawartej umowy oraz w celu prowadzenia przez Administratora marketingu bezpośredniego.
  8. Dane osobowe nie są przekazywane od krajów trzecich w rozumieniu przepisów o ochronie danych osobowych. Oznacza to, że nie przesyłamy ich poza teren Unii Europejskiej.

5. Informacje w formularzach

  1. Serwis zbiera informacje podane dobrowolnie przez użytkownika, w tym dane osobowe, o ile zostaną one podane.
  2. Serwis może zapisać informacje o parametrach połączenia (oznaczenie czasu, adres IP).
  3. Serwis, w niektórych wypadkach, może zapisać informację ułatwiającą powiązanie danych w formularzu z adresem e-mail użytkownika wypełniającego formularz. W takim wypadku adres e-mail użytkownika pojawia się wewnątrz adresu url strony zawierającej formularz.
  4. Dane podane w formularzu są przetwarzane w celu wynikającym z funkcji konkretnego formularza, np. w celu dokonania procesu obsługi zgłoszenia serwisowego lub kontaktu handlowego, rejestracji usług itp. Każdorazowo kontekst i opis formularza w czytelny sposób informuje, do czego on służy.

6. Logi Administratora

  1. Informacje zachowaniu użytkowników w serwisie mogą podlegać logowaniu. Dane te są wykorzystywane w celu administrowania serwisem.

7. Istotne techniki marketingowe

  1. Operator stosuje analizę statystyczną ruchu na stronie, poprzez Google Analytics (Google Inc. z siedzibą w USA). Operator nie przekazuje do operatora tej usługi danych osobowych, a jedynie zanonimizowane informacje. Usługa bazuje na wykorzystaniu ciasteczek w urządzeniu końcowym użytkownika. W zakresie informacji o preferencjach użytkownika gromadzonych przez sieć reklamową Google użytkownik może przeglądać i edytować informacje wynikające z plików cookies przy pomocy narzędzia: https://www.google.com/ads/preferences/

8. Informacja o plikach cookies

  1. Serwis korzysta z plików cookies.
  2. Pliki cookies (tzw. „ciasteczka”) stanowią dane informatyczne, w szczególności pliki tekstowe, które przechowywane są w urządzeniu końcowym Użytkownika Serwisu i przeznaczone są do korzystania ze stron internetowych Serwisu. Cookies zazwyczaj zawierają nazwę strony internetowej, z której pochodzą, czas przechowywania ich na urządzeniu końcowym oraz unikalny numer.
  3. Podmiotem zamieszczającym na urządzeniu końcowym Użytkownika Serwisu pliki cookies oraz uzyskującym do nich dostęp jest operator Serwisu.
  4. Pliki cookies wykorzystywane są w następujących celach:
    1. utrzymanie sesji użytkownika Serwisu (po zalogowaniu), dzięki której użytkownik nie musi na każdej podstronie Serwisu ponownie wpisywać loginu i hasła;
    2. realizacji celów określonych powyżej w części "Istotne techniki marketingowe";
  5. W ramach Serwisu stosowane są dwa zasadnicze rodzaje plików cookies: „sesyjne” (session cookies) oraz „stałe” (persistent cookies). Cookies „sesyjne” są plikami tymczasowymi, które przechowywane są w urządzeniu końcowym Użytkownika do czasu wylogowania, opuszczenia strony internetowej lub wyłączenia oprogramowania (przeglądarki internetowej). „Stałe” pliki cookies przechowywane są w urządzeniu końcowym Użytkownika przez czas określony w parametrach plików cookies lub do czasu ich usunięcia przez Użytkownika.
  6. Oprogramowanie do przeglądania stron internetowych (przeglądarka internetowa) zazwyczaj domyślnie dopuszcza przechowywanie plików cookies w urządzeniu końcowym Użytkownika. Użytkownicy Serwisu mogą dokonać zmiany ustawień w tym zakresie. Przeglądarka internetowa umożliwia usunięcie plików cookies. Możliwe jest także automatyczne blokowanie plików cookies Szczegółowe informacje na ten temat zawiera pomoc lub dokumentacja przeglądarki internetowej.
  7. Ograniczenia stosowania plików cookies mogą wpłynąć na niektóre funkcjonalności dostępne na stronach internetowych Serwisu.
  8. Pliki cookies zamieszczane w urządzeniu końcowym Użytkownika Serwisu wykorzystywane mogą być również przez współpracujące z operatorem Serwisu podmioty, w szczególności dotyczy to firm: Google (Google Inc. z siedzibą w USA), Facebook (Facebook Inc. z siedzibą w USA), Twitter (Twitter Inc. z siedzibą w USA).

9. Zarządzanie plikami cookies – jak w praktyce wyrażać i cofać zgodę?

  1. Jeśli użytkownik nie chce otrzymywać plików cookies, może zmienić ustawienia przeglądarki. Zastrzegamy, że wyłączenie obsługi plików cookies niezbędnych dla procesów uwierzytelniania, bezpieczeństwa, utrzymania preferencji użytkownika może utrudnić, a w skrajnych przypadkach może uniemożliwić korzystanie ze stron www
  2. W celu zarządzania ustawienia cookies wybierz z listy poniżej przeglądarkę internetową, której używasz i postępuj zgodnie z instrukcjami:
Zapisz ustawienia
Ustawienia ciasteczek