【问题标题】:Java auto update all references when replacing (not changing) object?替换(不更改)对象时Java自动更新所有引用?
【发布时间】:2013-01-14 01:22:04
【问题描述】:

所以我有一个我自己类的对象的 ArrayList:

public class A
{...}

现在假设我有一个变量来保存列表中的这些对象之一。现在我知道如果我更改该对象(例如在该对象上设置一个字段),这会更改保存对象本身的内存,因此两个引用(保存列表中对象的变量和内部数组变量)仍然相同,并且两者仍然显示给相同的,现在修改的对象。正常行为!

但我也希望在我替换变量指向的列表中的对象时也是这种情况。那是我做的时候

myList.set(theObjectPosition, newInstanceOfA);

...在这种情况下,我还希望我的变量自动引用这个新实例。实现这种行为的最简单方法是什么?

这就是我需要这个的原因:

简而言之,我有一组类来处理对 ArrayList 所做的更改的撤消/重做(我没有扩展它,这无论如何都不能解决问题?)。这些类被称为

UndoAction, Add, Remove, Modify (the latter 3 inherit from UndoAction)

所有类都有方法 undo() 和 redo()。

通过描述举例(如果需要,代码在最后):

假设我想将一个项目添加到列表中:我执行 new Add(myObject),然后我想修改列表中的项目,因此我执行 new Modify(myObject),之后它会创建对象的 DeepCopy状态为备份,然后我更改对象本身(在完成对 new Modify(..) 的调用之后)。然后我在 Modify 对象上执行 undo() 所以它确实 list.set(somePos, previousDeepCopy) (因此这篇文章,本质上,deepcopy 是一个新的实例,它把事情搞砸了!!!)....

所以你可以想象这个 list.set 会带来一些问题。对被替换对象的任何引用都将消失。所以如果它总是替换对这样的对象的引用,我就不能有效地使用这样的列表,因此我的撤消管理器注定要以这种方式失败。

那么我该如何应对这些设计缺陷呢?欢迎提出任何建议。


