对于需要相互调用的两种方法:如何防止无限循环?

时间:2009-06-06 11:16:33

标签: java design-patterns

我有两个类别(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提供了方法 remove(someElement)和 Element还提供了一种方法 removeMe() 两种方法都相互依赖,但不能连接到一种方法 方法,因为这两种方法另外执行一些内部的东西,这是唯一的 每个班级都可以访问内部

8 个答案:

答案 0 :(得分:8)

我会通过重新思考逻辑来处理这个问题。循环依赖通常表示某些东西有点......关闭。如果没有更深入地了解确切的问题,我就不能更具体了。

答案 1 :(得分: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.
    }
}

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

答案 2 :(得分:1)

所呈现的解决方案在给定方案中看起来完全可以接受。但这实际上取决于行为意味着这种行为是否正确。如果这正是您想要的行为,那么我不确定您是否有问题。

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

答案 3 :(得分:1)

我的建议是减少对代码的思考,更多地考虑设计。可以将共享操作抽象为一个新类,这两个类都可以与之通信吗?是否在两个类中错误地共享了功能?

今年在Uni,他们引入了“代码味道”的概念,这是代码需要重新分解的一堆“直觉反应”。也许可以帮忙吗?

如需概述,请尝试: Wikipedia Code Smellsthis book

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

我希望这会有所帮助。

答案 4 :(得分:1)

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

这就是我如何做到的。

答案 5 :(得分:0)

如果引发异常,您的实施将无法解锁。锁可能很昂贵,稍微轻一点的方法是使用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.
   }
}

答案 6 :(得分:0)

正如其他人所说,我认为在大多数情况下你应该尽量避免这种情况并重构你的代码,这样就不会出现问题。但是在某种情况下,这可能不是一种选择,也可能是完全不可避免的。最通用的方法是确保在方法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)

答案 7 :(得分:0)

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

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

我想这就是要走的路。但是,我同意其他人关于重新思考设计并避免这种递归的评论。

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

if not elements.contains(e):
            return

if container is none:
            return