Сокращение IF-оператора Java

Я пытаюсь сделать конвертер единиц измерения, используя javafx - я два дня искал, как мне уменьшить эти операторы if. каким-то образом я нашел похожую проблему, но она мне не помогла, так как я новичок в Java - я не знаю правильного подхода в моем случае.

Надеюсь, вы, ребята, могли бы помочь мне -

Спасибо

/**
     * CELSIUS TO
     */
    Celsius celsius = new Celsius(parseInput);
        if(cbOne.getValue().equals("Celsius")) {
            if(cbTwo.getValue().equals("Celsius") ) {
                showAnswer.setText(celsius.celsiusToCelsius());
            }
            if(cbTwo.getValue().equals("Fahrenheit")) {
                showAnswer.setText(celsius.celsiusToFahrenheit());
            }
            if(cbTwo.getValue().equals("Kelvin")) {
                showAnswer.setText(celsius.celsiusToKelvin());
            }
        }
    /**
     * FAHRENHEIT TO
     */
    Fahrenheit fahr = new Fahrenheit(parseInput);
        if(cbOne.getValue().equals("Fahrenheit") ) {
            if(cbTwo.getValue().equals("Celsius") ) {
                showAnswer.setText(fahr.fahrenheitToCelsius());
            }
            if(cbTwo.getValue().equals("Fahrenheit")) {
                showAnswer.setText(fahr.fahrenheitToFahrenheit());
            }
            if(cbTwo.getValue().equals("Kelvin")) {
                showAnswer.setText(fahr.fahrenheitToKelvin());
            }
        }
    /**
     * KELVIN TO
     */
    Kelvin kelvin = new Kelvin(parseInput);
        if(cbOne.getValue().equals("Kelvin")) {
            if(cbTwo.getValue().equals("Celsius") ) {
                showAnswer.setText(kelvin.kelvinToCelsius());
            }
            if(cbTwo.getValue().equals("Fahrenheit")) {
                showAnswer.setText(kelvin.kelvinToFahrenheit());
            }
            if(cbTwo.getValue().equals("Kelvin")) {
                showAnswer.setText(kelvin.kelvinToKelvin());
            }
        }
    }

8 ответов

Решение

Вам не нужно 3 класса для представления температуры в разных масштабах. Создайте класс, который всегда поддерживает температуру в Кельвинах внутри и может преобразовать ее в любой другой масштаб для вывода. Имея такой класс:

public final class Temperature {
  public enum Scale {
    Celsius, Fahrenheit, Kelvin
  }

  private final double temperature;

  private Temperature(double temperature) {
    this.temperature = temperature;
  }

  public static Temperature create(double temperature, Scale scale) {
    switch (scale) {
      case Celsius:
        return new Temperature(temperature + 273.15);
      case Fahrenheit:
        return new Temperature((temperature + 459.67) * 5.0 / 9.0);
      case Kelvin:
        return new Temperature(temperature);
      default:
        throw new IllegalArgumentException("Unknown scale");
    }
  }

  public double convertTo(Scale scale) {
    switch (scale) {
      case Celsius:
        return temperature - 273.15;
      case Fahrenheit:
        return temperature * 9.0 / 5.0 - 459.67;
      case Kelvin:
        return temperature;
      default:
        throw new IllegalArgumentException("Unknown scale");
    }
  }
}

Ваш код становится:

Temperature temp = Temperature.create(parseInput, Scale.valueOf(cbOne.getValue()));
showAnswer.setText(temp.convertTo(Scale.valueOf(cbTwo.getValue())));

На самом деле ваши заявления if хороши для небольшой программы. Они совершенно понятны. Однако вы можете уменьшить избыточность, проверив cbOne.getValue().equals(cbTwo.getValue()), Это будет торговать 3 ваших if заявления за 1.

Если бы у вас их было много, вы бы выиграли от карты и схемы интерфейса.

interface Converter {
    double convert(double from);
}

