2012-02-21 18 views
7

En un proyecto existente, he de heredar una clase de controlador (MVC) declarada como Singleton para definir mi propio tratamiento. ¿Cómo derivar apropiadamente esta clase de Singleton?C++ clase Singleton - buenas prácticas de herencia

Primero, amplío el contexto y la necesidad de esta herencia.

La aplicación que agregué al software existente desea usar un módulo MVC que realiza casi la misma tarea que la que estoy dispuesto a realizar. Está utilizando los mismos métodos hasta la firma y ligeras modificaciones. Reescribir mi propio módulo MVC definitivamente sería una duplicación de código. El módulo existente está intrínsecamente orientado hacia su aplicación a otra parte del software, y no puedo simplemente usar el mismo módulo. Pero está escrito como un patrón Model-View-Controller donde Controller es Singleton. Ya saqué View.

En segundo lugar, tengo dudas de que pueda derivar Classically clase Singleton.

El constructor de llamadas de la clase heredada simplemente llama a getinstance() para la clase padre y no devuelve un objeto de la clase derivada (?).

Tercero, así es como veo una manera de lidiar. Por favor comenta/ayúdame a mejorar!

Copio toda la clase Singleton en una clase que podría llamar AbstractController. Derivo esta clase dos veces. El primer hijo es soltero y adopta el tratamiento completo de la clase de padres. El segundo hijo es el Controlador para mi parte de la aplicación, con un tratamiento propio y redefinido.

Gracias!

+0

En su mayor parte, si una clase tiene una clase base que es singleton, entonces es completamente inútil: nunca puede crear ninguna instancia de su clase derivada, porque cada instancia también sería una instancia de la clase base, y no puedes hacer otra de esas. (Puedo imaginar excepciones "razonables", pero solo en el caso en que la clase base se diseñó * específicamente * con la idea de que se tenga en cuenta ...) – Hurkyl

Respuesta

3

No estoy seguro de entender la situación con la que se trata completamente, y si es posible o apropiado derivar del singleton depende mucho de cómo se implemente el singleton.

Pero ya que ha mencionado "buenas prácticas" que hay algunos puntos generales que vienen a la mente al leer la pregunta:

  1. herencia no suele ser la mejor herramienta para lograr la reutilización de código. Ver: Prefer composition over inheritance?

  2. El uso de singleton y "buenas prácticas" generalmente no van de la mano. Ver: What is so bad about singletons?

Espero que ayude.

21

La verdad es que los singletons y la herencia no funcionan bien juntos.

Sí, sí, los amantes Singleton y GoF culto será todo sobre mí para esto, diciendo "bueno, si usted hace su constructor protegido ..." y "no hacer han de tener un método getInstance en la clase, puedes ponerlo ... ", pero solo están demostrando mi punto. Los Singletons tienen que saltar a través de una serie de aros para ser tanto un singleton como una clase base.

Pero solo para responder a la pregunta, digamos que tenemos una clase base singleton. Incluso puede, hasta cierto punto, hacer cumplir su soltería a través de la herencia. (El constructor hace una de las pocas cosas que pueden funcionar cuando ya no puede ser privado: arroja una excepción si ya existe otro Base). Digamos que también tenemos una clase Derived que hereda de Base.Dado que estamos permitiendo la herencia, digamos que puede haber cualquier cantidad de otras subclases de Base, que pueden o no heredar de Derived.

Pero hay un problema: el mismo con el que ya se está encontrando, o lo hará pronto. Si llamamos al Base::getInstance sin haber construido un objeto, obtendremos un puntero nulo. Nos gustaría recuperar cualquier objeto singleton que exista (puede ser un Base, y/o un Derived, y/o un Other). Pero es difícil hacerlo y seguir todas las reglas, porque solo hay un par de maneras de hacerlo, y todas tienen algunos inconvenientes.

  • Podríamos simplemente crear un Base y devolverlo. Tornillo Derived y Other. Resultado final: Base::getInstance() siempre devuelve exactamente un Base. Las clases de niños nunca llegan a jugar. Un poco derrota el propósito, IMO.

  • Podríamos poner un getInstance de nuestra propia en nuestra clase derivada, y tener la persona que llama dice Derived::getInstance() si quieren específicamente un Derived. Esto aumenta significativamente el acoplamiento (porque una persona que llama ahora tiene que saber para solicitar específicamente un Derived, y termina uniéndose a esa implementación).

  • Podríamos hacer una variante de esta última, pero en lugar de obtener la instancia, la función solo crea una. (Mientras estamos en ello, cambiemos el nombre de la función a initInstance, ya que no nos interesa especialmente lo que sucede, solo lo llamamos para que cree un nuevo Derived y lo establece como la instancia de una sola instancia).

