design - patrones - uso de singleton c#
¿Cuándo NO se debe usar el patrón Singleton?(Además de lo obvio) (8)
Sé bien que quiere usar Singleton para proporcionar un punto de acceso global a algún estado o servicio. Los beneficios del patrón de Singleton no necesitan ser enumerados en esta pregunta.
Lo que me interesa son las situaciones en las que Singleton podría parecer una buena opción al principio, pero podría volver a morderte. Una y otra vez, he visto autores en libros y carteles en SO que dicen que el patrón de Singleton a menudo es una muy mala idea.
The Gang of Four afirma que querrás usar Singleton cuando:
- debe haber exactamente una instancia de una clase, y debe ser accesible para los clientes desde un punto de acceso bien conocido.
- cuando la única instancia debe ser extensible mediante subclases, y los clientes deben poder usar una instancia extendida sin modificar su código.
Estos puntos, aunque ciertamente notables, no son los prácticos que busco.
¿Alguien tiene un conjunto de reglas o advertencias que usted usa para evaluar si realmente está realmente seguro de que quiere usar un Singleton?
Los mayores errores con Singleton que he visto son que está diseñando un sistema de un solo usuario (por ejemplo, un programa de escritorio) y usa Singleton para muchas cosas (por ejemplo, Configuración) y luego desea convertirse en multiusuario, como un sitio web o un servicio.
Es similar a lo que sucedió con las funciones C con búferes estáticos internos cuando se usaron en programas de subprocesos múltiples.
No lo evitaría por completo en ningún diseño. Sin embargo, uno debe tener cuidado con su uso. Puede convertirse en el objeto de Dios en muchos escenarios y, por lo tanto, vencer el propósito.
Tenga en cuenta que este patrón de diseño es una solución para resolver algunos problemas, pero no todos. De hecho, es lo mismo para todos los patrones de diseño.
No me considero un programador experimentado, pero mi opinión actual es que en realidad no necesita el Singleton ... sí, parece más fácil trabajar con él al principio (de manera similar a los globales), pero luego viene el "oh mi "momento en que uno necesita otra instancia".
Siempre se puede pasar o inyectar la instancia, realmente no veo una situación donde sería significativamente más fácil o necesario usar Singleton
Incluso si rechazamos todo, aún queda la cuestión de la capacidad de prueba del código
Singleton a menudo se utiliza como una forma de atrapar todo para cosas que las personas no pueden molestarse en encapsular adecuadamente en el lugar donde realmente se necesita, con los accesorios adecuados.
El resultado final es un tarball que finalmente reúne todas las static
en todo el sistema. ¿Cuántas personas aquí NUNCA han visto una clase llamada Globals
en un supuesto código OOD con el que han tenido que trabajar? Ugh.
Una palabra: testing
Una de las características distintivas de la capacidad de prueba es un acoplamiento flexible de las clases, que le permite aislar una sola clase y probarla por completo. Cuando una clase usa un singleton (y estoy hablando de un singleton clásico, uno que impone su propia singularidad a través de un método estático getInstance ()), el usuario singleton y el singleton se unen inextricablemente. Ya no es posible probar al usuario sin probar también el singleton.
Los Singleton son un desastre para probar. Como son estáticos, no se pueden eliminar con una subclase. Dado que son globales, no se puede cambiar fácilmente la referencia a la que apuntan sin una recompilación o levantamiento de objetos pesados. Cualquier cosa que use el singleton mágicamente obtiene una referencia global a algo que es difícil de controlar. Esto hace que sea difícil limitar el alcance de una prueba.
Vea la primera respuesta en la discusión " Qué es alternativo a Singleton ".
Yo diría que evite los singleton a toda costa. Restringe la escala de las aplicaciones. Analice realmente el problema con el que se enfrenta y piense en la escalabilidad y tome decisiones en función de qué tan escalable desee su aplicación.
Al final del día, un singleton actúa como un cuello de botella de recursos si está diseñado incorrectamente.
Algunas veces introduce este cuello de botella sin entender completamente las implicaciones de hacerlo en su aplicación.
Me he encontrado con problemas cuando se trata de aplicaciones de subprocesos múltiples que intentan acceder a un recurso singleton, pero se meten en interbloqueos. Es por eso que trato de evitar un singleton tanto como sea posible.
Si introduce singletons en su diseño, asegúrese de comprender las implicaciones de tiempo de ejecución, hacer algunos diagramas y descubrir dónde podría causar un problema.
Versión resumida:
¿Sabes con qué frecuencia usas los globals? Ok, ahora usa Singletons AUN MENOS. Mucho menos de hecho. Casi nunca. Comparten todos los problemas que tienen los globales con el acoplamiento oculto (que afecta directamente la capacidad de prueba y la capacidad de mantenimiento), y con frecuencia la restricción de "solo uno puede existir" es en realidad una suposición errónea.
Respuesta detallada:
Lo más importante que hay que saber sobre un singleton es que es un estado global. Es un patrón para exponer una sola instancia de acceso global sin mitigación . Esto tiene todos los problemas de programación que tienen los globales, pero también adopta algunos detalles de implementación nuevos e interesantes y, de lo contrario, muy poco valor real (o, de hecho, puede tener un costo adicional innecesario con el aspecto de instancia única). La implementación es lo suficientemente diferente como para que la gente a menudo la confunde con un método de encapsulación orientado a objetos cuando en realidad es solo una instancia global sofisticada.
La única situación en la que debe considerar un singleton es cuando tener más de una instancia de datos ya globales sería en realidad un error de acceso lógico o de hardware. Incluso entonces, por lo general, no se debe tratar directamente con el singleton, sino que se debe proporcionar una interfaz de contenedor que se pueda instanciar tantas veces como se necesite, pero solo se accede al estado global. De esta manera, puede continuar usando la inyección de dependencia y si puede desmarcar el estado global del comportamiento de la clase, no es un cambio radical en su sistema.
Sin embargo, existen problemas sutiles cuando parece que no se depende de datos globales, pero sí lo está. De modo que (usar la inyección de dependencia de la interfaz que envuelve el singleton) es solo una sugerencia y no una regla. En general, es aún mejor porque al menos se puede ver que la clase se basa en el singleton, mientras que el uso de la función :: instance () dentro del vientre de una función miembro de la clase oculta esa dependencia. También le permite extraer clases confiando en el estado global y hacer mejores pruebas de unidad para ellos, y puede pasar en simulacros de objetos que no hacen nada, donde si se basa en el singleton directamente en la clase, esto es MUCHO más difícil.
Cuando se hornea una llamada singleton :: instance que también se crea una instancia en una clase, haces que la herencia sea imposible . Las soluciones alternativas suelen romper la parte de "instancia única" de un singleton. Considere una situación en la que tenga varios proyectos que dependan del código compartido en una clase de NetworkManager. Incluso si desea que este NetworkManager sea un estado global y una instancia única, debe ser muy escéptico sobre cómo convertirlo en un singleton. Al crear un singleton simple que se ejemplifica a sí mismo, básicamente hace que sea imposible que cualquier otro proyecto se derive de esa clase.
Muchos consideran que el ServiceLocator es un anti-patrón, sin embargo, creo que es un medio paso mejor que el Singleton y efectivamente eclipsa el propósito del patrón Go4. Hay muchas formas de implementar un localizador de servicios, pero el concepto básico es que divide la construcción del objeto y el acceso del objeto en dos pasos. De esta forma, en tiempo de ejecución, puede conectar el servicio derivado apropiado y luego acceder a él desde un único punto de contacto global. Esto tiene el beneficio de un orden de construcción de objeto explícito, y también le permite derivar de su objeto base. Esto sigue siendo malo para la mayoría de las razones indicadas, pero es menos malo que el Singleton y es un reemplazo inmediato.
Un ejemplo específico de singleton aceptable (léase: servicelocator) puede ser envolviendo una interfaz de estilo c de instancia única como SDL_mixer. Un ejemplo de un singleton a menudo ingenuamente implementado donde probablemente no debería estar es en una clase de registro (¿qué sucede cuando usted desea iniciar sesión en la consola Y en el disco? O si desea registrar subsistemas por separado).
Sin embargo, los problemas más importantes de confiar en el estado global siempre surgen cuando intentas implementar pruebas unitarias adecuadas (y deberías tratar de hacer eso). Se vuelve mucho más difícil lidiar con su aplicación cuando las entrañas de las clases a las que realmente no tiene acceso intentan hacer una escritura y lectura sin límites, conectarse a servidores en vivo y enviar datos reales, o disparar el sonido de sus parlantes willy nilly. Es mucho, MUCHO, mejor usar inyección de dependencia para que puedas simular una clase de no hacer nada (y ver que tienes que hacer eso en el constructor de la clase) en caso de un plan de prueba y apuntar a eso sin tener que adivinar todo el estado global de su clase depende.
Enlaces relacionados:
- Fragilidad invocada por Global State y Singletons
- Inyección de dependencia para evitar Singletons
- Fábricas y Singletons
Uso del patrón frente a la aparición
Los patrones son útiles como ideas y términos, pero desafortunadamente las personas parecen sentir la necesidad de "usar" un patrón cuando realmente se implementan los patrones según lo requiera la necesidad. A menudo, el singleton específicamente se calza simplemente porque es un patrón comúnmente discutido. Diseñe su sistema con una conciencia de los patrones, pero no diseñe su sistema específicamente para doblegarse solo porque existan. Son herramientas conceptuales útiles, pero así como no usa todas las herramientas en la caja de herramientas solo porque puede, no debería hacer lo mismo con los patrones. Úselos según sea necesario y no más ni menos.
Ejemplo de Localizador de Servicio de Instancia Única
#include <iostream>
#include <assert.h>
class Service {
public:
static Service* Instance(){
return _instance;
}
static Service* Connect(){
assert(_instance == nullptr);
_instance = new Service();
}
virtual ~Service(){}
int GetData() const{
return i;
}
protected:
Service(){}
static Service* _instance;
int i = 0;
};
class ServiceDerived : public Service {
public:
static ServiceDerived* Instance(){
return dynamic_cast<ServiceDerived*>(_instance);
}
static ServiceDerived* Connect(){
assert(_instance == nullptr);
_instance = new ServiceDerived();
}
protected:
ServiceDerived(){i = 10;}
};
Service* Service::_instance = nullptr;
int main() {
//Swap which is Connected to test it out.
Service::Connect();
//ServiceDerived::Connect();
std::cout << Service::Instance()->GetData() << "/n" << ((ServiceDerived::Instance())? ServiceDerived::Instance()->GetData() :-1);
return 0;
}