static final Map<String, Map<String, Converter>> converters = (
    new HashMap<String, Map<String, Converter>>()
);
static {
    Map<String, Converter> fromCelsius = new HashMap<String, Converter>();

    fromCelsius.put(   "Celsius", new NoConversionConverter()       );
    fromCelsius.put("Fahrenheit", new CelsiusToFahrenheitConverter());
    fromCelsius.put(    "Kelvin", new CelsiusToKelvinConverter()    );

    converters.put("Celsius", fromCelsius);

    ...
}

static Converter getConverter(String from, String to) {
    Map<String, Converter> fromMap = converters.get(from);
    return fromMap == null ? null : fromMap.get(to);
}

Карта является распространенным решением ООП. Вместо обязательного / структурированного принятия решений мы настраиваем Карту, а принятие решений скрыто за абстракцией.

Это чрезвычайно лаконично в Java 8 в сочетании с enum:

public enum Scale {
    CELSIUS, FAHRENHEIT, KELVIN;
    private final Map<Scale, DoubleUnaryOperator> ops = new HashMap<>();

    public DoubleUnaryOperator to(Scale to) {
        return to == this ? DoubleUnaryOperator.identity() : ops.get(to);
    }

    static {
        put(    CELSIUS, FAHRENHEIT, c -> c * 9.0 / 5.0 + 32.0     );
        put(    CELSIUS,     KELVIN, c -> c + 273.15               );
        put( FAHRENHEIT,    CELSIUS, f -> (f - 32.0) * 5.0 / 9.0   );
        put( FAHRENHEIT,     KELVIN, f -> (f + 459.67) * 5.0 / 9.0 );
        put(     KELVIN, FAHRENHEIT, k -> k * 9.0 / 5.0 + 459.67   );
        put(     KELVIN,    CELSIUS, k -> k - 273.15               );
    }

    private static void put(Scale from, Scale to, DoubleUnaryOperator op) {
        from.ops.put(to, op);
    }
}

Также чрезвычайно читабельно:

Scale      source = Scale.valueOf("CELSIUS");
Scale destination = Scale.valueOf("FAHRENHEIT");
double     result = source.to(destination).applyAsDouble(0.0);

Ваша самая большая проблема заключается в том, что вы создали проблему n × m, которая должна быть проблемой n + m.

Чтобы решить эту проблему, сначала определите каноническую единицу и распустите задачу на два этапа, преобразуйте из исходной единицы в каноническую единицу, а затем преобразуйте из канонической единицы в целевую единицу.

Например, если вы определили kelvin как каноническую единицу, ваш код может выглядеть следующим образом:

switch(inputUnit.getValue())
{
  case "Fahrenheit": kelvin=fahrenheitToKelvin(input); break;
  case "Celsius":    kelvin=celsiusToKelvin(input); break;
  case "Kelvin":     kelvin=input; break;
  default: throw new AssertionError();
}
switch(outputUnit.getValue())
{
  case "Fahrenheit": output=kelvinToFahrenheit(kelvin); break;
  case "Celsius":    output=kelvinToCelsius(kelvin); break;
  case "Kelvin":     output=kelvin; break;
  default: throw new AssertionError();
}
showAnswer.setText(output);

Я пропустил преобразование строки в число и число в строку, поскольку должно быть очевидно, что эти преобразования необходимо выполнять только один раз, вне селектора.

Этот принцип можно использовать также, если вы используете enum с вместо String или заменить switch с Map основанный на подходе, предложенный другими. Но важным моментом является двухэтапный подход, который позволяет поддерживать n блок ввода в канонический плюс m канонический для преобразования выходных единиц, а не n время ввода единиц m выходные единицы преобразования.

Вы можете преобразовать любое входное значение в Кельвины, а затем преобразовать из Кельвина в желаемый результат:

String unit = cbOne.getValue();
double inputInKelvin;
String outUnit = cbTwo.getValue();

// parse
if ( unit.equals("Celsius") ) inputInKelvin = new Celsius(parseInput).celsiusToKelvin();
else if ( unit.equals("Fahrenheit") ) inputInKelvin = new Fahrenheit(parseInput).fahrenheitToKelvin();
else inputInKelvin = new Kelvin(parseInput).kelvinToKelvin();

