Сокращение 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));
}
}