Рефакторинг кода на Java, альтернативы большому if

Я перерабатываю некоторый код в проекте, над которым я работаю, и я столкнулся с большим оператором if/else if, который следует за форматом:

if (changer instanceof AppleChanger)
{
   panel = new ApplePanel();
}
else if (changer instanceof OrangeChanger)
{
   panel = new OrangePanel();
} 

Теперь мой первый импульс состоял в том, чтобы реорганизовать его с помощью полиморфизма, чтобы он выглядел как

panel = changer.getChangerPanel();

Однако, к сожалению, пакет классов не имеет доступа к пакету панели.

Мой следующий импульс состоял в том, чтобы создать класс PanelChooser с перегруженным методом:

PanelChooser.getPanel(changer);

//Overloaded Method
public Panel getPanel(OrangeChanger changer)
{
   Panel orangePanel = new OrangePanel();
   return orangePanel;
}
public Panel getPanel(AppleChanger changer)
{
   Panel applePanel = new ApplePanel();
   return applePanel;
}

Это хорошее решение или есть лучший способ решить эту проблему?

Ответ 1

Основная "проблема" здесь заключается в том, что у вас есть параллельные иерархии классов. Вы не сможете заменить это выражение без какого-либо довольно тяжелого рефакторинга. Некоторые предложения на c2 wiki.

Лучшее, что вы можете сделать, и, возможно, идеальное решение, - переместить оператор if в класс factory и убедиться, что он не дублируется нигде.

Ответ 2

Я думаю, что это хорошо, что ваш первый импульс не сработал:) В противном случае вы бы связали код чейнджера (который должен быть чем-то вроде логики) с кодом UI (панель) и его неправильным.

Теперь я могу предложить вам следующее решение:

создать интерфейс PanelCreator с помощью метода Panel createPanel следующим образом:

interface PanelCreator {
   Panel createPanel();
}

Теперь представьте 2 реализации:

public class OrangePanelCreator implements PanelCreator{
   Panel createPanel() {
        return new OrangePanel();
   }
}

public class ApplePanelCreator implements PanelCreator {

  Panel createPanel() {
        return new ApplePanel();
  }
}

И вот теперь интересная часть:

Создать карту, PanelCreator > это будет действовать как реестр для ваших панелей:

Map<Class<Changer>, PanelCreator> registry = new HashMap<>;
registry.put(OrangeChanger.class, new OrangePanelCreator());
registry.put(AppleChanger.class, new ApplePanelCreator());

Теперь в вашем коде вы можете сделать следующее:

panel = registry.get(changer.getClass()).createPanel();

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

Надеюсь, что это поможет

Ответ 3

Если в коде больше, чем одна из конструкций if/else, зависит от типа экземпляра Changer, вы можете использовать шаблон посетителя следующим образом:

public interface ChangerVisitor {
  void visit(OrangeChanger changer);
  void visit(AppleChanger changer);
  ...
}

public class ChangerVisitorEnabler<V extends ChangerVisitor> {
  public static <V extends ChangerVisitor> ChangerVisitorEnabler<V> enable(V) {
    return new ChangerVisitorEnabler<V>(visitor);
  }

  private final V visitor;

  private ChangerVisitorEnabler(V visitor) {
    this.visitor = visitor;
  }

  public V visit(Charger changer) {
    if (changer instanceof OrangeChanger) {
      visitor.visit((OrangeChanger)changer);
    } else if (changer instanceof AppleChanger) {
      visitor.visit((AppleChanger)changer);
    } else {
      throw new IllegalArgumentException("Unsupported charger type: " + changer);
    }
    return visitor;
  }
}

Теперь у вас есть блок кода проверки на один тип и безопасный интерфейс типа:

public PanelChooser implements ChangerVisitor {

  public static Panel choosePanel(Changer changer) {
    return ChangerVisitorEnabler.enable(new PanelChooser()).visit(changer).panel;
  }

  private Panel panel;

  private PanelChooser() {
  }

  void visit(OrangeChanger changer) {
    panel = orangePanel();
  }

  void visit(AppleChanger changer) {
    panel = applePanel();
  }

}

