【问题标题】:For two methods who need to call each other: how do I prevent an infinite loop?对于需要互相调用的两种方法:如何防止无限循环?
【发布时间】:2009-06-06 23:20:41
【问题描述】:

我有两个类(A 和 B),它们在以下意义上相互依赖:
每个类都有一个执行某些操作的方法。
每个类的动作依赖于另一个类的动作。

所以,如果用户调用A类的动作,它应该自动调用 B类的行动。
其他方式也一样。但是应该防止无限循环。

我找到了一些处理这个问题的代码,但它似乎是一个 对我来说有点愚蠢:锁定可以防止无限循环。

import java.util.concurrent.locks.*;
import static java.lang.System.*;
import org.junit.*;

public class TEST_DependentActions {

  static class A {
    private B b = null;
    private final ReentrantLock actionOnBLock = new ReentrantLock();

    public void setB(B b) {
      this.b = b;
    }

    public void actionOnB() {
      if (!actionOnBLock.isLocked()) {
        actionOnBLock.lock();
        b.actionOnA();
        actionOnBLock.unlock();
      }
    }
  }

  static class B {
    private A a = null;
    private final ReentrantLock actionOnALock = new ReentrantLock();

    public void setA(A a) {
      this.a = a;
    }

    public void actionOnA() {
      if (!actionOnALock.isLocked()) {
        actionOnALock.lock();
        a.actionOnB();
        actionOnALock.unlock();
      }
    }
  }

  @Test
  public void test1()
      throws Exception {

    out.println("acting on class A first:");

    A a = new A(); B b = new B();
    a.setB(b);     b.setA(a);

    a.actionOnB();
  }

  @Test
  public void test2()
      throws Exception {

    out.println("acting on class B first:");

    A a = new A(); B b = new B();
    a.setB(b);     b.setA(a);

    b.actionOnA();
  }
}

输出如下:

acting on class A first:
A : calling class B's action.
B : calling class A's action.
acting on class B first:
B : calling class A's action.
A : calling class B's action.

嗯,它有效,但似乎不是最佳解决方案。

会怎么做?
是否有处理此类问题的模式

编辑:
我想大致了解一下。
但是,假设我有一个 Container,其中包含多个 Element。 Container 提供方法 remove(someElement) 和 Element 还提供了一个方法 removeMe()
两种方法相互依赖,但不能连接到一个 方法,因为这两种方法都额外执行一些内部工作,这只是 内部每个类都可以访问。

