¿Cómo evitar que el patrón Singleton se refleje, serialice y clone?

Prerrequisito: Patrón Singleton

En este artículo, veremos cuáles son los diversos conceptos que pueden romper la propiedad singleton de una clase y cómo evitarlos. Hay principalmente 3 conceptos que pueden romper la propiedad singleton de una clase. Discutámoslos uno por uno.

  1. Reflexión : se puede hacer que la reflexión destruya la propiedad singleton de la clase singleton, como se muestra en el siguiente ejemplo:

    Java

    // Java code to explain effect of Reflection
    // on Singleton property
      
    import java.lang.reflect.Constructor;
      
    // Singleton class
    class Singleton 
    {
        // public instance initialized when loading the class
        public static Singleton instance = new Singleton();
          
        private Singleton() 
        {
            // private constructor
        }
    }
      
    public class GFG 
    {
      
        public static void main(String[] args)
        {
            Singleton instance1 = Singleton.instance;
            Singleton instance2 = null;
            try
            {
                Constructor[] constructors = 
                        Singleton.class.getDeclaredConstructors();
                for (Constructor constructor : constructors) 
                {
                    // Below code will destroy the singleton pattern
                    constructor.setAccessible(true);
                    instance2 = (Singleton) constructor.newInstance();
                    break;
                }
            }
          
            catch (Exception e) 
            {
                e.printStackTrace();
            }
              
        System.out.println("instance1.hashCode():- " 
                                          + instance1.hashCode());
        System.out.println("instance2.hashCode():- " 
                                          + instance2.hashCode());
        }
    }
    Output:-
    instance1.hashCode():- 366712642
    instance2.hashCode():- 1829164700
    

    Después de ejecutar esta clase, verá que los hashCodes son diferentes, lo que significa que se crean 2 objetos de la misma clase y se destruye el patrón singleton.

    Superar el problema de reflexión: para superar el problema planteado por la reflexión, se utilizan enumeraciones porque Java garantiza internamente que el valor de enumeración se instancia solo una vez. Dado que Java Enums es accesible globalmente, se pueden usar para singletons. Su único inconveniente es que no es flexible, es decir, no permite la inicialización diferida.

    Java

    //Java program for Enum type singleton
    public enum Singleton 
    {
      INSTANCE;
    }

    Como las enumeraciones no tienen ningún constructor, no es posible que Reflection las utilice. Las enumeraciones tienen su constructor predeterminado, no podemos invocarlas nosotros mismos. JVM maneja la creación e invocación de constructores de enumeración internamente. Como las enumeraciones no dan su definición de constructor al programa, no es posible que accedamos a ellas mediante Reflection también. Por lo tanto, la reflexión no puede romper la propiedad singleton en caso de enumeraciones.

  2. Serialización: la serialización también puede provocar la rotura de la propiedad singleton de las clases singleton. La serialización se utiliza para convertir un objeto de flujo de bytes y guardarlo en un archivo o enviarlo a través de una red. Suponga que serializa un objeto de una clase singleton. Luego, si deserializa ese objeto, creará una nueva instancia y, por lo tanto, romperá el patrón de singleton.

    Java

    // Java code to explain effect of 
    // Serialization on singleton classes
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInput;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutput;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
      
    class Singleton implements Serializable 
    {
        // public instance initialized when loading the class
        public static Singleton instance = new Singleton();
          
        private Singleton() 
        {
            // private constructor
        }
    }
      
      
    public class GFG 
    {
      
        public static void main(String[] args) 
        {
            try
            {
                Singleton instance1 = Singleton.instance;
                ObjectOutput out
                    = new ObjectOutputStream(new FileOutputStream("file.text"));
                out.writeObject(instance1);
                out.close();
          
                // deserialize from file to object
                ObjectInput in 
                    = new ObjectInputStream(new FileInputStream("file.text"));
                  
                Singleton instance2 = (Singleton) in.readObject();
                in.close();
          
                System.out.println("instance1 hashCode:- "
                                                     + instance1.hashCode());
                System.out.println("instance2 hashCode:- " 
                                                     + instance2.hashCode());
            
              
            catch (Exception e) 
            {
                e.printStackTrace();
            }
        }
    }
    Output:- 
    instance1 hashCode:- 1550089733
    instance2 hashCode:- 865113938
    

    Como puede ver, el código hash de ambas instancias es diferente, por lo tanto, hay 2 objetos de una clase única. Por lo tanto, la clase ya no es singleton.

    Superar el problema de serialización: – Para superar este problema, tenemos que implementar el método readResolve().

    Java

    // Java code to remove the effect of 
    // Serialization on singleton classes
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInput;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutput;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
      
    class Singleton implements Serializable 
    {
        // public instance initialized when loading the class
        public static Singleton instance = new Singleton();
          
        private Singleton() 
        {
            // private constructor
        }
          
        // implement readResolve method
        protected Object readResolve()
        {
            return instance;
        }
    }
      
    public class GFG 
    {
      
        public static void main(String[] args) 
        {
            try
            {
                Singleton instance1 = Singleton.instance;
                ObjectOutput out 
                    = new ObjectOutputStream(new FileOutputStream("file.text"));
                out.writeObject(instance1);
                out.close();
              
                // deserialize from file to object
                ObjectInput in 
                    = new ObjectInputStream(new FileInputStream("file.text"));
                Singleton instance2 = (Singleton) in.readObject();
                in.close();
              
                System.out.println("instance1 hashCode:- "
                                               + instance1.hashCode());
                System.out.println("instance2 hashCode:- "
                                               + instance2.hashCode());
            
              
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
    Output:- 
    instance1 hashCode:- 1550089733
    instance2 hashCode:- 1550089733
    

    Por encima de ambos códigos hash son iguales, por lo que no se crea ninguna otra instancia.

  3. Clonación: La clonación es un concepto para crear objetos duplicados. Usando clon podemos crear una copia del objeto. Supongamos que creamos un clon de un objeto singleton, luego creará una copia en la que hay dos instancias de una clase singleton, por lo tanto, la clase ya no es singleton.

    Java

    // Java code to explain cloning 
    // issue with singleton
    class SuperClass implements Cloneable
    {
      int i = 10;
      
      @Override
      protected Object clone() throws CloneNotSupportedException 
      {
        return super.clone();
      }
    }
      
    // Singleton class
    class Singleton extends SuperClass
    {
      // public instance initialized when loading the class
      public static Singleton instance = new Singleton();
      
      private Singleton() 
      {
        // private constructor
      }
    }
      
    public class GFG
    {
      public static void main(String[] args) throws CloneNotSupportedException 
      {
        Singleton instance1 = Singleton.instance;
        Singleton instance2 = (Singleton) instance1.clone();
        System.out.println("instance1 hashCode:- "
                               + instance1.hashCode());
        System.out.println("instance2 hashCode:- " 
                               + instance2.hashCode()); 
      }
    }
    Output :- 
    instance1 hashCode:- 366712642
    instance2 hashCode:- 1829164700
    

    Dos hashCode diferentes significan que hay 2 objetos diferentes de clase singleton.

    Superar el problema de clonación: – Para superar este problema, anule el método clone() y lance una excepción del método de clonación que sea CloneNotSupportedException. Ahora, cada vez que el usuario intente crear un clon del objeto singleton, generará una excepción y, por lo tanto, nuestra clase seguirá siendo singleton.

    Java

    // Java code to explain overcome 
    // cloning issue with singleton
    class SuperClass implements Cloneable
    {
      int i = 10;
      
      @Override
      protected Object clone() throws CloneNotSupportedException 
      {
        return super.clone();
      }
    }
      
    // Singleton class
    class Singleton extends SuperClass
    {
      // public instance initialized when loading the class
      public static Singleton instance = new Singleton();
      
      private Singleton() 
      {
        // private constructor
      }
      
      @Override
      protected Object clone() throws CloneNotSupportedException 
      {
        throw new CloneNotSupportedException();
      }
    }
      
    public class GFG
    {
      public static void main(String[] args) throws CloneNotSupportedException 
      {
        Singleton instance1 = Singleton.instance;
        Singleton instance2 = (Singleton) instance1.clone();
        System.out.println("instance1 hashCode:- " 
                             + instance1.hashCode());
        System.out.println("instance2 hashCode:- " 
                             + instance2.hashCode()); 
      }
    }
    Output:-
    Exception in thread "main" java.lang.CloneNotSupportedException
        at GFG.Singleton.clone(GFG.java:29)
        at GFG.GFG.main(GFG.java:38)
    

    Ahora hemos detenido al usuario para crear un clon de la clase singleton. Si no desea lanzar una excepción, también puede devolver la misma instancia desde el método de clonación.

    Java

    // Java code to explain overcome 
    // cloning issue with singleton
    class SuperClass implements Cloneable
    {
      int i = 10;
      
      @Override
      protected Object clone() throws CloneNotSupportedException 
      {
        return super.clone();
      }
    }
      
    // Singleton class
    class Singleton extends SuperClass
    {
      // public instance initialized when loading the class
      public static Singleton instance = new Singleton();
      
      private Singleton() 
      {
        // private constructor
      }
      
      @Override
      protected Object clone() throws CloneNotSupportedException 
      {
        return instance;
      }
    }
      
    public class GFG
    {
      public static void main(String[] args) throws CloneNotSupportedException 
      {
        Singleton instance1 = Singleton.instance;
        Singleton instance2 = (Singleton) instance1.clone();
        System.out.println("instance1 hashCode:- " 
                               + instance1.hashCode());
        System.out.println("instance2 hashCode:- "
                               + instance2.hashCode()); 
      }
    }
    Output:-
    instance1 hashCode:- 366712642
    instance2 hashCode:- 366712642
    

    Ahora, como el código hash de ambas instancias es el mismo, significa que representan una sola instancia.

Este artículo es una contribución de Vishal Garg . Si te gusta GeeksforGeeks y te gustaría contribuir, también puedes escribir un artículo usando write.geeksforgeeks.org o enviar tu artículo por correo a review-team@geeksforgeeks.org. Vea su artículo que aparece en la página principal de GeeksforGeeks y ayude a otros Geeks.

Escriba comentarios si encuentra algo incorrecto o si desea compartir más información sobre el tema tratado anteriormente.

Publicación traducida automáticamente

Artículo escrito por GeeksforGeeks-1 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *