I. Wzorce kreacyjne
1. Singleton
2. Budowniczy
3. Prototyp
4. Fabryka
5. Fabryka abstrakcyjna
II. Wzorce Strukturalne
1. Adapter
2. Most
3. Kompozyt
4. Dekorator
5. Fasada
6. Pyłek
7. Pełnomocnik

Singleton - wzorzec projektowy (design pattern) - java

1. Cel:
Wzorzec projektwoy Singleton ma za zadanie dostarczyć tylko jedną instancje klasy.
Zapenia jeden globalny punkt dostępu do tej instancji.

2. Problem:
Singleton używany jest na przykład gdy chcemy zapewnić kontrole dostępu do dzielonego zasobu takiego jak baza danych lub plik itp.

3. Rozwiązanie:
Prywatny konstruktor który zapobiega użyciu słowa kluczowego new, dla instancji klasy.
Stworzenie metody statycznej zwracającej stworzony tylko jeden obiekt.

4. Diagram klas klasy Singleton:


5. Implementacja:
Mamy kilka rodzajów implementacji singleton-a.

a) Jednowątkowy:
  1. package pl.edu.java.singleton;
  2.  
  3. public final class SingletonNonThreadSafe {
  4.     private static SingletonNonThreadSafe instance;
  5.  
  6.     private SingletonNonThreadSafe() {
  7.     }
  8.  
  9.     public static SingletonNonThreadSafe getInstance() {
  10.         if (instance == null) {
  11.             instance = new SingletonNonThreadSafe();
  12.         }
  13.         return instance;
  14.     }
  15. }
  16.  
b) gorliwe ładowanie (Eagar loading), jest bezpieczne dla wielu wątków
  1. package pl.edu.java.singleton;
  2.  
  3. public final class SingletonEager {
  4.    
  5.     private static final SingletonEager instance = new SingletonEager();
  6.    
  7.     private SingletonEager(){}
  8.  
  9.     public static SingletonEager getInstance(){
  10.         return instance;
  11.     }
  12. }
  13.  
c) synchronizowana metoda
  1. package pl.edu.java.singleton;
  2.  
  3. public class SingletonSynchMethod {
  4.  
  5.     private static SingletonSynchMethod instance;
  6.    
  7.     private SingletonSynchMethod(){}
  8.    
  9.     public static synchronized SingletonSynchMethod getInstance(){
  10.         if(instance == null){
  11.             instance = new SingletonSynchMethod();
  12.         }
  13.         return instance;
  14.     }
  15.    
  16. }
  17.  
d) synchronizowany blok
  1. package pl.edu.java.singleton;
  2.  
  3. public class SingletonSynchBlock {
  4.  
  5.     private static SingletonSynchBlock instance;
  6.    
  7.         public static SingletonSynchBlock getInstance(){
  8.             if(instance == null){
  9.                 synchronized (SingletonSynchBlock.class) {
  10.                     if(instance == null){
  11.                         instance = new SingletonSynchBlock();
  12.                         }
  13.                     }
  14.             }
  15.             return instance;
  16.        }
  17.   }
e) ale pomimo tego że mamy blok synchronizowany zmienna instance może się zmienić w linii 11, a nie zostać zauważona w linii 8.
Aby temu zapobiec dodajemy na zmienną instance słowo kluczowe volatile - mówi ono:
"Uważaj - bo może ona zmienić wartość nawet wtedy, gdy się nie spodziewasz!"
to zapobiegnie że nie zostanie zauważona zmiana w jednym wątku gdy zmiana dokonuje się w drugim.
Technicznie rzecz biorąc, zmienna będzie pobierana z pamięci komputera nie z cache-u procesora
  1. package pl.edu.java.singleton;
  2.  
  3. public class SingletonSynchBlockVolatile {
  4.  
  5.     private static volatile SingletonSynchBlockVolatile instance;
  6.    
  7.         public static SingletonSynchBlockVolatile getInstance(){
  8.             if(instance == null){
  9.                 synchronized (SingletonSynchBlockVolatile.class) {
  10.                     if(instance == null){
  11.                         instance = new SingletonSynchBlockVolatile();
  12.                         }
  13.                     }
  14.             }
  15.             return instance;
  16.        }
  17.   }
f) Singleton Bill-a Pugh-a
Wydaje się najlepszym podejściem, ponieważ nie jest ładowana do pamięci do póki nie zostanie wywołana metoda statyczne getInstance();
  1. package pl.edu.java.singleton;
  2.  
  3. public class SingletonBillPugh {
  4.  
  5.     private SingletonBillPugh(){}
  6.    
  7.     private static class SingletonHelper{
  8.         private static final SingletonBillPugh INSTANCE = new SingletonBillPugh();
  9.     }
  10.    
  11.     public static SingletonBillPugh getInstance(){
  12.         return SingletonHelper.INSTANCE;
  13.     }
  14. }
  15.  
