Inicio > Apunte, C++, Independiente > Clases singleton

Clases singleton


A lo largo de todo el tiempo que C++ lleva de existencia, han surgido muchos métodos de diseño. Al principio, era una mezcla rara de C con C++. Posteriormente y conforme la orientación a objetos iba ganando adeptos, surgieron nuevas técnicas. En particular, los llamados "patrones de diseño". Estos son simples directivas para crear software –clases por lo general– que resuelven ciertos problemas particulares. Cómo y cuándo aplicar un patrón es el objetivo.

Quizás el patrón de diseño más sencillo es la clase llamada singleton. En esencia, una clase singleton es una que garantiza que solo podrá haber una instancia de ésta durante toda la ejecución de la aplicación. Ese de hecho es el patrón: una instancia, no más.

¿Para qué nos sirve un singleton? En esencia, es como si fuera una variable global. Una instancia solamente, accesible siempre. Pero a diferencia de una variable global, es orientada a objetos, lo cuál implica que podemos sacar ventaja a –por ejemplo– la encapsulación. En lugar de acceder directamente a la variable, lo hacemos a través de los métodos que ofrece la clase. Pero… también podríamos crear una clase que encapsule el valor, y tener una instancia de ésta en una variable global. ¿De qué nos sirve, entonces, el condenado singleton?

Un ejemplo, el más común para emplear un singleton: un archivo de configuración de nuestra aplicación. Ciertamente en uno de estos casos, la información tiene que ser accesible a toda la aplicación. Pero pensemos. Supongamos que tenemos un proceso que hace uso de esa información. Corremos el proceso y después cambiamos la configuración del archivo. Al tener varias instancias, tendríamos que asegurarnos de que nuestro proceso vuelva a leer dichos valores, lo cuál es una pérdida de tiempo. Pero para eso tenemos nuestra variable global, ¿cierto? Basta con acceder a dicha variable global.

Pero supongamos ahora que nuestro proceso corre en un hilo separado. Entonces sí estamos en problemas, porque podríamos tener desincronizada nuestra variable. Y en este caso, ni siquiera el empleo de volatile nos salvaría. Más aún, si hay varias instancias de la clase los valores quedarían completamente desincronizados, pudiendo ser que un proceso tenga valores totalmente distintos a otro proceso. Mala cosa.

Aquí es donde entra nuestro singleton al rescate. Por lo antes expuesto, nos aseguramos de que solo haya una instancia. Y aquí mismo podríamos controlar la sincronización entre hilos con suma facilidad.

Otra razón más se me ocurre. Imaginemos que la configuración del sistema no sale de un archivo, sino a través de un procedimiento más complicado, como conectarse a una base de datos remota, a otro sistema (un ERP como SAP) o a un servicio web. Cada vez que obtuviésemos los datos gastaríamos tiempo de proceso y memoria. Con el singleton ya no sería necesario más que una sola vez para obtener los datos, y otra para guardarlos. Más aún, ¿qué pasa en el caso de que bajo ciertas condiciones, la aplicación no necesitase leer dicho archivo? ¡Estaríamos leyéndolo a lo güey! otra de las características de un singleton es que se instancia solamente la primera vez que se necesita. Problema resuelto.

Si no te convencen las razones para emplear el singleton, puedes suspender la lectura en este punto e ir a ver algún otro artículo. Si sí, a continuación presento cómo sería el singleton. Siguiendo el ejemplo de nuestro archivo de configuración, presento una clase singleton de este tipo.

class configuracion
{
    private:
        string _val1;
        string _val2;
        string _val3;

        configuracion()
        {
            cargar(); 
        }

    public:
        ~configuracion()
        {
            guardar();
        }

        void cargar()
        {
            // lee la configuración desde el archivo
        }

        void guardar()
        {
            // guarda la configuración al archivo
        }

        string get_val1() const { ... }
        string get_val2() const { ... }
        string get_val3() const { ... }
        void set_val1(const string& val1) { ... }
        void set_val2(const string& val2) { ... }
        void set_val3(const string& val3) { ... }

        static configuracion& instancia()
        {
            static configuracion config;
            return config;
        }
};

Lo primero que notarás es que el constructor es privado. En efecto, de esta forma nos aseguramos que nadie pueda crear una instancia más que la clase misma. Por lo mismo, tenemos que crear un método público y eventualmente estático que nos genere la única instancia que va a haber corriendo en la aplicación. ¿Cómo lo consigue? Sencillo. Creamos una variable estática dentro del método. Esta instancia se iniciará la primera vez que se llame al método estático, con lo cuál cumplimos otro de los requisitos. Y al ser una referencia, nos aseguramos de que el objeto se destruye al finalizar la aplicación. Así, su utilización sería de la siguiente forma:

configuracion& config = configuracion::instancia();
string val2 = config.get_val2();
config.set_val1("hola");
config.guardar(); // si queremos guardar en este momento, claro está

Una cuestión. Notarás que instancia() regresa una referencia. ¿Por qué no un puntero? Sería más adecuado, y menos engorroso. Posiblemente. Pero entonces algún novato podría hacer lo siguiente:

configuración* pconfig = configuracion::instancia();
delete pconfig;

Sobra decir que estamos fritos. Entonces, por cuestiones de seguridad, mejor regresamos una referencia, ya que hacer un delete sobre la referencia produciría un error en tiempo de compilación.

Por supuesto, hay varias formas de realizar un singleton. He presentado la más común. Podrías emplear asignación dinámica de memoria y regresar una referencia para evitar el problema anterior. En cuyo caso tendrías el problema de en qué momento hacer el delete del objeto. Lo podrías poner en el destructor, pero te arriesgas a un comportamiento indefinido. O podrías hacer un "envoltorio" estilo el std:.auto_ptr. Creo yo, sin embargo, que esta es la forma más sensata y la más usual. De emplear otra, deberías tener buenas razones para ello. Y con suerte, estas otras formas las trataremos en otros temas. Mientras, ya sabes qué es un singleton, para qué sirve, y cómo emplearlo.

Categorías:Apunte, C++, Independiente Etiquetas: ,
  1. Aún no hay comentarios.
  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s