Así que (salvo alguna rareza en paradero desconocido aún), funciona un poco como esto ...

class Base { 
    static Base * theOneTrueInstance; 

    public: 
    static Base & getInstance() { 
     if (!theOneTrueInstance) initInstance(); 
     return *theOneTrueInstance; 
    } 
    static void initInstance() { new Base; } 

    protected: 
    Base() { 
     if (theOneTrueInstance) throw std::logic_error("Instance already exists"); 
     theOneTrueInstance = this; 
    } 

    virtual ~Base() { } // so random strangers can't delete me 
}; 

Base* Base::theOneTrueInstance = 0; 


class Derived : public Base { 
    public: 
    static void initInstance() { 
     new Derived; // Derived() calls Base(), which sets this as "the instance" 
    } 

    protected: 
    Derived() { } // so we can't be instantiated by outsiders 
    ~Derived() { } // so random strangers can't delete me 
}; 

Y en su código de inicio, que dicen Base::initInstance(); o Derived::initInstance();, dependiendo de lo que escriba quiero que el singleton sea. Tendrá que emitir el valor de retorno desde Base::getInstance() para usar cualquier función específica de Derived, por supuesto, pero sin conversión puede usar cualquier función definida por Base, incluidas las funciones virtuales anuladas por Derived.

Tenga en cuenta que esta forma de hacer que también tiene una serie de inconvenientes propios, sin embargo:

  • Se pone la mayor parte de la carga de la aplicación de la soltería en la clase base. Si la base no tiene esta o una funcionalidad similar, y no puedes cambiarla, estás medio jodido.

  • La clase base no puede tomar toda de la responsabilidad, sin embargo - cada clase tiene que declarar un destructor protegido, o alguien podría venir y eliminar el único caso que después de la fundición (en) adecuadamente y todo se va al infierno Lo que es peor, esto no puede ser aplicado por el compilador.

  • Debido a que estamos utilizando destructores protegidos para evitar que algunos idiotas aleatorios eliminen nuestra instancia, a menos que el compilador sea más inteligente de lo que me temo, incluso el tiempo de ejecución no podrá eliminar correctamente su instancia cuando termine el programa. Adiós, RAII ... hola advertencias "detección de fuga de memoria". (Por supuesto, la memoria finalmente será reclamada por cualquier SO decente).Pero si el destructor no se ejecuta, no puede depender de él para hacer la limpieza por usted. Tendrá que llamar a una función de limpieza de algún tipo antes de salir, y eso no le proporcionará las mismas garantías que RAII puede proporcionarle)

  • Expone un método initInstance que, IMO, doesn Realmente pertenecen a una API que todos pueden ver. Si lo desea, puede hacer que initInstance sea privado y deje que su función init sea friend, pero luego su clase está haciendo suposiciones sobre el código fuera de sí mismo, y la cosa de acoplamiento vuelve a entrar en juego.

También tenga en cuenta que el código anterior no es en absoluto seguro para subprocesos. Si lo necesitas, estás solo.

En serio, la ruta menos dolorosa es olvidar tratar de imponer la soltería. La forma menos complicada de asegurarse de que solo hay una instancia es solo crear uno. Si necesita usarlo en varios lugares, considere la posibilidad de inyección de dependencia. (La versión no marco de eso equivale a "pasar el objeto a las cosas que lo necesitan"): P) Fui y diseñé las cosas anteriores solo para probar y probar que estoy equivocado sobre singletons y herencia, y me reafirmé a mí mismo cómo el mal la combinación es No recomendaría hacerlo en realidad en código real.

+1

Esta es una respuesta excelente, minuciosamente detallada y tiene un lenguaje colorido y humor Es increíble que haya volado bajo el radar SO. –