Proyecto FizzBuzz · C# · SOLID

FizzBuzz explicado como proyecto de arquitectura y no solo como ejercicio de lógica

Esta página resume el funcionamiento del juego FizzBuzz, una forma ordenada de resolverlo en C# y la diferencia entre dos variantes del algoritmo, junto con los diagramas de clases y de flujo del servicio.

¿En qué consiste FizzBuzz?

FizzBuzz es un ejercicio clásico de programación. Se toma una secuencia de números y se sustituyen ciertos valores por palabras según reglas de divisibilidad. En el caso tradicional, los múltiplos de 3 se convierten en Fizz, los de 5 en Buzz y los que cumplen ambas reglas en FizzBuzz.

Aunque parece un problema simple, es muy útil para evaluar claridad mental, diseño de condiciones y organización del código. En este proyecto, además, se convierte en una excusa perfecta para aplicar principios de diseño limpio y arquitectura mantenible.

Flujo básico

1

Entrada numérica

El sistema recibe un número y evalúa si es divisible por los divisores configurados.

2

Regla FizzBuzz

Si el número es múltiplo de ambos divisores, devuelve FizzBuzz. Si solo cumple uno, devuelve Fizz o Buzz.

3

Separación de responsabilidades

La clase FizzBuzz resuelve el tipo de número y ResultHandler se encarga del conjunto de resultados.

4

Persistencia y concurrencia

El servicio responde al cliente y delega la escritura para evitar bloqueos innecesarios.

Una solución en C# con responsabilidades separadas

En lugar de resolver todo con una sola función grande, el proyecto organiza la lógica en clases e interfaces. De ese modo, cada pieza cumple una tarea concreta y el sistema queda preparado para crecer.

IDivisibilityCalculator

Define el contrato NumberType(int number). Esto permite sustituir implementaciones sin cambiar al consumidor.

FizzBuzz

Contiene la lógica principal para clasificar un número. No asume responsabilidades de iteración ni de lectura de archivos.

ResultHandler

Genera respuestas simples o múltiples usando una implementación de IDivisibilityCalculator.

FileManager

Aísla la lectura y escritura del fichero, dejando el acceso a disco fuera del núcleo del algoritmo.

Diagrama UML del proyecto

El esquema visual muestra cómo se relacionan FizzBuzz, ResultHandler, ServiceController, FileManager y las interfaces del sistema.

Diagrama visual del flujo del servicio y componentes relacionados
Diagrama utilizado en el proyecto para explicar el paso desde la petición del cliente hasta el tratamiento de datos.

Cómo se reparte la lógica en C#

La clase FizzBuzz resuelve un único número. ResultHandler se ocupa del rango o de la respuesta unitaria. FileManager encapsula la escritura y lectura del fichero, manteniendo el acceso a disco fuera del algoritmo central.

FizzBuzz.csLógica de divisibilidad
public class FizzBuzz : IDivisibilityCalculator
{
    private int fizz, buzz, fizzBuzz;

    public FizzBuzz(int fizz, int buzz)
    {
        this.fizz = fizz;
        this.buzz = buzz;
        this.fizzBuzz = fizz * buzz;
    }

    public string NumberType(int number)
    {
        if (number % this.fizzBuzz == 0) return "FizzBuzz";
        if (number % this.fizz == 0) return "Fizz";
        if (number % this.buzz == 0) return "Buzz";
        return number.ToString();
    }
}
ResultHandler.csGeneración de resultados
public class ResultHandler : IMultipleResult, ISingleResult
{
    private readonly IDivisibilityCalculator gameFizzBuzz;

    public ResultHandler(IDivisibilityCalculator gameFizzBuzz)
    {
        this.gameFizzBuzz = gameFizzBuzz;
    }

    public string[] MultipleResult(int LIMIT, int startNumber)
    {
        string[] numberList = new string[(LIMIT - startNumber) + 1];
        int i = 0;

        while (startNumber <= LIMIT)
        {
            numberList[i] = this.gameFizzBuzz.NumberType(startNumber);
            startNumber++;
            i++;
        }

        return numberList;
    }
}
FileManager.csEscritura en fichero
public class FileManager : IReadFromFile, IWriteToFile
{
    private string filePath;

    public FileManager(string filePath)
    {
        this.filePath = filePath;
    }

    public void WriteContent(string[] contentSet)
    {
        using (StreamWriter streamWriter = new StreamWriter(this.filePath, true))
        {
            foreach (string auxNumber in contentSet)
            {
                streamWriter.Write(auxNumber + " ");
            }
        }
    }
}

Diferencia entre la imagen A y la imagen B

Las dos versiones resuelven el mismo problema, pero la segunda simplifica la primera comprobación. Esa diferencia es precisamente la optimización destacada en la memoria del proyecto.

Imagen A

Comprobación doble con &&

Algoritmo A de FizzBuzz en C Sharp
  • La primera condición evalúa number % fizz == 0 y number % buzz == 0.
  • Eso implica dos comprobaciones de divisibilidad en la rama inicial.
  • Es correcta, pero añade una evaluación extra en el primer filtro.

Imagen B

Comprobación directa con fizzBuzz

Algoritmo B optimizado de FizzBuzz en C Sharp
  • Se calcula fizzBuzz como el producto de fizz y buzz.
  • La primera condición pasa a ser number % fizzBuzz == 0.
  • Se simplifica el if inicial y se reduce trabajo repetido.

¿Por qué B es mejor?

En la versión A, la primera condición combina dos operaciones de módulo con un operador lógico. En la versión B, se aprovecha que FizzBuzz = Fizz × Buzz, así que basta con comprobar si el número es divisible por ese producto para detectar el caso combinado desde el primer paso.

El resultado es un algoritmo más directo, con menos trabajo en la rama inicial y una lectura más limpia del código.

Cómo fluye una petición hasta la escritura final

El servicio puede recibir múltiples clientes. Sin embargo, escribir todos a la vez sobre el mismo fichero sería problemático. Por eso se introduce un Lock sobre el recurso compartido y se divide la carga usando Task, de forma que la respuesta al cliente no se quede esperando innecesariamente.

  • El servicio puede atender múltiples peticiones concurrentes.
  • El Lock protege el recurso compartido cuando toca escribir en fichero.
  • La creación de Task evita que el cliente quede bloqueado esperando la escritura.
  • No se usa Parallel.ForEach en la generación del listado para no romper el orden del resultado.
Diagrama del flujo de concurrencia del servicio FizzBuzz
Clientes, creación de tareas, Lock, Thread Pool, FileManager y escritura a fichero.

Control de errores y registros

El proyecto contempla errores críticos y advertencias. Para ello utiliza clases específicas de excepción, separando los casos que impiden procesar la petición de aquellos que solo afectan al registro en fichero.

Esta idea ayuda a mantener la estabilidad del servicio y a registrar incidentes sin mezclar lógica de negocio con gestión de logs.

Más que un ejercicio básico

Aquí FizzBuzz deja de ser un simple reto de entrevista y se transforma en una demostración de diseño: interfaces, responsabilidades claras, posibilidad de extensión, mejora algorítmica y tratamiento de concurrencia.

La clave no está solo en que funcione, sino en que la solución sea comprensible, mantenible y preparada para evolucionar.