Μοτίβο σχεδίασης Singleton | Java Development Journal

1
Μοτίβο σχεδίασης Singleton |  Java Development Journal

Σε αυτό το άρθρο του Μοτίβα σχεδίασης Javaθα συνεχίσουμε να μαθαίνουμε σχέδια σχεδίασης και να καλύψουμε το Μοτίβο σχεδίασης Singleton. Θα καταλάβουμε τι σημαίνει αυτό το μοτίβο για τη χρήση μιας εφαρμογής Java. Στη συνέχεια θα δούμε τα πλεονεκτήματα του σχεδιασμού του Singleton Design Pattern.

Μοτίβο σχεδίασης Singleton

Το μοτίβο σχεδίασης Singleton είναι ένα μέρος του μοτίβου δημιουργίας σχεδιασμού που διασφαλίζει ότι μια κλάση έχει μία και μόνο μία παρουσία ανά JVM. Παρέχει καθολική πρόσβαση σε αυτήν την παρουσία για οποιονδήποτε χρήστη αυτής της κλάσης. Η εφαρμογή του μοτίβου Java Singleton ήταν πάντα ένα αμφιλεγόμενο θέμα μεταξύ των προγραμματιστών.

Μοτίβο σχεδίασης Singleton λύνει μερικά προβλήματα. Επίσης παραβιάζει το Αρχή Ενιαίας Ευθύνης. Ας δούμε μερικά προβλήματα που επιλύονται με μονότονο μοτίβο.

  1. Το Singleton Design Pattern διασφαλίζει ότι υπάρχει μόνο μία παρουσία για αυτήν την κατηγορία. Αυτό είναι πολύ χρήσιμο με κοινόχρηστους πόρους.
  2. Το Singleton Design Pattern παρέχει καθολική πρόσβαση στα στιγμιότυπα και επίσης προστατεύει το στιγμιότυπο από αλλοίωση/αντικατάσταση από άλλους κωδικούς.

Τις περισσότερες φορές, οι άνθρωποι αποκαλούν επίσης αυτό το μοτίβο ως μονότονο ως κοντό.

1. Εφαρμογή Singleton Pattern

Για να το καταλάβουμε καλύτερα, ας εφαρμόσουμε το Singleton Design Pattern στην Java. Υπάρχουν 2 κύριες περιπτώσεις χρήσης ενώ εφαρμόζουμε αυτό το σχέδιο σχεδίασης.

  • Περιβάλλον Single Threaded.
  • Περιβάλλον πολλαπλών νημάτων.

2. Μοτίβο Singleton με Περιβάλλον Μονόκλωστη

Για εφαρμογές μονού νήματος, θα πρέπει να κάνουμε:

  • Δημιουργήστε μια ιδιωτική στατική παρουσία της κλάσης Singleton.
  • Κρατήστε τον κατασκευαστή ιδιωτικό.
  • Δημιουργήστε ένα ιδιωτικό στατικό getInstance() μέθοδος για να επιστρέφετε πάντα το ίδιο αντικείμενο.
public class Singleton {
    
    /* private instance variable */
    private static Singleton instance = new Singleton();

    /* private constructor */
    private Singleton() {}

    /* returns the same object */
    public static Singleton getInstance() {
        return instance;
    }
}

2.1. Breaking Singleton με χρήση Reflection

Μπορούμε να σπάσουμε το μοτίβο Singleton χρησιμοποιώντας την ανάκλαση χρησιμοποιώντας τον παρακάτω κώδικα. Θα πρέπει να είμαστε προσεκτικοί όταν χρησιμοποιούμε αυτό το μοτίβο, ειδικά με τον προβληματισμό.