【问题讨论】:

    标签: java design-patterns


    【解决方案1】:

    我会通过重新思考逻辑来处理这个问题。循环依赖通常表明有些事情有点……不对劲。如果没有更深入地了解确切的问题,我无法更具体。

    【讨论】:

    • 我有点同意...但是抽象命名的动作和类使得提出更好的解决方案有点困难。
    • 我已阅读您的编辑,但我仍然认为逻辑需要重新考虑,但如果您对这个 API 死心塌地,请参阅 Scott Langham 的帖子以获得可行的解决方案。
    【解决方案2】:

    您能否将其中一种方法设为私有/内部。 这应该确保客户端代码只能调用另一个,并且您始终知道调用的工作方式。

    或者,使用您的容器/元素示例,如下所示:

    public class Container
    {
        public void Remove(Element e)
        {
            e.RemoveImplementation();
            RemoveImplementation();
        }
    
        // Not directly callable by client code, but callable
        // from Element class in the same package
        protected void RemoveImplementation()
        {
           // Mess with internals of this class here
        }
    }
    
    
    public class Element
    {
        private Container container;
    
        public void Remove()
        {
           RemoveImplementation();
           container.RemoveImplementation();
        }
    
        // Not directly callable by client code, but callable
        // from Container class in the same package
        protected void RemoveImplementation()
        {
            // Mess with internals of this class here.
        }
    }
    

    我不确定这种模式是否有通用名称。

    【讨论】:

    • 如果我必须有一个 Elment.Remove() 方法,我会这样做,但我觉得有这样的方法是错误的。 Element 不应该知道关于可能持有或不持有它的容器的任何信息。
    【解决方案3】:

    我的建议是少考虑代码,多考虑设计。可以将共享操作抽象为两个类都可以通信的新类吗?是否在 2 个类之间错误地共享了该功能?

    在今年的 Uni 上,他们引入了“代码气味”的概念,这是代码需要重构的一堆“直觉反应”。或许能帮上忙?

    对于概述尝试: Wikipedia Code Smellsthis book

    也许您可以告诉我们更多关于您试图在代码中表示的内容?

    我希望这会有所帮助。

    【讨论】:

      【解决方案4】:

      给出的解决方案对于给定的场景看起来完全可以接受。但这实际上取决于这种行为是否正确。如果这正是您想要的行为,那么我不确定您是否有问题。

      附录:
      在这种情况下,我会说,将“额外的东西”拆分成一个单独的方法,以便容器上的remove 可以在没有递归回调的情况下调用元素上的“额外的东西”。类似地,容器上多余的东西可以分开,这样removeMe 可以调用容器上的一个方法,该方法只做非递归的东西。

      【讨论】:

        【解决方案5】:

        我认为这可以通过类的全局布尔值来解决(当然,让它成为一个实例变量)。它检查 if([布尔变量]) 是否为真;它运行对另一个方法的调用,如果它是假的,它不会。就在 if 语句中,将检查设置为 false。然后在每个方法结束时将其设置为 true。

        这就是我的做法。

        【讨论】:

          【解决方案6】:

          如果引发异常,您的实现将不会被解锁。锁可能很昂贵,更轻量级的方法是使用 AtomicBoolean

          private final AtomicBoolean actionOnBLock = new AtomicBoolean();
          
          public void actionOnB() {
              if (!actionOnBLock.getAndSet(true))
                  try {
                      b.actionOnA();
                  } finally {
                      actionOnBLock.set(false);
                  }
          }
          

          正如其他人所建议的,更好的方法是编写代码,这样一个人就不必调用另一个人了。相反,您可以有一个调用 A 和 B 的方法,因此这两个对象不需要相互了解。

          public class AB {
             private final A a;
             private final B b;
             public AB(A a, B b) {
               this.a = a;
               this.b = b;
             }
          
             public void action() {
               a.action(); // doesn't call b.
               b.action(); // doesn't call a.
             }
          }
          

          【讨论】:

            【解决方案7】:

            正如其他人所说,我认为在大多数情况下,您应该尽量避免这种情况并重构您的代码,以免出现问题。但是在某些情况下,这可能不是一种选择或完全不可避免。最通用的方法是确保在方法 A 调用方法 B 之前(反之亦然),它必须确保 A 的对象处于对 A 的附加调用立即返回并因此导致无操作的状态。这确实非常抽象,很难应用于具体的类和实现。

            以容器和元素类(比如 C 和 E)为例,其中 C 有一个方法 C.remove(E e),E 有一个方法 E.remove(),你可以像这样实现它:

            class C:
                elements = [...] // List of elements
            
                remove(e):
                    if not elements.contains(e):
                        return
            
                    elements.remove(e)
                    // Do necessary internal stuff here...
                    // and finally call remove on e
                    e.remove()
            
            class E:
                container = ... // The current container of E
            
                remove():
                    if container is none:
                        return
            
                    c = container
                    container = none
                    // Do necessary internal stuff here...
                    // and finally call remove on c
                    c.remove(this)
            

            【讨论】:

              【解决方案8】:

              function fooA() 调用 fooB() 和 fooB() 再次调用 fooA() 是有效的递归。 递归并不总是必须是调用自身的同一个函数。

              那么,我们如何防止递归函数无限循环呢? 通过终止条件对吗?

              我想这就是要走的路。但是,我同意其他 cmets 重新思考设计并避免此类递归。

              Simon 下面的解决方案也依赖于终止条件,例如

              if not elements.contains(e):
                          return
              

              if container is none:
                          return
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2021-03-29
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多