Разработка программного обеспечения Java

Сценарий:

Рассмотрим следующий абстрактный класс MeasurementType и его подтипы Acceleration и Density:

public abstract class MeasurementType {
  protected String name;
  protected String[] units;

  public MeasurementType() {}

  public MeasurementType(String name) {
    this.name = name;
  }

  protected void setName(String name) {
    this.name = name;
  }

  protected String getName() {
    return name;
  }

  protected void setUnits(String[] values) {
    this.units = values;
  }
}

public class Acceleration extends MeasurementType {
  private String name;

  public Acceleration () {
    super();
  }

  public Acceleration(String name) {
    super();
  }

}

public class Density extends MeasurementType {
  private String name;

  public Density() {
    super();
  }

  public Density(String name) {
    super();
  }

}

Вопрос:

В чем заключается необходимость в подтипах, так как в этих классах нет ничего, что бы их отличать отдельно (по крайней мере, в моих реализациях), чтобы дать им право на существование как свои собственные сущности, но ради Например, можно сказать, что у них были свои собственные массивы, содержащие единицы для каждого отдельного типа, поэтому плотность имела бы килограммы, граммы и т.д., а ускорение имела бы mph, kmph и т.д.

Наконец, у меня есть этот класс запуска, который я быстро составил:

public class run {

  public run() {}

  public static MeasurementType testType() {
    Acceleration a = new Acceleration("meters per second");
    return (MeasurementType) a;
  }

  public static void main(String[] args) {
    MeasurementType mt = testType();
    System.out.println(mt.getClass().toString());
    System.out.println(mt.getClass().getName());

    String type = "acceleration";
    String[] units = new String[8];

    Object mType;
    if(type.equals("acceleration")) {
      mType = new Acceleration();
    } else if(type.equals("density")) {
      mType = new Density();
    } else {
      mType = new Object();
    }

    System.out.println(mType.getName());
  }
}

Вот где я столкнулся с проблемой, о которой я думал. Я использовал строки для определения типа, который я бы создавал, но проблема в том, что по умолчанию в конце условных операторов я создаю экземпляр объекта, потому что иначе компилятор будет жаловаться на то, что переменная mType не была бы инициализирована. Я не могу создать объект MeasurementType, поскольку он является абстрактным классом.

Я думаю, что это скорее вопрос дизайна, но есть ли лучший способ определить класс для создания экземпляра? Предположим, что String type = "acceleration" пришел из ниспадающего или где-то из некоторого пользовательского интерфейса, я просто его закодировал, потому что это не обязательно для вопроса.

Может кто-то пролить свет на эту ошибку дизайна, как я ее вижу.

Ответ 1

что-то вроде этого factory -подобных кодов может помочь. Это сохраняет 100 if equals and new Xxxx() операторов, если у вас есть 100 подтипов.

public abstract class MeasurementType {
    protected  String name;

    private static final Map<String, Class<? extends MeasurementType>> typeMap = new HashMap<>();
    static{
        typeMap.put("accelaration", Acceleration.class);
        typeMap.put("density", Density.class);
       //put here if you have others
    }

    public static MeasurementType getMeasurement(String keyStr) throws RuntimeException {
        if (typeMap.containsKey(keyStr)) {
            try {
                return typeMap.get(keyStr).newInstance();
            }
            catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("creation failed.");
            }
        }
            //this could be a speical exception type
        throw new RuntimeException("creation failed: unknown type"); 
    }

}

в классе run:

        String s = "accelaration";
        String s2 = "trash";
        MeasurementType t = null;

        try {
            t =MeasurementType.getMeasurement(s);
            t = MeasurementType.getMeasurement(s2); //throw ex
        }catch (RuntimeException e){
            //do something
        }

Ответ 2

Глядя на суть вашего вопроса, что делать, когда строка не является "ускорением" или "плотностью".

if(type.equals("acceleration")) {
  mType = new Acceleration();
} else if(type.equals("density")) {
  mType = new Density();
} else {
  mType = new Object();
}