一些代码:

 protected abstract class UndoAction {
    protected HashSet<Integer> ids = new HashSet<Integer>();
    protected Marker marker;

    public UndoAction(List<E> l) {
      for (E e : l) {
        if (!entities.contains(e)) {
          entities.add(e);
          trace.append(entities.indexOf(e), new Stack<UndoAction>());
        }
        ids.add(entities.indexOf(e));
      }
    }

    public boolean sameAffectedTargets(UndoAction undoAction) {
      if (this.ids.containsAll(undoAction.ids) && undoAction.ids.containsAll(this.ids))
        return true;
      return false;
    }

    public Marker getMarker() {
      return new Marker(this);
    }

    public void firstRun() {
    }

    public void undo() {
    }

    public void redo() {
    }

    protected List<Data> getCopyEntities() {
      List<Data> l = new ArrayList<Data>();
      for (Integer id : ids) {
        E e = entities.get(id);
        int pos = adapterList.indexOf(e);
        l.add(new Data(id, pos, e));
      }
      return l;
    }

    protected List<Data> getDeepCopyEntities() {
      List<Data> l = new ArrayList<Data>();
      for (Integer id : ids) {
        E e = DeepCopy.copy(entities.get(id));
        int pos = adapterList.indexOf(entities.get(id));
        l.add(new Data(id, pos, e));
      }
      return l;
    }

    public void addEntities(List<Data> datas) {
      for (Data d : datas)
        d.addEntity();
    }

    public void setEntities(List<Data> datas) {
      for (Data d : datas)
        d.setEntity();
    }

    public void removeEntities(List<Data> datas) {
      for (Data d : datas)
        d.removeEntity();
    }

    protected class Data {
      public int id;
      public int pos;
      public E entity;

      public void addEntity() {
        entities.set(this.id, this.entity);
        adapterList.add(this.pos, this.entity);
      }

      public void setEntity() {
        entities.set(this.id, this.entity);
        E oldEntity = adapterList.get(this.pos);
        adapterList.set(this.pos, this.entity);
        notifyEntityReplaced(oldEntity, adapterList.get(this.pos), this.pos);
      }

      public void removeEntity() {
        entities.set(this.id, null);
        adapterList.remove(this.entity);
      }

      public Data(int id, int pos, E entity) {
        this.id = id;
        this.pos = pos;
        this.entity = entity;
      }
    }
  }

  protected class Add extends UndoAction {
    protected List<Data> addBackup;

    public Add(List<E> l) {
      super(l);
    }

    @Override
    public void undo() {
      super.undo();
      addBackup = getCopyEntities();
      removeEntities(addBackup);
    }

    @Override
    public void firstRun() {
      super.firstRun();
      adapterList.addAll(entities);
    }

    @Override
    public void redo() {
      super.redo();
      addEntities(addBackup);
    }
  }

  // call before modifying
  protected class Modify extends UndoAction {
    protected List<Data> beforeDeepCopies;
    protected List<Data> afterDeepCopies;

    public Modify(List<E> l) {
      super(l);
    }

    @Override
    public void undo() {
      super.undo();
      if (!skipModifying) {
        if (afterDeepCopies == null)
          afterDeepCopies = getDeepCopyEntities();
        setEntities(beforeDeepCopies);
      }
    }

    @Override
    public void firstRun() {
      super.firstRun();
      if (!skipModifying) // TODO
        beforeDeepCopies = getDeepCopyEntities();
    }

    @Override
    public void redo() {
      super.redo();
      if (!skipModifying)
        setEntities(afterDeepCopies);
    }
  }

  protected class Remove extends UndoAction {
    protected List<Data> removeBackup;

    public Remove(List<E> l) {
      super(l);
    }

    public List<E> getRemoved() {
      List<E> l = new ArrayList<E>();
      for (Data data : removeBackup)
        l.add(data.entity);
      return l;
    }

    @Override
    public void undo() {
      super.undo();
      addEntities(removeBackup);
    }

    @Override
    public void firstRun() {
      super.firstRun();
      removeBackup = getCopyEntities();
    }

    @Override
    public void redo() {
      super.redo();
      removeEntities(removeBackup);
    }
  }

【问题讨论】:

  • 我是否理解正确,问题在于您还希望能够重做和操作您撤消的操作?我不确定我是否理解正确 - 如果您执行 undo(),您的 Modify 操作不应该只是将旧对象放回原处吗?

标签: java reference undo deep-copy redo


【解决方案1】:

最简单的方法是确保您的类是“可变的”,也就是说,您可以使用新对象之一更改内部状态。

myOldObject.replaceValues(myNewObject);

如果对象本身由于某种原因不能做到可变,只需围绕它构建一个包装器。

【讨论】:

    【解决方案2】:

    如果 Java 中的对象引用(ClassName)类似于 C 指针(ClassName*),您似乎想要 C 指针指向指针(ClassName**)的行为。

    一种明显的方法是创建 Holder 类,其唯一目的是保存对您的类的引用。这样您就可以更改对对象本身的引用,并且只要所有人都持有对 Holder 的引用,它们现在也将指向新的替换对象。

    public class AHolder{
        public A aRef; // or private with getter setter.
    }
    

    【讨论】:

      【解决方案3】:

      我认为你需要一个额外的间接层。而不是让其他代码引用“A”,您可能希望它引用“Stack”,并使用“peek()”来引用当前值(当您更改数组中的值时,您将可能想要“push()”新值)。

      然而,为了代码的简洁,您可以通过将“A”变成一个接口并创建一个“A”的实现(我们称之为“DelegatingStackAImpl”),它提供与“A”除了支持“push()”和“pop()”并将调用转发到当前位于堆栈顶部的任何实例。因此,您不会破坏“A”的现有用法,同时仍支持这一额外的间接层。

      【讨论】:

        【解决方案4】:

        如果您要替换的对象仅通过其接口引用,那么您可以首先使用JDK dynamic proxy,它将委托给实际实现。要更改实际实现,您需要修改代理调用处理程序。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-03-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-16
          • 1970-01-01
          相关资源
          最近更新 更多