Problem with polymorphism

I have a problem with polymorphism. But before I explain it, I want to make it clear that I am learning a lot yet, if you want to throw my structure in the trash, feel free. I have a project that converts types of temperatures into others. I have an interface that says each temperature should convert from one type of temperature to another type.

public interface ITemperatura
{
    double Convert(double valor, ITemperatura temperatura);
}

Celsius, Farenheit and Kelvin sign Itemperature. That is, Celsius, for example, would use Convert to convert a temperature in Celsius to another that is passed in Itemperature, like Farenheit, for example.

public class Celsius : ITemperatura
{
    public double Convert(double valor, ITemperatura temperatura)
    {
        return 0; // Não deve entrar aqui, esse método só está aqui para respeitar a interface,
                  // A ideia é fazer o polimorfismo e entrar nos métodos especializados abaixo
    }

    public double Convert(double valor, Celsius temperatura)
    {
        return 1; // implementacao de calculo de Celsius  para Celsius
    }

    public double Convert(double valor, Farenheit temperatura)
    {
        return 2; // implementacao de calculo de Celsius para Farenheit (pois esta classe é celsius e o parametro é farenheit)
    }

    public double Convert(double valor, Kelvin temperatura)
    {
        return 3; // implementacao de calcula de Celsius para Kelvin
    }
}

Notice above that in addition to the methods of each temperature that are 3, I have one more that receives Itemperature. It's only there because the interface asks. But it should not be called, the idea is to call the specialized types, Celsius, Farenheit and Kelvin. Obs.: I did not implement correctly with the specific calculations, only return 1 2 and 3, representing return of Celsius, Farenheit and Kelvin, respectively, in addition to the 0 that would be an error to return.

I then have the convert class that has a Convert method as well, but does not sign Itemperature. This method is responsible for doing the intermediate between the main and the conversion classes. It receives two temperatures, from and to, and also the value that will be converted. It uses the classes in this way:

public class Converter
{
    public double Convert(double valor, ITemperatura de, ITemperatura para)
    {
        return de.Convert(valor, para);
    }
}   

Already all ready, for consumption in the main, I have ali 2 cases, one of success and one of failure.

static void Main(string[] args)
    {
        // 1º caso (ok):
        Celsius celsius = new Celsius();
        double valor = celsius.Convert(100, new Farenheit());
        Console.WriteLine(valor);

        // 2º caso (entrou no ITemperatura, não na especializada):
        Converter converter = new Converter();
        valor = converter.Convert(100, new Celsius(), new Farenheit());
        Console.WriteLine(valor);
        Console.Read();
    }
    //outputs: 2
    //         0

The first case is success, it creates an instance of Celsius and from this instance it calls Convert to turn it into Farenheit. By doing this, it will enter the correct method of overloading in the Celsius class which has Farenheit parameter. resulting in 2 (Return of Farenheit ok).

The second case is error. Now using the convert Class. In this case, he is no longer going straight to the Celsius class. Now, he's passing before by converting, which receives 2 Itemperature, the from and the to, each being a temperature, and only then sends to the Celsius class to make its overload.

insert the description of the image here

In this way, when he calls the of.Convert (value, to); I wanted it to take the same instance of Farenheit in the to that I sent from main and forward to the Celsius class in its correct overload which has a Farenheit in the parameter.

However, when sending to the Celsius class, it does not do this, it forwards to the abstract method that receives Itemperature, returning 0;

Important to note that, in Convert, I have the correct instance that I receive in main, and even then, when it sends to Celsius in the method that has Itemperature, I debugged and there is the Farenheit instance, but it still entered the generic method that uses Itemperature.

Is there any way to, even getting no convert the to as ITemperatura, get it into the specific Celsius method without having to do if to test your specific type? Say something to the compiler like,"I want to call the most specific method for that class, always."

My intention is to learn. You can throw away my solution and say that it has another totally different way and that it does not use explicit ifs or switches, it will be great to know. If they have a suggestion on top of this project it would also be interesting.

Thank you for the analysis, Robson Faxes.

Author: Robson Faxas, 2016-09-29

4 answers

I don't know what the purpose of the AP is, but I shouldn't do it, nor as a learning. Learning is doing right. Unless I wanted to prove it wrong.

From OOP's point of view all this is complete nonsense. And if someone says that this is good, or does not understand OOP or will confirm that OOP only serves to disrupt.

In fact all these question classes do is complicate something simple.

