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

Java 更改一个对象的属性时替换重复代码的设计模式,java,design-patterns,Java,Design Patterns,我有4个自定义微调器,可以修改一个选定小部件的宽度、高度、X位置和Y位置。我的小部件可以在屏幕上拖动,其想法是使用这些微调器来更改某些属性,例如宽度或高度,并查看对更改的即时影响。 是否有一种模式可以用来将所有这些类(XSpinnerListener、YSpinnerListener…)替换为一个类,并指示当前对象(JButton)的哪个属性需要更改?这是一种好的设计方法吗 public void init(){ widthSpinner.setListener(new WidthSpi

我有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());
    }
 }
一些思考

您可以通过为SpinnerListener
spinnerValueChanged(…)
方法提供一个SpinnerEvent参数来模拟Swing的设计,该参数指示正在更改的轴。轴可以由枚举封装

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);
}
也许您的具体实现可以用于实现可移动接口的对象:

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

}

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

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
component)同步


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

就设计而言,您的主要问题似乎是将视图与控件隔离。我想您正在寻找模型视图控制器

该模型在OP中被称为
current

如果设置X位置是一个主要的模型操作,那么在当前的API中应该有一个
setXLocation(int X)


如果这是真的,那么所有四个操作都是一行程序,将滑块与适当的操作关联起来,使用一个接口作为Java假设Java 8,并暂时接受微调器实际上有一个
setListener()
方法,我只是将
SpinnerListener
实现为一个利用lambdas的具体类。请注意,
SpinnerListener
s中的每一个调用current上的一个方法,该方法使用两个
int
参数,您可以在SpinnerListener中设想类似的情况:

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

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

听起来你是在寻找答案,这几乎是主观的。请尽量使它尽可能纯粹客观。有一些公认的模式和反模式,但如果不是,这是非常主观的。如果您认为问题不符合标准,请根据您的意愿编辑问题。基本上,我不希望一个单独的小部件有4个侦听器,每个侦听器修改某些内容,我只希望有一个侦听器,并告诉它要修改哪个属性,以及如何向微调器侦听器构造函数添加一个参数,例如一个枚举,以标识哪个微调器正在侦听。然后在您的侦听器代码中,打开枚举以查看您执行的4个操作中的哪一个。我认为这种方法会带来一些开销,因为如果您更改这些微调器中的任何一个的值,您需要执行updateLocationAndSize()中的其他3条指令。确实,但开销可以忽略不计。考虑一下
setLocation
方法,它的原理是相同的,您设置了两个坐标,尽管您只是用相同的值重写了一个坐标。
组件
API设计人员不想用API和实现的可读性来换取可以忽略不计的性能增益,而性能增益只能通过设置一个坐标来获得。实际上,如果您查看
setLocation
setSize
setWidth
以及所有类似的
组件
方法,您会注意到它们都委托给
重塑(int x,int y,int width,int height)
。您可以通过调用
setBounds(int x,int y,int width,int height)来改进我的解决方案
。实际上,我编辑了我的答案,使其包含两个调用,而不是四个。另外,我认为单独调用
setLocation
setSize
比使用四个参数调用
setBounds
可读性更强。在回答中,我着重于对解决类似问题的方法的更一般性描述;当然,如果需要,每个具体案例都可以进一步改进。非常感谢您的回答。但是,我不喜欢这种更新或检查所有小部件更新的方法。除了这会产生一些额外的开销之外,它可以是任何类型的侦听器,需要更改对象的其他属性。假设我们有100万听众。在这种极端情况下,你别无选择,只能在可读性和性能之间取得平衡。如果您有100万个侦听器,那么您的应用程序可能无法使用,因为数千个侦听器都有bug。您可能需要将这些功能分成1000个侦听器,每个侦听器执行1000个操作,尽管许多操作都是多余的。您能提供一些代码来解释您的观点吗?而且,它可以是一个班轮,也可以是更多班轮,我知道它现在的工作方式,从设计的角度来看。然而,我不喜欢创造不同的听众。不喜欢X(“X”)、Y(“Y”)、宽度(“宽度”)、高度(“高度”);如果我们有1个呢
public void init() {
    widthSpinner.setListener(
        new SpinnerListener(
            { a, b -> current.setSize(a, b) },
            { widthSpinner.getValue() },
            { current.getHeight() }));
    // ... etc.
}