g) Ale ciągle jest możliwe jest tworzenie instancji przez reflekcje:
  1. public class SingletonRef {
  2.     public static void main(String[] args) {
  3.        Singleton s1 = Singleton.getInstance();
  4.        Singleton s2 = Singleton.getInstance();
  5.        Class clazz = Class.forName("pl.org.java.singleton.Singleton");
  6.        Constrictor<Singleton> cs = clazz.getDeclaredConstructor();
  7.        cs.setAccessable(true);
  8.        Singleton s3 = cs.newInstance();
  9.        print("s1", s1);
  10.        print("s2", s2);
  11.        print("s3", s3);
  12.     }
  13.        
  14.     private static void print(String name, Singleton s) {
  15.             System.out.println(String.format("Instancja o nazwie: %s, posiada hashcode: ", name, s.hashcode()));
  16.     }
  17. }
  18.  
Aby temu zapobiec możemy oddac w konstruktorze:
  1.         private Singleton() {
  2.                 if(instance != null) {
  3.                         throw new RuntimeException("Instance exist! You can't create second!");
  4.                 }
  5.         }

h) Serializacja/deserializacja: wynikają z niej problemy z inną wersją instancji
Klasa testowa
  1. package pl.edu.java.singleton;
  2.  
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.ObjectInputStream;
  6. import java.io.ObjectOutputStream;
  7.  
  8. public class SingletonSerializableTest {
  9.     public static void main(String[] args) throws Exception {
  10.         Singleton s1 = Singleton.getInstance();
  11.         Singleton s2 = Singleton.getInstance();
  12.  
  13.         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c:\\matmad\\obj1"));
  14.         oos.writeObject(s2);
  15.         ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c:\\matmad\\obj1"));
  16.  
  17.         Singleton s3 = (Singleton)ois.readObject();
  18.  
  19.         print("s1", s1);
  20.         print("s2", s2);
  21.         print("s3", s3);
  22.  
  23.     }
  24.     private static void print(String name, Singleton s) {
  25.         System.out.println(String.format("Instancja o nazwie: %s, posiada hashcode: %d", name, s.hashCode()));
  26.     }
  27. }
Rozwiązaniem tego jest metoda w klasie singletonu:
  1. private Object readResolve() {
  2.         return instance;
  3. }
Klasa singleton-u będzie wyglądać następująco
  1. package pl.edu.java.singleton;
  2.  
  3. import java.io.Serializable;
  4.  
  5. class Singleton implements Serializable {
  6.     private static Singleton instance = new Singleton();
  7.     private Singleton() {
  8.     }
  9.  
  10.     public static Singleton getInstance() {
  11.         return instance;
  12.     }
  13.     private Object readResolve() {
  14.         return instance;
  15.     }
  16.  
Wadą tego rozwiązania jest taka że wywołanie deserializacji nie nadpisze naszego singletonu
Więc jeżeli będziemy mieć zmienną 'value', zrobimy setter i getter, ustawimy value = 1, zapiszemy do pliku
zmienimy value = 2, deserializujemy obiekt Singleton, zmienna zostanie ta po zmianie czyli value = 2, a nie
ta którą deserializowaliśmy czyli value = 1 (ta została zapisana do pliku i deserializowana).

i) Zapobieganie clone-owania:
  1. package pl.edu.java.singleton;
  2. public class SingletonClone implements Cloneable {
  3.     private static SingletonClone instance = new SingletonClone();
  4.     private SingletonClone() {
  5.     }
  6.  
  7.     public static SingletonClone getInstance() {
  8.         return instance;
  9.     }
  10.         public Object clone() throws CloneNotSupportedException {
  11.                 if ( instance != null ) {
  12.                         throw new CloneNotSupportedException();
  13.                 }
  14.                 return super.clone();
  15.         }
  16. }
j) Joshua Bloch zaleca tworzenie singletonu na podstawie typu enum:
  1. package pl.edu.java.singleton;
  2.  
  3. public enum SingletonEnum {
  4.     INSTANCE;
  5.  
  6.     int value;
  7.  
  8.     public int getValue() {
  9.         return value;
  10.     }
  11.     public void setValue(int value) {
  12.         this.value = value;
  13.     }
  14. }
  15.  
6. Zastosowanie w kodzie java:

java.lang.Runtime#getRuntime() - zastowowany SingletonEager:
java.lang.System#getSecurityManager() - zastosowana metoda synchronizowana SingletonSynchMethod,
ale wewnątrz innej metody. Zmienna ze słowem kluczowym volatile;
java.awt.Desktop#getDesktop() - zastosowana metoda synchronizowana SingletonSynchMethod
created by freelancer.java.org.pl © 2020