Является ли это признаком того, что что-то в вашей программе нарушило, что оно безвозвратно неправильно? В таком случае; исключение.

if(type.equals("acceleration")) {
  mType = new Acceleration();
} else if(type.equals("density")) {
  mType = new Density();
} else {
  throw new RuntimeException("variable type is not a valid type, valid types are 'acceleration' and 'density'";
}

Это осознание, к которому вы придете; исключения - ваш друг. Они означают, что когда программа не так, она кричит, где и почему, а затем вы ее исправляете. С другой стороны, тихое игнорирование проблемы приводит к тонким ошибкам и странному поведению, которое может занять несколько дней для диагностики.

С другой стороны, это признак того, что пользователь вводит неправильный ввод? В этом случае подтвердите ввод и, если его недопустимый, сообщите конечному пользователю и попросите его снова.

Используя String, и если такие утверждения, скорее всего, не будут тем, что вы хотите делать вообще, но в предположении, что это то, что вы хотите сделать, это путь вперед (оператор switch будет немного приятнее).

Ответ 3

Если строка не является одним из имен измененных типов измерений, вы не должны создавать объект. Вы должны сообщить о проблеме, выбросив исключение:

public MeasurementType fromString(String type) {
    if (type.equals("acceleration")) {
        return new Acceleration();
    } 
    else if (type.equals("density")) {
        return new Density();
    } 
    else {
        throw new IllegalArgumentException(type + " is not a known measurement type");
    }
}

Ответ 4

одним из способов является добавление подкласса DefaultMesurementType к подклассам MesurementType. это также известно как Образец нулевого объекта...

Другой способ обойти ошибку компилятора - просто присвойть null mType:

MesurementType mType = null;
if(type.equals("acceleration")) {
    mType = new Acceleration();
} else if(type.equals("density")) {
    mType = new Density();
}

Ответ 5

Что касается вашей конкретной проблемы, ваше окончательное предложение else должно либо вернуть значение null (IMO уродливое), либо, предпочтительно, выбросить исключение.

else {
  throw new IllegalArgumentException(type + " is not a legal type.");
}

Кроме того, будьте очень осторожны в передаче массивов в конструктор/сеттер, поскольку вызывающий объект все еще имеет ссылку на него и может его модифицировать. Вероятно, вы хотите создать защитную копию.

Ответ 6

Положите некоторый сарай на дизайн и попытайтесь улучшить его для гибкости и ремонтопригодности...
1) Прежде всего используйте конструкторский принцип "Программа для интерфейса, но не реализация" в следующей части вашего кода.

public static MeasurementType testType() {
    MeasurementType a = new Acceleration("meters per second");
    return a;
  }

2) Вы можете использовать Factory Шаблон, чтобы получить правильный тип MeasurementType в зависимости от mType, предоставленного клиентом.

public static MeasurementType testType(String mType) {
        MeasurementType a = MeasurementFactory.getMeasurementType(mType);
        return a;
      }

3) Всегда помните о самом важном принципе дизайна - "Инкапсулировать то, что меняется" . Теперь пришло время извлечь часть кода, которая может измениться в будущем. Сделайте factory...

public class MeasurementFactory {

    public static MeasurementType getMeasurementType(String mType) {

        if (mType.equals("acceleration")) {
            return new Acceleration();
        } else if (mType.equals("density")) {
            return new Density();
        } else {
            throw new RuntimeException("Invalid MeasurementType");
        }
    }
}

Теперь ваш дизайн более гибкий и лучший. Каждый раз, когда вы добавляете новый MeasurementType, вам нужно изменить класс MeasurementFactory, а не каскадировать изменение вниз.

Ответ 7

Да, ты прав. Это проблема дизайна. Всегда помните, что разные классы должны иметь разное поведение, поэтому только создавайте разные классы, где имеет смысл их создавать. Также, чтобы ответить на вашу проблему, вы можете взглянуть на шаблон стратегии . Ваша классическая проблема программного обеспечения, а также классический ответ. Проверьте его и попробуйте выполнить его.