馃帗 Caso de estudio:
Ejemplo de modularizaci贸n de un programa ya hecho
Supongamos el siguiente enunciado:
Deberemos mostrar un tablero de ajedrez y sobre 茅l, marcar con
*las celdas a las que se puede mover un alfil desde una posici贸n dada por el usuario.Para indicar la posici贸n de la casilla vamos a utilizar 铆ndices del 1 al 8 tanto para las filas como para las columnas, de tal manera que la casilla (1, 1) es la esquina superior izquierda del tablero y la casilla (8, 8) es la esquina inferior derecha.
Vamos imprimir colores en el terminal puedes usar las propiedades de consola:
Console.BackgroundColor = ConsoleColor.Color; Console.ForegroundColor = ConsoleColor.Color;Adem谩s, puedes imprimir en una posici贸n espec铆fica en pantalla con.
Console.SetCursorPosition(Columna, Fila);Para m谩s informaci贸n sobre la clase Console
Ejemplo de ejecuci贸n:
Introduce fila: 4 Introduce columna: 4 12345678 1* * 2 * * 3 * * 4 A 5 * * 6 * * 7* * 8 *
Una propuesta de soluci贸n v谩lida ser铆a el siguiente c贸digo 'monol铆tico', largo y susceptible de ser modularizado puede ser el siguiente:
馃搶 Nota: Puedes descargarlo con desde el siguiente enlace.
public class Programa
{
public static void Main()
{
Console.Clear();
for (int i = 1; i <= 8; i++)
{
Console.SetCursorPosition(i, 0);
Console.Write(i);
Console.SetCursorPosition(0, i);
Console.Write(i);
}
for (int fila = 1; fila <= 8; fila++)
{
for (int columna = 1; columna <= 8; columna++)
{
bool esCasillaNegra;
if (fila % 2 == 0)
esCasillaNegra = columna % 2 != 0;
else
esCasillaNegra = columna % 2 == 0;
if (esCasillaNegra)
Console.BackgroundColor = ConsoleColor.DarkGray;
else
Console.BackgroundColor = ConsoleColor.White;
Console.SetCursorPosition(columna, fila);
Console.Write(" ");
}
}
Console.BackgroundColor = ConsoleColor.Black;
Console.Write("\n");
int columnaAlfil;
int filaAlfil;
Console.SetCursorPosition(10, 4);
Console.Write("Introduce fila: ");
filaAlfil = int.Parse(Console.ReadLine() ?? "1");
Console.SetCursorPosition(10, 5);
Console.Write("Introduce columna: ");
columnaAlfil = int.Parse(Console.ReadLine() ?? "1");
for (int fila = 1; fila <= 8; fila++)
{
for (int columna = 1; columna <= 8; columna++)
{
bool esDiagonalAlfil = Math.Abs(filaAlfil - fila) == Math.Abs(columnaAlfil - columna);
if (esDiagonalAlfil)
{
bool esCasillaNegra;
Console.SetCursorPosition(columna, fila);
if (fila % 2 == 0)
esCasillaNegra = columna % 2 != 0;
else
esCasillaNegra = columna % 2 == 0;
if (esCasillaNegra)
{
Console.BackgroundColor = ConsoleColor.DarkGray;
Console.ForegroundColor = ConsoleColor.White;
}
else
{
Console.BackgroundColor = ConsoleColor.White;
Console.ForegroundColor = ConsoleColor.Black;
}
Console.Write("*");
}
}
}
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.Gray;
Console.SetCursorPosition(0, 15);
Console.Write("\n");
}
}
Lo primero ser铆a identificar los bloques de c贸digo relacionado y que hace una 煤nica funcionalidad y tratar de ponerles nombre y pasarles todos los datos que precisen por par谩metro. Adem谩s, durante el proceso vamos a...
Importante
Este caso de estudio es simplemente para que veamos un ejemplo de una de las posibles formas de plantear la modularizaci贸n de un c贸digo ya implementado. Quiz谩 un programador experimentado es capaz de realizar ambos refinamientos de una sola vez, pero eso se logra con la pr谩ctica y la experiencia.
Este proceso es interesante abordarlo porque en la realidad la mayor铆a de casos de modularizaci贸n se nos dar谩n refactorizando c贸digo nuestro o de un compa帽ero al que le estemos a帽adiendo nuevas caracter铆sticas.
Paso 1 - Dibujo del tablero vac铆o:
Si analizamos los dos primeros bucles, cl谩ramente se hace un proceso de dibujo del tablero vac铆o, tanto de la numeraci贸n, como de las casillas.
馃搶 Nota: Lee los comentarios en el c贸digo.
static void DibujaNumeracion(int dimensionTablero)
{
for (int i = 1; i <= dimensionTablero; i++)
{
Console.SetCursorPosition(i, 0);
Console.Write(i);
Console.SetCursorPosition(0, i);
Console.Write(i);
}
}
static void DibujaCuadricula(int dimensionTablero)
{
// Nos guardamos el color de fondo para restaurarlo al final
// aunque el nombre del identificador autodocumenta ahorr谩ndonos este comentario.
ConsoleColor copiaRespaldoDeColorDeFondo = Console.BackgroundColor;
for (int fila = 1; fila <= dimensionTablero; fila++)
{
for (int columna = 1; columna <= dimensionTablero; columna++)
{
// Refactorizamos simplificando las expresiones
// y la l贸gica a partir del c贸digo dado.
bool esCasillaNegra = (fila % 2 == 0) ? columna % 2 != 0 : columna % 2 == 0;
Console.BackgroundColor = esCasillaNegra
? ConsoleColor.Black
: ConsoleColor.White;
Console.SetCursorPosition(columna, fila);
Console.Write(" "); // Dibuja una casilla del fondo en una posici贸n.
}
}
// Restauramos el color.
Console.BackgroundColor = copiaRespaldoDeColorDeFondo;
}
public static void Main()
{
// Guardo los colores de la consola y la limpio.
ConsoleColor fondo = Console.BackgroundColor;
ConsoleColor primerPlano = Console.ForegroundColor;
Console.Clear();
// Pasamos la dimensi贸n de tablero a trav茅s de una cte. en lugar de usar un n煤mero 'm谩gico'.
const int DIMENSION_TABLERO = 8;
DibujaTablero(DIMENSION_TABLERO);
...
}
Paso 2 - Pedir la casilla donde se encuentra el 谩lfil:
El c贸digo a continuaci贸n del dibujo del tablero est谩 pidiendo la fila y columna de la casilla seg煤n la numeraci贸n dibujada. En este primer refinamiento de modularizaci贸n nos vamos a limitar a pasarlo a un m贸dulo que devuelva ambos valores en forma de tupla ya que est谩n relacionados.
static (int columna, int fila) CasillaAlfil()
{
Console.SetCursorPosition(10, 4);
Console.Write("Introduce fila: ");
int f = int.Parse(Console.ReadLine() ?? "1");
Console.SetCursorPosition(10, 5);
Console.Write("Introduce columna: ");
int c = int.Parse(Console.ReadLine() ?? "1");
return (c, f);
}
Importante
F铆jate que estamos dibujando en posiciones absolutas (10, 4) y (10, 5).
驴C贸mo sabemos que no estamos dibujando encima del tablero?
Este m贸dulo necesita saber donde est谩 dibujando otro m贸dulo del mismo nivel lo cual nos indica un claro acoplamiento. Para solucionarlo haremos le pasaremos la posici贸n absoluta donde dibujar ...
static (int columna, int fila) CasillaAlfil(int columna, int fila)
{
Console.SetCursorPosition(columna, fila);
Console.Write("Introduce fila: ");
int f = int.Parse(Console.ReadLine() ?? "1");
Console.SetCursorPosition(columna, fila + 1);
Console.Write("Introduce columna: ");
int c = int.Parse(Console.ReadLine() ?? "1");
return (c, f);
}
public static void Main()
{
...
const int DIMENSION_TABLERO = 8;
DibujaTablero(DIMENSION_TABLERO);
// Realizamos la entrada de datos en funci贸n de la dimensi贸n del tablero...
(int columnaAlfil, int filaAlfil) = CasillaAlfil(DIMENSION_TABLERO + 2, DIMENSION_TABLERO / 2);
...
}
Paso 3 - Dibujar las posiciones del 谩lfil a partir de la casilla dada:
El 煤ltimo bucle en el Main dibujar谩 las casillas alcanzables por el 谩lfil a partir de la casilla dada.
static void DibujaDiagonalesAlfil(int columnaPieza, int filaPieza, int dimensionTablero)
{
for (int fila = 1; fila <= dimensionTablero; fila++)
{
for (int columna = 1; columna <= dimensionTablero; columna++)
{
// Asignamos la expresi贸n a un identificados booleano para autodocumentarla.
bool estanEnLaMismaDiagonal = Math.Abs(filaPieza - fila) == Math.Abs(columnaPieza - columna);
if (estanEnLaMismaDiagonal)
{
bool esCasillaNegra = (fila % 2 == 0) ? columna % 2 != 0 : columna % 2 == 0; //L铆nea 11
if (esCasillaNegra)
{
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.White;
}
else
{
Console.BackgroundColor = ConsoleColor.White;
Console.ForegroundColor = ConsoleColor.Black;
}
Console.SetCursorPosition(columna, fila);
Console.Write("*");
}
}
}
}
Si nos fijamos en la l铆nea 11, la expresi贸n para saber si una casilla es negra o no, se repet铆a en DibujaCuadricula. Esto nos est谩 diciendo que podemos aumentar la cohesi贸n del c贸digo llev谩ndola a un m贸dulo com煤n usado por ambos para evitar as铆 repetir la l贸gica.
static bool EsCasillaNegra(int columna, int fila)
{
return (fila % 2 == 0) ? columna % 2 != 0 : columna % 2 == 0;
}
// Ahora en ambas l铆neas donde se repet铆a la expresi贸n podremos poner directamente...
if (EsCasillaNegra(columna, fila)) ...
Paso 4 - Completando las llamadas en el programa principal:
Si nos fijamos en las l铆neas 10, 12 y 16 tendremos las llamadas resultado de la modularizaci贸n.
public static void Main()
{
// Guardo los colores de la consola y la limpio.
ConsoleColor fondo = Console.BackgroundColor;
ConsoleColor primerPlano = Console.ForegroundColor;
Console.Clear();
const int DIMENSION_TABLERO = 8;
DibujaTablero(DIMENSION_TABLERO);//10
(int columnaAlfil, int filaAlfil) = CasillaAlfil(DIMENSION_TABLERO+2, DIMENSION_TABLERO/2);
Console.CursorVisible = false; // Ocultamos el cursor para que no se vea.
DibujaDiagonales(columnaAlfil, filaAlfil, DIMENSION_TABLERO);//16
Console.ReadKey(true); // Leemos una tecla sin eco para finalizar
Console.CursorVisible = true; // Restauro el cursor.
// Restauro los colores de la consola y la limpio.
Console.BackgroundColor = fondo;
Console.ForegroundColor = primerPlano;
Console.Clear();
}
Pero si nos fijamos, debemos saber donde coloca el tablero en la consola DibujaTablero para despu茅s los valores correctos a CasillaAlfil y eso es una forma de acoplamiento.
As铆 mismo, DibujaCuadricula y DibujaNumeracion que son m贸dulos en el mismo nivel, est谩n acoplados porque uno debe saber donde ha situado las cosas el otro para que encajen.
Por tanto en este segundo refinamiento, vamos a refactorizar el c贸digo para que las cosas se dibujen relativas a una posici贸n en consola absoluta que se recibir谩 por par谩metro.
馃搶 Nota: En este refinamiento el DEM no se modificar谩 y solo pasaremos la informaci贸n necesaria para evitar los acoplamientos descritos.
Paso 5 - Pasando posiciones absolutas de dibujo para eliminar acoplamientos:
馃搶 Nota: Puedes descargar el resultado final desde el siguiente enlace. Debes ejecutar el programa en una consola externa para que funcione correctamente.
static bool EsCasillaNegra(int columna, int fila)
{
return (fila % 2 == 0) ? columna % 2 != 0 : columna % 2 == 0;
}
// Recibir谩 columna y fila absolutas de dibujo para evitar acoplamiento yo dibujo desde
// donde me dicen. El no solaparse ahora es responsabilidad del m贸dulo padre.
static void DibujaNumeracion(int columna, int fila, int dimensionTablero)
{
for (int i = 1; i <= dimensionTablero; i++)
{
// A帽ado los desplazamientos absolutos a las posiciones relativas anteriores
Console.SetCursorPosition(i + columna, fila);
Console.Write(i);
Console.SetCursorPosition(columna, i + fila);
Console.Write(i);
}
}
// Recibir谩 columna y fila absolutas de dibujo para evitar acoplamiento
// yo dibujo desde donde me dicen. El no solaparse ahora es responsabilidad del
// m贸dulo padre.
static void DibujaCuadricula(int columna, int fila, int dimensionTablero)
{
ConsoleColor copiaRespaldoDeColorDeFondo = Console.BackgroundColor;
for (int fRelativa = 1; fRelativa <= dimensionTablero; fRelativa++)
{
for (int cRelativa = 1; cRelativa <= dimensionTablero; cRelativa++)
{
Console.BackgroundColor = EsCasillaNegra(cRelativa, fRelativa)
? ConsoleColor.Black
: ConsoleColor.White;
// A帽ado los desplazamientos absolutos a las posiciones relativas anteriores
Console.SetCursorPosition(cRelativa + columna, fRelativa + fila);
Console.Write(" ");
}
}
Console.BackgroundColor = copiaRespaldoDeColorDeFondo;
}
// Recibir谩 columna y fila absolutas de dibujo para evitar acoplamiento
// yo dibujo desde donde me dicen. El no solaparse ahora es responsabilidad del
// m贸dulo padre.
static void DibujaTablero(int columna, int fila, int dimensionTablero)
{
// DibujaTablero ahora se encarga de que el dibujo de ambos m贸dulos por debajo encaje.
DibujaNumeracion(columna, fila, dimensionTablero);
DibujaCuadricula(columna, fila, dimensionTablero);
}
// Recibir谩 columna y fila absolutas de dibujo para evitar acoplamiento
// yo dibujo desde donde me dicen. El que las posiciones del tablero coincidan
// con el tablero es responsabilidad del m贸dulo padre.
static void DibujaDiagonalesAlfil(
int columnaTablero, int filaTablero, int columnaPieza, int filaPieza,
int dimensionTablero)
{
for (int fila = 1; fila <= dimensionTablero; fila++)
{
for (int columna = 1; columna <= dimensionTablero; columna++)
{
// Asignamos la expresi贸in a un identificados booleano para autodocumentarla.
bool estanEnLaMismaDiagonal = Math.Abs(filaPieza - fila) == Math.Abs(columnaPieza - columna);
if (estanEnLaMismaDiagonal)
{
if (EsCasillaNegra(columna, fila))
{
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.White;
}
else
{
Console.BackgroundColor = ConsoleColor.White;
Console.ForegroundColor = ConsoleColor.Black;
}
// A帽ado los desplazamientos absolutos a las posiciones relativas anteriores
Console.SetCursorPosition(columna + columnaTablero, fila + filaTablero);
Console.Write("*");
}
}
}
}
public static void Main()
{
// Guardo los colores de la consola y la limpio.
ConsoleColor fondo = Console.BackgroundColor;
ConsoleColor primerPlano = Console.ForegroundColor;
Console.Clear();
const int DIMENSION_TABLERO = 8;
// Defino las dimensiones de la consola como constantes
// y las establezco en la que me estoy ejecutando.
// Puede que solo funcione en Windows 10 y ejecutando en una consola externa.
const int COLUMNAS_CONSOLA = 80;
const int FILAS_CONSOLA = 24;
Console.SetWindowSize(COLUMNAS_CONSOLA, FILAS_CONSOLA);
// Decido que el tablero se va a dibujar ahora en el centro de la consola.
int columnaSuperiorIzquierdaTablero = COLUMNAS_CONSOLA / 2 - DIMENSION_TABLERO / 2;
int filaSuperiorIzquierdaTablero = FILAS_CONSOLA / 2 - DIMENSION_TABLERO / 2;
// El Main tiene la responsabilidad o es el encargado ahora de coordinar las posiciones
// absolutas de todos los dibujos de tal manera que no se solapen.
// Cada m茅todo solo sabe donde le ha dicho el Main que se dibuje.
DibujaTablero(
columnaSuperiorIzquierdaTablero, filaSuperiorIzquierdaTablero,
DIMENSION_TABLERO);
(int columnaAlfil, int filaAlfil) = CasillaAlfil(
columnaSuperiorIzquierdaTablero + DIMENSION_TABLERO + 2,
filaSuperiorIzquierdaTablero + DIMENSION_TABLERO / 2);
Console.CursorVisible = false; // Ocultamos el cursor para que no se vea.
DibujaDiagonalesAlfil(
columnaSuperiorIzquierdaTablero, filaSuperiorIzquierdaTablero,
columnaAlfil, filaAlfil, DIMENSION_TABLERO);
Console.ReadKey(true); // Leemos una tecla sin eco para finalizar
Console.CursorVisible = true; // Restauro el cursor.
// Restauro los colores de la consola y la limpio.
Console.BackgroundColor = fondo;
Console.ForegroundColor = primerPlano;
Console.Clear();
}