public class BreakSingletonUsingReflection {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = null;
        try {
            Constructor[] constructors = Singleton.class.getDeclaredConstructors();
            for (Constructor constructor: constructors) {
                constructor.setAccessible(true);
                singleton2 = (Singleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("instance1.hashCode(): " + singleton1.hashCode());
        System.out.println("instance2.hashCode(): " + singleton2.hashCode());
    }
}

Εάν εκτελέσετε αυτό το πρόγραμμα, αυτό είναι αυτό που θα δούμε ως έξοδο:

instance1.hashCode(): 1163157884
instance2.hashCode(): 1956725890

Από Java 5, είναι καλύτερο να το χρησιμοποιήσετε Enum για τη δημιουργία singleton αντικειμένων για να διασφαλιστεί ότι Αντανάκλαση δεν μπορεί να το σπάσει. Δεν έχουμε καλύψει τους τρόπους διάσπασης του κώδικα χρησιμοποιώντας Cloneable και Serializable Ωστόσο, είναι εύκολο να εφαρμοστούν σύμφωνα με τα παρακάτω:

Κλωνοποιήσιμο -> Πετάξτε την εξαίρεση από το εσωτερικό του clone() μέθοδος

@Override
protected Object clone() throws CloneNotSupportedException{
    throw new CloneNotSupportedException();
}

Σειριοποιήσιμο -> χρησιμοποιήστε το readResolve() μέθοδος για να αποφύγουμε τη δημιουργία ενός νέου αντικειμένου ενώ αποσειρώνουμε το αντικείμενο

protected Object readResolve(){
    return instance;
}

3. Μοτίβο σχεδίασης Singleton με περιβάλλον πολλαπλών νημάτων

Για το περιβάλλον πολλαπλών νημάτων, πρέπει να είμαστε ιδιαίτερα προσεκτικοί, καθώς δύο ή περισσότερα νήματα μπορούν να καλέσουν το αντικείμενο δημιουργίας και στο ότι θα έχουμε πάνω από ένα αντικείμενο που έχει δημιουργηθεί για το Singleton και, ως εκ τούτου, θα καταργηθεί ο σκοπός του μοτίβου Singleton. Για να ξεπεραστεί αυτό το ζήτημα, πρόκειται να χρησιμοποιήσουμε διπλός έλεγχος κλειδώματος για ένα περιβάλλον πολλαπλών νημάτων και όπως θα κάνετε αργότερα, όλα τα νήματα θα έχουν το ίδιο αντίγραφο του αντικειμένου.

Διαφημίσεις

public class SingletonMultiThreaded {

    /* private instance variable  */
    private static volatile SingletonMultiThreaded INSTANCE;

    /* private constructor */
    private SingletonMultiThreaded() {}

    public static SingletonMultiThreaded getInstance() {
        /* double-checking lock */
        if (null == INSTANCE) {
            /* synchronized block */
            synchronized(SingletonMultiThreaded.class) {
                if (null == INSTANCE) {
                    INSTANCE = new SingletonMultiThreaded();
                }
            }
        }
        return INSTANCE;
    }
}

Θέμα 1:

public class Thread1 implements Runnable {
    @Override
    public void run() {
        SingletonMultiThreaded singletonMultiThreaded = SingletonMultiThreaded.getInstance();
        System.out.print(singletonMultiThreaded.hashCode() + " ");
    }
}

Thread2:

public class Thread2 implements Runnable {
    @Override
    public void run() {
        SingletonMultiThreaded singletonMultiThreaded = SingletonMultiThreaded.getInstance();
        System.out.print(singletonMultiThreaded.hashCode() + " ");
    }
}

Thread3:

public class Thread3 implements Runnable {
    @Override
    public void run() {
        SingletonMultiThreaded singletonMultiThreaded = SingletonMultiThreaded.getInstance();
        System.out.println(singletonMultiThreaded.hashCode());
    }
}

Να θυμάστε ότι η δοκιμή μιας κατηγορίας Singleton σε περιβάλλον ασφαλές για νήμα δεν είναι τόσο απλή, λόγω των συνθηκών αγώνα και του χρονισμού στα πέλματα που κλειδώνουν το κρίσιμο τμήμα του κώδικα όπου ελέγχουμε nullability του παραδείγματος.

4. Πρόγραμμα πελάτη

Ας δούμε το δείγμα προγράμματος πελάτη για καλύτερη κατανόηση:

public class SingletonPatternDemo {
    public static void main(String[] args) {
        /* Let's create 3 objects and see their hashcode and they will be same. */
        System.out.println("in single-threaded environment");
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        Singleton singleton3 = Singleton.getInstance();
        System.out.println(singleton1.hashCode() + " " + singleton2.hashCode() + " " + singleton3.hashCode());

        System.out.println("in multi-threaded environment");
        Thread1 t1 = new Thread1();
        t1.run();
        Thread2 t2 = new Thread2();
        t2.run();
        Thread3 t3 = new Thread3();
        t3.run();

    }
}

4.1. Έξοδος κώδικα

Ας δούμε την έξοδο του προγράμματος-πελάτη τόσο για προγράμματα μονού νήματος όσο και για προγράμματα πολλαπλών νημάτων, και στις δύο περιπτώσεις δημιουργούμε 3 διαφορετικά αντικείμενα, ωστόσο, όπως μπορείτε να δείτε Μοναδικό χαρτί και SingletonMultiThreaded οι τάξεις επιστρέφουν τα ίδια hashcode (ίδιο αντικείμενο) για αυτά τα 3 αντικείμενα, αντίστοιχα.

Διαφημίσεις

in a single-threaded environment
1163157884 1163157884 1163157884
In a multi-threaded environment
1956725890 1956725890 1956725890

Process finished with exit code 0

5. Μοτίβο σχεδίασης Singleton – Παραδείγματα πραγματικού κόσμου

Ακολουθεί η λίστα με μερικά από τα γνωστά παραδείγματα του πραγματικού κόσμου που χρησιμοποιούν το μοτίβο singleton:

  • Κόπτων δέντρα διά ξυλείαν:- Σε εταιρικές εφαρμογές, το αντικείμενο Logger είναι ένα πραγματικό παράδειγμα ενός μοτίβου Singleton. Εάν αυτή η κλάση δεν είναι singleton, θα δημιουργηθεί ένα νέο αρχείο καταγραφής για κάθε κλήση χρήστη (εφαρμογές πελάτη). Όλα τα αρχεία καταγραφής θα γραφτούν σε ένα μόνο αρχείο χρησιμοποιώντας αυτό το σχέδιο.
  • Κρύπτη:- Για να έχει ένα καθολικό σημείο αναφοράς, μπορεί επίσης να δημιουργήσει τις κλάσεις κρυφής μνήμης ως αντικείμενα Singleton. Για άλλη μια φορά, στην πραγματική ζωή οι εφαρμογές-πελάτες θα αλληλεπιδρούν με τα ίδια αντικείμενα κρυφής μνήμης για καταχωρήσεις κρυφής μνήμης.
  • Αρχείο Διαμόρφωσης:- Σε εταιρικές εφαρμογές, η διαμόρφωση/ιδιότητες μπορούν να βασίζονται στο μοτίβο Singleton. Με αυτό, πολλές εφαρμογές-πελάτες θα χρησιμοποιούν τα ίδια αρχεία (αυτή είναι η ιδέα ούτως ή άλλως, αυτή η διαμόρφωση παραμένει ως επί το πλείστον η ίδια για όλους τους πελάτες).

Σε όλα τα παραπάνω παραδείγματα, θα δημιουργήσει την παρουσία του Singleton για πρώτη φορά και στη συνέχεια από τη δεύτερη κλήση και μετά, οι εφαρμογές-πελάτες χρησιμοποιούν την ίδια παρουσία. Ακολουθεί το διάγραμμα κλάσης για να κατανοήσετε το μοτίβο:

Διαφημίσεις

Μοτίβο σχεδίασης Singleton
Μοτίβο σχεδίασης Singleton

6. Πλεονεκτήματα και μειονεκτήματα

Υπάρχουν πολλά πλεονεκτήματα αυτού του μοτίβου. Ας δούμε μερικά από αυτά:

  1. Έλεγχος περιπτώσεων: Αυτό το μοτίβο διασφαλίζει ότι όλοι έχουν πρόσβαση στην ίδια παρουσία και κανείς δεν μπορεί να την αντικαταστήσει ή να δημιουργήσει νέες παρουσίες.
  2. Ευκαμψία: Εφόσον η κλάση Singleton ελέγχει την παρουσίαση του στιγμιότυπου, μπορεί να αλλάξει τη συμπεριφορά όποτε θέλει.
  3. Είναι πολύ χρήσιμο σε μια ομάδα πολλαπλών νημάτων και θέλουμε να διαχειριστούμε τους πόρους που είναι περιορισμένης χωρητικότητας.

6.1. Μειονεκτήματα

Υπάρχουν λίγα μειονεκτήματα στη χρήση αυτού του σχεδίου σχεδίου.

  1. παραβιάζει την Αρχή της Ενιαίας Ευθύνης, καθώς αυτό λύνει 2 προβλήματα ταυτόχρονα.
  2. Μπορεί να κρύψει τα κακά σχέδια.
  3. Αυτό μπορεί να προσθέσει αρκετές προκλήσεις σε ένα περιβάλλον πολλαπλών νημάτων.

7. Πότε να το χρησιμοποιήσετε;

  1. Θα πρέπει να χρησιμοποιούμε το μοτίβο σχεδίασης Singleton όταν θέλουμε να μοιραστούμε ένα μεμονωμένο αντικείμενο μεταξύ πολλών χρηστών/καλούντων. Για παράδειγμα, μια σύνδεση βάσης δεδομένων.
  2. Θα πρέπει να χρησιμοποιούμε το Singleton Design Pattern όταν θέλουμε αυστηρότερο έλεγχο σε καθολικές μεταβλητές. Θυμηθείτε, κανείς εκτός από την ίδια την κλάση Singleton δεν μπορεί να αντικαταστήσει την κρυφή παρουσία.

7.1. JDK / Άνοιξη με χρήση μοτίβου Singleton

  1. java.lang.Runtime#getRuntime()
  2. java.awt.Desktop#getDesktop()
  3. java.lang.System#getSecurityManager()
  4. Πεδίο εφαρμογής Spring Bean

8. Εναλλακτικές υλοποιήσεις:

Ας δούμε μερικές από τις άλλες δημοφιλείς υλοποιήσεις:

8.1 Enum Singleton

Για να ξεπεράσουμε αυτήν την κατάσταση με το Reflection, μπορούμε να χρησιμοποιήσουμε το Enum για να εφαρμόσουμε το μοτίβο σχεδίασης Singleton. Η Java διασφαλίζει ότι οποιαδήποτε τιμή enum δημιουργείται μόνο μία φορά σε ένα πρόγραμμα Java.

package com.javadevjournal.singleton;

public enum EnumSingleton {

    INSTANCE;

    public static void work() {
        //do something
    }
}

8.2. Μοτίβο Singleton με σειριοποίηση

Σε ορισμένες περιπτώσεις, μπορεί να αναγκαστείτε να εφαρμόσετε τη διασύνδεση Serializable στην κλάση Singleton, ειδικά στα κατανεμημένα συστήματα. Κάθε φορά που αποσειρώνουμε μια τάξη, δημιουργείται μια νέα παρουσία της κλάσης. Αυτό μπορεί να είναι δύσκολο, ειδικά με το μοτίβο Singleton.

import java.io.Serializable;

public class SingletonWithSerialized implements Serializable {

    private static final long serialVersionUID = -xxxxxx;

    private SingletonWithSerialized() {}

    private static class Helper {
        private static final SingletonWithSerialized instance = new SingletonWithSerialized();
    }

    public static SingletonWithSerialized getInstance() {
        return SingletonWithSerialized.instance;
    }
}

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class SerializedSingletonTest {

    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        SingletonWithSerialized instanceOne = SingletonWithSerialized.getInstance();
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "test.ser"));
        out.writeObject(instanceOne);
        out.close();
        //deserailize from file to object
        ObjectInput in = new ObjectInputStream(new FileInputStream("test.ser"));
        SingletonWithSerialized instanceTwo = (SingletonWithSerialized) in.readObject();
        in.close();
        
        System.out.println("instanceOne-- "+instanceOne.hashCode());
        System.out.println("instanceTwo-- "+instanceTwo.hashCode());
    }
}

//output
instanceOne--      1011114567
instanceTwo--      1095644325

Αυτό προκαλεί πρόβλημα καθώς λαμβάνουμε ένα νέο παράδειγμα και ακυρώνουμε τον σκοπό του Singleton. Μπορούμε να το χειριστούμε αυτό εφαρμόζοντας το readResolve().

protected Object readResolve() {
    return getInstance();
}

Περίληψη

Σε αυτό το άρθρο, εξετάζουμε το Μοτίβο σχεδίασης Singleton με τις περιπτώσεις χρήσης του. Καλύψαμε ορισμένα από τα πλεονεκτήματα και τα μειονεκτήματα της χρήσης αυτού του σχεδίου σχεδίου. Στο τελευταίο μέρος, μιλήσαμε για το πότε να χρησιμοποιήσουμε αυτό το μοτίβο σχεδίασης στο πραγματικό σενάριο.

Schreibe einen Kommentar