c# - patterns - Refactorización del uso excesivo de Singleton
patrones de diseño c# (7)
Usted (el OP) parece preocupado con el diseño de OO, bueno, lo pondré de esta manera cuando piense en las variables estáticas. El concepto central es la encapsulación y la reutilización; A algunas personas les puede importar menos reutilizar, pero casi siempre quieren la encapsulación. Si se trata de una variable estática, no está realmente encapsulada, ¿o sí? Piense en quién necesita acceder, por qué y hasta dónde puede OCULTARlo del código del cliente. Los buenos diseños a menudo pueden cambiar sus partes internas sin mucho daño para los clientes, eso es lo que desea pensar . Estoy de acuerdo con Scott Meyers (Effective C ++) sobre muchas cosas. OOP va mucho más allá de la palabra clave class. Si nunca has oído hablar de él, busca propiedades: sí, pueden ser estáticas, y C # tiene una muy buena forma de usarlas. A diferencia de usar literalmente una variable estática. Como indiqué al comienzo de este elemento de la lista: piense en cómo no dispararse en el pie más adelante a medida que la clase cambia con el tiempo, eso es algo que muchos programadores no hacen al diseñar las clases.
Eche un vistazo a ese marco Rx que alguien mencionó. El modelo de subprocesamiento a usar, para una situación como la que describió, no es fácil de decidir sin más detalles sobre el caso de uso en mi humilde opinión. Asegúrate de saber lo que estás haciendo con los hilos. Mucha gente no puede descifrar los hilos para salvar sus vidas; no es tan difícil, estar seguro de pisar puede ser al (re) usar el código. Recuerde que los controladores a menudo deben estar separados de los objetos que controlan (p. Ej., No la misma clase); si no lo sabe, busque un libro en MVC y compre una pandilla de cuatro.
Depende de lo que necesites. Para muchas aplicaciones, una clase que está casi completamente llena de datos estáticos, es lo suficientemente buena; como un singleton gratis. Se puede hacer muy OO. A veces preferirías tener varias instancias o jugar con inyección, eso lo hace más complejo.
Sugiero hilos y eventos. La facilidad de hacer que el código conduzca a eventos es en realidad una de las cosas más agradables de C # en mi humilde opinión.
Hmm, matando solos ...
En mi experiencia, muchos de los usos más comunes que los programadores jóvenes hacen de los singletons son poco más que un desperdicio de la palabra clave class. A saber, algo que querían decir como un módulo con estado que se está incorporando a una clase de highlander ; y hay algunas implementaciones singleton malas para que coincidan. Si esto es porque no aprendieron lo que están haciendo, o solo tenían Java en la universidad, no sé. De vuelta en C land, se llama usar datos en el alcance del archivo y exponer una API. En C # (y Java) estás obligado a ser una clase más que muchos idiomas. OOP! = Palabra clave class; Aprende bien el lhs.
Una clase escrita decentemente puede usar datos estáticos para implementar de manera efectiva un singleton, y hacer que el compilador haga todo lo posible para mantenerlo como uno, o como uno que siempre va a obtener de cualquier cosa. NO reemplace los singletons con herencia a menos que sepa seriamente qué diablos está haciendo. La herencia mal hecha de tales cosas, conduce a un código más frágil que sabe mucho. Las clases deben ser tontas, los datos son inteligentes. Eso suena estúpido a menos que mires la declaración profundamente . Usar la herencia en mi humilde opinión para tal cosa, generalmente es algo malo (tm), los idiomas tienen el concepto de módulos / paquetes por una razón.
Si te gusta, oye lo has convertido a singletons hace años, ¿verdad? Siéntese y piense un poco: ¿cómo puedo estructurar mejor esta aplicación, para hacerla funcionar XXX, y luego pensar cómo hacerlo XXX afecta las cosas de manera diferente, por ejemplo, hacerlo de esta manera va a ser una fuente de disputas entre los hilos? ? Puedes revisar muchas cosas en una hora como esa. Cuando crezcas, aprenderás mejores técnicas.
Aquí hay una sugerencia para una forma XXX de comenzar con: (visualizar) escribir (^ Hing) una clase de controlador compuesto, que funciona como administrador sobre los objetos a los que hace referencia. Esos objetos eran sus singletons, no el controlador que los contiene, y no son más que instancias de esas clases. Este no es el mejor diseño para muchas aplicaciones (particularmente puede ser un problema en mi humilde opinión), pero en general resolverá qué es lo que hace que la mayoría de los niños lleguen a un singleton, y funcionará adecuadamente para una amplia gama de programas. Es como el patrón de diseño CS 102. Olvídese del singleton que aprendió en CS 607.
Esa clase controladora, tal vez "Aplicación" sería más adecuada;), básicamente resuelve tu necesidad de singletons y de almacenar la configuración. Cómo hacerlo de manera sublime OO (suponiendo que entiendes OOP) y no dispararse en el pie (nuevamente), es un ejercicio para su propia educación.
Si se muestra, no soy partidario del llamado patrón singleton, particularmente de cómo a menudo se usa indebidamente. Mover una base de código lejos de ella, a menudo depende de la cantidad de refactorización que esté preparado para usar. Los singletons son como variables globales: convenientes pero no de mantequilla. Hmm, creo que pondré eso en mi archivo de citas, tiene una bonita frase para eso ...
Honestamente, usted sabe más sobre el código base y la aplicación en cuestión que cualquier persona aquí. Entonces nadie puede realmente diseñarlo para ti, y el consejo habla menos que la acción, al menos de donde vengo.
Hoy tuve una epifanía, y fue que estaba haciendo todo mal. Un poco de historia: Heredé una aplicación C #, que en realidad era solo una colección de métodos estáticos, un desastre completamente procesal del código C #. Refactoreé esto como lo mejor que sabía en ese momento, trayendo muchos conocimientos de POO post-universidad. Para abreviar, muchas de las entidades en el código han resultado ser Singletons.
Hoy me di cuenta de que necesitaba 3 nuevas clases, cada una de las cuales seguiría el mismo patrón de Singleton para que coincida con el resto del software. Si sigo cayendo por esta pendiente resbaladiza, eventualmente todas las clases en mi aplicación serán Singleton, lo que realmente no será lógicamente diferente del grupo original de métodos estáticos.
Necesito ayuda para repensar esto. Sé sobre Inyección de Dependencia, y esa sería, en general, la estrategia a utilizar para romper la maldición de Singleton. Sin embargo, tengo algunas preguntas específicas relacionadas con esta refactorización, y todo sobre las mejores prácticas para hacerlo.
¿Qué tan aceptable es el uso de variables estáticas para encapsular información de configuración? Tengo un bloqueo cerebral en el uso de estática, y creo que se debe a una clase de OO temprano en la universidad donde el profesor dijo que la estática era mala. Pero, ¿debería volver a configurar la clase cada vez que tengo acceso a ella? Al acceder al hardware, ¿está bien dejar un puntero estático a las direcciones y variables necesarias, o debo realizar continuamente operaciones de
Open()
yClose()
?En este momento tengo un solo método que actúa como controlador. Específicamente, sondeé continuamente varios instrumentos externos (a través de controladores de hardware) para obtener datos. ¿Debería este tipo de controlador ser el camino a seguir, o debería generar hilos separados para cada instrumento al inicio del programa? Si es este último, ¿cómo hago que este objeto esté orientado? ¿Debo crear clases llamadas
InstrumentAListener
eInstrumentBListener
? ¿O hay alguna forma estándar de abordar esto?¿Hay una mejor manera de hacer una configuración global? En este momento, simplemente tengo
Configuration.Instance.Foo
espolvoreado generosamente en todo el código. Casi todas las clases lo usan, así que quizás mantenerlo como Singleton tiene sentido. ¿Alguna idea?Muchas de mis clases son cosas como
SerialPortWriter
oDataFileWriter
, que deben esperar a que lleguen estos datos. Como están activos todo el tiempo, ¿cómo debería organizarlos para escuchar los eventos que se generan cuando entran los datos?
Cualquier otro recurso, libro o comentario sobre cómo alejarse de Singletons y el uso excesivo de otros patrones sería útil.
Bien, esta es mi mejor oportunidad para atacar esta pregunta:
(1) Estática
El problema con la static
que puede estar teniendo es que significa cosas diferentes en .NET y decir, C ++. Estático básicamente significa que es accesible en la clase misma. En cuanto a su id de aceptabilidad, dicen que es más de algo que usarías para hacer operaciones no específicas de instancia en una clase. O solo cosas generales como Math.Abs(...)
. Lo que debe usar para una configuración global es probablemente una propiedad accedida estáticamente para mantener la configuración actual / activa. También tal vez algunas clases estáticas para cargar / guardar configurando la configuración, sin embargo, la configuración debe ser un Objeto para que pueda ser pasada por manipulada, etc. public class MyConfiguration {public const string DefaultConfigPath = "./config.xml";
protected static MyConfiguration _current;
public static MyConfiguration Current
{
get
{
if (_current == null)
Load(DefaultConfigPath);
return _current;
}
}
public static MyConfiguration Load(string path)
{
// Do your loading here
_current = loadedConfig;
return loadedConfig;
}
// Static save function
//*********** Non-Static Members *********//
public string MyVariable { get; set; }
// etc..
}
(2) Controlador / Hardware
Probablemente debería considerar un enfoque reactivo, IObserver<>
o IObservable<>
, es parte del Reactive Framework (Rx) .
Otro enfoque es utilizar un ThreadPool para programar sus tareas de sondeo, ya que puede obtener una gran cantidad de subprocesos si tiene mucho hardware para agrupar. Antes de usar cualquier tipo de Threading, asegúrese de aprender mucho al respecto. Es muy fácil cometer errores que quizás ni siquiera te des cuenta. Este libro es una excelente fuente y te enseñará mucho.
De cualquier manera, probablemente debas construir servicios (solo un nombre realmente) para administrar tu hardware que son responsables de recopilar información sobre un servicio (esencialmente un patrón de modelo). Desde allí, su controlador central puede usarlos para acceder a los datos que mantienen la lógica del programa en el controlador y la lógica del hardware en el servicio.
(3) Configuración global
Puede que haya tocado este tema en el punto # 1, pero generalmente es allí donde vamos, si te das cuenta que estás escribiendo demasiado, siempre puedes sacarlo de ahí asumiendo que la .Instance
es un objeto.
MyConfiguration cfg = MyConfiguration.Current
cfg.Foo // etc...
(4) Escuchando datos
Una vez más, el marco reactivo podría ayudarlo, o podría construir un modelo impulsado por eventos que use desencadenantes para los datos entrantes. Esto asegurará que no esté bloqueando un hilo hasta que ingresen los datos. Puede reducir enormemente la complejidad de su aplicación.
Dado que usted conoce la Inyección de dependencia, ¿ha considerado usar un contenedor IoC para administrar la vida útil? Ver mi respuesta a una pregunta sobre clases estáticas.
Gran pregunta Algunos pensamientos rápidos de mí ...
static
en C # solo debe usarse para datos que sean exactamente iguales para todas las instancias de una clase dada. Como actualmente estás atrapado en el infierno de Singleton, solo tienes una instancia de todo, pero una vez que salgas de ella, esta es la regla general (al menos, es para mí). Si comienza a enhebrar sus clases, es posible que desee reducir el uso de estática porque entonces tiene posibles problemas de concurrencia, pero eso es algo que puede abordarse más adelante.
No estoy seguro de cómo funciona realmente tu hardware, pero suponiendo que haya alguna funcionalidad básica que sea la misma para todos (como, por ejemplo, cómo interactúas con ellos en un nivel de datos brutos o similar), esta es una instancia perfecta para crear un jerarquía de clase La clase base implementa el nivel bajo / similar con métodos virtuales para que las clases descendientes lo sobrescriban para interpretar los datos de manera correcta / alimentarlos hacia adelante / lo que sea.
Buena suerte.
Me limito a un máximo de dos singletons en una aplicación / proceso. Uno se suele llamar SysConfig y contiene cosas que de otro modo podrían terminar como variables globales u otros conceptos corruptos. No tengo un nombre para el segundo porque, hasta ahora, nunca llegué a mi límite. :-)
Las variables de miembros estáticos tienen sus usos, pero los veo cuando veo a los proctólogos. Un salvavidas cuando lo necesite, pero las probabilidades deberían ser "de un millón a uno" (referencia de Seinfeld) de que no puede encontrar una mejor manera de resolver el problema.
Cree una clase de instrumento base que implemente un oyente con subprocesos. Las clases derivadas de eso tendrían controladores específicos del instrumento, etc. Crea una instancia de una clase derivada para cada instrumento y luego almacena el objeto en un contenedor de algún tipo. En el momento de la limpieza simplemente itera a través del contenedor. Cada instancia de instrumento debe construirse pasando información de registro sobre dónde enviar su salida / estado / lo que sea. Usa tu imaginación aquí. Las cosas de OO se vuelven bastante poderosas.
Recientemente tuve que enfrentar un problema similar, y lo que hice me pareció funcionar bien, tal vez lo ayude:
(1) Agrupe toda la información "global" en una sola clase. Vamos a llamarlo Configuration
.
(2) Para todas las clases que solían utilizar estos objetos estáticos, cámbielos (en última instancia) a heredar de una nueva clase base abstracta que se asemeje más o menos a
abstract class MyBaseClass {
protected Configuration config; // You can also wrap it in a property
public MyBaseClass(Configuration config) {
this.config = config;
}
}
(3) Cambie todos los constructores de clases que se derivan de MyBaseClass
consecuencia. Luego solo crea una instancia de Configuration
al inicio y pásala a todas partes.
Contras:
- Necesita refactorizar muchos de sus constructores y cada lugar en el que se llaman
- Esto no funcionará bien si no deriva sus clases de nivel superior de Object. Bueno, puedes agregar el campo de
config
a la clase derivada, es menos elegante.
Pros
- No hay mucho esfuerzo para simplemente cambiar la herencia y los constructores, y bang - puedes cambiar toda la
Configuration.Instance
.config
conconfig
. - Te deshaces de las variables estáticas por completo; así que no hay problemas ahora si, por ejemplo, su aplicación se convierte de pronto en una biblioteca y alguien intenta invocar múltiples métodos al mismo tiempo o lo que sea.
para empezar, puede limitar el uso de singleton a través del patrón "Registro", lo que significa que tiene un singleton que le permite acceder a un montón de otros objetos preconfigurados.
Esto no es una "solución" sino una mejora, hace que los muchos objetos que son únicos sean un poco más normales y comprobables. por ejemplo ... (ejemplo totalmente inventado)
HardwareRegistry.SerialPorts.Serial1.Send("blah");
pero el verdadero problema parece ser que estás luchando por crear un conjunto de objetos que funcionen bien juntos. Hay dos tipos de pasos en OO ... configurar objetos y dejar que los objetos hagan lo suyo.
así que tal vez mire cómo puede configurar objetos que no sean singleton para que funcionen juntos y luego colgarlos de un registro.
Estático: -
Hay muchas excepciones a las reglas aquí, pero en general, evítelas, pero es útil para hacer singletons y crear métodos que hagan cálculos "generales" fuera del contexto de un objeto. (como Math.Min)
Monitoreo de datos: -
a menudo es mejor hacer lo que sugiere, crear un hilo con un montón de objetos preconfigurados que harán su monitoreo. Utilice el paso de mensajes para comunicarse entre subprocesos (a través de una cola segura de subprocesos) para limitar los problemas de bloqueo de subprocesos. Use el patrón de registro para acceder a los recursos de hardware.
quieres algo así como un InstrumentListner que use un InstrumentProtocol (que subclases para cada protocolo) no lo sé, LogData. El patrón de comando puede ser útil aquí.
Configuración:-
tenga su información de configuración y use algo como el patrón "generador" para traducir su configuración en un conjunto de objetos configurados de una manera particular. es decir, no hagas que tus clases tomen conciencia de la configuración, crea un objeto que configure los objetos de una manera particular.
Puertos seriales :-
Hago un montón de trabajo con estos, lo que tengo es una conexión en serie, que genera una secuencia de caracteres que se publica como un evento. Luego tengo algo que interpreta la secuencia de protocolo en comandos significativos. Mis clases de protocolo funcionan con una "IConnection" genérica de la cual una SerialConnection hereda ..... También tengo TcpConnections, MockConnections, etc., para poder inyectar datos de prueba, o canalizar puertos serie de una computadora a otra, etc. Las clases de protocolo simplemente interpretan una secuencia, tienen una máquina statemachine y envían comandos. El protocolo está preconfigurado con una conexión. Varias cosas se registran con el protocolo, de modo que cuando tenga datos significativos se activarán y harán lo suyo. Todo esto está construido desde una configuración al principio, o reconstruido sobre la marcha si algo cambia.