Рефакторинг кода в Java, альтернативы инструкции large 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;
}

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

9 ответов


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

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


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

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

создайте интерфейс PanelCreator с помощью панели методов 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();

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

надеюсь, что это помогает


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

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

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

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, но это то, что я бы попробовал, если бы это было на C#. Я также не знаю, будет ли это работать с вашими пакетами.

удачи!


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

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


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

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

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

есть другая реализация CascadingPanelChooser, которая принимает список Panelchooser в аргументах конструктора и при вызове его метод просит каждого PanelChooser предоставить панель, пока он не получит панель not null, а затем возвращает эту панель.


ваше решение не будет работать, потому что 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();

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


посмотри завод и Абстрактная Фабрика Шаблоны.

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

Абстрактная Фабрика Pattern является творческим шаблоном, поскольку он используется для управления экземпляром класса. Абстрактный шаблон фабрики используется для предоставления клиенту набора связанных или зависимых объектов. Семейство объектов, создаваемых заводом, определяется во время выполнения в соответствии с выбором конкретного Заводского класса.


не используйте instanceof.почему полиморфизм не Единственное место для использования instanceof внутри равна метод.

чтобы ответить на ваш вопрос. Следуйте за этим ссылке. Кредиты Коуэн и районе .

  • С Помощью Отражения.
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) { /* ... */ }
  • цепи Ответственность!--5-->
 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;
   }
}