设计模式在更改一个对象的属性时替换重复代码

时间:2015-05-10 00:58:07

标签: java design-patterns

我有4个自定义微调器,可以修改一个选定小部件的宽度,高度,X位置和Y位置。我的小部件可以在屏幕上拖动,并且想法是使用这些微调器来更改某些属性,例如宽度或高度,并立即看到对更改的影响。 是否有一个模式可以用来替换所有这些类(XSpinnerListener,YSpinnerListener ...)只有一个,并指示我当前对象(JButton)的哪个属性需要更改?这是一个很好的设计方法吗?

public void init(){
    widthSpinner.setListener(new WidthSpinnerListener());
    heightSpinner.setListener(new HeightSpinnerListener());
    xSpinner.setListener(new XSpinnerListener());
    ySpinner.setListener(new YSpinnerListener());
}


public class XSpinnerListener implements SpinnerListener {

    @Override
    public void spinnerValueChanged() {
         current.setLocation(xSpinner.getValue(), current.getY());
    }
}

public class YSpinnerListener implements SpinnerListener {

    @Override
    public void spinnerValueChanged() {
        current.setLocation(current.getX(), ySpinner.getValue());
    }
}

public class WidthSpinnerListener implements SpinnerListener {

    @Override
    public void spinnerValueChanged() {
         current.setSize(widthSpinner.getValue(), current.getHeight());
    }
}

public class HeightSpinnerListener implements SpinnerListener {

    @Override
    public void spinnerValueChanged() {
         current.setSize(current.getWidth(), heightSpinner.getValue());
    }
 }

4 个答案:

答案 0 :(得分:6)

有些想法......

您可以通过为SpinnerListener spinnerValueChanged(...)方法提供SpinnerEvent参数来模拟Swing的设计,该参数指示正在更改哪个轴。 Axis可以用enum封装,......

enum Axis {
   X("X"), Y("Y"), WIDTH("Width"), HEIGHT("Height");

   private String name;

   private Axis(String name) {
      this.name = name;
   }

   public String getName() {
      return name;
   }

}

SpinnerEvent参数类可能类似于:

class SpinnerEvent {
   private Object source;
   private Axis axis;
   private int value;

   public SpinnerEvent(Object source, Axis axis, int value) {
      this.source = source;
      this.axis = axis;
      this.value = value;
   }

   public Object getSource() {
      return source;
   }

   public Axis getAxis() {
      return axis;
   }

   public int getValue() {
      return value;
   }

}

您的SpinnerListener界面(您不会向我们展示)必须更改:

interface SpinnerListener {
   public void SpinnerValueChanged(SpinnerEvent e);
}

也许你的具体实现可以在实现Movable接口的对象上工作:

interface Movable {

   public abstract int getX();
   public abstract void setX(int x);
   public abstract int getY();
   public abstract void setY(int y);
   public abstract int getWidth();
   public abstract void setWidth(int width);
   public abstract int getHeight();
   public abstract void setHeight(int height);
   public abstract void move(Axis axis, int value);
}

使用密钥方法,可以像下面那样实现移动:

@Override
public void move(Axis axis, int value) {
  switch (axis) {
  case X:
     x += value;
     break;
  case Y:
     y += value;
     break;
  case WIDTH:
     width += value;
     break;
  case HEIGHT:
     height += value;
  default:
     break;
  }
}

小具体实施

class ConcreteSpinnerListener implements SpinnerListener {
   private Movable movable;

   public ConcreteSpinnerListener(Movable movable) {
      this.movable = movable;
   }

   @Override
   public void SpinnerValueChanged(SpinnerEvent e) {
      movable.move(e.getAxis(), e.getValue());
   }

}

答案 1 :(得分:3)

在类似情况下,如果没有性能损失,我总会在发生变化时执行所有操作:

public void init(){
    SpinnerListener spinnerListener = new MySpinnerListener();
    widthSpinner.setListener(spinnerListener);
    heightSpinner.setListener(spinnerListener);
    xSpinner.setListener(spinnerListener);
    ySpinner.setListener(spinnerListener);
}

public class MySpinnerListener implements SpinnerListener {
    @Override
    public void spinnerValueChanged() {
         updateLocationAndSize();
    }
}

