Interface de C#: Entendiendo la Conexión del Formulario de Premio (Tim Corey, Lección 09)
En la serie "C# App Start To Finish" de Tim Corey, la Lección 09 se centra en conectar un formulario de premios. En apariencia, este formulario parece simple: solo reunir la entrada del usuario, validarla, crear un modelo y guardarlo. Pero Tim explica que la verdadera complejidad radica en decidir dónde guardar los datos: una base de datos, un archivo de texto o ambos. El video de Tim nos guía a través de la solución introduciendo un concepto clave en la programación C#: interfaces.
En este artículo, vamos a profundizar en las interfaces a través de la explicación de Tim para que puedas entender mejor cómo ayudan a crear aplicaciones escalables y mantenibles.
El Problema: ¿Dónde Guardamos los Datos?
Tim comienza indicando el propósito del formulario de premios: acepta la entrada, la valida y la guarda en almacenamiento. Pero advierte que la parte complicada es decidir dónde almacenar los datos. Él enfatiza que los tutoriales a menudo omiten esto porque no es fácil, pero quiere que los aprendices lo enfrenten directamente.
Él explica que inicialmente, podrías intentar una solución simple: verificar si estás usando SQL o un archivo de texto, luego ejecutar el proceso de guardado correcto. Pero Tim rápidamente muestra lo feo y poco mantenible que se vuelve esto. Si cada formulario tiene que verificar qué tipo de almacenamiento usar, el código se duplica, se desordena y se dificulta cambiarlo.
La Manera Fea: Condiciones Codificadas
Tim traza un ejemplo de pseudo-código. Él explica que podrías comenzar comprobando un valor Booleano como usingSQL == true, luego abriendo una conexión de base de datos, guardando el modelo y devolviéndolo con un ID. Luego podrías hacer lo mismo para archivos de texto, generando manualmente un ID porque los archivos de texto no lo hacen automáticamente.
Él señala que esto rápidamente se vuelve repetitivo. Múltiples formularios necesitan esta lógica, y cada vez que agregas una nueva fuente de datos como MySQL, debes actualizar cada formulario. Tim llama a esto "no escalable" y enfatiza que viola el principio "DRY" (Don't Repeat Yourself). Él afirma claramente: "Tiene que haber una mejor manera."
Tirando del Hilo: El Mejor Enfoque
Tim introduce su estrategia: tirar del hilo. Él comienza preguntando qué información necesita el código y de dónde proviene. Él identifica dos preguntas clave:
¿Cómo sabemos qué fuente de datos usar?
¿Cómo conectamos a dos fuentes de datos diferentes para hacer la misma tarea?
Tim explica que el acto real de guardar es la única cosa que difiere. Desde la perspectiva del formulario, solo necesita decir: "Aquí está el modelo. Guárdalo." El formulario no debería preocuparse si está guardando en SQL o un archivo de texto.
Solución: Configuración Global + Interfaz
Tim sugiere un sistema de configuración global. Él dice que para saber qué fuente de datos usar, la aplicación necesita datos accesibles globalmente, y propone usar una clase estática para almacenar esta información. Reconoce que generalmente se evitan las variables globales, pero en este caso, los datos globales son exactamente lo que se necesita.
A continuación, Tim explica el concepto clave: interfaces. Define una interfaz como un contrato, una promesa de que cualquier clase que la implemente contendrá ciertos métodos o propiedades. Tim enfatiza que esto permite a la aplicación llamar al mismo método sin importar la fuente de datos. Al formulario no le importa si es SQL o un archivo de texto; solo le importa llamar al método.
Tim dice: "Si necesitas hacer la misma tarea pero detrás del escenario se hará de dos maneras diferentes, necesitas una interfaz."
Creando la Interfaz
Tim pasa a la implementación práctica creando una interfaz en la Biblioteca Tracker. La nombra IDataConnection y explica la convención de anteponer interfaces con "I". Destaca que esto es importante para identificar claramente que es una interfaz.
Tim añade un solo método a la interfaz:
PrizeModel CreatePrize(PrizeModel model);
Explica que este método es un contrato: debe existir en cualquier clase que implemente IDataConnection. El formulario llamará a este método y esperará obtener un PrizeModel con un ID. Tim explica que así es como el formulario se mantiene agnóstico al tipo de almacenamiento.
Creando la Clase Estática de Configuración Global
A continuación, Tim crea una clase estática denominada GlobalConfig. Explica que una clase estática no puede instanciarse y es accesible globalmente. Aquí es donde la aplicación almacenará la lista de conexiones de datos disponibles.
Define una propiedad:
public static List<IDataConnection> Connections { get; private set; }
Tim explica el uso del private set para que solo la clase misma pueda modificar la lista, mientras que otras partes de la aplicación solo pueden leerla.
Luego crea el método:
public static void InitializeConnections(bool database, bool textFiles)
Este método configura las conexiones de datos disponibles. Tim enfatiza que la lista permite múltiples conexiones, lo que significa que la aplicación puede guardar en SQL, archivos de texto, o ambos.
Entendiendo las Interfaces: Un Ejemplo del Mundo Real
Tim hace una pausa para asegurar a los aprendices que este es un material complejo, pero alcanzable. Recomienda ver el video una vez, luego volver a ver mientras se codifica junto.
Explica que la interfaz es un contrato, y cualquier clase que la implemente debe seguir el contrato. Demuestra esto creando una clase SQLConnector que implementa IDataConnection.
Cuando se crea la clase, Visual Studio advierte que el contrato no está cumplido. Tim muestra cómo usar "Implement Interface" para generar automáticamente el método CreatePrize. También explica el andamiaje NotImplementedException y por qué existe: permite que el código se compile sin pretender que el método funciona.
Creando los Conectores SQL y de Texto
Tim agrega la clase SQLConnector y la clase TextConnector, ambas implementando IDataConnection. Explica que aunque guardar en una base de datos SQL y guardar en un archivo de texto son procesos muy diferentes, ambos satisfacen el mismo contrato de interfaz.
Añade un valor de retorno de muestra simple por ahora y coloca comentarios TODO para recordarse implementar la lógica de guardado real más tarde. Esto mantiene la aplicación funcional mientras aún se avanza en la lección.
Configuración Final: Cableando la Configuración Global
Tim regresa a la clase GlobalConfig y conecta las conexiones reales. Muestra cómo inicializar la lista Connections y agregar instancias de SQLConnector y TextConnector a ella.
Explica por qué son necesarias dos sentencias if separadas en lugar de if-else: porque el usuario puede querer guardar en ambas fuentes de datos simultáneamente.
¿Dónde Llamar a InitializeConnections?
Tim explica que InitializeConnections debe llamarse al inicio de la aplicación. Modifica Program.cs y llama:
GlobalConfig.InitializeConnections(true, true);
antes de lanzar el formulario. Esto asegura que la lista de conexiones esté lista y accesible a lo largo de la aplicación.
Luego cambia el formulario de inicio a CreatePrizeForm para poder probar la funcionalidad de inmediato.
Validando el Formulario de Premios
Tim abre el formulario y explica la primera tarea: validar los cuatro campos. Prefiere mantener los controladores de eventos limpios, por lo que crea un método privado llamado ValidateForm().
Tim explica que este método puede llamarse desde cualquier lugar, no solo desde el clic del botón. Devuelve un Booleano indicando si el formulario es válido o no. Muestra su patrón de usar una variable de salida:
bool output = true; return output;
Dice que le gusta comenzar con true porque es más fácil cambiarlo a false cuando algo está mal que configurarlo en true después de cada verificación.
Verificando el Número de Lugar
Tim explica la primera validación: el número de lugar debe ser un entero mayor que cero.
Usa int.TryParse para convertir el PlaceNumberValue.Text (una cadena) en un entero. Tim desglosa cómo funciona TryParse:
-
Toma una cadena e intenta convertirla en un número.
-
Devuelve un Booleano indicando éxito o fallo.
- Usa un parámetro out para dar salida al valor convertido.
Tim enfatiza que TryParse es más seguro que Parse porque no falla con una entrada incorrecta: devuelve false y establece la salida en cero.
Luego explica la lógica:
-
Si placeNumberValidNumber es false, establece output = false.
- Si placeNumber < 1, establece output = false.
Tim advierte contra el uso de sentencias else aquí porque este método tiene múltiples verificaciones. Si una verificación falla, el método aún debe evaluar las otras para recolectar todos los errores.
Validando el Nombre del Lugar
Tim pasa a la siguiente validación: el nombre del lugar no debe estar vacío.
Él verifica:
if (placeNameValue.Text.Length == 0) { output = false; }
Tim explica que en una aplicación real, mostrarías mensajes de error para cada validación fallida. Pero por ahora, lo mantiene simple y solo devuelve true/false.
Validando el Monto del Premio vs Porcentaje del Premio
Tim explica que el formulario debe contener ya sea un monto de premio o un porcentaje de premio (uno de ellos debe ser mayor que cero). Nota la diferencia importante:
-
El porcentaje de premio es un número entero (int)
- El monto del premio es un decimal (decimal) porque el dinero puede incluir centavos.
Crea variables:
decimal prizeAmount = 0; int prizePercentage = 0;
Luego usa TryParse para ambos:
bool prizeAmountValid = decimal.TryParse(prizeAmountValue.Text, out prizeAmount); bool prizePercentageValid = int.TryParse(prizePercentageValue.Text, out prizePercentage);
Tim explica que ambos deben ser números válidos. Si uno es inválido, el formulario es inválido.
A continuación, verifica que al menos uno sea mayor que cero:
if (prizeAmount <= 0 && prizePercentage <= 0) { output = false; }
Tim también añade una verificación para asegurar que el porcentaje está entre 0 y 100:
if (prizePercentage < 0 || prizePercentage > 100) { output = false; }
Él explica por qué: 150% significaría que estás dando más de lo que hay en el fondo de premios, lo cual es imposible.
Usando el Resultado de la Validación
Después de que todas las verificaciones están hechas, Tim explica cómo usar el resultado:
if (ValidateForm()) { // crear modelo y guardar } else { MessageBox.Show("Este formulario tiene información inválida. Por favor revisa y vuelve a intentarlo."); }
Tim nota que podrías retornar temprano en el primer fallo, pero él elige realizar todas las verificaciones para que los usuarios puedan ver todos los errores de validación a la vez. Esto reduce la frustración porque pueden arreglar todo de una vez.
Creando el PremioModel
Tim explica que una vez que el formulario es válido, el siguiente paso es crear un PrizeModel.
Muestra cómo instanciar un modelo:
PrizeModel model = new PrizeModel(); model.PlaceName = placeNameValue.Text; model.PlaceNumber = placeNumberValue.Text; // problema: esto es una cadena
Tim resalta el problema: PlaceNumber es un int, pero el valor del formulario es una cadena. Para resolver esto, explica dos opciones:
-
Convertir cada valor nuevamente en el formulario (repetitivo).
- Añadir una sobrecarga de constructor en PrizeModel.
Tim elige la opción 2.
Constructor Sobrecargado en PrizeModel
Tim añade un constructor sobrecargado que acepta cuatro cadenas:
public PrizeModel(string placeName, string placeNumber, string prizeAmount, string prizePercentage)
{
PlaceName = placeName;
PlaceNumber = int.TryParse(placeNumber, out int placeNumberValue) ? placeNumberValue : 0;
PrizeAmount = decimal.TryParse(prizeAmount, out decimal prizeAmountValue) ? prizeAmountValue : 0;
PrizePercentage = double.TryParse(prizePercentage, out double prizePercentageValue) ? prizePercentageValue : 0;
}
Tim explica que no le importa si falla la conversión porque se establecerá en cero, que es el valor por defecto para los números de todos modos.
Este constructor permite que el formulario cree un PrizeModel directamente con entradas de cadena y permita que el modelo maneje la conversión.
Guardando el Modelo Usando IDataConnection
Ahora que el modelo existe, Tim explica cómo guardarlo usando la lista global de conexiones.
Usa un bucle foreach:
foreach (IDataConnection db en GlobalConfig.Connections) { db.CreatePrize(model); }
Tim explica que este bucle llama a CreatePrize() en cada conexión (SQL y archivo de texto). Aunque los métodos no están implementados todavía, el formulario funciona y finge guardar los datos. Esto demuestra que el patrón de interfaz y configuración global está funcionando.
Probando el Formulario
Tim destaca la importancia de probar temprano. Agrega puntos de interrupción y ejecuta la aplicación.
-
Primero prueba un formulario vacío.
-
Recorre ValidateForm().
-
Ve que la salida es false y la validación falla como se esperaba.
-
Luego llena datos válidos y confirma que el constructor llena el modelo correctamente.
- También confirma que el bucle recorre ambas conexiones.
Tim demuestra que el formulario es funcional y el patrón está validado.
Limpieza Final: Limpiando el Formulario
Tim hace un par de ajustes finales:
-
Después de crear un premio exitosamente, vacíe los campos del formulario.
- Establezca valores predeterminados de 0 para el monto del premio y el porcentaje para que los usuarios no tengan que escribir cero cada vez.
Luego confirma que el formulario se vacía correctamente después de una presentación válida.
¿Qué sigue?
Tim termina el video diciendo que el siguiente paso es conectar las clases de conexión SQL y de texto para que realmente guarden los datos.
Recuerda a los espectadores que estén atentos a la próxima lección, donde implementará el conector SQL y se conectará realmente a la base de datos.