Использование очень просто:

panel = PanelChooser.choosePanel(chooser);

Ответ 4

Возможно, вы можете сделать:

public Panel getPanel(Changer changer)
{
    String changerClassName = changer.class.getName();
    String panelClassName = changerClassName.replaceFirst("Changer", "Panel");
    Panel panel = (Panel) Class.forName(panelClassName).newInstance();
    return panel;
}

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

Удачи!

Ответ 5

Я не вижу достаточно существующего кода и дизайна в целом. Вероятно, прежде всего, я попытался бы переместить код с помощью экземпляра панели в том же месте, где создан экземпляр Changer. Поскольку выбор панели является тем же самым решением, что и при выборе Changer.

Если выбранный Changer динамически выбран, вы можете просто создать эти панели, а затем отобразить/скрыть их соответственно.

Ответ 6

Ваше решение не будет работать, потому что Java выбирает метод на основе типа compiletime (который здесь, вероятно, Changer). Вы можете использовать Map<Class<? extends Changer>, Panel> (или Map<Class<? extends Changer>, Class<? extens Panel>>, если вам нужно создавать новые экземпляры каждый раз). Это решение требует дополнительной работы, если вам нужно это работать для - пока неизвестных - подклассов, например, OrangeChanger.

например, для одного экземпляра для подкласса Changer

changerToPanel.get(changer.getClass());

или если вам нужны новые экземпляры:

changerToPanelClass.get(changer.getClass()).newInstance();

Другой вариант заключается в том, чтобы перейти к вашей первоначальной догадке и сообщить Changer о панели.

Ответ 7

Взгляните на Factory и Аннотация Factory Шаблоны.

Шаблон Factory - это шаблон создания, который используется для управления созданием экземпляра класса. Шаблон factory используется для замены конструкторов классов, абстрагирования процесса генерации объекта, чтобы тип экземпляра объекта был определен во время выполнения.

Абстрактный Factory Шаблон - это шаблон создания, так как он используется для управления созданием класса. Абстрактный шаблон factory используется для предоставления клиенту набора связанных или зависимых объектов. Семейство объектов, созданных factory, определяется во время выполнения в соответствии с выбором конкретного класса factory.

Ответ 8

Я бы сделал следующее:

Имеет интерфейс PanelChooser с единственным методом, возвращающим панель для чейнджера.

Имейте реализацию ClassBasedPanelChooser, возвращающую панель, когда Change реализует определенный класс и null в противном случае. Класс и панель, которую нужно вернуть, передаются в конструктор.

Есть ли другая реализация CascadingPanelChooser, которая принимает список PanelChoosers в аргументах конструктора и при вызове его метода запрашивает каждую PanelChooser для предоставления панели до тех пор, пока не получит непустую панель, затем она вернет эту панель.

Ответ 9

Не используйте экземпляр. Почему не удается выполнить полиморфизм Единственное место, где можно использовать экземпляр, находится внутри метода equals.

Чтобы ответить на ваш вопрос. Следуйте этой ссылке . Кредиты Cowan и jordao.

  • Использование отражения.
public final class Handler {
  public static void handle(Object o) {
    for (Method handler : Handler.class.getMethods()) {
      if (handler.getName().equals("getPanel") && 
          handler.getParameterTypes()[0] == o.getClass()) {
        try {
          handler.invoke(null, o);
          return;
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    }
    throw new RuntimeException("Can't handle");
  }
  public static void handle(Apple num) { /* ... */ }
  public static void handle(Orange num) { /* ... */ }
  • Цепочка ответственности
 public abstract class Changer{
   private Changer next;

   public final boolean handle(Object o) {
      boolean handled = doHandle(o);
      if (handled) { return true; }
      else if (next == null) { return false; }
      else { return next.handle(o); }
   }

   public void setNext(Changer next) { this.next = next; }

   protected abstract boolean doHandle(Object o);
}

public class AppleHandler extends Changer{
   @Override
   protected boolean doHandle(Object o) {
      if (!o instanceof Apple ) {
         return false;
      }
      OrangeHandler.handle((Orange) o);
      return true;
   }
}