// output
Kelvin kelvin = new Kelvin(inputInKelvin);
if ( unit.equals("Celsius") ) showAnswer.setText(kelvin.kelvinToCelsius());
else if ( unit.equals("Fahrenheit") ) showAnswer.setText(kelvin.kelvinToFahrenheit());
else showAnswer.setText( kelvin.kelvinToKelvin() );

Это станет еще более читабельным, если вы сначала разберете String в double, а затем просто получите один класс Converter.

Поскольку вам не нужно будет модифицировать этот код позже, добавив больше модулей, я бы предложил вам троичный оператор:

String s = cbTwo.getValue();
showAnswer.setText(s.equals("Celsius") ? fahr.fahrenheitToCelsius() :
    s.equals("Farenheit") ? fahr.fahrenheitToFahrenheit() : kelvin.kelvinToKelvin()); 

Обратите внимание, что это не совсем эквивалентно, если s не соответствует ни одной из строк, которые есть в вашем if заявления.

Хотя троичный оператор будет работать, его трудно читать и поддерживать.

Немного лучше может быть оператор switch в первом операторе if. Однако ваш код в том виде, в котором он написан, достаточно понятен и не требует изменений с этой точки зрения.

Однако, чтобы действительно сделать этот код лучше, его нужно пересмотреть.

Есть две неприятные характеристики написанного кода. Во-первых, вы создаете экземпляры всех температурных объектов, когда используется только один.

Во-вторых, пользовательский интерфейс и бизнес-логика слишком тесно связаны. если вы измените имя элементов пользовательского интерфейса, вам придется изменить бизнес-логику. Кроме того, если вы добавите в свое приложение дополнительные способы преобразования температуры, вы не сможете повторно использовать этот код.

Вы также можете сделать что-то вроде этого:

 final int CELSIUS = 1,FARANHITE = 2,KELVIN = 3;

затем используйте переключатель уставки, как это

 int key = StringToInt(firstValue)*10 + StringTOInt(secondValue);
 //This will give you 9 unique codes 11 12 13 21 22 23 ....
 switch(key)
 {
       default: case 11: case 22: case 33: break;
       //do nothing since no conversion required

       case 12://Celsius to faranhite
       case 13://celsius to kelvin
       .
       .
       //and so on     

 }

Делай так

создать InterfaceForConvertorTypes

public interface Convertor {
    public String convert(int parseInt);
    public boolean accept(String from, String to);
}

затем реализуйте типы конверторов и зарегистрируйте их в регистровой коллекции. с одним IF, если вы можете достичь того, что вы хотите.

List<Convertor> convertors = new ArrayList<Convertor>();

    Convertor CelsiusToCelsius = new Convertor() {

        @Override
        public String convert(int parseInt) {
            Celsius celsius = new Celsius(parseInt);
            return celsius.celsiusToCelsius();
        }

        @Override
        public boolean accept(String from, String to) {
            return from.equals("Celsius") && to.equals("Celsius");
        }
    };


    Convertor CelsiusToFah = new Convertor() {

        @Override
        public String convert(int parseInt) {
            Celsius celsius = new Celsius(parseInt);
            return celsius.celsiusToFahrenheit();
        }

        @Override
        public boolean accept(String from, String to) {
            return from.equals("Celsius") && to.equals("Fahrenheit");
        }
    };

    Convertor CelsiusToKelvin = new Convertor() {

        @Override
        public String convert(int parseInt) {
            Celsius celsius = new Celsius(parseInt);
            return celsius.celsiusToFahrenheit();
        }

        @Override
        public boolean accept(String from, String to) {
            return from.equals("Celsius") && to.equals("Kelvin");
        }
    };

   // create Rest of Convertor like above

    convertors.add(CelsiusToFah);
    convertors.add(CelsiusToCelsius);
    convertors.add(CelsiusToKelvin);
    // register rest of convertor


    //Thats it!
    for(Convertor convertor:convertors) {
        if(convertor.accept(cbOne.getValue(), cbTwo.getValue())) {
            showAnswer.setText(convertor.convert(parseInput));
        }
    }
Другие вопросы по тегам