I could even do something more object-oriented, but the classes of temperature units would have to be completely different, starting with the fact that they really are temperature objects and not just a vehicle for methods that do not make sense. I even tried to take away if this was the intention of the AP in the comments, but it was clear that the intention of the classes is only to generate a marker but not to be the object. That doesn't make sense.

It becomes clearer that you are not doing anything useful, and not even object-oriented when in fact the conversion generates an unqualified number that is a qualified specific temperature measurement.

What one is actually intending to do is just have temperature conversion methods. This can be done very simply.

public static class TemperatureConverter {
    public double CelsiusToFahrenheit(double valor)  { //calcula aqui }
    public double CelsiusToKelvin(double valor) { //calcula aqui }
    public double FahrenheitToCelsius(double valor) { //calcula aqui }
    public double FahrenheitToKelvin(double valor) { //calcula aqui }
    public double KelvinToFahrenheit(double valor) { //calcula aqui }
    public double KelvinToCelsius(double valor) { //calcula aqui }
}

In the main class of the application:

public static void Main(string[] args) {
    double valor = Temperature.ConverCelsiusToKelvin(100);
}

I put on GitHub for future reference.

There is zero gain in doing other than that. There are losses in doing the intended way.

The impression that I have is that one is trying to do for not understanding what is object orientation and language resources. The first version of the question did something even worse. Probably because I was trying to fix a problem that only existed because the architecture was already all wrong. This is the problem of wrong architecting. It begins to look for insane solutions to fix what started wrong.

The answer was already prepared from the beginning just waiting for better clarification. When I was post I saw that this solution is what ramaral proposed in the end, which is the" only " sane solution to this. His initial would still be bad. But I will understand any answer that tries to go the way the AP wants.

In chat the subject was discussed and Dener Carvalho talked about using enum to mark the temperatures. It would already be a slightly better solution, but still meaningless. I reaffirm that temperature classes are just markers and an attempt to use delegation where not fit.

Even if this made some sense, the use of ITemperatura is conceptually wrong. Creating an interface, using it in the class, and not implementing it properly is a scandal:)

Something like this could even be used in very specific cases where there would be gain. That's not the case. Related subject: is it bad practice to use empty interfaces?

 11
Author: Maniero, 2020-03-10 19:10:51

Putting aside any opinion on what is the best way to implement the converter, this would be the implementation of the way it suggests in the question.

Interface that each "temperature unit" should implement:

public interface ITempertureUnit
{
    double FromKelvin(double value);
    double ToKelvin(double value);
}

Methods should implement unit conversion from and to Kelvin

Implementation of units Celsius, Kelvin and Farenheit

public class Celsius : ITempertureUnit
{
    public double FromKelvin(double value)
    {
        return value - 273.15;
    }
    public double ToKelvin(double value)
    {
        return value + 273.15;
    }
}

public class Kelvin : ITempertureUnit
{
    public double FromKelvin(double value)
    {
        return value;
    }
    public double ToKelvin(double value)
    {
        return value;
    }
}

public class Farenheit : ITempertureUnit
{

    public double FromKelvin(double value)
    {
        return value * 1.8 - 459.67;
    }
    public double ToKelvin(double value)
    {
        return (value + 459.67) / 1.8;
    }

}

Converter implementation:

public static class Converter
{
    public static double Convert(double value, ITempertureUnit from, ITempertureUnit to)
    {
        return to.FromKelvin(from.ToKelvin(value));
    }
}

Mode usage:

double valor = Converter.Convert(100, new Celsius(), new Farenheit());

See working on ideone

 7
Author: ramaral, 2016-09-30 21:53:31

The answers obtained are all very good and already provide much of what the AP needs to learn, considering that the need for it is in learning the concepts of interfaces and polymorphism. Still, as these concepts are part of a larger concept (from orientation to Object), I thought it was worth providing a complementary answer (with a suggestion of alternative implementation without inheritance or polymorphism).

In other words, I just I offer here a complementary description with the solution already proposed by @Maniero in his answer when he mentions:

"[...] but the classes of temperature units would have to be completely different, starting with the fact that they really are objects of temperature and not just a vehicle for methods that do not make sense."

It's okay that you're learning. But essentially I agree with @Maniero about you being using something unnecessarily and therefore misguided.

The Temperature is an entity that is more for an attribute (a value used in some other context, perhaps even in another class) than a class itself. It makes very little sense to think of temperature as a class because it would have no behavior (something it would literally "do", and that would be implemented in the form of object-oriented methods). You have the need to convert temperature values according to different units, but it is somewhat forced to transform these conversion functions into methods because these conversions are not really behaviors of a temperature class (or any class inherited from it). In fact, inheriting from a base class (or even an interface) "temperature" also sounds strange, because there is not even any specialization to be done in the child classes!

That way, if you keep in mind that the temperature is more for a value or an attribute than for a class itself, it may be easier to see that your problem is not about polymorphism, but about representation . That is, how would you represent a temperature so that it is easily converted into different units?

Thus, a possible solution is to implement a class that represents a temperature. Internally she does it the way her developer wish. It's a black box. That is, it does not matter how she stores the value, as long as it is easily accessible from outside and in any of the units. Yeah, this implementation will be a class anyway, but there's no polymorphism involved because there's no inheritance. The " class "is just the way of implementing" a new type", which conceptually you can still continue to think of as a value or an attribute.

If you stop to think, there are countless examples that they do just that. The Class String itself, or the Class DateTime are like this. They implement a " structure "(and this name is quite conducive, even more than" class", although the implementation is in fact a class) that stores, represents, and handles that type of data more" complex " than a int or a float.

Well, having said that, consider this example of implementation in C#:

/**
 * Implementa uma classe genérica e geral para manipulação de temperaturas
 * EM DIFENRENTES UNIDADES. 
 */
public class Temperature
{
    /** Valor da temperatura (sempre em Celsius, por convenção minha). */
    private double m_dValue;

    /**
     * Construtor protegido, para evitar que a classe seja instanciada
     * diretamente. Para instanciação, deve-se utilizar os métodos
     * apropriados para cada unidade.
     * @param dValue Double com o valor da temperatura EM CELSIUS.
     */
    protected Temperature(double dValue)
    {
        m_dValue = dValue;
    }

    /**
     * Método estático de criação de temperaturas em Celsius. 
     * @param dValue Double com o valor da temperatura em Celsius.
     * @return Instância do objeto Temperature com a temperatura dada.
     */
    public static Temperature asCelsius(double dValue)
    {
        return (new Temperature(dValue));
    }

    /**
     * Método estático de criação de temperaturas em Fahrenheit. 
     * @param dValue Double com o valor da temperatura em Fahrenheit.
     * @return Instância do objeto Temperature com a temperatura dada.
     */
    public static Temperature asFahrenheit(double dValue)
    {
        Temperature oRet = new Temperature(0);
        oRet.Fahrenheit = dValue;
        return oRet;
    }

    /**
     * Método estático de criação de temperaturas em Kelvin. 
     * @param dValue Double com o valor da temperatura em Kelvin.
     * @return Instância do objeto Temperature com a temperatura dada.
     */
    public static Temperature asKelvin(double dValue)
    {
        Temperature oRet = new Temperature(0);
        oRet.Kelvin = dValue;
        return oRet;
    }

    /** Propriedade de acesso ao valor em Celsius. */
    public double Celsius
    {
        get { return m_dValue; }
        set { m_dValue = value; }
    }

    /** Propriedade de acesso ao valor em Fahrenheit. */
    public double Fahrenheit
    {
        get { return m_dValue * 1.8 + 32; }
        set { m_dValue = (value - 32) / 1.8; }
    }

    /** Propriedade de acesso ao valor em Fahrenheit. */
    public double Kelvin
    {
        get { return m_dValue + 273; }
        set { m_dValue = value - 273; }
    }

    /**
     * Implementação de exemplo do operator de soma.
     * @param t1 Instância da primeira temperatura.
     * @param t2 Instância da segunda temperatura.
     * @return Instância da temperatura com o resultado da soma. 
     */
    public static Temperature operator+(Temperature t1, Temperature t2)
    {
        return new Temperature(t1.Celsius + t2.Celsius);
    }

    /**
     * Retorna a representação da temperatura em string com todos os
     * valores em cada uma das unidades suportadas.
     * @return String com a representação da temperatura.
     */
    public override string ToString()
    {
        return string.Format("Temperatura: {0} °C ({1} °F ou {2} °K)", Celsius, Fahrenheit, Kelvin);
    }
}

Note that:

  • The Constructor was made protected (protected) from it cannot be used directly (outside of this class). This is due to the fact that the signature of the constructor simply called Temperature with a value double is confusing. Note in other answers there is always something like fromKelvin(double value), since the name of the unit in the method name indicates the intent. In this case, I created static methods to construct a temperature from a given unit(asCelcius, asFahrenheit and asKelvin) in the same way, only instead of calling "from" ("de", in Portuguese) I I called it " as " because I want to convey the intention that it is not a conversion, but a creation. The "structure" internally turns to keep everything right.

  • There are properties for accessing the values in the different units. Want to read or update in Kelvin? Just do x = t.Kelvin or t.Kelvin = x. Simple and straightforward (as if you were updating the day of a date by doing d.day = 10 in another such structure). Note that internally I I chose to keep the value in Celsius. Could I have a variable for each and always update all? It could, but it's unnecessary. Therefore, I always convert and keep in Celsius in updates, and convert to return in accesses if necessary.

  • This approach gives room for other interesting things. If you overload an operator, you can do counts with temperatures, compare values, etc, in the same way as you would if you used double, but without worry about unity! I implemented the sum operator (operator+) to exemplify, in the code below.

Example of using Class Temperature:

class Program
{
    static void Main(string[] args)
    {
        Temperature t = Temperature.asCelsius(100);
        Console.WriteLine(t);

        t.Fahrenheit = 48;
        Console.WriteLine(t);

        t.Kelvin = 397;
        Console.WriteLine(t);

        Temperature t1 = Temperature.asKelvin(380);
        Temperature t2 = Temperature.asCelsius(22);
        Temperature t3 = Temperature.asFahrenheit(80);
        Console.WriteLine(t1 + t2 + t3);
    }
}

This program generates the following output (the last line shows the result of the sum t1 + t2 + t3 in the code above):

Temperatura: 100 °C (212 °F ou 373 °K)
Temperatura: 8,88888888888889 °C (48 °F ou 281,888888888889 °K)
Temperatura: 124 °C (255,2 °F ou 397 °K)
Temperatura: 155,666666666667 °C (312,2 °F ou 428,666666666667 °K)
 7
Author: Luiz Vieira, 2019-09-17 16:53:14

@Maniero's answer is quite simple, but it does not solve an AP requirement that is to avoid using if.

To meet this requirement, you need to go a little further, eventually using interfaces like you tried in your initial solution. But fortunately C# has delegates, which allows you to store references for methods and dispenses with interfaces!

Then, considering this converter statement:

public static class TemperatureConverter 
{
    public double CelsiusToFahrenheit(double valor)  { //calcula aqui }
    public double CelsiusToKelvin(double valor) { //calcula aqui }
    public double FahrenheitToCelsius(double valor) { //calcula aqui }
    public double FahrenheitToKelvin(double valor) { //calcula aqui }
    public double KelvinToFahrenheit(double valor) { //calcula aqui }
    public double KelvinToCelsius(double valor) { //calcula aqui }
}

You can declare a dictionary that associates every possible user entry to a given conversion method, something like this:

delegate double TemperatureConvert(double valor);

static Dictionary<String, TemperatureConvert> converters = 
        new Dictionary<string, TemperatureConvert>
{
    {"CelsiusToFahrenheit", TemperatureConverter.CelsiusToFahrenheit},
    {"CelsiusToKelvin", TemperatureConverter.CelsiusToKelvin},
    {"FahrenheitToCelsius", TemperatureConverter.FahrenheitToCelsius},
    {"FahrenheitToKelvin", TemperatureConverter.FahrenheitToKelvin},
    {"KelvinToFahrenheit", TemperatureConverter.KelvinToFahrenheit},
    {"KelvinToCelsius", TemperatureConverter.KelvinToCelsius}
};

And then you select a converter from the user input, like this:

static void Main(string[] args)
{
    var temperature = double.Parse(args[0]);
    var conversor = args[1] + args[2] + args[3];

    var convertedTemperature = converters[conversor](temperature);

    Console.WriteLine(convertedTemperature);
}

Now this application can be invoked by passing the temperature, the original scale and the scale to be converted, more or less like this:

converter.exe 50 Fahrenheit To Celsius

And in this case the output will be the conversion implemented in the method FahrenheitToCelsius :-)

See working on ideone .

Of course you might want to worry about validations, case tolerance, etc. And of course also, in an application with a graphical interface, user inputs can be limited by using comboboxes, for example.

And if you want to exercise the use of interfaces (which is really unnecessary in this example), you can implement each converter in its own Class, All Implementing an interface common for the conversion method, and reference instances of the converters in the dictionary instead of just the conversion method.

If you used Java, for example, which does not support delegates, using interfaces would probably be a valid solution to avoid the ifs.

 6
Author: Caffé, 2019-09-22 14:49:25