Problem with polymorphism

Asked

Viewed 549 times

12

I have a polymorphism problem. But before I explain it, I want to make it clear that I’m still learning a lot, so if you want to throw my structure in the garbage, be my guest. 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 Itemperatura. That is, Celsius, for example, would use Convert to convert a temperature to Celsius for another temperature passed in Itemperatura, 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
    }
}

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

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

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

Already everything ready, for consumption on main, I have there 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 that of success, he creates an instance of Celsius and from this instance he calls the Convert to turn it into Farenheit. In doing this, it will enter the correct overload method in the Celsius class that has Farenheit parameter. resulting in 2 (return from Farenheit ok).

The second case is the error case. Now using the Convert class. In this case, it is no longer going straight to the Celsius class. Now, it is passing before by Converter, which receives 2 Itemperature, the DE and the PARA, each being a temperature, and only then sends to the Celsius Class to make its overload.

inserir a descrição da imagem aqui

This way, when he calls it. Convert(value, to); I wanted it to take the same instance of Farenheit in the PARA I sent from main and forward to the Celsius class in its correct overload that 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 Itemperatura, returning 0;

Important to note that, in Converter, I have the correct instance that I receive in main, and even later, when it sends to Celsius in the method that has Itemperatura, I debugged and there is the instance of Farenheit, but he nevertheless entered the generic method that uses Itemperatura.

Is there any way, even getting in the Convert TO as Itemperatura, to get you 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 this class, always".

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

Thank you in advance for the analysis, Robson Faxas.

4 answers

11


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

From the point of view of OOP all this is a complete nonsense. And if someone says it’s good, or doesn’t understand OOP or confirms that OOP is only good for you.

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

I could even do something more object-oriented, but the temperature unit classes would have to be completely different, starting with the fact that they really are temperature objects and not just a vehicle for methods that don’t make sense. I even tried to take out if this was the intention of the AP in the comments, but it became 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 is clearer that nothing useful is being done, and not even object-oriented when in fact the conversion generates a number with no qualification whatsoever of which is a qualified specific temperature measure.

What one is actually intending to do is just to 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 application class:

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

I put in the Github for future reference.

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

The impression I have is that it is being tried by not understanding what is object orientation and the resources of language. The first version of the question did something even worse. Probably because he was trying to fix a problem that only existed because the architecture was all wrong. That’s the problem with wrong architecture. Start looking for insane solutions to fix what got wrong.

The answer was already prepared from the beginning only waiting for further clarification. When I posted I saw that this solution is what the ramaral proposed in the end, which is the "only" sound solution to this. His initial would still be bad. But I’ll understand any response that tries to go the way the AP wants.

In chat the subject was discussed and Dener Carvalho spoke of using enum to mark 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 it doesn’t fit.

Even if it had any meaning, 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. Not the case. Related subject: It’s a bad practice to use empty interfaces?

  • 1

    Bigown, I tried to show a more experienced colleague to suggest me something better. He tried to use patterns like Strategy, but turned into a bazooka at the end and too complex, at least for me, to understand. This answer that you and @Ramaral were trying to tell me, I see now how much simpler it got and how I was complicating too much.

  • 2

    That’s what I said, starts conceptualizing wrong, the knot gets more tangled. Note that if a classes Celsius if it were a real object, then it would have a different path. A complicated solution, but try to do it well: http://stackoverflow.com/q/7851448/221800. One that goes in a better way, but not yet what I would do: http://stackoverflow.com/q/3995920/221800.

  • 1

    I didn’t realize that the question had been reopened. Anyway my answer would be more or less in those terms.

  • I totally agree with that answer. I only published one to complement, offering the solution described in "[...] 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."

7

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);
}

The methods shall implement the 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;
    }

}

Implementation of the converter:

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

Mode of use:

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

See working on ideone

7

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

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

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

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

To 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 by itself. It makes very little sense to think of temperature as a class because it would have none demeanor (something that she literally "did", and which would be implemented in the form of methods according to Object Orientation). You have the need to convert temperature values according to different units, but it is somewhat forced to turn 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 from an interface) "Temperature" also sounds strange, because there is even no specialization to be made in the child classes!

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

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

If you think about it, there are numerous examples that do just that. The class itself String, or the class DateTime are like that. 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 manipulates that type of data more "complex" than a int or a float.

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

