DEV Community

Programación Orientada a Objetos... sin if's.

Jerarquía de clases con una clase base.

Pero... los if's y los switch's aún siguen siendo necesarios, ¿no? La respuesta es sí, pero la programación orientada a objetos tiene como uno de sus objetivos, precisamente, eliminar la toma de decisiones realmente importante.

Veamos un ejemplo, esta vez con código C#. La idea es un sistema de puntos de interés en un mapa. La clase POI guarda Lat (latitud) y Lon (longitud), un nombre y... un tipo.

class POI {
    public enum Kind { City, Monument, Mountain }
    public required string Name { get; init; }
    public required Kind Type { get; init; }
    public required double Lat { get; init; }
    public required double Lon { get; init; }
}
Enter fullscreen mode Exit fullscreen mode

De acuerdo, ¿cómo podríamos hacer para visualizar un informe de POI's?

class Reporter {
    public Reporter(IEnumerable<POI> lpois)
    {
        this.lpois = new List<POI>( lpois );
    }

    public IList<POI> LPOIS => new List<POI>( this.lpois );

    public string Report()
    {
        var toret = new StringBuilder();

        foreach(var poi in this.lpois) {
            toret.AppendLine( this.Report( poi ));
        }

        return toret.ToString();
    }

    private string Report(POI poi)
    {
        var toret = "";

        if ( poi.Type == POI.Kind.City ) {
            toret += "Ciudad de ";
        }
        else
        if ( poi.Type == POI.Kind.Monument ) {
            toret += "Monumento de ";
        }
        else
        if ( poi.Type == POI.Kind.Mountain ) {
            toret += "Cima del ";
        }

        toret += $"{poi.Name}: W{poi.Lon}º N{poi.Lat}º";
        return toret;
    }

    private List<POI> lpois;
}
Enter fullscreen mode Exit fullscreen mode

Podemos crear un informe de dos o tres POI's con tan solo unas líneas de código.

var l1 = new List<POI> {
            new () { Name = "El Cabo",
                     Type = POI.Kind.City,
                     Lat = -34.0558188,
                     Lon = 18.3003856 },
            new () { Name = "Everest",
                     Type = POI.Kind.Mountain,
                     Lat = 27.9881186,
                     Lon = 86.9043755 },
            new () { Name = "Torre Eiffel",
                     Type = POI.Kind.Monument,
                     Lat = 48.8583,
                     Lon = -2.2945 },
        };

        Console.WriteLine( new Reporter( l1 ).Report() );
Enter fullscreen mode Exit fullscreen mode

La salida que obtenemos es esta:

Ciudad de El Cabo: W18.3003856º N-34.0558188º
Cima del Everest: W86.9043755º N27.9881186º
Monumento de Torre Eiffel: W-2.2945º N48.8583º
Enter fullscreen mode Exit fullscreen mode

¡Funciona! ¿Todo correcto?
En realidad, tenemos un pequeño problema: el método Report(poi: POI): string. Supongamos que queremos añadir un nuevo tipo de POI: lagos. Veamos. Tenemos que añadir un nuevo tipo de POI, en POI.Kind. Después, tenemos que modificar Reporter.Report(), y añadir una nueva condición, una nueva cláusula if, para el nuevo tipo.

El problema es que, como sabemos, modificar código es difícil y tedioso. Aún peor, se corre el riesgo de introducir errores al hacerlo. Lo realmente ideal, sería poder realizar la modificación sin tener que "tocar" nada más. ¿Y si pudiéramos añadir el nuevo tipo a POI.Kind y punto?

Pues sí que se puede.

Vamos a utilizar herencia en este punto. Como veremos, no es que vayamos tanto a reutilizar atributos o métodos como a establecer una jerarquía de clases relacionadas entre sí. Seguiremos teniendo POI, pero será una clase base que, en realidad, no podremos instanciar. De las clases que hereden de ella (City, Monument...), sí podremos crear objetos. El enumerado Kind desaparecerá, así como el método Reporter.Report(). ¡Veámoslo!

Jerarquía de POI's

class POI {
    public required string Name { get; init; }
    public required double Lat { get; init; }
    public required double Lon { get; init; }

    public override string ToString()
    {
        return $"{this.Name}: W{this.Lon}º N{this.Lat}º";
    }
}

class City: POI {
    public override string ToString()
    {
        return "Ciudad de " + base.ToString();
    }
}

class Monument: POI {
    public override string ToString()
    {
        return "Monumento de " + base.ToString();
    }
}

class Mountain: POI {
    public override string ToString()
    {
        return "Cima del " + base.ToString();
    }
}

class Reporter {
    public Reporter(IEnumerable<POI> lpois)
    {
        this.lpois = new List<POI>( lpois );
    }

    public IList<POI> LPOIS => new List<POI>( this.lpois );

    public string Report()
    {
        var toret = new StringBuilder();

        foreach(var poi in this.lpois) {
            toret.AppendLine( poi.ToString() );
        }

        return toret.ToString();
    }

    private List<POI> lpois;
}
Enter fullscreen mode Exit fullscreen mode

Ahora, con el código que muestra como utilizar POO para eliminar if's, son las propias clases City, Mountain y Monument las que se encargan de configurar su propio listado, a través del método ToString(). Al tener diferentes clases para cada "tipo" de POI, ya no es necesario un enumerado. Además, el método Reporter.Report() ha desaparecido, pues ya solo es necesrio llamar a ToString().

De acuerdo, ¿qué modificaciones debemos realizar para añadir el nuevo tipo de POI's, los lagos? Solo crear una nueva clase, la clase Lake.

class Lake: POI {
    public override string ToString()
    {
        return "Lago " + base.ToString();
    }
}
Enter fullscreen mode Exit fullscreen mode

Tan sencillo como esto. Ahora, si creamos un nuevo objeto de la clase Lake en l1, tendremos el siguiente listado.

Ciudad de El Cabo: W18.3003856º N-34.0558188º
Cima del Everest: W86.9043755º N27.9881186º
Monumento de Torre Eiffel: W-2.2945º N48.8583º
Lago Español: W173.1834932º N43.6150755º
Enter fullscreen mode Exit fullscreen mode

¡La POO mola!

Top comments (0)