void updateLocationAndSize() {
    current.setLocation(xSpinner.getValue(), ySpinner.getValue());
    current.setSize(widthSpinner.getValue(), heightSpinner.getValue());
}

这样,代码更短,其他读者的意图也很明确。

此外,此方法的另一个优点是可以更轻松地将初始状态触发器(微调器)与操作使用者(current组件)同步。

在您的示例中,它可能适用也可能不适用,但假设您使用current的已保存大小和位置属性打开屏幕。您使用保存的值初始化微调器,然后调用updateLocationAndSize以使current的大小和位置与微调器同步,然后注册侦听器以通知增量更改。< / p>

答案 2 :(得分:2)

假设Java 8并且接受了你的微调器实际上有setListener()方法的那一刻,我只会将SpinnerListener实现为利用lambdas的单个具体类。注意到你的每个SpinnerListener调用一个带有两个int参数的当前方法,可以在SpinnerListener中设想这样的东西:

BiConsumer<Integer, Integer> currentSetter;
Supplier<Integer> firstArg;
Supplier<Integer> secondArg;

public void spinnerValueChanged() {
    currentSetter.accept(firstArg.get(), secondArg.get());
}

可以在init中调用它:

public void init() {
    widthSpinner.setListener(
        new SpinnerListener(
            { a, b -> current.setSize(a, b) },
            { widthSpinner.getValue() },
            { current.getHeight() }));
    // ... etc.
}

答案 3 :(得分:0)

在设计方面,您的主要问题似乎是从控件中隔离视图。您正在寻找模型视图控制器。

该模型是OP中所谓的coord_cartesian

如果设置X位置是主模型操作,则当前API中应该有d <- data.frame(a = rnorm(n = 100, mean = 0, sd = 1), b = rnorm(n = 100, mean = 0, sd = 1)) ggplot(d, aes(a,b)) + geom_point(position=position_jitter(width=0.3, height=.3), size=2) + theme(panel.background=element_blank()) + coord_cartesian(xlim=c(-3,3), ylim=c(-3,3))

如果这是真的,那么所有四个动作都是一个将滑块与相应动作相关联的衬垫,使用接口作为传递函数指针的Java&lt; = 1.7方式,所以你很好。我可能会使监听器匿名(如果没有重用,请将实际代码保持为接近Slider定义)但这只是风格。

基本上,我们将视图(滑块本身)和控件(听众代码)混合了一下,但在我看来,在大多数情况下它都可以(在我的例子中,我会这样做,并坚持你当前的解决方案)。可读性仍然相当不错,尽管它很冗长。那只是Java,在我看来它会运行良好并且可以维护(就像任何GUI一样)。

如果你有很多这样的属性需要编辑,或者模型的API已经很好并且不需要这些精细控件,你可能想要使用一个显式控制器,即一个解释细粒度操作的类{ {1}}并在实际当前对象public class FormPanel extends JPanel { private JLabel usernameLabel, passwordLabel; private JTextField usernameField; private JPasswordField passwordField; private JButton submitButton, clearButton; private Collection<FormPanelListener> formPanelListeners = new ArrayList<>(); public FormPanel() { ... submitButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String username = usernameField.getText().trim(); char[] password = passwordField.getPassword(); FormPanelEvent e = new FormPanelEvent(this or submitButton) } }); ... } 上适当地委托他们。

在Hovercraft的答案中已经提出了该方向的完整方案:使您的命令显式(根据DP),使视图元素生成命令,以及控制器来解释它们。你需要一个非常繁重的用例来走这条路,尽管最终它更强大和可扩展。如果你没有做任何愚蠢的事情,GUI就不会有性能问题。所以德拉甘的回答也让我感到务实和明智。在某种程度上,它已经部分地将控制代码与实际的setLocationAndSize方法中的视图隔离开来。

总而言之,如果您在听众中添加的代码是单行代码,那么从设计的角度来看,您就可以了。

如果听众代码很复杂,则不应该隐藏&#34;在一个监听器中:这会产生代码碎片,在各种监听器中有很多重复,重要的代码隐藏在GUI代码中,而它永远不应该是这种情况。

对于多个单行,请让侦听器将实际操作委托给更复杂的控制器,或者考虑扩展模型API以将此操作包含为它提供的高级服务。