/**
 * 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 builder was made protected (protected) so that 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), because the unit name in the method name indicates the intention. In this case, I created static methods to build a temperature from a given unit (asCelcius, asFahrenheit and asKelvin) in the same way, only instead of calling it "from" ("of", in Portuguese) I called it "as" ("as" in Portuguese) because I want to convey the intention that it is not a conversion, but rather a creation. The "structure" internally turns to keep everything right.

  • There are properties to access the values in the different units. Want to read or update in Kelvin? Just do x = t.Kelvin or t.Kelvin = x. Simple and direct (as if updating the day of a date when doing d.day = 10 in another structure of this type). Note that internally I chose to keep the value in Celsius. Could I have a variable for each and always update all? Could, but it is unnecessary. Therefore, I always convert and keep Celsius in the updates, and convert to return in the accesses if necessary.

  • This approach gives scope for other interesting things. If you overload an operator, you can account for temperatures, compare values, etc, just as you would if you used double, but without worrying about the unit! I implemented the sum operator (operator+) to illustrate, in the code below.

Example of class usage 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 displays the sum result 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)
  • 2

    Except for the fact that the code looks like C++, it’s pretty good :P

  • 1

    @Bigown Must be the fault of the comments. rs

  • 2

    Pretty good, except for the fact that, um?. Never mind, there are no perfect solutions.:)

  • @ramaral now I was curious. There are some things that in production code would have to be different even but I wanted to know what you are thinking :)

  • @Nothing special about mustache. " Very good" doesn’t have enough characters, so I remembered playing with you for your comment, which is intended to be a compliment, but starts by pointing out defect.:)

  • 1

    @ramaral tendí... have things that can be improved, or questioned, but it would be exaggeration to discuss these things in a response that does not intend to be the definitive solution and that would become part of the . Net :) I realized I had voted, so I knew it was no big deal.

  • @Now who was curious was me. rs Since C# is not really my strong suit, for me it would be good to learn what could be improved. If you want to edit the answer and make the improvements directly, that would be nice, although I also agree that it is not absolutely necessary. P.S.: I can convert the answer to Wiki, if you prefer, without any problem (even). :)

  • 1

    @Luizvieira no, the answer is very good. The part of C#, is silly detail of style normally adopted, there is nothing wrong in the answer, the evaluation is sincere. To improve what I mean is that if it is to do something definitive and universal, have to think a lot about the subject, find all the problems that can find, it would take days, it would be good to several people analyze. Obviously the goal of an OS response is not to have a perfect API that everyone can use for everything. It’s just a good foundation that can be used in cases that don’t require perfection.

  • 1

    It’s better than most implementations of DateTime that are in the libraries of languages, that work, everyone uses, but it is full of problem when it needs perfection, then need to look for a non-standard library to solve the problem. Everything can be improved and you certainly agree.

  • @bigown Yes, of course I agree. Thanks. I thought it was something important in the language. As I said, C# isn’t really my strong suit. : ) Still, thanks for the tips!

  • 1

    @bigown and Luiz, I do not want my comment to be misunderstood, so I clarify: 1 - "Very good" does not have enough characters. 2 - "...., except for the fact that, um?." was because of Bigown’s comment (despite the :P at the end), because in fact it makes no sense to point out any defect, the answer is very good. However, nothing is perfect hence the "Never mind, there are no perfect solutions.". I should have stayed for the first part.

  • 1

    Don’t worry, @ramaral. : ) I already understood your comment when you replied to the bigown.

  • 1

    @At first I realized it was a joke, but I thought there was something minimal that I thought had to be changed anyway, then I realized it was just a joke.

  • 1

    I found your answer excellent, Luiz! Thank you to all who helped!

Show 9 more comments

6

The @Maniero response is quite simple, but it doesn’t solve a AP requirement that is to avoid using if.

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

So considering this converter declaration:

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 each possible user input to a particular conversion method, more or less 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 and case tolerance, etc. And of course, in a graphical user interface application, user entries can be limited 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 a common interface to the conversion method, and referencing 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.

  • 2

    My solution needs zero if. Zero complication. And almost put the implementation, is a line with a simple mathematical formula and nothing else. The programmer calls what he wants and it’s over. The question doesn’t even mention if, this requirement not to have if is invented.

  • 1

    I gave to invent requirement now :D . At the end of the question the AP talks about not using if. Clarify, please@bigown, as, from the user input, you select the conversion method without using if? Your answer doesn’t solve this question. Don’t get upset - I just want to help the AP just like you.

  • Just the person have brain. He doesn’t need to let the computer decide for him. The question clearly shows what he wants. There is no if nowhere. He put here as a remnant of what he was doing previously and had if no need. It has nothing to do with what was answered here. The Main() it shows that it needs nothing if. He doesn’t want anyone to type a string and find the function on its own. He wanted the programmer to decide this, and the programmer can decide only knowing the names of the methods when writing the code. KISS/YAGNI.

  • His previous solution used if and he wanted polymorphism. The current ended with if already. Then try to end with if where there is no if is invention. If you put an alternative to doing something extra, that’s fine, but what you like to do the most is to say that I did something wrong, when actually the wrong thing is what you posted (not that the solution is wrong, only that is not what was requested, is to do what was not requested, is to fill the gaps by own will).

Browser other questions tagged

You are not signed in. Login or sign up in order to post.